给出以下类型和值
type Item<'a, 'b> = Item of 'a * 'b
type X<'a, 'b> = {
y: Item<'a, int>
z: Item<'b, bool>
}
let a = {
y = Item (false, 2);
z = Item (1, true)
}
我想创建一个通用映射函数
tmap: X<'a, 'b> -> X<'x, 'y>
使用接口和对象表达式。到目前为止,我的方法是
type ITransform<'a, 'b, 'x, 'y> = abstract Apply : Item<'a,'b> -> Item<'x,'y>
let inline tmap (f:ITransform<_,_,_,_>) ({y = yi; z = zi}) =
{
y = f.Apply yi
z = f.Apply zi
}
但是我在z = f.Apply zi
收到错误,因为f
被推断为ITransform<'a, int, 'b, int>
let mkStringify () =
{
new ITransform<_,_,_,_> with
member __.Apply(Item(a,b)) = Item (sprintf "%A" a, b)
}
let mkDublicate () =
{
new ITransform<_,_,_,_> with
member __.Apply(Item(a,b)) = Item ((a, a), b)
}
let x = tmap (mkStringify()) a
let y = tmap (mkDoublicate()) a
这是How to define a fmap on a record structure with F#的后续问题 我可以通过使用其中一个答案而不是接口方法中描述的静态成员函数方法来解决这个问题
答案 0 :(得分:2)
您的ITransform
定义并不比功能更好。您可以使用签名Item<'a,'b> -> Item<'x,'y>
的直接函数,它的工作原理相同。
使用界面的原因是每次调用方法时都可以使用不同的通用参数 。但这反过来意味着通用参数无法在接口本身上修复。他们必须在方法上:
type ITransform = abstract Apply<'a, 'b, 'x, 'y> : Item<'a,'b> -> Item<'x,'y>
或者你可以完全删除它们,编译器会将它们从签名中删除:
type ITransform = abstract Apply : Item<'a,'b> -> Item<'x,'y>
现在tmap
编译得很好:即使接口本身不是通用的,它的方法Apply
也是,因此它可以在每次调用时都有不同的泛型参数。
但是,现在你有另一个问题:在mkStringify
中实现这样的接口并不是那么简单。现在Apply
完全是通用的,它的实现不能返回特定类型,例如string
。你也不能拥有自己的蛋糕并且吃掉它:界面是一个承诺&#34;消费者和需求&#34;在实施者身上,所以如果你的消费者希望能够做任何&#34;那么实施者就必须强制执行&#34; 一切 &#34;
要解决此问题,请退一步思考您的问题:您想要实现的目标是什么?你想把什么转换成什么?到目前为止,在我看来,你试图将所有Item
的第一个参数强制转换为string
,同时保持第二个参数不变。如果这是目标,那么ITransform
的定义是显而易见的:
type ITransform = abstract Apply : Item<'a,'b> -> Item<string,'b>
这反映了这个想法:传入Item
的第一个参数可能是任何东西,它会被转换为string
,第二个参数可能是任何东西,它保持不变。
使用此定义,tmap
和mkStringify
都将编译。
如果这不是您的目标,那么请描述一下,我们也许能够找到另一种解决方案。但请记住上面与蛋糕相关的评论:如果您希望tmap
适用于任何类型,那么ITransform
的实施者也必须支持任何类型。
从评论中的讨论可以看出,真正的问题描述如下:转换函数应该将Item
的第一个参数转换为其他参数,并保留第二个参数。还有其他的东西&#34;两个Items
都是一样的。
有了这个,实现变得清晰:接口本身应该修复&#34;其他东西&#34;输出部分,该方法应采用任何类型作为输入:
type ITransform<'target> = abstract Apply : Item<'a, 'b> -> Item<'target, 'b>
通过此定义,将编译所有三个函数tmap
,mkStringify
和mkDuplicate
。我们找到了一个共同点:对接口使用者有足够的承诺,而对接口实现者没有太多的需求。
话虽如此,我认为你在这里真的不需要一个界面,但它有点矫枉过正。你不能使用函数的原因是函数在按值传递时会失去其通用性,因此不适用于不同类型的参数。但是,这可以通过将函数传递两次来解决。它会在两个实例中失去通用性,但它会以不同的方式丢失它 - 即每次都会使用不同的参数进行实例化。是的,两次传递相同的功能感觉很尴尬,但它的语法仍然比界面少:
let inline tmap f1 f2 ({y = yi; z = zi}) =
{
y = f1 yi
z = f2 zi
}
let stringify x =
let f (Item(a,b)) = Item (sprintf "%A" a, b)
tmap f f x
stringify a