Haskell,在函数中乘以Int和Float

时间:2013-09-26 03:58:09

标签: haskell types

为什么在ghci中我可以输入:

5.0 * (3 - 1)
> 10.0

但是,如果我尝试在.hs文件中创建一个函数并将其加载到:

test :: Float -> Int -> Int -> Float
test a b c = a * (b - c)

我遇到了错误? “无法将预期类型'Float'与推断类型'Int'匹配? 我怎样才能编写一个函数,它接受一个浮点和2个整数参数并对它们执行上述操作?

我正在使用ghci v6.12.1,如果这有所不同......

4 个答案:

答案 0 :(得分:37)

数字文字(即只是在Haskell代码中键入数字)不是某种固定类型。它们是多态的。它们需要在某些需要具有特定类型的上下文中进行评估。

因此表达式5.0 * (3 - 1) Int乘以Float5.0必须是某种Fractional类型,31均为Num类型。 3 - 1表示3和1都必须是相同的 Num类型,但我们仍然没有(还)有更多关于哪个特定的约束是;减法的结果是相同的类型。

*表示两个参数必须是相同的类型,结果也是相同的类型。由于5.0Fractional类型,(3 - 1)必须也是3类型。我们已经知道1(3 - 1)Num必须是某种Fractional类型,但所有Num类型也是5.0 * (3 - 1)类型,所以这些要求没有冲突。

最终结果是整个表达式Fractional5.0的某种类型,而31:t都是相同的类型。您可以在GHCi中使用Prelude> :t 5.0 * (3 - 1) 5.0 * (3 - 1) :: Fractional a => a 命令来查看:

Float

但实际上评估该表达式,我们需要为某些具体类型执行此操作。如果我们对此进行评估并将其传递给需要DoubleFractional或其他特定Prelude> 5.0 * (3 - 1) 10.0 Prelude> :t it it :: Double 类型的函数,那么Haskell会选择那个。如果我们只是评估表达式而没有其他上下文要求它是一个特定的类型,Haskell有一些默认规则来自动为你选择一个(如果默认规则不适用它将改为给你一个关于模糊类型变量的类型错误)。

5.0 * (3 - 1)

上面我评估了it,然后询问了GHCi总是绑定到它评估的最后一个值的魔术Fractional a => a变量的类型。这告诉我GHCi默认我的Double类型为10.0,以便计算表达式的值为Double。在进行评估时,它只会倍增(和减去)Double s,它永远不会将Int乘以test


现在,当你尝试多个数字文字看起来它们可能属于不同类型时,就会发生这种情况。但是你的Int函数不是文字的倍增,它是特定已知类型的倍增变量。在Haskell中,您无法将Float乘以*,因为Num a => a -> a -> a运算符的类型为Int - 它采用相同数值类型的两个值并为您提供结果就是那种类型。您可以将Int乘以Int以获得Float,或将Float乘以Float以获得Int。您无法将Float乘以???来获得test :: Float -> Int -> Int -> Float test a b c = a * fromIntegral (b - c)

其他语言仅在某些情况下通过隐式插入对转换函数的调用来支持此类操作。 Haskell 从不隐式转换类型,但它具有转换函数。如果要调用它们,您只需要显式调用它们。这样就可以了:

{{1}}

答案 1 :(得分:4)

请尝试以下方法:

test :: Float -> Int -> Int -> Float
test a b c = a * fromIntegral (b - c)

为什么这样做?

  1. 由于bc都是Int,因此表达式(b - c)也是Int
  2. (*)的类型签名为Num a => a -> a -> a
  3. 由于a的类型为Float,因此Haskell会将(a*)的类型签名更新为Float -> Float
  4. 但是,如果(b - c)Int而不是Float,那么如果您尝试a * (b - c),Haskell就会抱怨。
  5. fromIntegral的类型签名为(Integral a, Num b) => a -> b
  6. 因此fromIntegral (b - c)的类型签名为Num b => b
  7. 由于Float是类型Num的实例,因此a * fromIntegral (b - c)的类型签名为(*),因此您可以Num a => a -> a -> a
  8. 结果,从test的类型签名评估为Float
  9. 希望这会有所帮助。

答案 2 :(得分:3)

在乘以浮点数之前,您需要在整数上使用fromIntegral

http://www.haskell.org/haskellwiki/Converting_numbers

在GHCI中,在您使用它们之前,数字不会被假定为浮点数或整数。 (例如:在运行时)。这对于REPL风格的开发来说效果更好。

在编译器中,没有任何自动强制。它 看到乘法假定这两个值必须属于支持乘法的类型类。例如:乘以整数或乘以浮点数。由于您没有使用任何其他显式类型的函数,因此它假设为int。然后,该假设与您的(可选)类型签名不同。

答案 3 :(得分:3)

在添加签名之前,您的test功能更为通用:

> let test a b c = a * (b - c)
> :t test
  test :: Num a => a -> a -> a -> a

您可以限制它,但所有类型必须相同:

test :: Fractional a => a -> a -> a -> a   -- some real types
test :: Integral   a => a -> a -> a -> a   -- all integer types
test :: Float -> Float -> Float -> Float  
test :: Int   -> Int   -> Int   -> Int   

test :: Int   -> Float -> Float -> Float  --wrong

顺便说一句,2不是Int0.2不是Float,请问gchi

> :t 2
 2 :: Num a => a
> :t 0.2
 0.2 :: Fractional a => a