【问题标题】:do I understand this haskell code with fromIntegral correctly?我用 fromIntegral 正确理解这个 haskell 代码吗?
【发布时间】:2020-09-30 14:11:32
【问题描述】:

我(认为)我完全理解为什么 6/(length [1,2,3]) 不起作用:length 返回一个 int (length :: Foldable t => t a -> Int) 而 Int 没有实现 / 函数。

一个可行的解决方案是6/fromIntegral (length [1,2,3])

这个问题是关于为什么这个解决方案有效。

我的猜想是:函数fromIntegral(见:https://hackage.haskell.org/package/numhask-0.6.0.2/docs/src/NumHask.Data.Integral.html#line-210)以多种方式实现(非显式,但仍然),因此它可以接收许多不同的类型并返回许多不同的类型。因此,在编译代码时,haskell 将在可能的返回类型中进行选择并使用合适的返回类型(在本例中为 double 或 float)。

我的猜想正确吗?

也感谢任何填补此解释中的空白

【问题讨论】:

  • (/) 的类型为Floating a => a -> a -> a,但length 返回一个Int,而Intnot Floating,带有fromIntegral,您转换可以将此Int 转换为任何Num 类型。
  • (/)Fractional 定义,而不是Floating
  • Fractional 本身不是FloatDouble,例如Num a => Ratio a 也是Fractional
  • Int 类型确实实现了 division,即使它没有实现 / 运算符。您可能想尝试div 6 (length [1,2,3]) 之类的方法。还有moddivMod 的功能。
  • 还有一点:类型不是在运行时选择的,而是在编译时选择的。而且,正如 chi 的回答中所解释的,如果这不可能,您仍然会收到编译时错误。

标签: haskell


【解决方案1】:

你的直觉大多是正确的。

与任何在返回类型上具有多态性的函数一样,fromIntegral 将生成程序类型检查所需的任何类型的值。粗略地说,它产生上下文所需的类型。

例如,fromIntegral someValue + (3 :: Double) 将使fromIntegral 产生一个Double,因为这是对所有内容进行类型检查所需要的。

如果在类型推断之后,Haskell 仍然不知道要使用哪种特定类型,它通常会引发错误,抱怨歧义。作为一个主要的例外,当需要数字类型时,会使用一些“默认”类型而不是引发错误。这个例外使print 3 无需编写print (3 :: Integer) 或类似内容即可工作。

【讨论】:

    【解决方案2】:

    fromIntegral 具有 (Integral a, Num b) => a -> b 类型,因此您可以获得所需的任何实现 Num 的类型。

    (/) 具有 Fractional a => a -> a -> a 类型,并且由于 FractionalNum 的子类,fromIntegral 将能够提供适当的类型。

    剩下的唯一一点是6 / ... 将实际返回什么类型;表达式本身的类型为Fractional a => a。这将取决于验证表达式的上下文。例如,在 GHCi 中,我相信它只会默认为 Double

    【讨论】:

      【解决方案3】:

      fromIntegral 的类型为 forall a b. (Integral a, Num b) => a -> b。如果您熟悉其他语言,forall a b. 类似于<A, B>(Java,C#)或template<typename A, typename B>(C++):它为泛型函数引入了类型变量的作用域。

      通常forall 是隐含的,但您可以使用例如:set -fprint-explicit-foralls 在 GHCi 中并在您的代码中使用 ExplicitForAll 扩展名启用它。 (注意-fprint-explicit-foralls 会在类型变量周围加上花括号,这是无效的语法;我将在下面解释。)

      类型参数ab(以及约束Integral aNum b)是由调用者传入函数的参数 > of fromIntegral,但与值级参数不同,这些类型级参数由编译器在编译时隐式传递。当你写这样的东西时:

      6.0 / fromIntegral (length [1, 2, 3]) :: Double
      

      fromIntegral 被推断为具有类型参数Int(因为length 返回Int)和Double(因为类型注释)。所以类型变量是这样填写的:

      (Integral Int, Num Double) => Int -> Double
      

      然后编译器找到instance Integral Intinstance Num Double 的定义,并使用它们的toInteger :: forall a. Integral a => a > Integer) 和fromInteger :: forall a. Num a => Integer -> a(专用于fromInteger :: Integer -> Double)的实现来进行转换。最后,这些调用 GHC 原始函数,如 smallIntegerdoubleFromInteger 进行实际转换。

      使用TypeApplications 扩展,您可以显式编写这些类型参数:

      > :set -fprint-explicit-foralls
      
      > :t fromIntegral -- original function
      fromIntegral :: forall {a} {b}. (Integral a, Num b) => a -> b
      
      > :t fromIntegral @Int -- applied to one type argument
      fromIntegral @Int :: forall {b}. Num b => Int -> b
      
      > :t fromIntegral @Int @Double -- applied to both type arguments
      fromIntegral @Int @Double :: Int -> Double
      
      > :t fromIntegral @Int @Double 5 -- both type arguments *and* value argument
      fromIntegral @Int @Double 5 :: Double
      

      TypeApplications-fprint-explicit-foralls 有时在 forall 量词中的类型变量周围打印大括号 {} 的原因:类型参数的顺序由类型签名定义,大括号表示存在是一个类型签名,指定您可以使用TypeApplications 填写哪些类型参数。如果没有大括号,则没有签名,并且您不能使用TypeApplications,因为没有指定参数顺序。

      当您使用:type / :t 时,它将总是打印大括号,因为此命令会推断表达式的类型;如果您想知道定义的签名,则需要改用:type +v / :t +v。例如:

      > :set -XTypeApplications -fprint-explicit-foralls
      
      > example1 x y = x
      
      > example2 :: a -> b -> a; example2 x y = x
      
      > :t example1 -- always prints braces
      example1 :: forall {p1} {p2}. p1 -> p2 -> p1
      
      > :t example2 -- always prints braces
      example2 :: forall {a} {b}. a -> b -> a
      
      > :t +v example1 -- definition has no signature; prints braces
      example1 :: forall {p1} {p2}. p1 -> p2 -> p1
      
      > :t +v example2 -- definition has a signature; no braces
      example2 :: forall a b. a -> b -> a
      

      您可以将TypeApplicationsexample2 一起使用,但不能使用example1

      > :t example2 @Int @Char
      example2 @Int @Char :: Int -> Char -> Int
      
      > :t example1 @Int @Char
      
      <interactive>:1:1: error:
          • Cannot apply expression of type ‘p10 -> p20 -> p10’
            to a visible type argument ‘Int’
          • In the expression: example1 @Int @Char
      

      【讨论】:

        猜你喜欢
        • 2011-06-26
        • 2013-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-19
        相关资源
        最近更新 更多