限制泛型类型继承F#中的泛型类型

3
let mapTuple f (a,b) = (f a, f b)

我试图创建一个函数,该函数将函数f应用于元组中的两个项目,并将结果作为元组返回。F#类型推理说mapTuple返回'b*'b元组。它还假定ab是相同类型的。
我想能够传递两种不同的参数类型。你可能认为这不起作用,因为它们都必须作为参数传递给f。所以我想如果它们从同一基类继承,可能会起作用。
以下是我要完成的 less generic 函数。
let mapTuple (f:Map<_,_> -> Map<'a,'b>) (a:Map<int,double>,b:Map<double, int>) = (f a, f b)

不过,它会出现类型不匹配的错误。

我该怎么做呢?在F#中是否有可能实现我想要的目标?


我没有看到一个好的方法可以按照你想要的方式工作(而不通过将映射装箱/拆箱来放弃类型信息)。在理论上,一个好的解决方案是使用联合将你的两个映射类型包装起来,这样f就有一个单一的具体类型可以使用,但是否是一个好主意取决于你的问题。 - scrwtp
那样做是可行的,但由于这个函数主要是为了方便而设计的,必须处理一个联合体会失去其意义。 - phil
3个回答

6

Gustavo大部分是正确的;您所要求的需要更高级别的类型。 然而,

  1. .NET(以及F#)支持(一种编码的)更高级别的类型。
  2. 即使在Haskell中,它支持一种“美好的”方式来表达这样的类型(一旦启用了正确的扩展),它们也不会被推断为您的示例。

深入探讨第2点可能是有价值的:给定 map f a b = (f a, f b), 为什么Haskell不会推断出比 map :: (t1 -> t) -> t1 -> t1 -> (t, t) 更通用的类型? 原因是一旦包括更高级别的类型,通常不可能为给定表达式推断出单个“最通用”的类型。 实际上,根据上面的简单定义,对于map,有许多可能的更高级别签名:

  1. map :: (forall t. t -> t) -> x -> y -> (x, y)
  2. map :: (forall t. t -> z) -> x -> y -> (z, z)
  3. map :: (forall t. t -> [t]) -> x -> y -> ([x], [y])

(加上无限多个)。 但是请注意,它们都是彼此不兼容的(没有一个比另一个更通用)。 给定第一个,您可以调用map id 1 'c',给定第二个,您可以调用map (\_ -> 1) 1 'c',给定第三个, 您可以调用map (\x -> [x]) 1 'c',但这些参数仅适用于每种类型,而不适用于其他类型。

因此,即使在Haskell中,您也需要指定要使用的特定多态签名-如果您来自更动态的语言,则可能会感到有点惊讶。在Haskell中,这相对清晰(我以上所使用的语法)。但是,在F#中,您将不得不跳过一个额外的障碍:没有一个“forall”类型的干净语法,因此您将不得不创建一个附加的命名类型。例如,要在F#中编码上面的第一种类型,我会写类似于以下内容:

type Mapping = abstract Apply : 'a -> 'a

let map (m:Mapping) (a, b) = m.Apply a, m.Apply b

let x, y = map { new Mapping with member this.Apply x = x } (1, "test")

请注意,与Gustavo的建议相反,您可以将map的第一个参数定义为表达式(而不是强制它成为某个单独类型的成员)。另一方面,很明显有比理想情况下更多的样板文件...

我认为在Haskell中增加一个扩展,允许您直接使用函数编写类似于我的示例代码中的通用映射,这将是很好的。签名不应受到任何限制。在此示例中,它应该是mapTuple ::(t1-> t2)->(a1,b1)->(a2,b2),然后在调用函数时,类型推断可以进一步进行。 - Gus

4
这个问题与rank-n类型有关,Haskell支持该类型(通过扩展),但.NET类型系统不支持。
我发现一个解决此限制的方法是传递一个只有一个方法的类型而不是函数,然后使用静态约束定义内联map函数。例如,假设我有一些通用函数:toStringtoOption,我想将它们映射到不同类型的元组。
type ToString = ToString with static member inline ($) (ToString, x) = string x
type ToOption = ToOption with static member        ($) (ToOption, x) = Some x

let inline mapTuple f (x, y) = (f $ x, f $ y)

let tuple1 = mapTuple ToString (true, 42)
let tuple2 = mapTuple ToOption (true, 42)

// val tuple1 : string * string = ("True", "42")
// val tuple2 : bool option * int option = (Some true, Some 42)

ToString将返回相同类型,但可操作任意类型。 ToOption将返回两个不同类型的泛型。

通过使用二元运算符类型推断为您创建静态约束,并且我使用$,因为在Haskell中它表示应用,因此对于Haskellers来说,不错的细节是f $ x已经读取将x应用于f


这不是很方便。对于我想使用mapTuple的每个函数,我都必须创建一个全新的类型。 - phil
当然,我同意。更方便的做法是使用Rank-n类型并直接传递函数,但目前不可能实现,而且我认为在不久的将来也不会实现。 - Gus

2

显而易见,一个足够好的解决方案可能是使用一个mapTuple,它需要两个函数而不是一个:

let mapTuple fa fb (a, b) = (fa a, fb b)

如果您的原始函数 f 是通用的,将其作为 fafb 传递将为您提供两个具体的函数实例,具有所需的类型。在最坏的情况下,当 ab 是相同类型时,您只需要传递相同的函数两次即可。请注意保留 HTML 标签。

我简直不敢相信我没想到那个。不幸的是,我计划将该函数转换为运算符,但这种解决方案并不完全适用。 - phil

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