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

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.


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) }