-
Notifications
You must be signed in to change notification settings - Fork 3
Overview
Fpiglet adds to Groovy the concept of Curried Functions and emphasizes function composition as a programming tool. Function composition is a very powerful way to code, and it really shines with curried functions, yet is has been overlooked by Groovy community.
Maybe I am wrong on this, I try to reduce imperative coding to absolute minimum. Fpiglet is not about exposing functional interface outside with lots of imperative code inside. It is very much about 'Eat your own dog food'.
Also, my goal is to develop on top of Groovy, and not to meta-program Groovy into something else. This is somewhat of a corollary because Groovy meta-programming techniques are imperative in nature. I intend to use them very rarely. Function composition is a very powerful 'meta-programmer' all by itself.
Also, I am not integrating http://functionaljava.org/ into Groovy (that is a nice idea on its own, but not a part of fpiglet).
Closures as CurriedFunctions (foundation of everything in Fpiglet):
If you never heard about curried functions: consider these simple 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}} ; ...
They are are all equivalent. You could think of them as a different syntax which represent the same 'curried function'. They obviously all behave very differently in Groovy. Fpiglet allows you to convert Groovy closures to curried functions. So you can do something like c1(1) or c3(1,2,3,4). This can be very powerful and can lead to a lot of code simplification.
import static fpig.common.functions.FpigBase.f
Closure c1 = {a, b, c, d-> a+b+c+d}
c1(1) //Groovy MissingMethodException
Closure fc1 = f(c1) //fpiglet converts closure to a curried function
assert fc1(1) instanceof Closure
assert fc1(1)(2)(3)(4) == 10
assert fc1(1,2,3,4) == 10
Closure c2 = {a -> {b -> {c -> {d -> a + b + c + d}}}}
c2(1, 2, 3, 4) //Groovy MissingMethodException
Closure fc2 = f(c2) //fpiglet converts closure to a curried function
assert fc2(1)(2)(3)(4) == 10
assert fc2(1,2,3,4) == 10
Fpiglet uses very few methods, instead it uses closures converted to curried functions. Closures are first class citizens in Groovy language, methods are not. (FunPiggyStyle.)
Lazy Lists (FunList)
Fpiglet implements immutable recursive lists called FunLists. You can easily move in and out between Groovy Lists and functional fpiglet FunLists using funlistOut and funlistIn functions.
//note << is function composition in Groovy, as well as, << passes argument to Closure
//finds square-plus-one numbers which are divisible by 5:
assert [5,10,50] == funlistOut << take(3) << filter {it % 5==0} << map {1 + it * it} << range(1, 10000)
using function composition decouples data from manipulation:
def manipulation = funlistOut << take(3) << filter {it % 5==0} << map {1 + it * it} << funlistIn
assert [5,10,50] == manipulation(1..10000)
Using just Groovy, you could define your own 'take', 'filter' and 'map' as Closures but then you would need to do this:
assert [5,10,50] == take.curry(3) << filter.curry({it % 5==0}) << map.curry({1 + it * it}) << (1..10000)
Which is much less readable (curry calls do not add to readability).
Fun with streams (Groovy goes to infinity):
FunList naturalnumbers = naturalNumbers()
def allSquaresPlusOneDivisibleBy5 = filter {it % 5==0} << map {1 + it * it} << naturalnumbers
assert [5,10,50] == funlistOutTake(3) << allSquaresPlusOneDivisibleBy5
Talking about infinity, Haskell programmers enjoy a nice brain teaser: Haskell can right-fold infinity. With a little trick Fpiglet can right-fold infinity too: FoldingInfinity .
Lazy Lists also support old OO style 'fluent programming' (but, such programming will be discouraged and maybe I will drop support for it):
FunList squaresPlusOneDivisibleBy5 = FunList.fromOoList(1..10000).map {1 + it * it}.filter {it % 5==0}
assert [5,10,50] == squaresPlusOneDivisibleBy5.fetch(3)
Also, Lazy Lists are lazy so not much happens until you start fetching records!
NOTE for developers new to functional programming: Would you be surprised if I told you that except of funlistIn
, funlistOut
functions, Fpiglet fully functional list library was developed without writing any loops?
In the above examples, we used funlistIn and funlistOut to move between Java/Groovy lists and functional FunLists. Fpiglet has other versions of funlistIn and funlistOut which work between FunLists and Strings and even between FunLists and I/O Streams (LazyIO).
import static fpig.strings.functions.InAndOutOfFunLists_forStrings.*
assert 4 == length << filter{it == 'e' as char} << funlistIn << "how many e's in this sentence?"
Fpiglet uses a very powerful high level polymorphism concept called Functor (FunctorPolymorphism). It allows Fpiglet to maintain a single functional list (FunList) library and simply 'map-over' functions in that library to other concepts! Here is one of the above examples repeated, but this time, functional list manipulation is done directly on Groovy List and results are again a Groovy List:
import static fpig.groovylists.functions.GroovyListAsFunList.*
assert [5,10,50] == take(3) << filter{it % 5==0} << map {1 + it * it} << (1..10000)
How about we do some functional computing on tokenized Strings:
//find length of shortest word
def fstrings = TokenizedStringsAsFunLists.withToken(' ')
assert 2 == fstrings.foldR(f (MIN << {it.length()}), 0) << 'that is not possible'
Obviously, there is no good support in Groovy for enforcing pure functional programming. Adhering to strict functional programming principles needs to become more of a convention. You can use Functor Mapping or funlistIn funlistOut functions as boundaries between pure and un-pure in your code.
Functor is a very powerful programming tool making the code for all of this converting almost trivial. The power of Functional Programming is not in writing lots of imperative code, do not work hard, work smart! (See also FunListToGroovyList, FunListToString, LazyIO.)
Functional Expressions:
Fpiglet offers DSL allowing to write more functional if-elseif-else conditional logic, where the if-else-elseif returns a value and even can become a Closure itself.
Two flavors of monadic comprehensions MonadicComprehensions are supported.
Here is one simple example using the 'select' syntax and based on the Maybe monad:
Closure maybeDivide = f { Maybe num, Maybe den ->
select{just(x/y)}.from {
x << { num }
y << { den }
where { y != 0}
}
}
assert just(2) == maybeDivide(just(8), just(4))
assert nothing() == maybeDivide(just(8), just(0))
Another DSL example is localized scope eval-where expression syntax (see Expressions).
Fpiglet has:
Functional types: Maybe (better alternative to null values) and Either (functional alternative to throwing Exceptions).
Monads: Fpiglet brings MonadPolymorphism to Groovy.
Applicative: Fpiglet supports elegant functional ApplicativePolymorphism extending zip functionality from functional lists to other concepts (such as Maybe monad or Either monad).