Haskell语言在参考透明度方面提供的确切承诺/保证是什么?至少Haskell报告没有提到这个概念。
考虑表达式
(7^7^7`mod`5`mod`2)
我想知道这个表达式是否为1.为了我的安全,我会做两次表演:
( (7^7^7`mod`5`mod`2)==1, [False,True]!!(7^7^7`mod`5`mod`2) )
现在为(True,False)
提供了GHCi 7.4.1。
显然,这个表达现在是参考不透明的。如何判断程序是否受此类行为影响?我可以用::
来淹没程序,但这并不能让它变得非常易读。我之间是否还有其他类的Haskell程序?那是在完全注释和未注释的之间吗?
(除了我在SO上发现的唯一related question之外,还必须有其他内容)
答案 0 :(得分:20)
对于“兼容”的任何合理定义,我认为无法保证在不同类型中评估多态类型表达式(如5
)会产生“兼容”结果。
GHCi会议:
> class C a where num :: a
> instance C Int where num = 0
> instance C Double where num = 1
> num + length [] -- length returns an Int
0
> num + 0 -- GHCi defaults to Double for some reason
1.0
由于length []
和0
应该相同,因此它的参照透明度正在下降,但是在不同类型下使用num
。
此外,
> "" == []
True
> [] == [1]
False
> "" == [1]
*** Type error
可以在最后一行预期False
。
因此,我认为仅当指定确切类型来解析多态时,引用透明性才会成立。系统F的显式类型参数应用程序可以始终用变量替换变量而不改变语义:据我所知,GHC在优化期间内部完成此操作以确保语义不受影响。实际上,GHC Core有明确的类型参数可以传递。
答案 1 :(得分:17)
问题是重载,这确实违反了引用透明度。你不知道像(+)
这样的东西在Haskell中做了什么;这取决于类型。
当数值类型在Haskell程序中不受约束时,编译器使用类型默认来选择一些合适的类型。这是为了方便,通常不会带来任何意外。但在这种情况下,它确实导致了一个惊喜。在ghc中,您可以使用-fwarn-type-defaults
来查看编译器何时使用默认值为您选择类型。您还可以将行default ()
添加到模块中以停止所有默认操作。
答案 2 :(得分:15)
我想到了一些可能有助于澄清事情的事情......
表达式mod (7^7^7) 5
的类型为Integral a
,因此有两种常用方法可将其转换为Int
:
Integer
操作和类型执行所有算术,然后将结果转换为Int
。Int
操作执行所有算术。如果在Int
上下文中使用表达式,Haskell将执行方法#2。如果你想强迫Haskell使用#1,你必须写:
fromInteger (mod (7^7^7) 5)
这将确保使用Integer
操作和类型执行所有算术运算。
当您在ghci REPL中输入表达式时,默认规则将表达式键入Integer
,因此使用方法#1。当您将表达式与!!
运算符一起使用时,它被输入为Int
,因此它是通过方法#2计算的。
我原来的回答:
在Haskell中评估表达式,如
(7^7^7`mod`5`mod`2)
完全取决于使用哪个Integral
实例,这是每个Haskell程序员都学会接受的。
每个程序员(使用任何语言)必须注意的第二件事是数字操作会受到溢出,下溢,精度损失等因素的影响,因此算术规则可能并不总是成立。例如,x+1 > x
并非总是如此;加法和多个实数并不总是相关的;分配法并不总是成立;当您创建溢出表达式时,您将进入未定义行为领域。
此外,在这种特殊情况下,有更好的方法来评估这个表达式,这样可以保留我们对结果应该是什么的更多期望。特别是,如果你想高效准确地计算一个^ b mod c,你应该使用“power mod”算法。
更新:运行以下程序以查看Integral
实例的选择如何影响表达式的计算结果:
import Data.Int
import Data.Word
import Data.LargeWord -- cabal install largeword
expr :: Integral a => a
expr = (7^e `mod` 5)
where e = 823543 :: Int
main :: IO ()
main = do
putStrLn $ "as an Integer: " ++ show (expr :: Integer)
putStrLn $ "as an Int64: " ++ show (expr :: Int64)
putStrLn $ "as an Int: " ++ show (expr :: Int)
putStrLn $ "as an Int32: " ++ show (expr :: Int32)
putStrLn $ "as an Int16: " ++ show (expr :: Int16)
putStrLn $ "as a Word8: " ++ show (expr :: Word8)
putStrLn $ "as a Word16: " ++ show (expr :: Word16)
putStrLn $ "as a Word32: " ++ show (expr :: Word32)
putStrLn $ "as a Word128: " ++ show (expr :: Word128)
putStrLn $ "as a Word192: " ++ show (expr :: Word192)
putStrLn $ "as a Word224: " ++ show (expr :: Word224)
putStrLn $ "as a Word256: " ++ show (expr :: Word256)
和输出(用GHC 7.8.3(64位)编译:
as an Integer: 3
as an Int64: 2
as an Int: 2
as an Int32: 3
as an Int16: 3
as a Word8: 4
as a Word16: 3
as a Word32: 3
as a Word128: 4
as a Word192: 0
as a Word224: 2
as a Word256: 1
答案 3 :(得分:7)
Haskell语言在参考透明度方面提供的确切承诺/保证是什么?至少Haskell报告没有提到这个概念。
Haskell没有提供准确的承诺或保证。存在许多函数,例如unsafePerformIO
或traceShow
,它们不是引用透明的。然而,名为 Safe Haskell 的扩展提供了以下承诺:
参考透明度 - 安全语言中的函数是确定性的,评估它们不会产生任何副作用。 IO monad中的函数仍然允许并且像往常一样运行。但是,根据其类型,任何纯粹的功能都保证确实是纯粹的。此属性允许安全语言的用户信任这些类型。这意味着,例如,unsafePerformIO :: IO a - >安全语言不允许使用某项功能。
Haskell提供了一个非正式的承诺:Prelude和基础库往往没有副作用,Haskell程序员倾向于用副作用来标记事物。
显然,这个表达现在是参考不透明的。如何判断程序是否受此类行为影响?我可以用:: all来淹没程序,但这并不能让它变得非常易读。我之间是否还有其他类的Haskell程序?那是在完全注释和未注释的之间吗?
正如其他人所说,问题出现在这种行为上:
Prelude> ( (7^7^7`mod`5`mod`2)==1, [False,True]!!(7^7^7`mod`5`mod`2) )
(True,False)
Prelude> 7^7^7`mod`5`mod`2 :: Integer
1
Prelude> 7^7^7`mod`5`mod`2 :: Int
0
这是因为7^7^7
是一个巨大的数字(大约700,000个十进制数字),很容易溢出64位Int
类型,但问题在32位系统上无法重现:
Prelude> :m + Data.Int
Prelude Data.Int> 7^7^7 :: Int64
-3568518334133427593
Prelude Data.Int> 7^7^7 :: Int32
1602364023
Prelude Data.Int> 7^7^7 :: Int16
8823
如果使用rem (7^7^7) 5
,则Int64的余数将报告为-3
,但由于-3相当于+2模5,mod
报告为+2。
由于Integer
类的默认规则,左侧使用Integral
答案;由于Int
的类型,右侧使用特定于平台的(!!) :: [a] -> Int -> a
类型。如果对Integral a
使用适当的索引运算符,则可以获得一致的结果:
Prelude> :m + Data.List
Prelude Data.List> ((7^7^7`mod`5`mod`2) == 1, genericIndex [False,True] (7^7^7`mod`5`mod`2))
(True,True)
这里的问题是不是引用透明度,因为我们调用^
的函数实际上是两个不同的函数(因为它们有不同的类型) )。让你绊倒的是类型类,它是Haskell中约束歧义的实现;你已经发现这种模糊性(与不受约束的模糊性 - 即参数类型不同)可以提供违反直觉的结果。这不应该太令人惊讶,但有时候它确实有点奇怪。
答案 4 :(得分:5)
已选择其他类型,因为!!
需要Int
。完整计算现在使用Int
而不是Integer
。
λ> ( (7^7^7`mod`5`mod`2 :: Int)==1, [False,True]!!(7^7^7`mod`5`mod`2) )
(False,False)
答案 5 :(得分:4)
您认为这与参考透明度有什么关系?您对7
,^
,mod
,5
,2
和==
的使用是应用变量到字典,是的,但我不明白为什么你认为这个事实使Haskell引用不透明。毕竟,经常将相同的函数应用于不同的参数会产生不同的结果!
参考透明度与此表达式有关:
let x :: Int = 7^7^7`mod`5`mod`2 in (x == 1, [False, True] !! x)
x
此处是单个值,并且应始终具有相同的单个值。
相比之下,如果你说:
let x :: forall a. Num a => a; x = 7^7^7`mod`5`mod`2 in (x == 1, [False, True] !! x)
(或使用内联的表达式,这是等效的),x
现在是一个函数,并且可以根据您提供给它的Num
参数返回不同的值。您也可以抱怨let f = (+1) in map f [1, 2, 3]
为[2, 3, 4]
,但let f = (+3) in map f [1, 2, 3]
为[4, 5, 6]
,然后说“Haskell根据上下文为map f [1, 2, 3]
提供不同的值,因此它是参考不透明“!
答案 6 :(得分:2)
可能另一种类型推理和参考 - 透明度相关的事情是“可怕的”Monomorphism restriction(确切地说,它的缺席)。直接引用:
一个例子,来自“哈斯克尔的历史”:
的genericLength函数
考虑来自Data.List
genericLength :: Num a => [b] -> a
考虑功能:
f xs = (len, len) where len = genericLength xs
len
的类型为Num a => a
,如果没有单态限制,则可以计算两次。
请注意,在这种情况下,两个表达式的类型都相同。结果也是如此,但替代并非总是可行。