2

我试图绕开 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 => a -> a -> a。所以,我猜测当我使用文字执行“3 * 20”时,Haskell 会以某种方式假设该表达式的结果是分数。但是,当使用变量时,它的类型根据赋值预定义为 Integer。

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

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

4

5 回答 5

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

因此,根据上下文,可以根据需要将其专门化为IntIntegerFloatDouble等类型Rational。特别是,作为Fractional的子类Num,它可以在除法中毫无问题地使用,但是约束将变得更强并适用于类Fractional

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

最大的区别是标识符c是一个Integer. GHCi 提示符中的简单 let 绑定未分配重载类型的原因是可怕的单态限制。简而言之:如果您定义一个没有任何显式参数的值,那么除非您提供显式类型签名,否则它不能具有重载类型。然后数字类型默认为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
    
于 2013-02-03T00:01:24.477 回答
5

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

如果您编写了一个完整的程序并对其进行编译,那么事情通常会“正常工作”,而无需您过多考虑 int/float 类型;只要你是一致的(即你不试图在一个地方把某个东西当作一个 Int 而在另一个地方当作一个 Float ),约束就会流过程序并为你找出类型。

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

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

然后一切正常,运行程序打印15.0。你可以从.0GHC 成功地找出它c必须是某种小数,并让一切正常工作,而无需我提供任何明确的类型签名。

c不能是整数,因为该/运算符用于数学除法,它没有在整数上定义。整数除法的运算由div函数表示(以运算符方式用作x `div` y)。我认为这可能是在你的整个程序中绊倒你的原因?不幸的是,如果您习惯了许多其他语言中/有时是数学除法,有时是整数除法的情况,那么这只是您必须通过被它绊倒来学习的那些东西之一。

当您在解释器中玩耍时,事情会变得一团糟,因为您倾向于在没有任何上下文的情况下绑定值。在解释器中,GHCi 必须自己执行let c = 20,因为您还没有进入3 * c / 4。它无法知道您是否打算将20其变为Int, Integer, Float, Double,Rational

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

然后,当您要求 GHCi 评估3 * c / 4时,为时已晚。cInteger,所以必须3 * c是 ,并且Integers 不支持/

因此,在解释器中,是的,有时如果您不为let绑定 GHC 提供显式类型,则会选择不正确的类型,尤其是数字类型。在那之后,你会被选择的具体类型 GHCi 支持的任何操作所困扰,但是当你遇到这种错误时,你总是可以重新绑定变量;例如let c = 20.0

但是我怀疑在您的实际程序中,问题只是您想要的操作实际上是div而不是/.

于 2013-02-03T00:04:39.270 回答
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

由于我们可以使用(+)Doubles,我们知道它们是一个Num实例。从那里开始很容易。

*> 0 :: Double
    0.0

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

truncate
floor
round
-- etc ...

我把它留给你去搜索。

于 2013-02-02T23:51:37.930 回答
0

Haskell 中的类型强制不是自动的(或者更确切地说,它实际上并不存在)。当您编写文字 20 时,它被推断为类型Num a => a(无论如何在概念上。我认为它不会像那样工作)并且将根据使用它的上下文(即您传递给它的函数)被实例化使用适当的类型(我相信如果没有应用进一步的约束,这将默认为Integer您在某个时候需要具体类型)。如果您需要不同类型的Num,则需要转换数字,例如(3* fromIntegral c / 4)在您的示例中。

于 2013-02-02T23:50:53.423 回答
0

(/) 的类型是小数 a => a -> a -> a。

要划分整数,请使用div而不是(/). 请注意,类型div

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

在大多数顶级语言中,编译器对我有用——而不是相反。

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

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

于 2013-02-03T00:11:44.027 回答