从概念上讲,执行输出的计算似乎与仅执行输入的计算非常不同。从某种意义上说,后者更纯粹。
我想,有一种方法可以将我的程序中仅输入的部分与可能实际写出的部分分开。
那么,为什么只有Monad没有输入?
为什么不能使用I monad(以及可以合并到IO Monad中的O Monad)的任何原因?
编辑:我主要是将输入视为阅读文件,而不是与用户交互。这也是我的用例,我可以假设输入文件在程序执行期间不会改变(否则,获取未定义的行为就好了。)
答案 0 :(得分:21)
我不同意bdonlan的回答。确实,输入和输出都不是更“纯粹”,但它们 完全不同。将IO视为单个“罪孽箱”并将所有效果挤在一起是非常有效的,它确实可以确保某些属性更难。例如,如果您知道许多函数只从某些内存位置读取,并且永远不会导致这些位置被更改,那么很高兴知道您可以重新排序它们的执行。或者如果你有一个使用forkIO和MVars的程序,那么根据它的类型知道它也不是在读取/ etc / passwd会很高兴。
此外,除了堆叠变换器之外,一个可以以一种方式构成单子效果。你不能用所有 monads(只是免费的monad)来做这件事,但是对于像这样的情况,你真正需要的是它。例如,iospec软件包提供了一个纯粹的IO规范 - 它没有单独的读写,但它确实将它们从例如STM,MVars,forkIO,soforth中分离出来。
http://hackage.haskell.org/package/IOSpec
Data Types a la Carte论文中描述了如何干净地组合不同monad的关键思路(精彩阅读,非常有影响力,不能推荐,等等。)
答案 1 :(得分:12)
IO monad的“输入”端输出和输入一样多。如果您消耗了一行输入,那么您消耗该输入的事实将被传递到外部,并且还被记录为不纯的状态(即,您之后不再使用相同的行) );它与putStrLn
的输出操作一样多。此外,必须根据输出操作对输入操作进行排序;这再次限制了你可以将两者分开的程度。
如果你想要一个纯粹的只读monad,你应该使用reader monad代替。
那就是说,你似乎对monads的组合有点困惑。虽然你确实可以组合两个monad(假设一个是monad变换器)并获得某种混合语义,但你必须能够运行结果。也就是说,即使您可以定义IT (OT Identity) r
,您如何运行它?在这种情况下,您没有根IO
monad,因此main必须是纯函数。这意味着你有main = runIdentity . runOT . runIT $ ...
。这是无稽之谈,因为你从纯粹的语境中获得了不纯净的效果。
换句话说,必须修复IO monad的类型。它不能是用户可选择的转换类型,因为它的类型被固定到main
。当然,你可以调用它I (O Identity)
,但你没有获得任何东西; O (I Identity)
将是无用的类型,I []
或O Maybe
,因为您永远无法运行其中任何一种。
当然,如果将IO
保留为基本IO
monad类型,则可以定义以下例程:
runI :: I Identity r -> IO r
这很有效,但是再一次,你不能轻易地拥有这个我monad下面的任何东西,并且你从这种复杂性中获得的收益并不高。无论如何,甚至意味着什么让一个输出monad转换为List base monad?
答案 2 :(得分:3)
当您获得输入时,会导致副作用改变外部世界的状态(消耗输入)和程序(使用输入)。输出时,会产生只会改变外界状态的副作用(产生输出);输出本身的行为不会改变程序的状态。所以你可能会说O
比I
更“纯粹”。
除了输出确实改变了程序的执行状态(它不会反复重复相同的输出操作;它必须进行某种状态更改才能继续)。这一切都取决于你如何看待它。但是将输入和输出的肮脏程度混淆到同一个monad中要容易得多。任何有用的程序都将输入和输出。您可以将您使用的操作分类为一个或另一个,但我没有看到使用类型系统执行任务的令人信服的理由。
要么你正在搞乱外面的世界,要么就是你没有。
答案 3 :(得分:2)
简短回答:IO不是I / O. 如果你愿意,其他人有更长的答案。
答案 4 :(得分:0)
我认为纯代码和不纯代码之间的区别有点武断。这取决于你放置屏障的位置。 Haskell的设计师决定将语言的纯功能部分与其他部分明确区分开来。
所以我们有IO monad,它包含了所有可能的效果(不同的,如磁盘读/写,网络,内存访问)。语言通过返回类型强制执行明确的划分。这引发了一种将纯粹和不纯洁的东西分开的思维方式。
如果涉及信息安全,将阅读和写作分开是很自然的。但是对于haskell的最初目标,要成为标准的懒惰纯函数语言,这是一种过度杀伤。