如何使用F#在记录结构上定义fmap

时间:2016-12-13 14:21:45

标签: interface f# polymorphism

是否有可能为记录创建一个fmap,以便我可以应用相同的功能来记录类似bur不同类型的字段

假设我有一个记录字段类型Item和一条记录X和一个功能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
        }

现在,行z = f this.z会抱怨给定的类型应为Item<'a, int>,但类型为Item<'a, bool>。显然作为类型传感器
已决定函数f的类型为Item<'a, int> -> Item<...>,但我希望f应用于多态。我怎么能这样做?

欢迎邪恶的黑客攻击!

2 个答案:

答案 0 :(得分:2)

一个明显的解决方案是使用bimap而不是fmap,然后在调用者网站上使用两倍的函数:

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
        }

另一种选择(这里是邪恶的黑客攻击)不是通过一个函数,而是通过我称之为“Invokable”的函数。哪种函数用一个名为Invoke的方法包装在一个类型中。像委托但是静态的东西。

这是一个例子。为简单起见,我使用$代替Invoke

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

现在问题是我无法想到许多其他有用的功能。你能吗?

否则,如果你使它更通用:

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

然后你可以使用像这样的Invokable:

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

另见this related question。提出的案例比较简单,但问题是一样的。

this one也是相关的。

答案 1 :(得分:2)

我同意@Fyodor如果你需要表达一个多态参数,使用接口是最干净的解决方案:

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