评估和执行 IO 操作之间的区别:是什么导致 Haskell 执行 IO



Haskell使用什么机制来实际决定调用以下4个操作?

main :: IO ()
main = getLine >>= putStrLn >> getLine >>= putStrLn

最初我以为这与懒惰评估有关,但是...从真实单词哈斯克尔,关于 IO 操作,他们

执行时产生效果,但在评估时不产生效果

所以我怀疑这是其他机制,而不是想要"评估"main的系统。这是什么机制?或者如果是评估,Haskell"想要"评估什么导致它执行行动链?

作为一阶近似,Haskell程序中唯一的评估来源是main。这意味着:

  • IO动作可以通过>>=>><*>fmap等来组合和组合,以产生任何其他IO动作,但
  • 只有mainIO行动才能产生效果。

从某种意义上说,Haskell程序所做的一切都是运行main :: IO ()。对于要评估的任何东西,它必须阻碍运行IO操作(这就是懒惰适合的地方)。这就引出了一个问题:实际运行IO操作意味着什么

在引擎盖下,IO最终表现得像一个(严格的)Statemonad,它穿过它一个RealWorld状态(不包含任何信息 - 它象征着副作用对世界所包含的状态),所以"运行"IO(有点相当于State RealWorld)就像调用runState。当然,对于任何程序,这种runState只能发生一次 - 这正是main所做的(也是让它变得神奇的原因)!

这可能看起来很奇怪,但运行 IO 操作实际上超出了普通 Haskell 语言的范围!1

Haskell内置的库提供了"基本"的IO操作,如getLine :: IO String,返回IO操作的函数,如putStrLn :: String -> IO (),以及从其他IO操作构建IO操作的方法(主要是通过提供Monad接口,所以任何适用于任何monad的东西,如Control.Monad中的所有东西都是使用IO的一种方式)。

所有这些都是纯粹和懒惰的,就像非IO Haskell代码一样。 对于你可以用普通的Haskell代码做任何事情来说,IO都不是一个特例(这就是为什么你可以在IO上使用Monad泛型代码;所有这些代码都是在不知道IO有任何特殊规则的情况下编写和编译的,所以它只有在没有任何规则的情况下才能工作)。

但是这些实际上都没有执行 IO 操作;它只是从其他操作中创建新的 IO 操作。这就是人们在谈论"评估IO操作不会产生效果"时的意思。类型String的值"apple" ++ "banana"可以用未计算的 thunk 表示;当它被评估为"applebanana"它仍然表示完全相同的值时,系统只是将其记录为内存中的数据,而不是指向可以运行以生成它1的一些代码的指针。以完全相同的方式,类型IO ()的值putStrLn "apple" >> putStrLn "banana"可以用未计算的 thunk 表示,当它被评估时,这意味着系统现在使用数据结构而不是指向代码的指针来表示相同的值,该代码将在另外两个 IO 操作上运行(纯、惰性)函数>>。但是我们只讨论了系统对 IO 操作的内存表示,没有讨论实际运行它们以产生一些副作用。

实际上,Haskell没有任何语言功能可以讨论IO操作是如何执行的。运行时系统"只知道"如何从Main模块3执行mainIO 操作。哈斯克尔语言无法谈论这种情况是如何发生的或是否发生的;这一切都由向您提供Haskell的系统(GHC或其他Haskell系统)处理。Haskell语言给你的唯一选择是main被定义为Haskell动作;作为main定义的一部分合并的任何 IO 操作都将运行。


1我假装像unsafePerformIO这样的东西不存在这个讨论的目的。顾名思义,它故意打破正常规则。它也不是为了引入"执行IO操作"作为Haskell语言的正常部分,而只是用于呈现"普通Haskell"界面的内部。

2通常这种情况部分发生:只有非常基本的类型(如Int)才会被"全有或全无"评估。大多数可以部分评估为包含更深层次的数据结构(以后可能会也可能不会对其进行评估)。

3或者 GHCi"只知道"如何执行您在提示符下输入的 IO 操作。

根据 https://wiki.haskell.org/IO_inside#Welcome_to_the_RealWorld.2C_baby 的说法,有一个"假"类型代表现实世界,RealWorldIO (a)实际上是一个函数。

type IO a  =  RealWorld -> (a, RealWorld)

所以main,正如您在其他语言中所期望的那样,实际上是一个函数

main :: RealWorld -> ((), RealWorld)

在程序运行时调用。因此,要评估最终输出,类型为((), RealWorld),Haskell 需要获取RealWorld组件的值,为此,它必须运行main函数。注意:运行此函数的是运行时。在 Haskell 中没有办法触发此函数的执行。

在以下情况下:

main = getLine >>= putStrLn >> getLine >>= putStrLn

每个动作实际上都是函数,为了计算出最终putStrLn结束时输出的RealWorld值,它需要运行它,以及导致它的所有动作。

所以它是懒惰的评估,但隐藏RealWorld值。

相关内容

  • 没有找到相关文章

最新更新