抱歉这里有新问题,但是 Haskell 怎么知道不对 eg 应用引用透明性,readLn
或者当putStrLn
两次 -ing 相同的字符串时?是因为IO
涉及吗?IOW,编译器不会对返回的函数应用引用透明性IO
吗?
4 回答
您需要区分评估和执行。
如果计算2 + 7,则结果为 9。如果将一个计算结果为 9 的表达式替换为另一个计算结果为 9 的不同表达式,则程序的含义没有改变。这就是参照透明性所保证的。我们可以将几个共享表达式共同减少到 9,或者将一个共享表达式复制到多个副本中,程序的含义不会改变。(性能可能,但不是最终结果。)
如果您评估 readLn
,它将评估为 I/O 命令对象。你可以把它想象成一个描述你想要执行的 I/O 操作的数据结构。但对象本身只是数据。如果您计算readLn
两次,它将返回相同的 I/O 命令对象两次。您可以将多个副本合并为一个;您可以将一份副本分成几份。它不会改变程序的含义。
现在,如果您想执行I/O 操作,那就另当别论了。显然,I/O 操作需要完全按照程序指定的方式执行,而不是随机复制或重新排列。但这没关系,因为这不是 Haskell 表达式评估引擎。您可以假装 Haskell 运行时运行main
,它构建了一个代表整个程序的巨大 I/O 命令对象。然后 Haskell 运行时读取这个数据结构并按照指定的顺序执行它请求的 I/O 操作。(实际上不是它是如何工作的,而是一个有用的心智模型。)
通常,您无需考虑在求值 readLn
以获取 I/O 命令对象和执行生成的 I/O 命令对象以获取结果之间的严格分离。但严格来说,这就是它的作用。
(您可能还听说过 I/O “形成一个 monad”。这只是一种奇特的说法,即有一组特定的运算符用于将 I/O 命令对象一起更改为更大的 I/O 命令对象。这不是核心理解评估和执行之间的分离。)
IO类型定义为:
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
注意与 State Monad 非常相似,其中状态是现实世界的状态,所以恕我直言,您可以将 IO 视为引用透明和纯粹的,不纯的是运行您的 IO 操作(代数)的 Haskell 运行时(解释器) .
看看 Haskell wiki,它更详细地解释了 IO:IO Inside
由于返回值被包装到IO
中,除非你“拉”出它们,否则你不能重用它们,有效地运行 IO 操作:
readLn :: IO String
twoLines = readLn ++ readLn -- can't do this, because (++) works on String's, not IO String's
twoLines' = do
l1 <- readLn
l2 <- readLn -- "pulling" out of readLn causes console input to be read again
return (l1 ++ l2) -- so l1 and l2 have different values, and this works
有点。您可能听说过它IO
是一个 monad,这意味着包裹在其中的值必须使用 monadic 操作,例如 bind( >>=
)、顺序组合 ( >>
) 和return
. 所以,你可以写一个这样的问候程序:
prompt :: IO String
prompt = putStrLn "Who are you?" >>
getLine >>=
\name ->
return $ "Hello, " ++ name ++ "!"
main :: IO ()
main = prompt >>=
putStrLn
您更有可能使用等效的do
符号看到这一点,这只是编写完全相同的程序的另一种方式。不过,在这种情况下,我认为未加糖的版本更清楚地表明,计算是一系列用>>
and链接在一起的语句>>=
,>>
当我们想要抛出上一步的结果时,以及>>=
当我们想要传递一个结果到链中的下一个函数。如果我们需要为该结果命名,我们可以将其捕获为参数,就像在 lambda 表达式\name ->
中一样prompt
。如果我们需要将一个简单的提升String
为一个IO String
,我们使用return
.
顺便说一下,等价的do
符号是:
prompt :: IO String
prompt = do
putStrLn "Who are you?"
name <- getLine
return $ "Hello, " ++ name ++ "!"
main :: IO ()
main = do
message <- prompt
putStrLn message
那么它如何知道main
什么都不返回的 不是参照透明的,prompt
而返回 an 的那个IO String
也不是?有一些特别之处IO
,或者至少IO
缺少一些东西:对于许多其他 monad,例如State
and Maybe
,有一种方法可以在 monad 内部进行惰性计算并丢弃包装器,从而得到一个纯值。您可以在其中声明一个State Int
单子、do
确定性、顺序、有状态的计算一段时间,然后使用它evalState
来获取纯Int
结果。您可以进行Maybe Char
计算,例如在字符串中搜索一个字符,检查它是否有效,如果有效,则将其读Char
回。
,IO
你不能这样做。如果你有一个IO String
,你所能做的就是将它绑定到一个IO
带String
参数的函数,例如PutStrLn
,或者将它传递给一个带IO String
参数的函数。如果你prompt
第二次调用,它不会默默地给你同样的结果;它实际上会再次运行。如果您告诉它暂停几毫秒,它不会懒惰地等到您稍后在程序中需要一些返回值来执行此操作。如果它返回一个空值,如IO ()
,编译器将不会仅通过返回该常量来优化它。
这在内部工作的方式是将对象与每个调用不同的世界状态参数包装在一起。这意味着两个不同的调用getLine
依赖于世界的不同状态,而 的返回值main
需要计算世界的最终状态,这取决于之前的所有IO
操作。