Sunday, June 30, 2013

Groovy more functional with fpiglet - Curried Functions vs. Groovy Closure.curry

Fpiglet add support for the powerful concept of curried functions. This post compares this new functionality against simply using Closure.curry() methods in Groovy.
Originally I have 3 posts on this topic. Two of them have been moved and are now part of Fpiglet documentation.

Let us consider these Groovy closures:
 def c1 = {a,b,c,d -> a + b + c + d} 
 def c2 = {a->{b->{c->{d-> a + b + c + d}}}}
 def c3 = {a,b -> {c,d-> a + b + c + d}}

In Groovy you need to call them like this:
  c1(1,2,3,4);  c2(1)(2)(3)(4);  c3(1,2)(3,4)

and not in any other way unless you start using verbose .curry() method. By contrast with Fpiglet you can call them any way you like:
 def fc = f c1 //could be c2 or c3 with the same results 
 assert fc(1)(2)(3)(4) == 10 
 assert fc(1,2)(3,4)   == 10 
 assert fc(1)(2,3,4)   == 10 
 assert fc(1,2,3,4)    == 10

So why is that so important?

How Groovy curry chains work:
The following code example describes how Groovy curry method works:
 //see top of this post for c1 definition
 assert 10 == c1(1,2,3,4) //as expected
 assert c1.curry(1).curry(2).curry(3).curry(4) instanceof Closure
 assert c1.curry(1).curry(2).curry(3).curry(4)() == 10

So even if we were to magically get rid of explicit curry() calls Groovy curry() would result in something like this:
 //Groovy curried equivalent of c1(1,2,3,4)
 //(x) represents curry(x) and () represents .call()
 c1(1)(2)(3)(4)() == 10 //UGLY!

and the following code in Groovy language means nothing no matter how you curry it!
 c1(1,2)(3,4) //makes no sense

Arbitrary number of parameters closures with Groovy chains:
Part of the ugliness of the previous example can be explained by Groovy's need to support Closures with arbitrary number of parameters. Here is a textbook example using the flexible Object[] arg type:
  //GROOVY code
 def sumAll={Integer[] args-> 
      args.inject(0){acc, a -> acc + a}}

 assert 4 == sumAll(1,1,1,1) //works great!
 assert sumAll.curry(1).curry(1).curry(1).curry(1) instanceof Closure
 assert sumAll.curry(1).curry(1).curry(1).curry(1)() == 4

So curring works the same way for Groovy Closures with arbitrary lists of parameters as for fix parameter sets. And that is different from what we need.  Arbitrary parameter lists can be convenient, but the benefits of curried functions outweigh that by several factors.

Fpiglet version of sumAll:
  Closure sumAll = reduceL(PLUS)   
  assert 11 == sumAll ([5,5,1])

That does look much better than Groovy's version!
Fpiglet functional equivalents of Groovy's collection inject method are reduceL and foldL curried functions. Both are implemented on FunLists (which is Fpiglet functional list implementation).  But as you can see the above code uses them directly on Groovy lists!

Standard functions like reduceL are mapped over to Groovy Lists. This mapping over is possible because FunList -> List   defined by Fpiglet is a Functor.   In fact, the above code is syntax sugar version of:
   Closure fmap = FunListToListFunctor.statics.fmap
   Closure sumAll = fmap(reduceL(PLUS))

Functors are truly very powerful tool!  But code like that relies heavily on curried functions.
Just notice that fmap needs 2 parameters but it is given 1, reduceL needs 2 parameters but it is given 1 and, yet, everything works.
(I wrote more about Functors in this post  Better Polymorphism Post)

There is one more important point here:  in standard Groovy code: list.inject, the method inject is owned by the list.   In Fpiglet code: reduceL, PlUS, and reduceL(PLUS) are first class citizens, not owned by anything!  Data and data manipulation are decoupled.

Conclusions:
As we have seen Fpiglet Haskell style curried functions improve over Groovy's curry in more than just terseness   In Groovy language program using Closure needs to know if it needs to invoke 'c.curry(x)' or 'c.call(x)' or (as we have seen) both.

What makes curried functions powerful is freeing programs from needing to know that.  The program simply 'applies x' by using simple syntax: '(x)' or if it needs to apply x and y then will do either '(x)(y)'  or '(x,y)'.  The result is a terse and beautiful code like the sumAll example shown above.  The additional benefit is that data manipulating logic becomes first class citizen and the data is effectively decoupled from manipulation.


No comments:

Post a Comment