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

Mic*_*mza 5 monads haskell lazy-evaluation

Haskell使用什么机制来实际决定调用下面的4个动作?

main :: IO ()
main = getLine >>= putStrLn >> getLine >>= putStrLn
Run Code Online (Sandbox Code Playgroud)

最初我认为这与懒惰的评估有关,但是......从Real Word Haskell,关于IO动作,他们

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

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

Ale*_*lec 12

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

  • IO动作可以被组装并通过组成>>=,>>,<*>,fmap,等,以产生其它任何IO行动,但
  • 只有main IO动作会产生效果.

从某种意义上说,所有Haskell程序都会运行main :: IO ().对于要评估的任何事物,它必须阻碍运行IO动作(这是懒惰适合的地方).这引出了一个问题:实际执行某项IO行动意味着什么

在引擎盖下,IO最终表现得像一个(严格)Statemonad,它通过它穿过一个RealWorld状态(它不包含任何信息 - 它象征着副作用包含在世界上的状态),所以"运行" IO(等同于State RealWorld)就像打电话runState.当然,runState对于任何程序来说,这只会发生一次 - 而这正是main它(以及它是什么让它变得神奇)!


Ben*_*Ben 8

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

内置库中的Haskell提供"基本"IO操作,例如getLine :: IO String,返回IO操作的函数putStrLn :: String -> IO (),以及从其他IO操作构建IO操作的方法(主要是通过提供Monad接口,所以任何可以在任何monad上工作的东西就像所有的东西一样)Control.Monad是一种合作方式IO).

所有这些都是纯粹和懒惰的,与非IO Haskell代码完全相同.IO不是任何东西,你可以用普通的Haskell代码做的(这就是为什么你可以在IO使用单子,通用代码的特殊情况,所有的代码编写,若不是IO有任何特殊规则的任何知识编译,所以它只能这样工作,如果没有).

但这些都不会执行IO动作; 它只会使新的IO动作脱离其他动作.当人们谈论"评估IO行为不会产生影响"时,这就是人们的意思."apple" ++ "banana"类型的值String可以由未评估的thunk表示; 当它被评估"applebanana"它仍然代表完全相同的值时,系统只是将它记录为内存中的数据,而不是指向可以运行以产生它的一些代码的指针1.以完全相同的方式putStrLn "apple" >> putStrLn "banana",类型的值IO ()可以由未评估的thunk表示,并且当它被评估时,所有这意味着系统现在用数据结构表示相同的值而不是指向将运行的代码的指针(>>其他两个IO动作的纯粹,懒惰的功能.但我们只讨论了系统在IO操作中的内存表示,而不是实际运行它们以产生一些副作用.

而实际上存在是谈论IO动作是如何进行的Haskell没有语言功能.运行时系统"只知道"如何mainMain模块3执行IO动作.Haskell语言无法讨论如何或是否发生这种情况; 这一切都由为您提供Haskell的系统(GHC或其他Haskell系统)处理.Haskell语言给你的唯一选择main是定义为Haskell动作; 您作为定义的一部分合并的任何IO操作都main将运行.


1我假装unsafePerformIO为了讨论的目的不存在这样的事情.顾名思义,它故意违反正常规则.它也不是为了将"执行IO动作"作为Haskell语言的正常部分引入,而是仅用于呈现"普通Haskell"接口的内容.

2通常这部分发生:只有非常基本的类型,如Int"全有或全无"评估.大多数可以部分地评估到包含更深层次的thunk的数据结构(以后可能会或可能不会自己评估).

3或GHCi"只知道"如何执行您在其提示符处输入的IO操作.


Mic*_*mza 0

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

type IO a  =  RealWorld -> (a, RealWorld)
Run Code Online (Sandbox Code Playgroud)

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

main :: RealWorld -> ((), RealWorld)
Run Code Online (Sandbox Code Playgroud)

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

如果是

main = getLine >>= putStrLn >> getLine >>= putStrLn
Run Code Online (Sandbox Code Playgroud)

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

所以它惰性评估,但具有隐藏的 RealWorld价值。