IO是自由Monad吗?

12
在Mark Seemann的博客文章和示例中,我初步了解到自由单子作为一种组织纯代码和IO代码之间边界的方式。我的基本理解是,自由单子允许您构建一个程序(抽象语法树-AST)的纯函数,然后解释器将其转换为一系列不纯的过程调用。因此,这个解释器将AST的纯操作转换为一系列单子IO操作。
我想知道这是否重复了Haskell运行时已经使用IO单子所做的事情。如果我将IO视为没有什么特别之处,而是一种常规的单子,其绑定函数>>=通过IO中的所有单子操作顺序地传递“Real World”的状态,那么这种排序本身并不提供任何计算(如在这里的优秀答案中对自由单子所解释的)。然后,我可以将所有IO操作(如getLinewriteFile等)视为自由IO单子中的操作,将Haskell运行时视为解释器。运行时通过某些底层系统调用、C FFI调用或类似方法解释每个IO操作,这显然是不纯的。
因此,在这种观点下,返回IO操作的函数只是构建了AST,然后由Haskell运行时解释。但到目前为止,一切都是纯的。在这种观点下,函数a -> IO b并不是不纯的,就像自由单子中的操作一样不纯。
这个直觉正确吗?如果不是,它的局限性在哪里?

这里的关键区别在于,可以根据运行先前程序所产生的值来构建新程序,而不是像已知完整组合树一样(这将是一种应用,而不是单子);它是在我们继续构建的过程中建立的,即在实际解释和运行该程序之后。 - Will Ness
@WillNess 这对于 IO 和自由单子都是正确的。 - Fyodor Soikin
只是想强调这一点。 - Will Ness
1个回答

14
您的直觉是正确的:使用 IO 类型的函数确实构建了一个操作树,然后由运行时解释执行。至少从这个角度来看是这样的(也可以参考 Will Ness 的评论)。
与自由单子不同的是,它只有一个解释器。您不能选择另一个解释器,也无法自己实现解释器。
自由单子的 AST 有两个主要属性:首先是可组合性;其次是可分析性。解释器可以通过匹配其构造函数对 AST 进行分析,并相应地执行解释。 IO 单子具有第一个属性,但不具备第二个属性。如果您有一个类型为 IO String 的值,则无法确定它是通过调用 readLnpure "foo" 或其他什么方式创建的。

关于第二个属性,这是因为只有一个构造函数(深藏在实现的深处),像readLn这样的东西(如果你将其视为类型级函数,其种类为Type -> IO Type),pure只使用了那个构造函数吗? - chepner
2
@chepner 是的,这是另一种半正确的看法:你可以说 IO 是一个自由单子,只有一个操作,它被命名为“_调用此函数_”。我认为正是所有可能的操作都表示为一个操作,使得它缺乏第二个属性。 - Fyodor Soikin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接