generics - 如何使用接口(interface)映射记录

标签 generics f#

给定以下类型和值

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>

使用接口(interface)和对象表达式。到目前为止我的方法是

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# 的后续问题.
我可以通过使用答案之一中描述的静态成员函数方法来解决这个问题,但不能使用接口(interface)方法

最佳答案

您的ITransform定义并不比函数好。您可以直接使用签名 Item<'a,'b> -> Item<'x,'y> 的直接函数,效果是一样的。

使用接口(interface)的原因是每次调用方法时都可以有不同的泛型参数。但这反过来又意味着通用参数不能固定在接口(interface)本身上。它们必须位于方法上:

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编译良好:即使接口(interface)本身不是通用的,它的方法 Apply是,因此每次调用时它都可以有不同的通用参数。

但是,现在你遇到了另一个问题:在 mkStringify 中实现这样的接口(interface)并不是那么简单。现在Apply是完全通用的,它的实现不能返回特定类型,例如 string 。鱼与熊掌不可兼得:界面是对消费者的“ promise ”,也是对实现者的“需求”,因此,如果您的消费者希望能够做“任何事情” >”,那么实现者必须遵守并实现“一切”。

要解决这个问题,请退一步思考您的问题:您到底想要实现什么?你想把什么转化成什么?到目前为止,在我看来,你试图强制所有 Item 中的第一个参数。发送至string ,同时保持第二个参数不变。如果这是目标,那么 ITransform 的定义显而易见:

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

这反射(reflect)了这个想法:传入 Item 的第一个参数可能是任何东西,它会被转换为 string ,第二个参数可以是任何东西,并且它保持不变。

根据这个定义,tmapmkStringify将编译。

如果这不是您的目标,请描述一下,我们也许可以找到其他解决方案。但请记住上面与蛋糕相关的评论:如果你想要 tmap适用于任何类型,那么 ITransform 的实现者还必须支持任何类型。

更新

从评论中的讨论可以明显看出,真正的问题描述如下:转换函数应该转换Item的第一个参数。到其他东西,并保持第二个参数不变。而“其他东西”对于两者来说都是相同的 Items .

这样,实现就变得清晰了:接口(interface)本身应该修复输出的“其他”部分,并且该方法应该采用任何类型作为输入:

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

根据这个定义,所有三个函数 tmap , mkStringify ,和mkDuplicate将编译。我们找到了一个共同点:对接口(interface)消费者有足够的 promise ,对接口(interface)实现者没有太多要求。

话虽如此,我认为您实际上并不需要这里的界面,这有点过分了。不能使用函数的原因是函数在按值传递时将失去其通用性,因此不适用于不同类型的参数。然而,可以通过两次传递该函数来解决这个问题。在这两种情况下它都会失去通用性,但它会以不同的方式失去通用性——即每次都会用不同的参数实例化。是的,两次传递同一个函数感觉很尴尬,但它仍然比接口(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

关于generics - 如何使用接口(interface)映射记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45088899/

相关文章:

java - 使用泛型时,List 内部如何检查方法调用的类型?

Swift 找不到正确的类型

f# - 如何避免在 F# 中双重包装到 Some

F#:此 'let' 之后的 block 未完成。期待一个表情

c# - 为什么不能在xunit.net理论中将List<int>转换为TCollection?

java - 将 String、ArrayList 等对象添加到 List<Object>

ios - Swift:非通用结构中的通用初始化

f# - 如何从 C#/WPF 使用的接口(interface)创建 F# 列表

c# - 将 Polly HTTP 请求转换为 F#

f# - 使用 Map.remove 和键列表