我试图围绕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类型。我的意思是有一种明显的方法可以在这些之间自动转换,为什么我不得不考虑这个并处理它?我开始做错了吗?
我基本上在寻找一种方法来轻松编写简单的算术表达式,而不必担心整洁的细节,并保持代码的美观和干净。在大多数顶级语言中,编译器对我有用 - 而不是相反。
答案 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
因此,根据上下文,这可以专门为Int
,Integer
,Float
,Double
,Rational
等类型,如需要。特别是,由于Fractional
是Num
的子类,它可以在分区中无问题地使用,但是约束将变为
更强大,适合上课Fractional
:
Prelude> :t 3 * 20 / 4
3 * 20 / 4 :: Fractional a => a
最大的区别是标识符c
是Integer
。 GHCi提示中的简单let-binding未分配重载类型的原因是可怕的monomorphism restriction。简而言之:如果您定义一个没有任何显式参数的值,
除非您提供显式类型签名,否则它不能具有重载类型。
然后将数字类型默认为Integer
。
c
为Integer
后,乘法结果也为Integer
:
Prelude> :t 3 * c
3 * c :: Integer
并且Integer
不在Fractional
类。
这个问题有两种解决方法。
确保您的标识符也有重载类型。在这种情况下,它会 就像说
一样简单 Prelude> let c :: Num a => a; c = 20
Prelude> :t c
c :: Num a => a
使用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
成为Int
,Integer
,Float
,Double
,Rational
等< / p>
Haskell将为数值选择默认类型;否则,如果您从未使用任何仅适用于某种特定类型数字的函数,则总是会收到有关模糊类型变量的错误。这通常可以正常工作,因为在读取整个模块时会应用这些默认规则,因此请考虑该类型的所有其他约束(例如,您是否曾将它与/
一起使用)。但是这里没有其他可以看到的约束,因此类型默认选择了第一个出租车并使c
成为Integer
。
然后,当你要求GHCi评估3 * c / 4
时,为时已晚。 c
是Integer
,因此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
显式转换。