Skip to content

Commit b8b1949

Browse files
Merge pull request #87 from paualarco/eval-data-type-exercises
Added Eval exercises
2 parents 221407d + 7ec6d8b commit b8b1949

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

src/main/scala/catslib/CatsLibrary.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ object CatsLibrary extends org.scalaexercises.definitions.Library {
2727
TraverseSection,
2828
IdentitySection,
2929
EitherSection,
30-
ValidatedSection
30+
ValidatedSection,
31+
EvalSection
3132
)
3233

3334
override def logoPath = "cats"
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* scala-exercises - exercises-cats
3+
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
4+
*
5+
*/
6+
7+
package catslib
8+
9+
import cats._
10+
import org.scalatest.flatspec.AnyFlatSpec
11+
import org.scalatest.matchers.should.Matchers
12+
13+
/** Eval is a data type for controlling synchronous evaluation.
14+
* Its implementation is designed to provide stack-safety at all times using a technique called trampolining.
15+
* There are two different factors that play into evaluation: memoization and laziness.
16+
* Memoized evaluation evaluates an expression only once and then remembers (memoizes) that value.
17+
* Lazy evaluation refers to when the expression is evaluated.
18+
* We talk about eager evaluation if the expression is immediately evaluated when defined and about lazy evaluation if the expression is evaluated when it’s first used.
19+
* For example, in Scala, a lazy val is both lazy and memoized, a method definition def is lazy, but not memoized, since the body will be evaluated on every call.
20+
* A normal val evaluates eagerly and also memoizes the result.
21+
* Eval is able to express all of these evaluation strategies and allows us to chain computations using its Monad instance.
22+
*
23+
* @param name Eval
24+
*/
25+
object EvalSection extends AnyFlatSpec with Matchers with org.scalaexercises.definitions.Section {
26+
27+
/** = Eval.now =
28+
*
29+
* First of the strategies is eager evaluation, we can construct an Eval eagerly using Eval.now:
30+
*
31+
* {{{
32+
* import cats.Eval
33+
* // import cats.Eval
34+
*
35+
* import cats.implicits._
36+
* // import cats.implicits._
37+
*
38+
* val eager = Eval.now {
39+
* println("Running expensive calculation...")
40+
* 1 + 2 * 3
41+
* }
42+
* // Running expensive calculation...
43+
* // eager: cats.Eval[Int] = Now(7)
44+
* }}}
45+
*
46+
* We can run the computation using the given evaluation strategy anytime by using the value method.
47+
* eager.value
48+
* // res0: Int = 7
49+
*
50+
*/
51+
def nowEval(resultList: List[Int]) = {
52+
//given
53+
val eagerEval = Eval.now {
54+
println("This is eagerly evaluated")
55+
1 :: 2 :: 3 :: Nil
56+
}
57+
58+
//when/then
59+
eagerEval.value shouldBe (resultList: List[Int])
60+
}
61+
62+
/** = Eval.later =
63+
*
64+
* If we want lazy evaluation, we can use Eval.later
65+
* In this case
66+
*
67+
* {{{
68+
* val lazyEval = Eval.later {
69+
* println("Running expensive calculation...")
70+
* 1 + 2 * 3
71+
* }
72+
* // lazyEval: cats.Eval[Int] = cats.Later@6c2b03e9
73+
*
74+
* lazyEval.value
75+
* // Running expensive calculation...
76+
* // res1: Int = 7
77+
*
78+
* lazyEval.value
79+
* // res2: Int = 7
80+
* }}}
81+
*
82+
* Notice that “Running expensive calculation” is printed only once, since the value was memoized internally.
83+
* Meaning also that the resulted operation was only computed once.
84+
* Eval.later is different to using a lazy val in a few different ways.
85+
* First, it allows the runtime to perform garbage collection of the thunk after evaluation, leading to more memory being freed earlier.
86+
* Secondly, when lazy vals are evaluated, in order to preserve thread-safety, the Scala compiler will lock the whole surrounding class, whereas Eval will only lock itself.
87+
*
88+
*/
89+
def laterEval(resultList: List[Int], counterResult: Int) = {
90+
//given
91+
val n = 2
92+
var counter = 0
93+
val lazyEval = Eval.later {
94+
println("This is lazyly evaluated with caching")
95+
counter = counter + 1
96+
(1 to n)
97+
}
98+
99+
//when/then
100+
List.fill(n)("").foreach(_ => lazyEval.value)
101+
lazyEval.value shouldBe (resultList: List[Int])
102+
counter shouldBe counterResult
103+
}
104+
105+
/** = Eval.always =
106+
*
107+
* If we want lazy evaluation, but without memoization akin to Function0, we can use Eval.always
108+
* Here we can see, that the expression is evaluated every time we call .value.
109+
* {{{
110+
* val alwaysEval = Eval.always(println("Always evaluated"))
111+
* //Always evaluated
112+
* alwaysEval.eval
113+
* //Always evaluated
114+
* alwaysEval.eval
115+
* //Always evaluated
116+
* alwaysEval.eval
117+
* }}}
118+
*
119+
*/
120+
def alwaysEval(resultList: List[Int], counterAfterListEval: Int, latestCounter: Int) = {
121+
//given
122+
val n = 4
123+
var counter = 0
124+
val alwaysEval = Eval.always {
125+
println("This is lazyly evaluated without caching")
126+
counter = counter + 1
127+
(1 to n)
128+
}
129+
130+
//when/then
131+
List.fill(n)("").foreach(_ => alwaysEval.value)
132+
counter shouldBe counterAfterListEval
133+
alwaysEval.value shouldBe (resultList: List[Int])
134+
counter shouldBe latestCounter
135+
}
136+
137+
/** = Eval.defer =
138+
*
139+
* Defer a computation which produces an Eval[A] value
140+
* This is useful when you want to delay execution of an expression which produces an Eval[A] value. Like .flatMap, it is stack-safe.
141+
* Because Eval guarantees stack-safety, we can chain a lot of computations together using flatMap without fear of blowing up the stack.
142+
*
143+
*/
144+
def deferEval(resultList: List[Int]) = {
145+
//given
146+
val list = List.fill(3)(0)
147+
148+
//when
149+
val deferedEval: Eval[List[Int]] = Eval.now(list).flatMap(e => Eval.defer(Eval.later(e)))
150+
151+
//then
152+
Eval.defer(deferedEval).value shouldBe (resultList: List[Int])
153+
}
154+
155+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* scala-exercises - exercises-cats
3+
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
4+
*
5+
*/
6+
7+
package catslib
8+
9+
import org.scalacheck.ScalacheckShapeless._
10+
import org.scalaexercises.Test
11+
import org.scalatest.refspec.RefSpec
12+
import org.scalatestplus.scalacheck.Checkers
13+
import shapeless.HNil
14+
15+
class EvalSpec extends RefSpec with Checkers {
16+
17+
def `eager eval (now) is evaluated` = {
18+
check(
19+
Test.testSuccess(
20+
EvalSection.nowEval _,
21+
List(1, 2, 3) :: HNil
22+
)
23+
)
24+
}
25+
26+
def `later eval is only evaluated` = {
27+
check(
28+
Test.testSuccess(
29+
EvalSection.laterEval _,
30+
List(1, 2) :: 1 :: HNil
31+
)
32+
)
33+
}
34+
35+
def `always eval is only evaluated` = {
36+
check(
37+
Test.testSuccess(
38+
EvalSection.alwaysEval _,
39+
List(1, 2, 3, 4) :: 4 :: 5 :: HNil
40+
)
41+
)
42+
}
43+
44+
def `defer eval chaining with flatMap` = {
45+
check(
46+
Test.testSuccess(
47+
EvalSection.deferEval _,
48+
List(0, 0, 0) :: HNil
49+
)
50+
)
51+
}
52+
}

0 commit comments

Comments
 (0)