首页 > 解决方案 > 是否可以编写一个genericFunction<'T>,其中'T 被限制为'T2*'T3 类型的元组?

问题描述

是否可以在 F# 中编写一个泛型函数,在其中指定类型 'T,但将 'T 约束为类型为 'T2*'T3 的元组?

这是我尝试过的一些只会产生编译器错误的事情:

let fun1<'T when 'T :> 'T2*'T3> () = …
let fun2<'T, 'T2, 'T3 when 'T :> 'T2*'T3> () = …

(在 fun2 中,'T2/'T3 最好由 'T1 推断。)

我想这样做的原因是我正在编写一个带有内存数据库的应用程序,并且我正在尝试编写一个通用函数来提取与指定元组形式中的数据库实体关联的数据。我有一个可行的解决方案,但有一些缺点:

let workingExtractEntity<'T> (id : EntityID) (db: Database) : EntityID*'T =
        let t = db.GetEntityComponent id typeof<'T>
        (id, t)

以及具有更多类型参数的类似函数:

let workingExtractEntity2<'T1, 'T2> (id : EntityID) (db: Database) : EntityID*'T1*'T2 = …

这种方法的主要问题是每次使用函数时都必须手动输入和排序通用参数。我希望能够指定单个元组类型,并结合常用元组类型的类型别名,如下所示:

//Obviously won’t compile, but illustrates what I would like to do.
let extractEntity2<'T when 'T :> EntityID*'T2*'T3> id db : 'T =
        let t2 = db.GetEntityComponent id typeof<'T2>
        let t3 = db.GetEntityComponent id typeof<'T3>
        (id, t2, t3)

type Player = EntityID*Position*Health
    
let extractPlayer id db : Player = extractEntity2<Player> id db

请注意,没有 Player 类型的对象存储在数据库中,而只有构成 Player 的组件(即 Position 和 Health)存储在那里。(任何具有与其关联的位置和生命值的实体都可以表示为玩家。)

有没有办法实现这样的目标?

标签: genericsf#

解决方案


我做了一些研究,我想我已经找到了自己问题的答案:

不幸的是,我想做的事情在 F# 中似乎是不可能的,用户 Gus在这里很好地总结了原因:

恐怕没有办法将子类型约束添加到基于 F# 中另一个的泛型类型参数。它们总是被假定为相等的,请参阅规范type :> 'b 的新约束再次解决为 type = 'b

这个限制在 C# 中不存在,这使得互操作有时很麻烦。F# 语言建议 github 页面上存在一个问题来解决它。

换句话说,您不能编写这样的函数:

let foo<'T, 'U when 'T :> 'U> () = …

这意味着你也不能这样做:

let foo2<'T, 'T2, 'T3 when 'T :> Tuple<'T2, 'T3>> () = …

这基本上是我想要的,除了额外的类型推断,而不是输入整个:

let callFoo2 = foo2<int*string, int, string> ()

你可以这样写,并从'T1推断'T2和'T3:

let callFoo2Differently = foo2<int*string> ()

推荐阅读