Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F#, concise way to define map4 using map2 and map3

Tags:

f#

This question is for amusement only. Please don't take this question too seriously.

I am currently learning F#, and I am interested to see if there is a concise way to define map4, using existing functions List.map2, List.map3, pipe forward/backward, forward/backward composition, etc.

i.e.

let map4 f a b c d = ......
map4 f [a1;a2] [b1;b2] [c1;c2] [d1;d2] 

// output: [f(a1,b1,c1,d1); f(a2,b2,c2,d2)]

I can solve this recursively, or by defining a new operator (see the following URL)

http://www.fssnip.net/9W/title/nary-Seqmap-

http://call-with-cc-en.blogspot.sg/2009/04/applicative-functors-mapping-function.html

I can also solve this by combining List.map2 and List.map3, using partially applied functions f(a,b,?,?)

let map4 f a b c d =
    List.map3 (fun g y -> g y) (List.map2 f a b) c d

I can try to shorten my code above using forward composition (and make it as abstract/confusing as possible)

let map4 f a =
    List.map2 f a >> List.map3 id;;

// Output type: f:('a -> 'b -> 'c -> 'd -> 'e) ->
// a:'a list -> ('b list -> 'c list -> 'd list -> 'e list)

I would like to know if I can shorten it even further by getting rid of the "f" and "a", resulting in:

let map4 = ...... (* Use only List.map2, List.map3, |>, |<, >>, <<, etc.*) ..........

It will probably make it unnecessarily confusing, but it will be pretty cool. Thank you.


EDIT:

Adapting TheInnerLight's answer:

let inline (<!>) f xList = List.map f xList

let inline (<*>) gList xList = List.map2 (id) gList xList

let map4 f w x y z = f <!> w <*> x <*> y <*> z
let map5 f v w x y z = f <!> v <*> w <*> x <*> y <*> z
let map6 f u v w x y z = f <!> u <*> v <*> w <*> x <*> y <*> z
like image 241
CH Ben Avatar asked Mar 20 '17 10:03

CH Ben


1 Answers

This is a good use for the applicative style of programming, i.e. using applicative functors.

Just define the apply function and some helper operators:

module List =
    // val apply : f:('a -> 'b) list -> x:'a list -> 'b list
    let apply f x = List.map2 (fun f x -> f x) f x

    // val inline ( <!> ) : f:('a -> 'b) -> x:'a list -> 'b list
    let inline (<!>) f x = List.map f x

    // val inline ( <*> ) : f:('a -> 'b) list -> x:'a list -> 'b list
    let inline (<*>) f x = apply f x

Then use map and apply to define mapN functions.

    // val map2 : f:('a -> 'b -> 'c) -> x:'a list -> y:'b list -> 'c list
    let map2 f x y = f <!> x <*> y

    // val map3 : f:('a -> 'b -> 'c -> 'd) -> x:'a list -> y:'b list -> z:'c list -> 'd list
    let map3 f x y z = f <!> x <*> y <*> z

    // val map4 : f:('a -> 'b -> 'c -> 'd -> 'e) -> x:'a list -> y:'b list -> z:'c list -> a:'d list -> 'e list
    let map4 f x y z a = f <!> x <*> y <*> z <*> a

    // val map8 : f:('a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'h -> 'i) -> x:'a list -> y:'b list -> z:'c list -> a:'d list -> b:'e list -> c:'f list -> d:'g list -> e:'h list -> 'i list
    let map8 f x y z a b c d e = f <!> x <*> y <*> z <*> a <*> b <*> c <*> d <*> e

As you can see, you can keep adding arguments to define arbitrary mapNs to your heart's content.


Since the question specifically asks about using map2 or map3, you can do this in the same style although it's a little less concise, e.g:

    let map4_2 f x y z a = List.map2 f x y <*> z <*> a

    let map4_3 f x y z a = List.map3 f x y z <*> a

Hopefully you get the idea.


As a small aside, I think it's worth noting that any monad is automatically an applicative functor, so there is a wide array of types you could use this pattern with, here is an Async example.

module Async = 
    // val map : f:('a -> 'b) -> x:Async<'a> -> Async<'b>
    let map f x = async.Bind(x, async.Return << f)

    // val apply : f:Async<('a -> 'b)> -> x:Async<'a> -> Async<'b>
    let apply f x = async.Bind(f, fun fe -> map fe x)

    // val inline ( <!> ) : f:('a -> 'b) -> x:Async<'a> -> Async<'b>
    let inline (<!>) f x = map f x

    // val inline ( <*> ) : f:Async<('a -> 'b)> -> x:Async<'a> -> Async<'b>
    let inline (<*>) f x = apply f x

    // val map4 : f:('a -> 'b -> 'c -> 'd -> 'e) -> x:Async<'a> -> y:Async<'b> -> z:Async<'c> -> a:Async<'d> -> Async<'e>
    let map4 f x y z a = f <!> x <*> y <*> z <*> a
like image 104
TheInnerLight Avatar answered Nov 10 '22 07:11

TheInnerLight