通用F#函数:如何获取F#标记联合的类型?

4

代码示例:http://www.tryfsharp.org/create/dutts/Generics.fsx

我在我的F#代码中有一些映射代码,它将一个C#对象包装在一个带标记联合的结构中。

module MyModule =
    type MappedThings =
        | DoThings of External.Things.DoThings

    type MappedStuff =
        | DoStuff of External.Stuff.DoStuff

由于我在联合类型中始终使用与外部对象相同的名称,因此我希望尝试使我的映射代码具有可扩展性。到目前为止,这是我尝试过的:

let toDomain<'T> external : 'T =
    let found = FSharpType.GetUnionCases(typeof<'T>) |> Seq.where (fun t -> t.Name = external.GetType().Name) |> Seq.head
    FSharpValue.MakeUnion(found, [| box external |]) :?> 'T  

我正在尝试这样使用它:
let testThings = toDomain<MyModule.MappedThings> doExternalThings
let testStuff = toDomain<MyModule.MappedStuff> doExternalStuff 

这对于第一次调用很好用,但如果我尝试将其用于MyModule.MappedStuff类型,则会出现以下错误:

This expression was expected to have type DoThings but here has type DoStuff

我尝试过使用静态解析类型参数,^T,但typeof<^T>报错了。
我在想,如果可以将“Type”(如果这是正确的术语,例如MyModule.Mapped)作为参数传递,那么就能使其工作,但我不知道如何以编程方式获取它。
有人能帮忙吗?

我认为你需要在toDomain前面加上一个反直觉的关键字“inline”,以防止编译器对你的函数做出过多的推断并将其固定为1种类型。 - BitTickler
可能会带有一些类型约束。 - BitTickler
我已经更新了我的问题,并提供了一些关于错误的更多信息。如果我使用内联,我会得到相同的错误。这可能是因为我在一个F#脚本和F#交互中进行实验吗?我还没有尝试将这个放入一个项目中。 - Dutts
无法复现。您的函数确实是通用的(签名为 val toDomain:external:obj->'T),当我将您未声明的值“doExternalThings”和“doExternalStuff”替换为“External.DoThings”和“External.DoStuff”的实际实例时,它按预期工作。 - kaefer
这是我在 @kaefer 那里看到的 http://www.tryfsharp.org/create/dutts/Generics.fsx - Dutts
刚刚意识到我的生产代码和这里的经过净化的版本之间的区别,我的输入外部对象位于不同的命名空间中...所以如果相关的话,是External.Things.DoThings和External.Stuff.DoStuff。我已经更新了问题中的代码示例。 - Dutts
1个回答

4

我认为你在Makeunion函数的第二个参数中引入的额外装箱会使类型推断失去轨迹,实际上,该函数不再是通用的。要么标注该参数,要么将box移除。

let toDomain<'T> external : 'T =
    let found = FSharpType.GetUnionCases(typeof<'T>) |> Array.find (fun t -> t.Name = external.GetType().Name) 
    FSharpValue.MakeUnion(found, [| external |]) :?> 'T 

1
太好了,非常感谢!这个boxing是我尝试将其泛化之前的遗留问题。 - Dutts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接