为什么floatRange,floatRadix和floatDigits起作用?

sch*_*ine 7 floating-point haskell

根据Hackage,这些RealFloat类的功能是

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

如果它们始终保持相同的值,无论参数如何,如本说明所示,为什么不简单地使用:

class (RealFrac a, Floating a) => RealFloat a where
    floatRadix :: Integer
    floatDigits :: Int
    floatRange :: (Int, Int)
    ...
Run Code Online (Sandbox Code Playgroud)

lef*_*out 11

您提出的非功能方法将具有类型

floatRadix' :: RealFloat a => Integer
floatDigits' :: RealFloat a => Int
...
Run Code Online (Sandbox Code Playgroud)

那些是不明确的类型:有一个a类型变量,但它实际上并不出现在右边,=>因此不能从上下文中推断出来.也就是说,在标准的Haskell中,确实是推断这种类型变量的唯一方法:本地类型签名也只能是签名头,而不是约束.所以不管你写的(floatDigits' :: Int)还是(floatDigits' :: RealFloat Double => Int),它不会实际工作-编译器不能推断出你的意思是instance RealFloat Double该方法的版本.

class RealFloat' a where
  floatDigits' :: Int
instance RealFloat' Double where
  floatDigits' = floatDigits (0 :: Double)
Run Code Online (Sandbox Code Playgroud)
*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',编译器将接受它.

但是,当实例无法在使用站点解析时,重点是什么?那么,它可以真正得到解决,但只用一个漂亮的新GHC的扩展:

*Main> :set -XTypeApplications 
*Main> floatDigits' @Double
53
Run Code Online (Sandbox Code Playgroud)


Wil*_*sem 5

这个问题是你要为多个实例构造函数,比如:

instance RealFloat Float where
    -- ...
    floatRadix = 2
    floatDigits = 24
    floatRange = (-125, 128)

instance RealFloat Double where
    -- ...
    floatRadix = 2
    floatDigits = 53
    floatRange = (-1021, 1024)
Run Code Online (Sandbox Code Playgroud)

但是现在它在您查询示例时会产生问题floatDigits:我们应该采取什么样的实例?一个用于Float,或一个用于Double(或另一种类型的)?所有这些都是有效的候选人.

通过使用a参数,我们可以消除歧义,例如:

Prelude> floatDigits (0 :: Float)
24
Prelude> floatDigits (0 :: Double)
53
Run Code Online (Sandbox Code Playgroud)

但它认为参数的值无关紧要,例如:

Prelude> floatDigits (undefined :: Float)
24
Prelude> floatDigits (undefined :: Double)
53
Run Code Online (Sandbox Code Playgroud)


dfe*_*uer 3

班级RealFloat很老了。当它被设计时,没有人找到真正好的方法来将额外的类型信息传递给函数。当时,采用相关类型的参数并期望用户传递undefined该类型是很常见的。正如 leftaroundabout 所解释的,GHC 现在的扩展在大多数情况下都可以很好地完成此任务。但在此之前TypeApplications,还发明了另外两种技术来更干净地完成这项工作。

要在没有 GHC 扩展的情况下消除歧义,您可以使用代理传递或基于新类型的标记。我相信这两种技术都是由 Edward Kmett 赋予的最终形式,并由 Shachaf Ben-Kiki 进行最后的多态旋转(参见谁发明了代理传递以及何时发明的?)。代理传递往往会提供易于使用的 API,而新类型方法在某些情况下可能会更有效。这是代理传递方法。这要求您传递某种类型的参数。传统上,调用者将使用Data.Proxy.Proxy,其定义为

data Proxy a = Proxy
Run Code Online (Sandbox Code Playgroud)

以下是使用代理传递时该类的外观:

class (RealFrac a, Floating a) => RealFloat a where
    floatRadix :: proxy a -> Integer
    floatDigits :: proxy a -> Int
    ...
Run Code Online (Sandbox Code Playgroud)

下面是它的使用方法。请注意,无需传入您正在讨论的类型的值;你只需传递代理构造函数。

foo :: Int
foo = floatDigits (Proxy :: Proxy Double)
Run Code Online (Sandbox Code Playgroud)

为了避免传递运行时参数,您可以使用标记。这通常是通过tagged包完成的,但自己推出也很容易。您甚至可以重用Control.Applicative.Const,但这并不能很好地传达意图。

newtype Tagged t a = Tagged
  { unTagged :: a }
Run Code Online (Sandbox Code Playgroud)

该类的外观如下:

class (RealFrac a, Floating a) => RealFloat a where
    floatRadix :: Tagged a Integer
    floatDigits :: Tagged a Int
    ...
Run Code Online (Sandbox Code Playgroud)

下面是你如何使用它:

foo :: Int
foo = unTagged (floatDigits :: Tagged Double Int)
Run Code Online (Sandbox Code Playgroud)