Software has two ingredients: opinions and logic (=programming). The second ingredient is rare and is typically replaced by the first.
I blog about code correctness, maintainability, testability, and functional programming.
This blog does not represent views or opinions of my employer.

Saturday, August 10, 2013

Monadic Comprehensions in Groovy and Fpiglet

This post is continuing my last 2 posts about monads.   In this post we will look at Groovy DSL syntax (as implemented in Fpiglet) for monadic comprehensions.

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 (.froma 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.

4 comments:

  1. Cool stuff!
    The examples would even read a bit more haskell-ish if you would omit parentheses where they are optional, e.g. second << { knightMove first }
    There may be more options in the "functional DSL" for using command chains and callables (objects with a call method can be used as methods).
    Keep it up!
    Dierk

    ReplyDelete
  2. if you rename "comprehend" with "do", you almost have "do-notation" :-)
    Since "return" is reserved, you cannot use it instead of "output" but you could use "retour"...

    ReplyDelete
  3. Maybe you right that do is better.
    I decided that comprehend is more 'explicit' and I think 'select' is simply cool.
    'forall' maybe a choice too. I believe Simon Peyton Jones said that Haskell decided on 'do' and 'return' to give comprehensions some 'imperative feel'. There is no need for that in Groovy!

    ReplyDelete