Legend:
f([1,2,3]) - converts list [1,2,3] to functional list with the same elements
f(myClosure) - converts closure to curried function
<< - is functions composition and passing param to function in Groovy,
it also has been DSL-ed to do other things in comprehensions.
What are comprehensions:
If monads are about putting things in a 'box', then monadic comprehensions are magically removing the box. Only there is no magic. Comprehension is a polymorphic (works across all monads) syntax which lets you operate on monads using 'unboxed' values.
Here is a simple example using Fpiglet Maybe type:
def res = comprehend {
x << from{ just(3) }
y << from{ just(2) }
output{ just(x+y) }
}
assert just(5) == res
One thing that Maybe and Either are famous for is that they are a great alternative to handling exceptions and nulls. Let us see a bit of that in action:
Closure maybeDivide = f { Maybe num, Maybe den ->
comprehend {
x << from { num }
y << from { den }
restrict { y != 0}
output{ just(x/y) }
}
}
def sixDividedBy = maybeDivide(just(6))
assert just(2) == sixDividedBy(just(3))
assert nothing() == sixDividedBy(just(0))
I think this is very cool and much more elegant than using if statements or throwing exceptions!
Fpiglet supports another syntax (somewhat LINQ inspired) which is also quite readable:
Closure maybeDivide = f { Maybe num, Maybe den ->
select{just(x/y)}.from {
x << { num }
y << { den }
where { y != 0}
}
}
Both versions are equivalent. I will use select syntax moving forward.
I hope you have enjoyed my Learn You a Haskell book inspired knight examples in previous posts.
Here is a definition of knightMove function expressed using comprehension (please recall that positions are modeled as 2D Groovy lists [x,y] where x,y are in 0..7 range, but the list of all outcomes is a functional list):
def knightMove = {pos ->
def (x,y) = pos
select{ f([newPos]) }.from{
newPos << {f([[x+2,y+1],[x+2,y-1],
[x-2,y+1],[x-2,y-1],
[x+1,y+2],[x+1,y-2],
[x-1,y+2],[x-1,y-2]]) }
where {def (newX, newY) = newPos; newX in 0..7 && newY in 0..7}
}
}
assert [[2, 1], [1, 2]] == funlistOut << knightMove([0,0])
I am very much interested in code readability, so let me read the above code:
The result of knight move is select list (select {f[..]}) of new positions (newPos) from (.from) a list of spelled out positions ({f[...]}), where (where) the new position coordinates are in 0..7 range (newX in 0..7).
One of the reasons for having comprehensions is that they read very nice!
And here are all possible positions after 3 moves:
def in3Moves = select { f([third]) }.from{
first << { knightMove([0,0]) }
second << { knightMove(first) }
third << { knightMove(second) }
}
I think it reads even better!
Summary:
In previous posts we did go beyond simple 3 time knightMove to solving problems like the shortest number of moves needed to move from A to B. Stuff like this still requires folds.
However comprehensions are a very powerful and very readable syntax.
Please check out Fpiglet website in a few weeks for more information about Fpiglet monadic comprehensions.