如何使用接口映射记录 [英] How can I map over a record using interfaces
问题描述
给出以下类型和值:
type Item<'a,'b> ='a *'的项目b
type X<'a,'b> = {
y:Item<'a,int>
z:项目<'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> =抽象应用:项目<'a,'b> - >项< X Y'GT;
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()=
{
新的ITransform< _,_,_,_> (Item,(a,b))= Item(sprintf%Aa,b)
}
let mkDublicate()=
{
新IT转换< _,_,_,_> (Item(a,b))= Item((a,a),b)
}
let x = tmap(mkStringify()) a
let y = tmap(mkDoublicate())a
这是后续问题如何定义fmap在F#上的记录结构。
我可以通过使用其中一个答案中描述的静态成员函数方法而不是接口方法来解决此问题。
<解决方案您的
ITransform
定义并不比函数更好。您可能刚刚使用了一个直接的签名功能 Item<'a,'b> - > Item<'x,'y>
,它的工作原理是一样的。 使用接口的原因是你可以有不同的每次调用方法时的通用参数。但是这反过来又意味着通用参数不能在接口本身上固定。它们必须在方法中:
type ITransform = abstract应用<'a,'b,'x,'y> :项目<'a,'b> - >项< X Y'GT;
或者您可以完全放弃它们,编译器会将它们从签名中提取出来:
type ITransform = abstract Apply:Item<'a,'b> - >项< X Y'GT;
现在 tmap
编译良好:即使接口本身不是泛型的,它的方法 Apply
是,所以它在每次调用时可以有不同的通用参数。
然而,现在你又遇到了另一个问题:在 mkStringify
中实现这样的接口并不那么简单。既然 Apply 是完全通用的,它的实现不能返回特定类型,比如
string
。你也不能拥有你的蛋糕,也不能吃它:界面是对消费者的承诺,对实施者是需求,所以如果你的消费者希望能够做到 what ,那么实现者必须强制执行 everything 。
要解决这个问题,请退后一步,思考一下你的问题:正是你想达到的目的?你想转换成什么?到目前为止,我认为你试图强制所有 Item
s的第一个参数为 string
,while保持第二个参数不变。如果这是目标,那么 ITransform
的定义是显而易见的:
type ITransform = abstract Apply:Item<'a,'b> - >项<串, B个
这反映了这个想法:传入 Item的第一个参数
可能是任何东西,它会被转换为一个字符串
,第二个参数可能是任何东西,并保持不变。
使用这个定义, tmap
和 mkStringify
会编译。
如果这不是您的目标,请描述它,我们可能会找到另一个解决方案。但请记住上面的与cake相关的注释:如果您希望 从评论中的讨论中可以看出,真正的问题描述如下:转换函数应该将 的其他内容将会相同。接口本身应该修复输出的其他部分,并且该方法应该接受任何类型的输入: 使用此定义,所有三个函数 话虽如此,我认为你并不是真的需要一个界面,这是过度的。你不能使用函数的原因是函数在通过值传递时会失去通用性,所以不适用于不同类型的参数。然而,这可以通过两次传递函数来击败。它在两种情况下都会失去通用性,但它会以不同的方式丢失它 - 即每次都会以不同的参数进行实例化。是的,传递相同函数两次会感觉很尴尬,但它的语法仍然比接口少: given the following types and value I want to create a generic mapping function using interfaces and object expressions. My approach so far was However I get an error at This is a follow up question to How to define a fmap on a record structure with F#. Your The reason to use an interface is that you can have different generic parameters every time you call the method. But this, in turn, means that the generic parameters cannot be fixed on the interface itself. They must be on the method: Or you can just drop them altogether, the compiler will lift them from the signature: Now However, now you have another problem: implementing such interface in To fix this, step back and think about your problem: what is it exactly that you want to achieve? What do you want to convert into what? So far it seems to me that you're trying to coerce the first argument of all This reflects the idea: the first argument of incoming With this definition, both If this is not your goal, then please describe it, and we may be able to find another solution. But keep in mind the cake-related remark above: if you want From discussion in the comments it became evident that the real problem description is as follows: the conversion function should convert the first argument of the With this, the implementation becomes clear: the interface itself should fix the "something else" part for the output, and the method should take any types as input: With this definition, all three functions Having said that, I think that you don't really need an interface here, it's overkill. The reason you can't use a function is that a function will lose its genericity when passed by value, and so wouldn't be applicable to the different types of arguments. This, however, can be defeated by passing the function in twice. It will lose genericity in both instances, but it will lose it in different ways - i.e. it will be instantiated with different arguments every time. Yes, it feels awkward to pass the same function twice, but it's still less syntax than the interface:
这篇关于如何使用接口映射记录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! tmap
可用于任何类型的任何类型,那么 ITransform $ c的实现者$ c $也必须支持任何类型。
更新
Item
的第一个参数转换成别的东西,并保留第二个参数不变。对于 Items
。
type ITransform<'靶> =抽象应用:项目<'a,'b> - >项目<'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%Aa,b)
tmap ffx
stringify a
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
as f
is inferred to be 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
I can get this solved by using the static member function approach described in one of the answers but not the interface approachITransform
definition is no better than a function. You could have just used a straight up function of signature Item<'a,'b> -> Item<'x,'y>
, it would have worked the same.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
compiles fine: even though the interface itself is not generic, its method Apply
is, and so it can have different generic arguments on every call.mkStringify
is not that straightforward. Now that Apply
is completely generic, its implementation cannot return specific types, such as string
. You can't have your cake and eat it, too: the interface is a "promise" to the consumer and a "demand" on the implementer, so if your consumer expects to be able to do "anything", then the implementer must oblige and implement "everything".Item
s to string
, while keeping the second argument intact. If this is the goal, then the definition of ITransform
is obvious:type ITransform = abstract Apply : Item<'a,'b> -> Item<string,'b>
Item
may be anything, and it gets converted to a string
, and the second argument may be anything, and it is left intact. tmap
and mkStringify
will compile.tmap
to work for any types whatsoever, then the implementers of ITransform
must also support any types whatsoever.Update
Item
to something else, and leave the second argument intact. And the "something else" would be the same for both Items
.type ITransform<'target> = abstract Apply : Item<'a, 'b> -> Item<'target, 'b>
tmap
, mkStringify
, and mkDuplicate
will compile. We have found a common ground: just enough promise to the interface consumer, and not too much demand on the interface implementer.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