F# Computations 2

Map

Common Names: map, fmap, lift, Select

Common Operators: <$> <!>

What it does: Lifts a function into the elevated world

Signature: (a->b) -> E<a> -> E<b>.

Alternatively with the parameters reversed: E<a> -> (a->b) -> E<b>

Description “map” is the generic name for something that takes a function in the normal world and transforms it into a corresponding function in the elevated world.

An alternative interpretation of map is that it is a two parameter function that takes an elevated value E<a> and a normal function a->b, and returns a new elevated value E<b> generated by applying the function a->b to the internal elements of E<a>.

/// map for Options
let mapOption f opt =
    match opt with
    | None ->
        None
    | Some x ->
        Some (f x)
// has type : ('a -> 'b) -> 'a option -> 'b option

/// map for Lists
let rec mapList f list =
    match list with
    | [] ->
        []
    | head::tail ->
        // new head + new tail
        (f head) :: (mapList f tail)
// has type : ('a -> 'b) -> 'a list -> 'b list

Return

Common Names: return, pure, unit, yield, point

Common Operators: None

What it does: Lifts a single value into the elevated world

Signature: a -> E<a>

Description “return” (also known as “unit” or “pure”) simply creates an elevated value from a normal value.

This function goes by many names, but I’m going to be consistent and call it return as that is the common term for it in F#, and is the term used in computation expressions.

// A value lifted to the world of Options
let returnOption x = Some x
// has type : 'a -> 'a option

// A value lifted to the world of Lists
let returnList x  = [x]
// has type : 'a -> 'a list

Apply

Common Names: apply, ap

Common Operators: <*>

What it does: Unpacks a function wrapped inside an elevated value into a lifted function E<a> -> E<b>

Signature: E<(a->b)> -> E<a> -> E<b>

Description “apply” unpacks a function wrapped inside an elevated value (E<(a->b)>) into a lifted function E<a> -> E<b>

An alternative interpretation of apply is that it is a two parameter function that takes an elevated value (E<a>) and an elevated function (E<(a->b)>), and returns a new elevated value (E<b>) generated by applying the function a->b to the internal elements of E<a>.

For example, if you have a one-parameter function (E<(a->b)>), you can apply it to a single elevated parameter to get the output as another elevated value.

If you have a two-parameter function (E<(a->b->c)>), you can use apply twice in succession with two elevated parameters to get the elevated output.

module Option =

    // The apply function for Options
    let apply fOpt xOpt =
        match fOpt,xOpt with
        | Some f, Some x -> Some (f x)
        | _ -> None

