Check out this page http://fsharpforfunandprofit.com/posts/elevated-world/ it has the best explanations with graphical representations of the idea of lifting.
The site is a treasure trove of good material for understanding F# in particular and functional programming in general.
In simple terms, the idea of lifting refers to taking a function f
that works with a simple type and creating a new version liftedF
that works with a generic type. How do we do this? We take the function f
pass it to another function and it returns a "new and improved" version of f
. For instance say you have a simple function that returns the square of an integer:
let square x = x * x // val square : int -> int
Simple right? You pass an int
and it returns another int
.
Now lets create a new version like this:
let squareArray xA = Array.map square xA // val squareArray : int [] -> int []
Wow! squareArray
can square a whole array of integers and it was so easy to create! All I needed to do is pass square
to Array.map
.
Look at the signatures square
is int -> int
and squareArray
is int [] -> int []
. That is lifting!
If you look at the signature of Array.map
is ('a -> 'b) -> 'a [] -> 'b []
which can be interpreted as receiving a function from type 'a
to 'b
and an array of 'a
s and returning an array of 'b
s. But it can also be interpreted as receiving a function from 'a
to 'b
'and returning a lifted function from 'a[]
to 'b[]
:
('a -> 'b) -> 'a [] -> 'b []
is the same as
('a -> 'b) -> ('a [] -> 'b [])
You can lift functions to any generic types.
What is a generic type? It is a type that has another type as a parameter, there are many generic types that you probably already know:
List<'t>
(also expressed as 't list
) is generic because you can have lists of different types like List<int>
, List<string>
, List<int * string>
, ...
Array<'t>
: Array<int>
, Array<string>
, Array<int * string>
, ...
Option<'t>
: Option<int>
, Option<string>
, Option<int * string>
, ...
Result<'t,'r>
: Result<int, string>
, Result<string, string>
, ...
You can lift functions to other generic types:
let squareOption xO = Option.map square xO // val squareOption : int option -> int option
let stringArray sL = Array.map string sL // val stringArray : int [] -> string []
It is all in the function signatures. We can take a function from...
int -> int
and get a List<int> -> List<int>
function
- ... or from
int -> int
to Option<int> -> Option<int>
- ... or from
string -> float
to string [] -> float[]