Haskell和Purity中的Monads

joe*_*oeA 3 io monads haskell

我的问题是haskell中的monad是否真的保持了hakell的纯度,如果是的话.我经常读到副作用是如何不纯的,但有用的程序(例如i/o)需要副作用.在下一句中,声明haskell的解决方案是monads.然后monad在某种程度上被解释,但实际上并不是他们如何解决副作用问题.

我已经看到了这个这个,我对答案的解释实际上就是我自己读到的那个 - IO monad的"动作"不是i/o本身,而是执行时执行的对象/ O.但是我发现可以为任何代码或任何已编译的可执行文件创建相同的参数.难道你不能说C++程序只在编译代码执行时产生副作用吗?所有的C++都在IO monad中,所以C++是纯粹的?我怀疑这是真的,但我老实说不知道它不是以什么方式存在.事实上,Moggi(sp?)最初是否使用monads来模拟命令式程序的指称语义?

一些背景:我是haskell和函数式编程的粉丝,我希望随着我的学习继续学习更多.例如,我理解参考透明度的好处.这个问题的动机是我是一名研究生,我将为编程语言课提供2个1小时的演示文稿,其中一个特别涉及haskell,另一个涉及函数式编程.我怀疑大多数班级不熟悉函数式编程,可能已经看过一些方案.我希望能够(合理地)清楚地解释monad如何解决纯度问题而不进入类别理论和monad的理论基础,我没有时间去讨论,无论如何我不完全了解自己 - 当然还不够好.

我想知道在这种情况下"纯度"是不是真的很明确?

Tik*_*vis 10

在任何一个方向上都难以最终确定,因为"纯粹"并不是特别明确.当然,有些东西使Haskell与其他语言根本不同,它与管理副作用和类型IO¹ 密切相关,但目前尚不清楚究竟什么.给定一个具体的定义来引用我们可以检查它是否适用,但这并不容易:这样的定义往往不符合每个人的期望或太宽泛而无用.

那么是什么让Haskell变得特别呢?在我看来,这是评估执行之间的分离.

与λ-caluclus密切相关的基本语言都是关于前者的.你与评估对其它表达式都工作,1 + 12.这里没有任何副作用,不是因为它们被压制或被删除,而仅仅因为它们首先没有意义.它们不是模型的一部分,比如回溯搜索是Java模型的一部分(而不是Prolog).

如果我们只是坚持使用这种基本语言而没有额外的设施IO,我认为将它称为"纯粹"是相当无争议的.它可能仍然有用,可能是Mathematica的替代品.您可以将程序编写为表达式,然后获取在REPL处计算表达式的结果.只不过是一个花哨的计算器,没有人指责你在计算器中使用的表达语言是不纯的³!

但是,当然,这太有限了.我们希望使用我们的语言来读取文件并提供网页,绘制图片和控制机器人以及与用户交互.因此,问题是如何保留我们喜欢的关于评估表达式的所有内容,同时扩展我们的语言以完成我们想要的任何事情.

我们得出的答案是什么?IO.一种特殊类型的表达式,我们的计算器式语言可以评估哪种表达式对应于执行一些有效的操作.至关重要的是,评估仍然像以前一样工作,即使对于事情也是如此IO.效果按结果IO值指定的顺序执行,而不是基于其评估方式.IO是我们用来将效果引入和管理我们纯粹的表达语言.

我认为这足以使Haskell描述为"纯粹"有意义.

脚注

¹请注意我的说法IO而不是一般的monad:monad的概念对于输入和输出无关的许多事情非常有用,而且IO类型必须不仅仅是 monad才有用.我觉得这两者在共同话语中的联系过于紧密.

²这就是为什么unsafePerformIO这么好,不安全:它打破了语言的核心抽象.这与使用C语言中的特定寄存器相同:它可以导致奇怪的行为并阻止代码被移植,因为它低于C的抽象级别.

³大多数情况下,只要我们忽略生成随机数等事情.


ama*_*loy 5

例如,具有类型的函数a -> IO b在给定相同输入时总是返回相同的 IO 操作;它是纯的,因为它不可能检查环境,并且遵守纯函数的所有通常规则。这意味着,除其他外,编译器可以将其所有常用优化规则应用于IO其类型中带有 an 的函数,因为它知道它们仍然是纯函数。

现在,返回的 IO 操作可能会在 run时查看环境、读取文件、修改全局状态等等,一旦您运行一个操作,所有赌注都将关闭。但是你不一定要运行一个动作;您可以将其中的五个放入一个列表中,然后按照您创建它们的相反顺序运行它们,或者如果您愿意,可以根本不运行其中的一些;如果 IO 操作在您创建它们时隐式地自行运行,则您无法执行此操作。

考虑这个愚蠢的程序:

main :: IO ()
main = do
  inputs <- take 5 . lines <$> getContents
  let [line1,line2,line3,line4,line5] = map print inputs
  line3
  line1
  line2
  line5
Run Code Online (Sandbox Code Playgroud)

如果你运行这个,然后输入 5 行,你会看到它们以不同的顺序打印给你,并且省略了一个,即使我们的 haskell 程序map print按照它们接收的顺序运行它们。你不能用 C's 做到这一点printf,因为它在调用时立即执行它的 IO;haskell 的版本只返回一个 IO 操作,您仍然可以将其作为一流的值进行操作并做任何您想做的事情。