-
Notifications
You must be signed in to change notification settings - Fork 3
CurriedFunctions
In Nutshell
Fpiglet Project Home page shows some examples of curried functions. Please review them if you are new to this concept.
Curried Functions are a new concept to Groovy. Curried functions introduced in Fpiglet are very similar to curried functions in Haskell (except Haskell is very strongly typed).
One way to think about Curried Functions is that they are equivalence classes of Closures which do not care about grouping of parameters:
def c1 = {a,b,c,d -> a + b + c + d}
is very much the same as curried function as this
def c2 = {a->{b->{c->{d-> a + b + c + d}}}}
or like this
def c3 = {a, b -> {c, d -> a + b + c + d}}
and other similar 'groupings' of parameters.
In fpiglet you can take any of the functions defined above and do this:
import static fpig.common.functions.FpigBase.*
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
//etc. etc.
Invoking curried function with just a few parameters simply partially applies these parameters.
Using fpiglet, you can even use the following underscore syntax to partially apply closures:
Closure expr = f({a,b,c,d -> a + 2*b + 3*c + 4*d})
//note expr(1,2,_,_) is equivalent to expr(1,2) .. or expr(1)(2)
Closure needs_a_c = expr(_, 0, _, 0)
assert needs_a_c(1,0) == 1
assert needs_a_c(0,1) == 3
Notice that, with just using Groovy.curry() it is not possible to make the above closures (c1, c2, c3) behave in identical way.
Also notice that to do that:
assert fc(1)(2)(3)(4) == 10
you would need to do this in Groovy (notice UGLY call() at the end of curry chain):
assert c1.curry(1).curry(2).curry(3).curry(4).call() == 10
Why Curried Functions?
Curried functions are simply more powerful than OO methods.
Consider this Groovy code:
list.inject{acc, el -> (el>acc)? el: acc }
inject
and anonymous Closure passed to inject are all tightly coupled to the list. The 'inject' method is owned by the list.
In Fpiglet code:
reduceL(MAX) << list
reduceL
is first class citizen, it lives independently of data, so does the MAX
function. Data and manipulation of data become decoupled.
If I really, really wanted to marry list
and reduceL
I could simply do this:
reduceL(_, list)
Here is a little more advanced Fpiglet code which relies on curried functions:
import static fpig.funlist.functions.BaseFL.*
import static fpig.groovylist.asfunlist.functions.InAndOutOfFunLists.*
Closure sumAllInGroovyList = withFunList (reduceL(PLUS))
//withFunList expects 2 params it is given one, reduceL expects 2 params it is given one
assert sumAllInGroovyList ([2,5,3]) == 10
Many other cool Fpiglet features rely on curried functions heavily.
Wiki page: CurriedFunctionsLogicalLimits
- describes logical limitations of curried functions in detail
Wiki page: CurriedFunctionsImplDecisions explains
- implementation decisions
- implementation limitations.