3

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

f (+)
(3, 3.0)

但是在不声明它的类型的情况下,f它的工作方式如下:

f (+)
(3.0, 3.0)

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

4

1 回答 1

6

问题是您Fractional通过将运算符应用于小数(例如1.0和)来强制运算符处理类型2.0。您的代码Fractional类型检查因为是Num(意味着每个实例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完全在 s 上工作,你只需要去掉那些“.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 #-}.hsf

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, t2)
f op = (op 1 2, op 1 2)

它在所有情况下都可以正常工作,以前的版本f会加上一些snd返回的对可能是的地方,例如Int

Prelude> f (+)
(3,3)
Prelude> f (+) :: (Int, Float)
(3,3.0)
于 2020-12-23T11:55:46.503 回答