约束泛型类型以继承f#中的泛型类型

时间:2014-12-23 19:12:34

标签: f#

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

我正在尝试创建一个函数,将函数f应用于元组中的两个项,并将结果作为元组返回。 F#类型推断表明mapTuple返回'b*'b元组。它还假定ab属于同一类型。

我希望能够传递两种不同的类型作为参数。您会认为这不起作用,因为它们都必须作为参数传递给f。所以我认为如果它们从同一个基类继承,它可能会起作用。

对于我想要完成的任务,这是一个不那么通用的函数。

let mapTuple (f:Map<_,_> -> Map<'a,'b>) (a:Map<int,double>,b:Map<double, int>) = (f a, f b)

但是,它会出现类型不匹配错误。

我该怎么办?我想在F#中完成什么呢?

3 个答案:

答案 0 :(得分:5)

古斯塔沃大多是对的;您要求的内容需要更高级别的类型。然而,

  1. .NET(以及扩展名为F#)支持(编码)更高级别的类型。
  2. 即使在Haskell,它支持&#34; nice&#34;表达此类类型的方式(一旦你启用了正确的扩展名),就不会推断出你的例子。
  3. 深入研究第2点可能很有价值:给定map f a b = (f a, f b),为什么Haskell推断出比map :: (t1 -> t) -> t1 -> t1 -> (t, t)更普遍的类型?原因是,一旦你包括更高级别的类型,通常不可能推断单个&#34;最通用的&#34;给定表达式的类型。实际上,鉴于上面的简单定义,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])
    4. (加上无限多)。但请注意,这些都是彼此不兼容的(没有一个比另一个更通用)。鉴于第一个,您可以拨打map id 1 'c',给定第二个,您可以拨打map (\_ -> 1) 1 'c',给定第三个,您可以拨打map (\x -> [x]) 1 'c',但这些参数只能 对每种类型都有效,而不是与其他类型有效。

      因此,即使在Haskell中,您也需要指定要使用的特定多态签名 - 如果您来自更动态的语言,这可能会有点意外。在Haskell中,这是相对干净的(语法是我上面使用的)。然而,在F#中你必须跳过一个额外的环:对于一个&#34; forall&#34;没有干净的语法。类型,所以你必须创建一个额外的名义类型。例如,要在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的第一个参数定义为表达式(而不是强制它成为某个单独类型的成员)。另一方面,显然比理想情况更多的样板......

答案 1 :(得分:4)

此问题与Haskell(通过扩展)支持的rank-n types有关,但在.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中它意味着 apply 所以对于haskellers f $ x来说,一个很好的细节读取已经将x应用于f

答案 2 :(得分:2)

冒着说明显而易见的风险,一个足够好的解决方案可能是让mapTuple采用两个函数而不是一个函数:

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

如果您的原始f是通用的,则将其作为fafb传递,将为您提供两个具有您正在寻找的类型的函数的具体实例。在最糟糕的情况下,当ab属于同一类型时,您只需要传递相同的函数两次。