Haskell类型强制
我试图围绕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 => A - > a - > a。所以,我猜测,当我使用文字做“3 * 20”时,Haskell以某种方式假定该表达式的结果是分数。但是,如果使用变量,则根据赋值将类型预定义为Integer。
我的第一个问题是如何解决这个问题。我需要转换表达式还是以某种方式转换它? 我的第二个问题是,对于我来说,这看起来很奇怪,因为如果不用担心int/float类型的问题,就无法进行基本的数学运算。我的意思是有一个显而易见的方法可以在这些之间自动转换,为什么我不得不考虑这个并处理它?我开始做错了什么?
我基本上正在寻找一种方法来轻松地编写简单的算术表达式,而不必担心整洁的伟大的细节,并保持代码的漂亮和干净。在大多数顶级语言中,编译器为我工作 - 而不是相反。
如果你只是想解决方案,看看结束。
你几乎已经回答了你自己的问题。在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
,的和更多,根据需要。特别地,如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
Haskell中的类型强制不是自动的(或者说,它实际上并不存在)。当你编写文字20时,推断它的类型为Num a => a
(概念上来说,我认为它不是那么工作),并且根据它使用的上下文(也就是你传递给它的函数)用一个适当的类型实例化(我相信如果没有进一步的限制,当你需要一个具体的类型时,这个参数默认为Integer
)。如果您需要其他种类的Num
,则需要将数字转换为在你的例子中为(3* fromIntegral c/4)
。
这样Haskell有点不同寻常。是的,你不能一起分为整数,但它很少是一个问题。
原因是,如果你看看Num
typeclass,有一个函数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中有很多实际用途,我们不会在这里讨论。
接下来的问题是如何从双倍到整数或反之亦然。这有一个功能。
从Integer到Double的情况下,我们也会使用fromInteger
。为什么?
,以及为它的类型签名是
(Num a) => Integer -> a
既然我们可以用(+)
与双打,我们知道他们是一支Num
实例。从那里很容易。
*> 0 :: Double
0.0
最后一块难题是Double -> Integer
。那么简单搜索Hoogle节目
truncate
floor
round
-- etc ...
我会让你去搜索。
哈斯克尔将永远自动转换当你把它传递给一个函数时,一种类型变成另一种类型它既可以与预期的类型兼容,在这种情况下,不需要强制,或者程序无法编译。
如果你编写一个完整的程序并编译它,通常情况下,这些东西是“正常工作”,而不必考虑太关于int/float类型;只要你是一致的(也就是说,你不要试图把某些东西当作一个Int中的某个东西,而将另一个中的某个东西当作一个Float),那么约束只是流过程序并为你找出类型。
例如,如果我把这个源文件和编译:
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
等
哈斯克尔将挑选数值的默认类型;否则,如果你从来没有使用任何函数只适用于一个特定类型的数字,你会总是得到一个关于不明确的类型变量的错误。这通常工作正常,因为这些默认规则是在读取整个模块时应用的,因此需要考虑类型上的所有其他约束(例如,您是否曾使用过/
)。但是在这里没有其他的限制,所以类型默认选择了第一个驾驶室,并且使得c
为Integer
。
然后,当您要求GHCi评估3 * c/4
时,已为时过晚。 c
是Integer
,所以必须3 * c
是,而Integer
不支持/
。
所以在解释器中,有的时候,如果你没有给出明确的类型绑定GHC会选择一个不正确的类型,尤其是数字类型。在那之后,你坚持被GHCi选择的具体类型支持的任何操作,但是当你遇到这种错误时,你总是可以重新绑定变量;例如let c = 20.0
。
但是我怀疑在你的真实程序中,问题只不过是你想要的操作实际上是div
而不是/
。
(/)的类型是分数a => a - > a - > a。
要分割整数,请使用div
而不是(/)
。注意,div
类型是
div :: Integral a => a -> a -> a
在最顶层语言编译器适用于我 - 不是周围的其他方式。
我认为Haskell编译器对于您的作用与您使用的其他语言的作用相同,甚至更多。 Haskell与传统的命令式语言(例如C,C++,Java等)不同,它可能是您使用过的不同的语言。这意味着编译器的工作方式也不同。
正如其他人所说,Haskell将从不自动从一种类型强制到另一种。如果您需要将一个整数用作Float,则需要使用fromInteger
明确进行转换。
快速提示:请尝试在源文件中执行此操作,而不是直接在GHCi中执行此操作。它会以这种方式为你提供更多的东西,因为它可以查看整个程序,而不是一次处理一行。 –
这个问题是基于一个不起作用的整个程序。我已经隔离了编译器正在抱怨的这个特定部分。我可以发布整个事情,但我认为它不会有多大帮助。 – oneself
你的问题已被回答,但是:“有一种明显的方法可以在这些之间自动转换。”有真的吗?当我想要一个'Double',或者甚至是一个'Int64'时,'2^128 :: Integer'应该自动变成什么?当然,由于四舍五入,从非整数类型到整数类型不可能是自动的。一般来说,Haskell没有子类型或自动类型强制;它具有(约束)参数多态性。但是一旦一个类型变成具体/单形的,那个类型的值就只有*这种类型。 –