如何使用接口映射记录 [英] How can I map over a record using interfaces

查看:90
本文介绍了如何使用接口映射记录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出以下类型和值:

  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相关的注释:如果您希望 tmap 可用于任何类型的任何类型,那么 ITransform

更新



从评论中的讨论中可以看出,真正的问题描述如下:转换函数应该将 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


given the following types and value

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

I want to create a generic mapping function

tmap: X<'a, 'b> -> X<'x, 'y>

using interfaces and object expressions. My approach so far was

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
    }

However I get an error at 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

This is a follow up question to How to define a fmap on a record structure with F#.
I can get this solved by using the static member function approach described in one of the answers but not the interface approach

解决方案

Your ITransform 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.

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:

type ITransform = abstract Apply<'a, 'b, 'x, 'y> : Item<'a,'b> -> Item<'x,'y>

Or you can just drop them altogether, the compiler will lift them from the signature:

type ITransform = abstract Apply : Item<'a,'b> -> Item<'x,'y>

Now 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.

However, now you have another problem: implementing such interface in 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".

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 Items 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>

This reflects the idea: the first argument of incoming Item may be anything, and it gets converted to a string, and the second argument may be anything, and it is left intact.

With this definition, both tmap and mkStringify will compile.

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 tmap to work for any types whatsoever, then the implementers of ITransform must also support any types whatsoever.

Update

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 Item to something else, and leave the second argument intact. And the "something else" would be the same for both Items.

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:

type ITransform<'target> = abstract Apply : Item<'a, 'b> -> Item<'target, 'b>

With this definition, all three functions 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.

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:

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

这篇关于如何使用接口映射记录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