我有一个函数f op = (op 1 2, op 1.0 2.0)
,它需要像这样工作:
f (+)
(3, 3.0)
但是在不声明它的类型的情况下,f
它的工作方式如下:
f (+)
(3.0, 3.0)
我正在努力声明f
. 它应该采用一个适用于所有实例的运算符Num
。在 Haskell 中甚至有可能吗?
我有一个函数f op = (op 1 2, op 1.0 2.0)
,它需要像这样工作:
f (+)
(3, 3.0)
但是在不声明它的类型的情况下,f
它的工作方式如下:
f (+)
(3.0, 3.0)
我正在努力声明f
. 它应该采用一个适用于所有实例的运算符Num
。在 Haskell 中甚至有可能吗?
问题是您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 #-}
.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 :: Int
并1.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)