从概念上讲,执行输出的计算与仅执行输入的计算非常不同。某种意义上,后者更加纯粹。
就我而言,我希望有一种方法将程序中仅涉及输入的部分与可能实际写出内容的部分分开。
那么,为什么没有仅限输入的Monad呢?
是否有任何原因不能使用I Monad(和O Monad),并将其组合成IO Monad?
编辑:我主要指读取文件作为输入,而不是与用户交互。这也是我的使用情况,我可以假设输入文件在程序执行期间不会改变(否则,获得未定义行为是可以接受的)。
从概念上讲,执行输出的计算与仅执行输入的计算非常不同。某种意义上,后者更加纯粹。
就我而言,我希望有一种方法将程序中仅涉及输入的部分与可能实际写出内容的部分分开。
那么,为什么没有仅限输入的Monad呢?
是否有任何原因不能使用I Monad(和O Monad),并将其组合成IO Monad?
编辑:我主要指读取文件作为输入,而不是与用户交互。这也是我的使用情况,我可以假设输入文件在程序执行期间不会改变(否则,获得未定义行为是可以接受的)。
http://hackage.haskell.org/package/IOSpec
如何干净地组合不同的单子的关键思想在Data Types a la Carte论文中有描述(非常好的阅读材料,具有很大的影响力,强烈推荐等等)。
Restr (Stdin :+: W) ()
的操作只能从标准输入读取并写入文件,不能进行其他操作。然后,您可以创建任意新的能力以满足您的需求。 - Iceland_jackputStrLn
一样,是一个输出操作。此外,输入操作必须与输出操作相对应;这再次限制了你能够将两者分离的程度。IT (OT Identity) r
,你如何运行它呢?在这种情况下,你没有根IO
单子,因此主函数必须是纯函数。这意味着你会得到从纯上下文中得到非纯效果的荒谬结果,因为你无法运行它。main
中。当然,你可以将其命名为I (O Identity)
,但你不会得到任何好处;O (I Identity)
将会是一个无用的类型,I []
或O Maybe
也一样,因为你永远无法运行其中的任何一个。IO
保持作为基本的IO
单子类型,你可以定义像下面这样的例程:runI :: I Identity r -> IO r
这个方案可以运行,但是你很难在这个 I Monad 下面添加任何内容,并且你并没有从中得到太多的复杂性收益。反正,一个基于列表的单元格被转换为输出单元格的意义是什么?
I
单子的特定保证,这些保证在纯代码中明确不存在。 - bdonlanO
比I
更加"纯粹"。我认为纯代码和非纯代码之间的划分有些主观。这取决于您在哪里设置障碍。Haskell的设计者决定明确地将语言的纯函数部分与其他部分区分开来。
因此,我们有IO monad,它包含所有可能的影响(如磁盘读写、网络、内存访问等)。语言通过返回类型强制执行清晰的分离。这引发了一种思考方式,将一切划分为纯和非纯。
如果涉及信息安全,自然而然地会将读取和写入分开。但对于Haskell最初的目标——成为标准的惰性纯函数语言而言,这是过度。
print
函数返回什么,那就有些困难了。print :: String -> ()
不起作用:如果你评估类似runAll [print "foo", print "foo"]
这样的表达式,由于 Haskell 是惰性求值的,你可能只会打印一个 "foo" ,因为一旦你评估了一个带有特定参数的纯函数,你就不需要再次评估它了。 (还可能发生其他“疯狂”的事情:乱序打印、根本什么也没有打印...) - R. Martinho Fernandes