module List =

    // The apply function for lists
    // [f;g] apply [x;y] becomes [f x; f y; g x; g y]
    let apply (fList: ('a->'b) list) (xList: 'a list)  =
        [ for f in fList do
          for x in xList do
              yield f x ]

let add x y = x + y 


let resultOption =
    let (<*>) = Option.apply
    (Some add) <*> (Some 2) <*> (Some 3)
// resultOption = Some 5

let resultList =
    let (<*>) = List.apply
    [add] <*> [1;2] <*> [10;20]
// resultList = [11; 21; 12; 22]

Apply vs. Map

The combination of apply and return is considered “more powerful” than map, because if you have apply and return, you can construct map from them, but not vice versa.

Here’s how it works: to construct a lifted function from a normal function, just use return on the normal function and then apply. This gives you the same result as if you had simply done map in the first place.

let resultOption2 =
    let (<!>) = Option.map
    let (<*>) = Option.apply
    add <!> (Some 2) <*> (Some 3)
// resultOption2 = Some 5

let resultList2 =
    let (<!>) = List.map
    let (<*>) = List.apply
    add <!> [1;2] <*> [10;20]
// resultList2 = [11; 21; 12; 22]

let batman =
    let (<!>) = List.map
    let (<*>) = List.apply
    // string concatenation using +
    (+) <!> ["bam"; "kapow"; "zap"] <*> ["!"; "!!"]

// result =
// ["bam!"; "bam!!"; "kapow!"; "kapow!!"; "zap!"; "zap!!"]

Zip, ZipList

Common Names: zip, zipWith, map2

Common Operators: <*> (in the context of ZipList world)

What it does: Combines two lists (or other enumerables) using a specified function

Signature: E<(a->b->c)> -> E<a> -> E<b> -> E<c> where E is a list or other enumerable type, or E<a> -> E<b> -> E<a,b> for the tuple-combined version.

Description Some data types might have more than one valid implementation of apply. For example, there is another possible implementation of apply for lists, commonly called ZipList or some variant of that.

In this implementation, the corresponding elements in each list are processed at the same time, and then both lists are shifted to get the next element. That is, the list of functions [f; g] applied to the list of values [x; y] becomes the two-element list [f x; g y]


// alternate "zip" implementation
// [f;g] apply [x;y] becomes [f x; g y]
let rec zipList fList xList  =
    match fList,xList with
    | [],_
    | _,[] ->
        // either side empty, then done
        []
    | (f::fTail),(x::xTail) ->
        // new head + new tail
        (f x) :: (zipList fTail xTail)
// has type : ('a -> 'b) -> 'a list -> 'b list

Bind

Called for let! and do! in computation expressions.

M<'T> * ('T -> M<'U>) -> M<'U>

The let! keyword binds the result of a call to another computation expression to a name. let! is defined by the Bind(x, f) member on the builder type.

The do! keyword is for calling a computation expression that returns a unit-like type (defined by the Zero member on the builder)

  • Bind is also actually functional concepts than functions
  • Bind is about lifting a function input that to an “Elavated” world
  • Bind is similar to Linq’s SelectMany method

Common Names: bind, flatMap, andThen, collect, SelectMany

Common Operators: >>= (left to right), =<< (right to left )

What it does: Allows you to compose world-crossing (“monadic”) functions

Signature: (a->E<b>) -> E<a> -> E<b>

Alternatively with the parameters reversed: E<a> -> (a->E<b>) -> E<b>

Description We frequently have to deal with functions that cross between the normal world and the elevated world.

An alternative interpretation of bind is that it is a two parameter function that takes a elevated value E<a> and a “monadic function” a -> E<b>, and returns a new elevated value E<b> generated by “unwrapping” the value inside the input, and running the function a -> E<b> against it.

For example: a function that parses a string to an int might return an Option<int> rather than a normal int, a function that reads lines from a file might return IEnumerable<string>, a function that fetches a web page might return Async, and so on.

These kinds of “world-crossing” functions are recognizable by their signature a -> E<b>; their input is in the normal world but their output is in the elevated world. Unfortunately, this means that these kinds of functions cannot be linked together using standard composition.

type Int32 with 
    static member ParseAsOption str = 
        match Int32.TryParse (str:string) with
        | false, _ -> None
        | true, x -> Some x

let bindOption f opt = 
    match opt with 
    | Some x -> f x
    | None -> None

let joinOption opt = 
    match opt with
    | Some innerOpt -> innerOpt
    | None -> None

let bindOption2 f opt = joinOption (Option.map f opt)


let input1 = Some "abcd" |> bindOption Int32.ParseAsOption
printfn "%A" input1
// None

let input2 = Some "100" |> bindOption2 Int32.ParseAsOption
printfn "%A" input3
// Some 100

let input3 = Some "200" |> Option.map Int32.ParseAsOption
printfn "%A" input3
// Some (Some 200)

let (>>=) m f = Option.bind f m

let add x y = x + y 

let liftedAdd = Some add

let apply f m = 
    match f, m with
    | Some f, Some m -> Some(f m)
    | _ -> None


let (<*>) = apply


let input4 = 
    liftedAdd <*>
        (Some "100" >>= Int32.ParseAsOption) <*>
        (Some "200" >>= Int32.ParseAsOption) 

printfn "%A" input4