This article is independent of our framework but at least for me, the mental model described here can be handy when it comes to developing reusable functional code. Also, such mental model helps understanding F# library primitives such as the lock expression.
Anatomy of ML like languages
Traditionally, imperative programming languages consist of a sequence of statements,
separated by the "do that after this" operator ;
.
F# on the other hand is expression based and there are no statements.
Beside arithmetic expressions, there are bindings which persistently assign
a value to identifier (let
).
Identifiers are immutable variables and cannot be changed after binding a value to it.
1: 2: 3: |
|
Everything is a let expression
A mental model for programming reusable F# code is to think of programs
being bindings.
Unlike to Haskell, expression can have side effects, while computing their value.
Expressions which have an effect but yield no useful value have the predefined
type unit (a type with one single constructor named ()
).
1:
|
|
Let expressions have the grammar let v = e0 in e1
where e0
and e1
are
expressions again.
Informally, let expressions evaluate the right hand side (e0
) and assign its
value to the binding variable v
.
Sidenode:
- F# is not a pure functional programming language, i.e. the evaluation of each
expression might additionally have side effects, not visible in their type.
Uncontrolled side effects are problematic, if the semantics of a program cannot
be deduced easily. F# uses call-by-value semantics which means, that each
function argument is evalauted before calling the function. For now,
it is important to note, that e1
is always evaluted before continuing
in the rest of the expression (after the in
). Later you might recognize that this semantics
arises naturally out of the definition of let and lambda expressions.
Consider the expression:
1:
|
|
The nesting structure of the let bindings can be used to model side effects,
occuring sequentially.
In F# it is possible to neglect all those in
keywords resuting in this code:
1: 2: 3: 4: 5: |
|
Here, each line stands for an implicit in
keyword.
Again consider s1''
which has type unit. In order not to bind unit values to useless variable
all the time,
F# allows to use do
keyword to bind a useless value and forget about it.
1: 2: |
|
Since writing do
is boring one can ommit this construction and simply write
the right hand side of the invisible let binding.
1:
|
|
It is also possible to write multiple nested expressions in a single line
by using the ;
thingy.
1:
|
|
So everything invisibly is a let binding i.e. everything is the same, and therefore composes :) This has some advantages when it comes to providing libraries which look like language primitives. Let us define a function for locking on objects.
1: 2: 3: 4: 5: |
|
Let us look at the type: val myLock : o:'a -> f:(unit -> 'b) -> 'b
Given this we can use myLock similarly to the lock statement in C#.
1: 2: 3: 4: 5: 6: 7: |
|
Did i just say as beautiful as the lock statement in C#? From a composability point of view the higher order function is even more composable than the statement. As exercise, try to write equivalent C# code using the uncomposable lock statement.
There is no let expression. Everything is a lambda expression!!
Finally, let us understand the semantics of let expressions. Informally, let expressions bind the evaluated value of the right handside to a variable. In the continuation we can now refer to the new variable. If you remember your basic computer science courses this might sound a bit like variable substituion, doesn't it? In fact the let expression is totally useless, when defining semantics for the programming language.
from here very sketsch and buggy.
Let us now grab some theory by writing code. The syntax of lambda expressions (lambda calculus) is given as:
1: 2: 3: 4: |
|
Let expressions can now be implemented by simple using Application and Abstraction!!!
1: 2: 3: |
|
Let us now write a simple and inefficient interpreter (which informally defines the semantics of lambda terms)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
|
now let us implement evaluation
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: |
|
now let us use church encoding to represent numbers. and compute with them
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
|
Success! We implemented a full fledged turing complete functional programing language in less than 50 lines of code.
Conclusions
F# is syntactically and semantically based on ML. The functional parts of the language have strong foundation and compose surprisingly well. As one can argue working with church encoding as numbers is rather useless in reality. Still, the implementation shows the imposing connection between theory and practical programming languages.
Full name: Functional-language-fundamentals.x
Full name: Functional-language-fundamentals.useless
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Functional-language-fundamentals.s1'
Full name: Functional-language-fundamentals.s2'
Full name: Functional-language-fundamentals.myLock
static member Enter : obj:obj -> unit + 1 overload
static member Exit : obj:obj -> unit
static member Pulse : obj:obj -> unit
static member PulseAll : obj:obj -> unit
static member TryEnter : obj:obj -> bool + 5 overloads
static member Wait : obj:obj -> bool + 4 overloads
Full name: System.Threading.Monitor
System.Threading.Monitor.Enter(obj: obj, lockTaken: byref<bool>) : unit
Full name: Functional-language-fundamentals.mon
Full name: Microsoft.FSharp.Core.obj
Full name: Functional-language-fundamentals.mutableState
val ref : value:'T -> 'T ref
Full name: Microsoft.FSharp.Core.Operators.ref
--------------------
type 'T ref = Ref<'T>
Full name: Microsoft.FSharp.Core.ref<_>
Full name: Functional-language-fundamentals.someValueComputedInMutex
Full name: Functional-language-fundamentals.Identifier
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
| Application of Expr * Expr
| Abstraction of Identifier * Expr
| Var of Identifier
Full name: Functional-language-fundamentals.Expr
Full name: Functional-language-fundamentals.Let
Full name: Functional-language-fundamentals.replaceVariables
Full name: Functional-language-fundamentals.freeVars
module Set
from Microsoft.FSharp.Collections
--------------------
type Set<'T (requires comparison)> =
interface IComparable
interface IEnumerable
interface IEnumerable<'T>
interface ICollection<'T>
new : elements:seq<'T> -> Set<'T>
member Add : value:'T -> Set<'T>
member Contains : value:'T -> bool
override Equals : obj -> bool
member IsProperSubsetOf : otherSet:Set<'T> -> bool
member IsProperSupersetOf : otherSet:Set<'T> -> bool
...
Full name: Microsoft.FSharp.Collections.Set<_>
--------------------
new : elements:seq<'T> -> Set<'T>
Full name: Microsoft.FSharp.Collections.Set.singleton
Full name: Microsoft.FSharp.Collections.Set.difference
Full name: Microsoft.FSharp.Collections.Set.union
Full name: Functional-language-fundamentals.eval
Full name: Microsoft.FSharp.Collections.Set.contains
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf
Full name: Functional-language-fundamentals.one
Full name: Functional-language-fundamentals.succ
Full name: Functional-language-fundamentals.p
Full name: Functional-language-fundamentals.result