Skip to content

Commit e865205

Browse files
author
Javier de Silóniz Sandino
authored
Merge pull request #4 from scala-exercises/js-IntroductionToScalaLanguageSection
First exercises for the "Getting Started with FP" section
2 parents dd0261f + eee0fe2 commit e865205

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package fpinscalalib
2+
3+
import org.scalaexercises.definitions._
4+
5+
/** Set of exercises based on Manning's "Functional Programming in Scala" (aka "The Red Book")
6+
*
7+
* @param name fp_in_scala
8+
*/
9+
object FPinScalaLibrary extends Library {
10+
override def owner = "scala-exercises"
11+
override def repository = "exercises-fpinscala"
12+
13+
override def color = Some("#E22D34")
14+
15+
override def sections = List(
16+
GettingStartedWithFPSection
17+
)
18+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package fpinscalalib
2+
3+
import org.scalatest.{FlatSpec, Matchers}
4+
5+
/** @param name getting_started_with_functional_programming
6+
*/
7+
object GettingStartedWithFPSection extends FlatSpec with Matchers with org.scalaexercises.definitions.Section {
8+
9+
/**
10+
* = Tail-recursive functions =
11+
*
12+
* We're going to introduce some of the basic techniques for how to write functional programs. Let's start by writing
13+
* loops using tail-recursive functions. For instance, let's take a look on how to functionally write a function that
14+
* calculates the factorial of a given number.
15+
*
16+
* {{{
17+
* def factorial(n: Int): Int = {
18+
* @annotation.tailrec
19+
* def go(n: Int, acc: Int): Int =
20+
* if (n <= 0) acc
21+
* else go(n - 1, n * acc)
22+
* go(n, 1)
23+
* }
24+
* }}}
25+
*
26+
* We're defining a recursive helper function inside the body of the `functional` function. We often call these helper
27+
* functions `go` or `loop`. Since it's local, the `go` function can only be referred to from within the body of the
28+
* `factorial` function, just like a local variable would.
29+
*
30+
* The arguments to `go` are the state for the loop. In this case, they're the remaining value `n`, and the current
31+
* accumulated factorial `acc`. To advance to the next iteration, we simply call `go` recursively with the new loop
32+
* state: `go(n-1, n*acc)`, and to exit from the loop we return a value without a recursive call (in this case, we
33+
* return the value of `acc` if `n <= 0`).
34+
*
35+
* Scala is able to detect this sort of self-recursion and compiles it to the same sort of bytecode as would be emitted
36+
* by a `while` loop, as long as the recursive call is in tail position. The basic idea is that this optimization
37+
* (tail call elimination) is applied when there's no additional work left to do after the recursive call returns.
38+
*
39+
* Let's do the same with a function to call the nth number from the Fibonacci sequence. The first two numbers
40+
* are 0 and 1. Then, the nth number is always the sum of the previous two, i.e.: 0, 1, 1, 2, 3, 5... The fib`
41+
* function starts by calling its `loop` helper function with the initial values of `n` (the position in the sequence
42+
* we need to calculate), and the previous and current values in the sequence.
43+
*
44+
* {{{
45+
* def fib(n: Int): Int = {
46+
* @annotation.tailrec
47+
* def loop(n: Int, prev: Int, cur: Int): Int =
48+
* if (n <= ???) prev
49+
* else loop(n - ???, cur, prev + cur)
50+
* loop(n, 0, 1)
51+
* }
52+
* }}}
53+
*
54+
* Try to fix the `loop` function inside `fib` so that it returns the correct values for each case in a tail-recursive
55+
* way. What should the missing expressions for the trivial case and the recursive call be?
56+
*/
57+
58+
def fibAssert(res0: Int, res1: Int) {
59+
def fib(n: Int): Int = {
60+
@annotation.tailrec
61+
def loop(n: Int, prev: Int, cur: Int): Int =
62+
if (n <= res0) prev
63+
else loop(n - res1, cur, prev + cur)
64+
loop(n, 0, 1)
65+
}
66+
67+
fib(5) should be(5)
68+
}
69+
70+
/**
71+
* = Polymorphic and higher-order functions =
72+
*
73+
* Polymorphic functions allow us to write code that works for any type it's given. For instance, take a look at
74+
* `findFirst`, a function that finds the first index in an array where the key occurs (or `-1` if it doesn't exist),
75+
* implemented more generally by accepting a function to use for testing a particular `A` value.
76+
*
77+
* {{{
78+
* def findFirst[A](as: Array[A], p: A => Boolean): Int = {
79+
* @annotation.tailrec
80+
* def loop(n: Int): Int =
81+
* if (n >= as.length) -1
82+
* // If the function `p` matches the current element,
83+
* // we've found a match and we return its index in the array.
84+
* else if (p(as(n))) n
85+
* else loop(n + 1)
86+
*
87+
* loop(0)
88+
* }
89+
* }}}
90+
*
91+
* To write a polymorphic function as a method, we introduce a comma-separated list of type parameters, surrounded by
92+
* square brackets (here, just a single `[A]`), following the name of the function, in this case `findFirst`.
93+
*
94+
* = Higher-order functions =
95+
*
96+
* Let's see an example of a higher-order function (HOF):
97+
* {{{
98+
* def formatResult(name: String, n: Int, f: Int => Int) = {
99+
* val msg = "The %s of %d is %d."
100+
* msg.format(name, n, f(n))
101+
* }
102+
* }}}
103+
*
104+
* Our `formatResult` HOF takes another function called `f`. We give a type to `f`, as we would for any other parameter.
105+
* Its type is `Int => Int`, which indicates that `f` expects an integer argument and will also return an integer.
106+
*
107+
* Let's create a polymorphic, tail-recursive higher-order function that checks if an array is sorted, according to
108+
* a given comparison function that will be passed as a parameter:
109+
*
110+
* {{{
111+
* def isSorted[A](as: Array[A], ordering: (A, A) => Boolean): Boolean = {
112+
* @annotation.tailrec
113+
* def go(n: Int): Boolean =
114+
* if (n >= as.length - 1) true
115+
* else if (ordering(as(n), as(n + 1))) false
116+
* else go(n + 1)
117+
*
118+
* go(0)
119+
* }
120+
* }}}
121+
*
122+
* When using HOFs, it's often convenient to be able to call these functions with anonymous functions, rather than
123+
* having to supply some existing named function. For instance, using the previously implemented `findFirst`:
124+
*
125+
* {{{
126+
* findFirst(Array(7, 9, 13), (x: Int) => x == 9)
127+
* }}}
128+
*
129+
* The syntax `(x: Int) => x == 9` is a `function literal` or `anonymous function`, defining a function that takes one
130+
* argument `x` of type `Int` and returns a `Boolean` indicating whether `x` is equal to 9.
131+
*
132+
* Let's do the same with `isSorted`. After taking a detailed look at its implementation, what would be the results of
133+
* applying the following anonymous functions to it?
134+
*/
135+
136+
def isSortedAssert(res0: Boolean, res1: Boolean, res2: Boolean): Unit = {
137+
def isSorted[A](as: Array[A], ordering: (A, A) => Boolean): Boolean = {
138+
@annotation.tailrec
139+
def go(n: Int): Boolean =
140+
if (n >= as.length - 1) true
141+
else if (ordering(as(n), as(n + 1))) false
142+
else go(n + 1)
143+
144+
go(0)
145+
}
146+
147+
isSorted(Array(1, 3, 5, 7), (x: Int, y: Int) => x > y) shouldBe res0
148+
isSorted(Array(7, 5, 3, 1), (x: Int, y: Int) => x < y) shouldBe res1
149+
isSorted(Array("Scala", "Exercises"), (x: String, y: String) => x.length > y.length) shouldBe res2
150+
}
151+
}
152+

src/test/scala/exercises/Test.scala

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package exercises
2+
3+
import cats.data.Xor
4+
5+
import shapeless._
6+
import shapeless.ops.function._
7+
8+
import org.scalacheck.{ Prop, Arbitrary }
9+
import org.scalacheck.Gen
10+
import Prop.forAll
11+
12+
import org.scalatest.Spec
13+
import org.scalatest.exceptions._
14+
import org.scalatest.prop.Checkers
15+
16+
import org.scalacheck.Shapeless._
17+
18+
object Test {
19+
20+
def testSuccess[F, R, L <: HList](method: F, answer: L)(
21+
implicit
22+
A: Arbitrary[L],
23+
fntop: FnToProduct.Aux[F, L R]
24+
): Prop = {
25+
val rightGen = genRightAnswer(answer)
26+
val rightProp = forAll(rightGen)({ p
27+
28+
val result = Xor.catchOnly[GeneratorDrivenPropertyCheckFailedException]({ fntop(method)(p) })
29+
result match {
30+
case Xor.Left(exc) exc.cause match {
31+
case Some(originalException) throw originalException
32+
case _ false
33+
}
34+
case _ true
35+
}
36+
})
37+
38+
val wrongGen = genWrongAnswer(answer)
39+
val wrongProp = forAll(wrongGen)({ p
40+
Xor.catchNonFatal({ fntop(method)(p) }).isLeft
41+
})
42+
43+
Prop.all(rightProp, wrongProp)
44+
}
45+
46+
def genRightAnswer[L <: HList](answer: L): Gen[L] = {
47+
Gen.const(answer)
48+
}
49+
50+
def genWrongAnswer[L <: HList](l: L)(
51+
implicit
52+
A: Arbitrary[L]
53+
): Gen[L] = {
54+
A.arbitrary.suchThat(_ != l)
55+
}
56+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package exercises
2+
3+
import fpinscalalib._
4+
import fpinscalalib.GettingStartedWithFPSection
5+
import shapeless.HNil
6+
import org.scalatest.Spec
7+
import org.scalatest.prop.Checkers
8+
import org.scalacheck.Shapeless._
9+
import org.scalacheck.{Arbitrary, Gen}
10+
11+
class GettingStartedWithFPSpec extends Spec with Checkers {
12+
def `fibonacci asserts` = {
13+
implicit val intArbitrary = Arbitrary[Int](Gen.choose(1, 100))
14+
check(Test.testSuccess(GettingStartedWithFPSection.fibAssert _, 0 :: 1 :: HNil))
15+
}
16+
17+
def `isSorted asserts` = {
18+
check(Test.testSuccess(GettingStartedWithFPSection.isSortedAssert _, true :: true :: true :: HNil))
19+
}
20+
}

0 commit comments

Comments
 (0)