The obvious solution is to use bimap instead of fmap , and then double the function on the callerโs website:
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 (hacking an evil type here) instead of passing a function passes what I call "Invokable", which is some kind of function wrapped in a type using a single method called Invoke . Something like a delegate, but static.
Here is 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 that I cannot come up with many other useful features. Can you?
Otherwise, if you make it more general:
type X<'a, 'b, 'c> = { y: Item<'a, 'b> z: Item<'a, 'c> }
then you can, for example, use Invokable as follows:
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 example presented there is simpler, but the problem is the same.
Also linked is this one .