很抱歉这里有一个新问题,但是Haskell如何知道不将引用透明度应用于例如readLn
或putStrLn
两次相同的字符串?是因为涉及IO
吗? IOW,编译器不会将引用透明度应用于返回IO
的函数吗?
答案 0 :(得分:11)
您需要区分评估和执行。
如果评估 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命令对象中。它不是理解评估和执行之间分离的核心。)
答案 1 :(得分:2)
由于返回值包含在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
答案 2 :(得分:2)
IO类型定义为:
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
通知非常类似于州Monad,其中州是现实世界的状态,因此,您可以将IO视为参考透明和纯粹,不纯的是运行IO操作的Haskell运行时(解释器)(代数)。
看看Haskell wiki,它更详细地解释了IO:IO Inside
答案 3 :(得分:1)
排序。您可能听说IO
是一个monad,这意味着包含在其中的值必须使用monadic操作,例如bind(>>=
),顺序组合(>>
)和{ {1}}。所以,你可以写一个像这样的问候语程序:
return
你更有可能用等效的prompt :: IO String
prompt = putStrLn "Who are you?" >>
getLine >>=
\name ->
return $ "Hello, " ++ name ++ "!"
main :: IO ()
main = prompt >>=
putStrLn
符号来看这个,这只是编写完全相同程序的另一种方式。但是,在这种情况下,我认为未加工版本更清楚地表明计算是与do
和>>
链接在一起的一系列语句,当我们想要抛出时我们使用>>=
当我们想要将结果传递给链中的下一个函数时,输出上一步的结果,>>
。如果我们需要为该结果命名,我们可以将其作为参数捕获,就像在>>=
内的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
的{{1}}也不是? prompt
有一些特别之处,或至少有IO String
缺少的内容:对于许多其他monad,例如IO
和IO
,有一种方法可以进行延迟计算在monad内部并丢弃包装器,获得纯粹的价值。您可以在其中声明State
monad,Maybe
确定性,顺序,有状态计算一段时间,然后使用State Int
获取纯do
结果。您可以执行evalState
计算,例如在字符串中搜索字符,检查它是否有效,如果是,请再次阅读纯Int
。
使用Maybe Char
,您无法执行此操作。如果您有一个Char
,那么您可以将它绑定到IO
函数,该函数接受IO String
参数,例如IO
,或将其传递给采用String
参数的函数。如果你第二次打电话给PutStrLn
,它就不会默默地给你相同的结果;它实际上会再次运行。如果你告诉它暂停几毫秒,它就不会懒得等到你在程序后面需要一些返回值才能做到这一点。如果它返回一个空值,如IO String
,则编译器不会通过返回该常量来优化它。
这在内部的工作方式是将对象与每个调用不同的world-of-world参数一起包装。这意味着对prompt
的两个不同调用取决于世界的不同状态,IO ()
的返回值需要计算世界的最终状态,这取决于之前的所有getLine
操作