给定以下类型和值
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#的后续问题。
我可以通过使用其中一个答案中描述的静态成员函数方法来解决这个问题,但不能使用接口方法
发布于 2017-07-13 19:21:22
您的ITransform
定义并不比一个函数更好。你可以直接使用signature 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
。你不能既有蛋糕又吃蛋糕:接口是对消费者的“承诺”和对实现者的“要求”,所以如果你的消费者希望能够做“任何事”,那么实现者必须满足并实现“一切”。
要解决这个问题,退一步想想你的问题:你到底想要实现什么?你想把什么转换成什么?到目前为止,在我看来,您试图将所有Item
中的第一个参数强制转换为string
,同时保持第二个参数不变。如果这是目标,那么ITransform
的定义是显而易见的:
type ITransform = abstract Apply : Item<'a,'b> -> Item<string,'b>
这反映了这样的想法:传入Item
的第一个参数可以是任何参数,它被转换为string
,第二个参数可以是任何参数,并且保持不变。
有了这个定义,tmap
和mkStringify
都可以编译。
如果这不是你的目标,那么请描述一下,我们也许能找到另一个解决方案。但请记住上面与蛋糕相关的一句话:如果您希望tmap
支持任何类型,那么ITransform
的实现者也必须支持任何类型。
更新
从注释中的讨论可以明显看出,真正的问题描述如下:转换函数应该将Item
的第一个参数转换为其他参数,并保持第二个参数不变。对于两个Items
来说,“其他的东西”是相同的。
这样,实现就变得清晰了:接口本身应该修复输出的“其他内容”部分,方法应该接受任何类型作为输入:
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
https://stackoverflow.com/questions/45088899
复制相似问题