Your 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>
Now tmap
编译良好:即使接口本身不是通用的,它的方法Apply
是,因此每次调用都可以有不同的通用参数。
然而,现在你有另一个问题:实现这样的接口mkStringify
并不是那么简单。现在Apply
是完全通用的,它的实现不能返回特定类型,例如string
。鱼与熊掌不可兼得:界面是对消费者的“承诺”,也是对实施者的“需求”,所以如果你的消费者期望能够做到“anything“,那么实施者必须遵守并实施”一切".
要解决这个问题,请退一步思考您的问题:您到底想要实现什么?你想把什么转化成什么?到目前为止,在我看来,你正试图强迫所有论点中的第一个论点Item
s to string
,同时保持第二个参数不变。如果这是目标,那么定义ITransform
很明显:
type ITransform = abstract Apply : Item<'a,'b> -> Item<string,'b>
这反映了这个想法:传入的第一个参数Item
可能是任何东西,它会被转换为string
,第二个参数可以是任何东西,并且它保持不变。
有了这个定义,两者tmap
and mkStringify
将编译。
如果这不是您的目标,请描述一下,我们也许可以找到其他解决方案。但请记住上面与蛋糕相关的评论:如果你想要tmap
为任何类型工作,那么实现者ITransform
还必须支持任何类型。
Update
从评论中的讨论可以明显看出,真正的问题描述如下:转换函数应该转换Item
到其他东西,并保持第二个参数不变。并且“其他东西”对于两者来说都是相同的Items
.
这样,实现就变得清晰了:接口本身应该修复输出的“其他”部分,并且该方法应该采用任何类型作为输入:
type ITransform<'target> = abstract Apply : Item<'a, 'b> -> Item<'target, 'b>
根据这个定义,所有三个函数tmap
, mkStringify
, and 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