IO与参考透明度

时间:2017-05-23 07:24:21

标签: haskell referential-transparency

很抱歉这里有一个新问题,但是Haskell如何知道不将引用透明度应用于例如readLnputStrLn两次相同的字符串?是因为涉及IO吗? IOW,编译器不会将引用透明度应用于返回IO的函数吗?

4 个答案:

答案 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,例如IOIO,有一种方法可以进行延迟计算在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操作