根据Hackage,RealFloat
类的这些功能是
...常量函数[s] ...
如果它们始终保持相同的值,那么不管此说明所建议的参数如何,为什么不简单使用:
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: Integer
floatDigits :: Int
floatRange :: (Int, Int)
...
答案 0 :(得分:11)
您建议的非功能方法应具有类型
floatRadix' :: RealFloat a => Integer
floatDigits' :: RealFloat a => Int
...
那些是模糊类型:存在一个a
类型变量,但是它实际上并未出现在=>
的右侧,因此无法推断从上下文。在标准的Haskell中,这实际上是您可以推断出类型变量的 only 方法:本地类型签名也只能传递给签名头,而不是约束。因此,无论您编写的是(floatDigits' :: Int)
还是(floatDigits' :: RealFloat Double => Int)
,它实际上都无法工作–编译器无法推断出您的意思是该方法的instance RealFloat Double
版本。
class RealFloat' a where
floatDigits' :: Int
instance RealFloat' Double where
floatDigits' = floatDigits (0 :: Double)
*Main> floatDigits' :: Int <interactive>:3:1: error: • No instance for (RealFloat' a0) arising from a use of ‘floatDigits'’ • In the expression: floatDigits' :: Int In an equation for ‘it’: it = floatDigits' :: Int *Main> floatDigits' :: RealFloat Double => Int <interactive>:4:1: error: • Could not deduce (RealFloat' a0) arising from a use of ‘floatDigits'’ from the context: RealFloat Double bound by an expression type signature: RealFloat Double => Int at :4:17-39 The type variable ‘a0’ is ambiguous • In the expression: floatDigits' :: RealFloat Double => Int In an equation for ‘it’: it = floatDigits' :: RealFloat Double => Int
由于这个原因,Haskell不允许您首先编写具有歧义类型的方法。实际上,如我在上面编写的那样尝试编译该类会给出以下错误消息:
• Could not deduce (RealFloat' a0) from the context: RealFloat' a bound by the type signature for: floatDigits' :: forall a. RealFloat' a => Int at /tmp/wtmpf-file3738.hs:2:3-21 The type variable ‘a0’ is ambiguous • In the ambiguity check for ‘floatDigits'’ To defer the ambiguity check to use sites, enable AllowAmbiguousTypes When checking the class method: floatDigits' :: forall a. RealFloat' a => Int In the class declaration for ‘RealFloat'’
突出显示的行引用了GHC扩展名,其中说:“没关系,我知道我在做什么”。因此,如果将{-# LANGUAGE AllowAmbiguousTypes #-}
添加到文件顶部并带有class RealFloat'
,则编译器会接受。
当在使用站点无法解析实例时,有什么意义呢?好吧,它实际上可以解决,但只能使用another pretty new GHC extension:
*Main> :set -XTypeApplications
*Main> floatDigits' @Double
53
答案 1 :(得分:5)
问题是您将为多个实例构造函数,例如:
instance RealFloat Float where
-- ...
floatRadix = 2
floatDigits = 24
floatRange = (-125, 128)
instance RealFloat Double where
-- ...
floatRadix = 2
floatDigits = 53
floatRange = (-1021, 1024)
但是,当您查询例如floatDigits
时,它会产生一个问题:我们应该选择哪种实例?一个用于Float
,一个用于Double
(或另一种类型)?所有这些都是有效的候选人。
通过使用a
参数,我们可以消除歧义,例如:
Prelude> floatDigits (0 :: Float)
24
Prelude> floatDigits (0 :: Double)
53
但它认为参数的值无关紧要,例如:
Prelude> floatDigits (undefined :: Float)
24
Prelude> floatDigits (undefined :: Double)
53
答案 2 :(得分:3)
RealFloat
类非常老。在设计之初,还没有人找到真正好的方法将多余的类型信息传递给函数。那时,通常采用相关类型的参数,并期望用户以该类型传递undefined
。如左上角所解释的那样,GHC现在具有扩展程序,可以在大多数情况下很好地执行此操作。但是在TypeApplications
之前,发明了另外两种技术来更干净地完成这项工作。
要在没有GHC扩展名的情况下消除歧义,可以使用代理传递或基于新类型的标记。我相信,爱德华·克梅特(Edward Kmett)赋予了这两种技术最终的形式,而沙恰夫·本·基奇(Shachaf Ben-Kiki)则将其最后多态化(参见Who invented proxy passing and when?)。代理传递倾向于提供易于使用的API,而新型方法在某些情况下可能更有效。这是代理传递方法。这要求您传递某种类型的参数。传统上,呼叫者将使用Data.Proxy.Proxy
,它已定义
data Proxy a = Proxy
这是通过代理传递时类的外观:
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: proxy a -> Integer
floatDigits :: proxy a -> Int
...
这是如何使用它。注意,没有必要传递您正在谈论的类型的值。您只需通过代理构造函数即可。
foo :: Int
foo = floatDigits (Proxy :: Proxy Double)
要完全避免传递运行时参数,可以使用标记。这通常是通过tagged
软件包完成的,但是也很容易自己滚动。您甚至可以重用Control.Applicative.Const
,但这并不能很好地传达意图。
newtype Tagged t a = Tagged
{ unTagged :: a }
这是课程的外观:
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: Tagged a Integer
floatDigits :: Tagged a Int
...
这是您的使用方式:
foo :: Int
foo = unTagged (floatDigits :: Tagged Double Int)