为什么有floatRange,floatRadix和floatDigits函数?

时间:2018-10-15 07:03:20

标签: haskell floating-point

根据HackageRealFloat类的这些功能是

  

...常量函数[s] ...

如果它们始终保持相同的值,那么不管此说明所建议的参数如何,为什么不简单使用:

class (RealFrac a, Floating a) => RealFloat a where
    floatRadix :: Integer
    floatDigits :: Int
    floatRange :: (Int, Int)
    ...

3 个答案:

答案 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)