Haskell类型强制

时间:2013-02-02 23:28:37

标签: haskell

我试图围绕Haskell式强制进行。意思是,何时可以将值传递给函数而不进行强制转换以及如何工作。这是一个具体的例子,但我正在寻找一个更为一般性的解释,我可以使用它来尝试理解发生了什么:

Prelude> 3 * 20 / 4
15.0
Prelude> let c = 20
Prelude> :t c
c :: Integer
Prelude> 3 * c / 4

<interactive>:94:7:
    No instance for (Fractional Integer)
      arising from a use of `/'
    Possible fix: add an instance declaration for (Fractional Integer)
    In the expression: 3 * c / 4
    In an equation for `it': it = 3 * c / 4

(/)的类型是分数a =&gt; a - &gt; a - &gt;一个。所以,我猜测当我使用文字做“3 * 20”时,Haskell会以某种方式假设该表达式的结果是一个Fractional。但是,当使用变量时,它的类型根据赋值预定义为Integer。

我的第一个问题是如何解决这个问题。我是否需要转换表达式或以某种方式转换它? 我的第二个问题是,对我来说这似乎很奇怪,你不能做基本的数学而不必担心int / float类型。我的意思是有一种明显的方法可以在这些之间自动转换,为什么我不得不考虑这个并处理它?我开始做错了吗?

我基本上在寻找一种方法来轻松编写简单的算术表达式,而不必担心整洁的细节,并保持代码的美观和干净。在大多数顶级语言中,编译器对我有用 - 而不是相反。

5 个答案:

答案 0 :(得分:13)

如果您只是想要解决方案,请查看结尾。

你几乎已经回答了你自己的问题。 Haskell中的文字超载:

Prelude> :t 3
3 :: Num a => a

由于(*)也有Num约束

Prelude> :t (*)
(*) :: Num a => a -> a -> a

这延伸到产品:

Prelude> :t 3 * 20
3 * 20 :: Num a => a

因此,根据上下文,这可以专门为IntIntegerFloatDoubleRational等类型,如需要。特别是,由于FractionalNum的子类,它可以在分区中无问题地使用,但是约束将变为 更强大,适合上课Fractional

Prelude> :t 3 * 20 / 4
3 * 20 / 4 :: Fractional a => a

最大的区别是标识符cInteger。 GHCi提示中的简单let-binding未分配重载类型的原因是可怕的monomorphism restriction。简而言之:如果您定义一个没有任何显式参数的值, 除非您提供显式类型签名,否则它不能具有重载类型。 然后将数字类型默认为Integer

cInteger后,乘法结果也为Integer

Prelude> :t 3 * c
3 * c :: Integer

并且Integer不在Fractional类。

这个问题有两种解决方法。

  1. 确保您的标识符也有重载类型。在这种情况下,它会   就像说

    一样简单
      Prelude> let c :: Num a => a; c = 20
      Prelude> :t c
      c :: Num a => a
    
  2. 使用fromIntegral将整数值强制转换为任意数值:

      Prelude> :t fromIntegral
      fromIntegral :: (Integral a, Num b) => a -> b
      Prelude> let c = 20
      Prelude> :t c
      c :: Integer
      Prelude> :t fromIntegral c
      fromIntegral c :: Num b => b
      Prelude> 3 * fromIntegral c / 4
      15.0
    

答案 1 :(得分:5)

当您将一种类型传递给函数时,Haskell将从不自动将一种类型转换为另一种类型。它已经与预期的类型兼容,在这种情况下不需要强制,或者程序无法编译。

如果你编写一个完整的程序并编译它,那么事情通常“只是工作”而你不必考虑太多关于int / float类型;只要你保持一致(即你没有尝试将某个东西视为一个地方的Int和另一个地方的浮动),约束就会流经整个程序,并为你找出类型。

例如,如果我把它放在源文件中并编译它:

main = do
    let c = 20
    let it = 3 * c / 4
    print it

然后一切都很好,并且运行程序会打印15.0。您可以从.0看到GHC成功地发现c必须是某种小数,并使一切正常,而我不必提供任何明确的类型签名。

