In the previous post, we talked about lazy evaluation in Scala. At the end of that post, we asked an interesting question: Does a Lazy
value hold an state?
In order to answer that question, we’ll try to define a type that could represent the Lazy values:
trait Lazy[T] { val evalF : () => T val value: Option[T] = None } object Lazy{ def apply[T](f: => T): Lazy[T] = new Lazy[T]{ val evalF = () => f } }
As you can see, our Lazy
type is parameterized by some T
type that represents the actual value type(Lazy[Int]
would be the representation for a lazy integer).
Besides that, we can see that it’s composed of the two main Lazy type features:
- evalF : Zero-parameter function that, when its ‘apply’ method is invoked, it evaluates the contained T expression.
- value : The result value of the interpretation of the
evalF
function. This concrete part denotes the state in theLazy
type, and it only admit two possible values:None
(not evaluated) orSome(t)
(if it has been already evaluated and the result itself).
We’ve also added a companion object that defines the Lazy instance constructor that receives a by-name parameter that is returned as result of the evalF
function.
Now the question is, how do we join both the evaluation function and the value that it returns so we can make Lazy
an stateful type? We define the ‘eval’ function this way:
trait Lazy[T] { lzy => val evalF : () => T val value: Option[T] = None def eval: (T, Lazy[T]) = { val evaluated = evalF.apply() evaluated -> new Lazy[T]{ mutated => val evalF = lzy.evalF override val value = Some(evaluated) override def eval: (T, Lazy[T]) = evaluated -> mutated } } }
The ‘eval’ function returns a two-element tuple:
- The value result of evaluating the expression that stands for the lazy value.
- a new
Lazy
value version that contains the new state: the T evaluation result.
If you take a closer look, what ‘eval’ method does in first place is to invoke the evalF function so it can retrieved the T value that remained until that point not-evaluated.
Once done, we return it as well as the new Lazy value version. This new version (let’s call it mutated
version) will have in its ‘value’ attribute the result of having invoked the evalF
function. In the same way, we change its eval
method, so in future invocations the Lazy instance itself is returned instead of creating new instances (because it actually won’t change its state, like Scala’s lazy definitions work).
The interesting question that comes next is: is this an isolated case? Could anything else be defined as stateful? Let’s perform an abstraction exercise.
Looking for generics: stateful stuff
Let’s think about a simple stack:
sealed trait Stack[+T] case object Empty extends Stack[Nothing] case class NonEmpty[T](head: T, tail: Stack[T]) extends Stack
The implementation is really simple. But let’s focus in the Stack
trait and in a hypothetical pop
method that pops an element from the stack so it is returned as well as the rest of the stack:
sealed trait Stack[+T]{ def pop(): (Option[T], Stack[T]) }
Does it sound familiar to you? It is mysteriously similar to
trait Lazy[T]{ def eval: (T, Lazy[T]) }
isn’t it?
If we try to re-factor for getting a common trait between Lazy
and Stack
, we could define a much more abstract type called State
:
trait State[S,T] { def apply(s: S): (T, S) }
Simple but pretty: the State
trait is parameterized by two types: S (state type) and T (info or additional element that is returned in the specified state mutation). Though it’s simple, it’s also a ver common pattern when designing Scala systems. There’s always something that holds certain state. And everything that has an state, it mutates. And if something mutates in a fancy and smart way…oh man.
That already exists…
All this story that seems to be created from a post-modern essay, has already been subject of study for people…that study stuff. Without going into greater detail, in ScalaZ library you can find the State
monad that, apart from what was previously pointed, is fully-equipped with composability and everything that being a monad means (semigroup, monoid, …).
If we define our Lazy type with the State monad, we’ll get something similar to:
import scalaz.State type Lazy[T] = (() => T, Option[T]) def Lazy[T](f: => T) = (() => f, None) def eval[T] = State[Lazy[T], T]{ case ((f, None)) => { val evaluated = f.apply() ((f, Some(evaluated)), evaluated) } case s@((_, Some(evaluated))) => (s, evaluated) }
When decrypting the egyptian hieroglyph, given the State[S,T]
monad, we have that our S state will be a tuple composed of what exactly represents a lazy expression (that we also previously described):
type Lazy[T] = (() => T, Option[T])
- A Function0 that represents the lazy evaluation of T
- The T value that might have been evaluated or not
For building a Lazy value, we generate a tuple with a function that stands for the expression pointed with the by-name parameter of the Lazy
method; and the None value (because the Lazy guy hasn’t been evaluated yet):
def Lazy[T](f: => T) = (() => f, None)
Last, but not least (it’s actually the most important part), we define the only state transition that is possible in this type: the evaluation. This is the key when designing any State type builder: how to model what out S type stands for and the possible state transitions that we might consider.
In the case of the Lazy type, we have two possible situations: the expression hasn’t been evaluated yet (in that case, we’ll evaluate it and we’ll return the same function and the result) or the expression has been already evaluated (in that case we won’t change the state at all and we’ll return the evaluation result):
def eval[T] = State[Lazy[T], T]{ case ((f, None)) => { val evaluated = f.apply() ((f, Some(evaluated)), evaluated) } case s@((_, Some(evaluated))) => (s, evaluated) }
In order to check that we can still count on the initial features we described for the Lazy type (it can only be evaluated once, only when necessary, …) we check the following assertions:
var sideEffectDetector: Int = 0 val two = Lazy { sideEffectDetector += 1 2 } require(sideEffectDetector==0) val (_, (evaluated, evaluated2)) = (for { evaluated <- eval[Int] evaluated2 <- eval[Int] } yield (evaluated, evaluated2)).apply(two) require(sideEffectDetector == 1) require(evaluated == 2) require(evaluated2 == 2)
Please, do notice that, as we mentioned before, what is defined inside the for-comprehension are the same transitions or steps that the state we decide will face. That means that we define the mutations that any S state will suffer. Once the recipe is defined, we apply it to the initial state we want.
In this particular case, we define as initial state a lazy integer that will hold the 2 value. For checking the amount of times that our Lazy guy is evaluated, we just add a very dummy var that will be used as a counter. After that, we define inside our recipe that the state must mutate twice by ussing the eval
operation. Afterwards we’ll check that the expression of the Lazy block has only been evaluated once and that the returning value is the expected one.
I wish you the best tea for digesting all this crazy story 🙂
Please, feel free to add comments/menaces at the end of this post or even at our gitter channel.
See you on next post.
Peace out!
hi,
I played around with your examples but could only get them to run with
((f, Some(evaluated)), evaluated) instead of (Some(f, Some(evaluated)), evaluated) in the eval method.
I’m using scalaz 7.2.7
Me gustaMe gusta
Hi Chris,
you’re totally right. It was a typo: sorry about that.
Thanks for being aware of it 🙂
Me gustaMe gusta
Should line 17 be
require(evaluated2 == 2)
?
Me gustaMe gusta
You’re totally right! Being in a rush is never a good option for publishing posts :-p
Thanks
Me gustaMe gusta