F# has a wonderfully light and succinct syntax. It uses the same keyword, let, for both values and functions.
let str = "Hello" let func a = a + str let another = func " World!" printfn "%s" another
Also, discriminated unions (sum types) are super simple
type SumType = |First |Second |Third of string * int |Fourth of a:string * b:int * c:double
Anytime I try out a new language I try to explore five things:
- Tail recursive functions
- Free Monad
- Pattern Matching
F#, as far as I can tell after using it for a total of about 10 hours, can be used for 1-3 and 5 but not 4. Here is a brief synopsis of my trials.
This was extremely simple in F#. Its two steps:
- Mark a function as recursive using the rec keyword
- Put the recursive step as the last operation
module Recursion //need a keyword for recursion, otherwise boilerplate free //very nice let rec check many acc = if(0 = many) then acc else let a = many - 1 let b = acc + many check a b
The Recursion.check function uses the accumulator pattern to add int values from 1 to many to the initial accumulator value acc.
Functor & Monad
This took some work! F# lacks higher kinded polymorphism. My first attempt at this was:
type Functor<'A> = abstract map: 'A<'B> -> ('B -> 'C) -> 'A<'C>
Which produced a compile time error: "Type parameter cannot be used as type constructor." Ouch!!!
Luckily, F# type inference is good enough that we don't really need to define type classes with a little boilerplate. Here is a Functor & Monad example:
//cannot do higher kinds so no type classes //do not need higher kinds; type syntax takes care of it module CategoryList type Functor<'Type>() = member this.map(l: list<'Type>)(f: 'Type -> 'Return) = List.map f l type Monad<'Type>(func: Functor<'Type>) = member this.point(t: 'Type) = [t] member this.flatMap(l: list<'Type>)(f: 'Type -> list<'Return>) = List.concat(func.map l f)
Defining a Functor and Monad instance is simple. We don't need a type hierarchy here; we only need 3 functions.
module Category let check() = //instantiate monad let func = CategoryList.Functor<int>() let monad = CategoryList.Monad(func) //define monadic chain functions let flatMap l f = monad.flatMap f l let map l f= func.map f l //define helpers let mapper (x:int) = [1 .. x] let mapperStr (x:int) = x.ToString() let hundred = [1 .. 100]//data //(|>) language level monad support let hundredMore = (hundred |> flatMap mapper) printfn "%A" hundredMore let bigString = (hundredMore |> flatMap mapper |> map mapperStr) printfn "%A" bigString
The key here is the |> operator (also known as pipe). By piping a list into a flatMap into another flatMap and so on, we can get language level monad support.
Here I have failed. The closest I've gotten is:
module Free type FreeMonad<'F, 'A> = |Pure of 'A |Suspend of 'F type FreeMonad<'F, 'A> with member this.fold fpure fsuspend = match this with |Pure(a) -> fpure(a) |Suspend(free) -> fsuspend(free) let inline liftF(t:'F)(func: 'T): FreeMonad<'F, 'A> when ^T: (member map: (^F -> (^A -> ^AA) -> ^FF)) = Suspend(t) let inline point(a: 'A)(func: 'T): FreeMonad<'F, 'A> when ^T: (member map: (^F -> (^A -> ^AA) -> ^FF)) = Pure(a) let check(): Unit = let func = CategoryList.Functor<int>() let lst: list<int> = [1..10] let monad:FreeMonad<list<int>, int> = liftF(lst)(func) ()
I could never get the liftF function to work out. The combination of type parameters never type checks against the parameterized types AA and FF. The compiler just cannot understand the second layer of type inference that needs to happen for the map method to be recognized.
Pattern matching is pretty straight forward. You can do direct
let func a = match a with |1 -> "one" |2 -> "two" |3 | 5 | 10 | 30 -> "large" |_ -> "else"
let func a = match a with |value when 0 > value -> "negative" |value when 0 < value -> "positive" |_ -> "zero"
let func a = match a with |(str: string) -> "string" |_ -> "not"
type Union = |Simple |Pair of int * int |Triple of int * int * int let func a = match a with |Simple -> "simple" |Pair(a, b) -> "(" + a.ToString() + "," + b.ToString() + ")" |Triple(a, b, c) -> "(" + a.ToString() + "," + b.ToString() + c.ToString() + ")"
That's most of pattern matching, by combining these patterns you can get some pretty serious control folow moving through your application.