Context
These days (this week-end) I wanted to put some work on a Neo4J Rest driver that I'm writing for Play 2.0 in Scala.
The only thing I wanted, actually, is to have the current embryo more functional. I mean I was throwing exceptions (frightening... huh!).
Since this driver is meant to be fully asynchronous (man... it's http, it MUST be) and non-blocking (Play's philosophy), I was hardly using the
Promise
thing via the use of the WS api of Play.This is the kind of thing I've got (briefly):
def getNode(id:Int) : Promise[Neo4JElement]
Where
Neo4JElement
stands for the wrapper of all Neo4J Rest response (Json). Hence, it can be either a Failure response (Json with stacktrace and message), it can be a Node, or .... throw an Exception (br...) when the service crashed (f.i.).Hmm, not so intuitive and goes against the functional paradigm that orders: "you can't ever introduce side-effects, boy!". An exception that blows in my face, is one side effect (head-aches, ...).
Diego Validation to the rescue
Validation is a very simple thing, it holds either a value, either an error...
Ok, why not just
Either
then. Actually, you're right but Validation
that I took in the ScalaZ library contains a lot of thing very helpful for the purpose of validation. But if you worry it, just replace Validation
by Either
in your mind from here.Now, here is the
getNode
signature:
def getNode(id:Int) : Promise[Validation[Aoutch, Node]]
Isn't it more intuitive? For sure, you get back our relevant type in the signature :
Node
. Great!So far, so good now what the heck is
Aoutch
: something that hurts... and what could hurt a runtime execution... exceptions yeah! Thus, Aoutch
is just a shortcut for Either[NonEmptyList[String], Failure]
. We can see that we represent with one single type an unexpected exception and a failure (missing node f.i.).Monad
If you don't know what a Monad is, from here thing about a structure that can evolve in a for-comprehension (in scala it must implement
flatMap
and map
).Promise
and Validation
are Monads. And their used one over the another. But what really interests us is the leaf of the chain Node
. That introduced some boilerplate code when you try to sequence actions like that:See... yes we have to skip the first level (validation) to be able to work with the meaningful objects.
But still that we can extract some pattern... no?
Monad Transformer
The pattern that we can extract is kind of two-level composition. I did this composition my-self trying to figure out what we'll be possible.
It was successful but, I've have to introduce a new type and a new method, that was like a
flatMap
.So I asked on StackOverflow ^^ (and it was my first question, yeepeee). You can find it here. So I want explain how I did, because the question explains it. But the real question was, is there a well-known functional construction for this problem?.
Thanks to @purefn, I knew that it was the case!
It was time to use Monad Transformer.
Monad Transformer
Briefly (and very roughly), a Monad Transformer is a construction that is able to transform a
M[N[_]]
into a P[_]
, where M, N, P are Monads.I won't explain here how it does, because it would be long, but here is a good link (you've to understand Haskell a bit, sorry).
With the help of such transformer, you'll get you back the opportunity to use for-comprehension... with the wrapped-twice type as bound value.
Here is the the transformer for our
Promise[Validation[E, A]]
:And how we can now link nodes:
Awesome! No! We get 3 async and non-blocking calls, totally typed checked and resistant to exceptions and failures... In 5 lines.
Future work
At the SO question, @purefn tolds me that scalaz 7 (snapshot) is defining (fully) this kind of Validation Transformer.
Why didn't I used it, yet:
- I'm trying to use not Snapshot (not a good reason, but still)
- In order to use
ValidationT
, I'll have first to create an instance of the Type ClassMonad
. Because theflatMap
signature needs it in the context.
Conclusion
I love functional (even if I'm still learning -- back -- the basis ^^)
On my project in Atlassian, we're using ValidationT from Scalaz7.
ReplyDeleteWorks well except not all functions return ValidationT. There's a lot of lifting and transforming things to ValidationT which makes the for comprehensions a bit ugly. We still need to come up with a bit of sugar to clean that up.
You might have to pimp a bit to have the non ValidationT being returned, with an implicit conversion to ValidationT (in case such conversion is possible...)
ReplyDeletehi,
ReplyDeleteshouldn't the final result be
for {
ns <- getNode(s); //get source
nt <- getNode(t); //get target
l <- link(ns, nt); //link them
} yield l //yield the link
ie ns and nt in the for rather than n and s.
Man ... you're right!
DeleteI oopsed again ^^