c不能是整数,因为/运算符用于数学除法,而未在整数上定义。整数除法的运算由div函数表示(在运算符时可用作x `div` y)。我想这可能是你整个计划中的绊倒?遗憾的是,如果你已经习惯了/有时是数学划分而且有时是整数除法的许多其他语言的情况,那么你必须通过它来学习这些东西中的一个。

当你在解释器中玩弄时,事情会变得混乱,因为你倾向于绑定没有任何上下文的值。在解释器中,GHCi必须自己执行let c = 20,因为您还没有输入3 * c / 4。它无法知道您是否希望20成为IntIntegerFloatDoubleRational等< / p>

Haskell将为数值选择默认类型;否则,如果您从未使用任何仅适用于某种特定类型数字的函数,则总是会收到有关模糊类型变量的错误。这通常可以正常工作,因为在读取整个模块时会应用这些默认规则,因此请考虑该类型的所有其他约束(例如,您是否曾将它与/一起使用)。但是这里没有其他可以看到的约束,因此类型默认选择了第一个出租车并使c成为Integer

然后,当你要求GHCi评估3 * c / 4时,为时已晚。 cInteger,因此3 * c必须Integer/不支持let

所以在解释器中,是的,有时如果你没有为let c = 20.0绑定提供明确的类型,GHC会选择一个不正确的类型,特别是对于数字类型。在那之后,你坚持使用GHCi选择的具体类型支持的任何操作,但是当你遇到这种错误时,你总是可以重新绑定变量;例如div

但是我怀疑在您的真实程序中,问题只是您想要的操作实际上是/而不是{{1}}。

答案 2 :(得分:3)

Haskell以这种方式有点不寻常。是的,你不能一起划分整数,但这很少是一个问题。

原因是,如果你查看Num类型类,那么有一个函数fromIntegral,这允许你将文字转换为适当的类型。这种类型推断缓解了99%的问题。快速举例:

newtype Foo = Foo Integer
    deriving (Show, Eq)
instance Num Foo where
   fromInteger  _  = Foo 0
   negate          = undefined
   abs             = undefined
   (+)             = undefined 
   (-)             = undefined 
   (*)             = undefined 
   signum          = undefined

现在我们将它加载到GHCi

*> 0 :: Foo
   Foo 0

*> 1 :: Foo
   Foo 0

所以你看到我们能够用GHCi解析原始整数的方式做一些非常酷的事情。这在DSL中有很多实际用途,我们在这里不会讨论。

接下来的问题是如何从Double变为Integer,反之亦然。有一个功能。

在从Integer转到Double的情况下,我们也使用fromInteger。为什么呢?

它的类型签名是

(Num a) => Integer -> a

由于我们可以将(+)用于双打,因此我们知道它们是Num个实例。从那里很容易。

*> 0 :: Double
    0.0

最后一块拼图是Double -> Integer。好吧,对Hoogle的简短搜索显示

truncate
floor
round
-- etc ...

我会留给你搜索。

答案 3 :(得分:0)

Haskell中的类型强制不是自动的(或者说,它实际上并不存在)。当您编写文字20时,它被推断为类型为Num a => a(概念上无论如何。我认为它的工作原理并不完全相同)并且将根据其使用的上下文(即您通过的函数)它可以用适当的类型进行实例化(我相信如果不再应用其他约束,当你需要某个具体的类型时,这将默认为Integer)。如果您需要其他类型Num,则需要转换数字,例如在你的例子中(3* fromIntegral c / 4)

答案 4 :(得分:0)

  

(/)的类型是分数a =&gt; a - &gt; a - &gt;一个。

要划分整数,请使用div代替(/)。请注意div的类型是

div :: Integral a => a -> a -> a
  

在大多数顶级语言中,编译器适合我 - 而不是相反。

我认为Haskell编译器对你的影响与你使用的其他语言一样多,甚至更多。 Haskell是一种非常不同的语言,而不是您可能习惯的传统命令式语言(如C,C ++,Java等)。这意味着编译器的工作方式也不同。

正如其他人所说,Haskell 永远不会自动强制从一种类型强制转换为另一种类型。如果您有一个需要用作Float的Integer,则需要使用fromInteger显式转换。