Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a fmap on a record structure with F#

is there a possibility to create an fmap for records so that I can apply the same function to record fields of similar bur different types

Let say I have a record field type Item and a record X and function transform

type Item<'a, 'b> = Item of 'a * 'b

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i
    Item (x, sprintf "%A" y)

type X<'a> = {
    y: Item<'a, int>
    z: Item<'a, bool>
}
with
    member inline this.fmap(f) =
        {
            y = f this.y
            z = f this.z
        }

now the line z = f this.z complains that the given type should be of Item<'a, int> but its of type Item<'a, bool>. Obviously as the type infererrer
has decided that the function f is of type Item<'a, int> -> Item<...> however i want f to be applied polymorphic. How can I get this done?

Evil type hacks are welcome!

like image 705
robkuz Avatar asked Dec 13 '16 14:12

robkuz


2 Answers

An obvious solution is to use bimap instead of fmap and then write twice the function at the caller site:

type Item<'a, 'b> = Item of 'a * 'b

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i
    Item (x, sprintf "%A" y)

type X<'a> = {
    y: Item<'a, int>
    z: Item<'a, bool>
}

with
    member inline this.bimap(f, g) =
        {
            y = f this.y
            z = g this.z
        }

Another alternative (here's the evil type hack) is instead of passing a function, pass what I call an 'Invokable' which some kind of function wrapped in a type with a single method called Invoke. Something like a delegate but static.

Here's an example. I use $ instead of Invoke for simplicity:

let inline fmap invokable ({y = y1; z = z1}) = {y = invokable $ y1; z = invokable $ z1}


type Id = Id with 
    static member ($) (Id, Item (a,b)) = Item (id a, id b)

type Default = Default with 
    static member ($) (Default, Item (a:'t,b:'u)) = 
        Item (Unchecked.defaultof<'t>, Unchecked.defaultof<'u>)

let a = {y = Item ('1', 2); z = Item ('3', true) }

let b = fmap Id a
let c = fmap Default a

Now the problem is I can't think of many other useful functions. Can you?

Otherwise if you make it more generic:

type X<'a, 'b, 'c> = {
    y: Item<'a, 'b>
    z: Item<'a, 'c>
}

then you can for instance use an Invokable like this:

type ToList = ToList with static member ($) (ToList, Item (a,b)) = Item ([a], [b])

let d = fmap ToList a
// val d : X<char list,int list,bool list> = {y = Item (['1'],[2]);
                                       z = Item (['3'],[true]);}

See also this related question. The case presented there is simpler but the problem is the same.

Also this one is related.

like image 199
Gus Avatar answered Sep 29 '22 09:09

Gus


I agree with @Fyodor that using an interface is the cleanest solution if you need to express a polymorphic argument:

type Item<'a, 'b> = Item of 'a * 'b

let transform (i: Item<'a, 'b>) : Item<'a, string> = 
    let (Item (x, y)) = i
    Item (x, sprintf "%A" y)

type ITransform<'a,'x> = abstract Apply : Item<'a,'b> -> Item<'x,'b>

type X<'a> = {
    y: Item<'a, int>
    z: Item<'a, bool>
}
with
    member inline this.fmap(f:ITransform<_,_>) =
        {
            y = f.Apply this.y
            z = f.Apply this.z
        }
{ y = Item(1,2); z = Item(3,true) }.fmap 
    { new ITransform<_,_> with member __.Apply(Item(i,x)) = Item(i+1, x) }
like image 31
kvb Avatar answered Sep 29 '22 11:09

kvb