【问题标题】:Apply a polymorphic function to two different types of inputs [duplicate]将多态函数应用于两种不同类型的输入
【发布时间】:2019-01-10 22:43:54
【问题描述】:

考虑这个函数:

doToBoth f x y = (f x, f y)

它在简单的情况下按预期工作:

doToBoth (2 *) 10 15 == (20, 30)
doToBoth head [1,2] [3,4,5] == (1, 3)

然后我尝试了这些:

doToBoth head [1,10,100] "apple"
doToBoth pred 2 'b'

我希望它们都导致(1, 'a'),但它们只会导致类型错误。问题是doToBoth 的推断类型不够多态:

doToBoth :: (a1 -> a2) -> a1 -> a1 -> (a2, a2)

看到这一点,我尝试添加显式类型签名来修复它:

doToBoth :: (t ~ (i1 -> o1), t ~ (i2 -> o2)) => t -> i1 -> i2 -> (o1, o2)

这个类型签名被接受了,但它并没有解决问题,检查:t doToBoth 发生的事情发现它最终得到了一个与原始推断的类型相同的类型:

doToBoth :: (i2 -> o2) -> i2 -> i2 -> (o2, o2)

编写类型签名以使此函数按我想要的方式工作的正确方法是什么?

【问题讨论】:

  • ^ 如果这不能完全回答您的问题,您可以告诉我。在这里得到满足您特定问题的东西应该是一个稍微小的修改,但如果您愿意,我可以提供有关该修改的更多详细信息。
  • @DavidYoung 你的答案看起来几乎完美。将其转换为使用可见类型应用程序而不是代理并添加新的类/实例以支持 head 函数非常简单(如果需要,我可以将其中一个或两个编辑到您的答案中)。唯一的缺点是它实际上不适用于length 函数,而是用于length . toList 函数。自从你在 2015 年写了这些新的 GHC 扩展后,我尝试了一段时间来解决这个问题,但没有运气。

标签: haskell types parametric-polymorphism


【解决方案1】:

接受多态参数会使您的函数 rank-2 多态。 GHC 有an extension for that,但只有当你能以某种方式量化参数必须支持的类型时才能使用它——使用类型构造函数或类。例如,对于列表,您可以编写

{-# LANGUAGE Rank2Types, UnicodeSyntax #-}
doToBoth_listelem :: (∀ x . [x] -> x) -> [a] -> [b] -> (a,b)
doToBoth_listelem f x y = (f x, f y)
&gt; doToBoth_listelem head [1,10,100] "apple"<br>(1,'a')

这也适用于pred 示例,并且更有用。在这种情况下,您需要对受 Enum 类约束的参数进行量化:

doToBoth_enum :: (Enum a, Enum b)
      => (∀ x . Enum x => x -> x) -> a -> b -> (a,b)
doToBoth_enum f x y = (f x, f y)
&gt; doToBoth_enum pred 2 'b'<br>(1,'a')

我认为,编写它使其自动适用于参数可能需要的任何此类约束是不可能的。也许可以用一些聪明的类型族和约束类型来近似,但我怀疑它最终是否会实际可用。

【讨论】:

  • 使用您描述的解决方案,似乎对于每种不同类型的f,我都需要一个不同版本的doToBoth,这不是很方便。我真正想要的是你在最后一段中所说的不可能或至少不切实际。我将把它开放几天,以防其他人能想到一种方法,或者找到一个答案来解释 Haskell 的类型系统中缺少的东西,这使得它变得不可能。
  • @JosephSible 我找不到通用的方法来做到这一点,除非我利用 Haskell 中可用的交集类型(IIRC,对于交集类型的推断是不可确定的,并且类型检查太昂贵了)。我认为您需要像 doBoth :: ((a-&gt;a') &amp;&amp; (b-&gt;b')) -&gt; a -&gt; b -&gt; (a', b') 这样的东西,而 Haskell 没有。
  • 我想写forall c. (c a, c b) =&gt; (forall x. c x =&gt; x -&gt; x) -&gt; a -&gt; b -&gt; (a, b),但没有成功。如果您明确传递字典,我找到了一种使用ConstraintKinds 的方法,但这不值得麻烦:给定both :: forall a b c. Dict (c a) -&gt; Dict (c b) -&gt; (forall x. Dict (c x) -&gt; x -&gt; x) -&gt; a -&gt; b -&gt; (a, b),您将其调用为both (Dict :: Dict (Enum Int)) (Dict :: Dict (Enum Char)) (\ Dict -&gt; pred) 2 'b'。不过可能是更聪明的事情的起点。
  • @JonPurdy 这看起来只有在 f 是自同态时才有效,而 head 不是。
  • @JosephSible:是的,只是在这个答案中的第二个例子(doToBoth_enum
猜你喜欢
  • 1970-01-01
  • 2022-01-25
  • 1970-01-01
  • 1970-01-01
  • 2022-01-24
  • 2023-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多