有没有办法在haskell中传递未知类型的运算符?

问题描述

我有一个函数 f op = (op 1 2,op 1.0 2.0),它需要像这样工作:

f (+)
(3,3.0)

但是没有声明 f 的类型,它是这样工作的:

f (+)
(3.0,3.0)

我正在努力声明 f 的类型。它应该采用一个适用于所有 Num 实例的运算符。在 Haskell 中甚至可能吗?

解决方法

问题在于您通过将运算符应用于 Fractional1.0 等小数来强制运算符处理 2.0 类型。您的代码类型检查是因为 FractionalNum 的子类型(意味着 Fractional 的每个实例也是 Num 的实例)。

以下 GHCi 中的实验应该清楚:

Prelude> :t 0
0 :: Num p => p
Prelude> :t 0.0
0.0 :: Fractional p => p
Prelude> :t 0 + 0.0  -- Fractional taking advantage!
0 + 0.0 :: Fractional a => a

因此,如果您想让它完全在 Num 上工作,您只需要摆脱那些“.0”:

Prelude> f op = (op 1 2,op 1 2)
Prelude> f (+)
(3,3)

不过

如果您真的需要返回元组的第二个元素是 Fractional 而第一个元素是一些更通用的 Num 的行为,事情会变得有点复杂。

您传递给 f 的运算符(或实际上是函数)必须与同时出现两种不同的类型。这在普通的 Haskell 中通常是不可能的,因为每个类型变量在每个应用程序中都有一个固定的赋值——这意味着 (+) 需要决定它是 Float 还是 Int 或什么。

然而,这可以改变。您需要做的是通过在 GHCi 中编写 Rank2Types 或在 :set -XRank2Types 文件的最顶部添加 {-# LANGUAGE Rank2Types #-} 来打开 .hs 扩展。这将允许您以参数类型更加动态的方式编写 f

f :: (Num t1,Fractional t2)
  => (forall a. Num a => a -> a -> a) -> (t1,t2)
f op = (op 1 2,op 1.0 2.0)

现在类型检查器不会将任何固定类型分配给 op,而是将其保留为多态以进行进一步的专业化。因此,它可以应用于同一上下文中的 1 :: Int1.0 :: Float

确实

Prelude> f (+)
(3,3.0)

来自评论的提示:可以放宽类型约束以使函数更通用:

f :: (Num t1,Num t2)
  => (forall a. Num a => a -> a -> a) -> (t1,op 1 2)

它在所有情况下都能正常工作,以前版本的 f 可以做更多一些,其中返回的对的 snd 可能是例如 Int

Prelude> f (+)
(3,3)
Prelude> f (+) :: (Int,Float)
(3,3.0)