将多态函数应用于两种不同类型的输入

Jos*_*ica 5 haskell types parametric-polymorphism

考虑这个功能:

doToBoth f x y = (f x, f y)
Run Code Online (Sandbox Code Playgroud)

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

doToBoth (2 *) 10 15 == (20, 30)
doToBoth head [1,2] [3,4,5] == (1, 3)
Run Code Online (Sandbox Code Playgroud)

然后我尝试了这些:

doToBoth head [1,10,100] "apple"
doToBoth pred 2 'b'
Run Code Online (Sandbox Code Playgroud)

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

doToBoth :: (a1 -> a2) -> a1 -> a1 -> (a2, a2)
Run Code Online (Sandbox Code Playgroud)

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

doToBoth :: (t ~ (i1 -> o1), t ~ (i2 -> o2)) => t -> i1 -> i2 -> (o1, o2)
Run Code Online (Sandbox Code Playgroud)

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

doToBoth :: (i2 -> o2) -> i2 -> i2 -> (o2, o2)
Run Code Online (Sandbox Code Playgroud)

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

lef*_*out 4

接受多态参数会使您的函数具有 2 级多态性。GHC 有一个扩展,但只有当您能够以某种方式量化参数必须支持带有类型构造函数或类的 \xe2\x80\x93 的类型时,您才能使用它。例如,对于列表的情况,你可以写

\n\n
{-# LANGUAGE Rank2Types, UnicodeSyntax #-}\ndoToBoth_listelem :: (\xe2\x88\x80 x . [x] -> x) -> [a] -> [b] -> (a,b)\ndoToBoth_listelem f x y = (f x, f y)\n
Run Code Online (Sandbox Code Playgroud)\n\n
> doToBoth_listelem head [1,10,100] "apple"
(1,\'a\')
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
doToBoth_enum :: (Enum a, Enum b)\n      => (\xe2\x88\x80 x . Enum x => x -> x) -> a -> b -> (a,b)\ndoToBoth_enum f x y = (f x, f y)\n
Run Code Online (Sandbox Code Playgroud)\n\n
> doToBoth_enum pred 2 \'b\'
(1,\'a\')
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

  • @JosephSible我找不到通用的方法来做到这一点,除非我利用交叉类型,而交叉类型在Haskell中*不*可用(IIRC,交叉类型的推理是不可判定的,并且类型检查太昂贵)。我认为你需要像 `doBoth :: ((a->a') && (b->b')) -> a -> b -> (a', b')` 这样的东西,Haskell 没有。 (3认同)
  • 我想写“forall c”。(ca, cb) => (forall x.cx => x -> x) -> a -> b -> (a, b)`,但这不起作用。如果你显式地传递字典,我找到了一种使用“ConstraintKinds”来做到这一点的方法,但这不值得麻烦:给定“both :: forall ab c”。Dict (ca) -> Dict (cb) -> (forall x.Dict (cx) -> x -> x) -> a -> b -> (a, b)` 您将其调用为 `both (Dict : : Dict (Enum Int)) (Dict :: Dict (Enum Char)) (\ Dict -> pred) 2 'b'`。不过,这可能是更聪明的事情的起点。 (2认同)