单哈斯克尔和纯度
我的问题是haskell中的monad是否确实保持hakell的纯度,如果是的话。 我经常阅读有关副作用是如何不纯的,但是有用的程序(例如I / O)需要副作用。 在接下来的句子中,有人说Haskell解决这个问题的方法是单子。 然后,单子们在某种程度上被解释了,但是他们并没有真正解决副作用问题。
我已经看到了这个和这个,而我对这些答案的解释实际上是在我自己的读物中找到的 - IO monad的“行为”不是I / O本身,而是当它们执行时执行i / O。 但是对我而言,可以为任何代码或者任何已编译的可执行文件提供相同的参数。 难道你不能说C ++程序只在编译代码执行时产生副作用吗? 所有的C ++都在IO monad中,所以C ++是纯粹的? 我怀疑这是真的,但我真的不知道它不是什么方式。 事实上,Moggi(sp?)最初是否使用monads来模拟命令式程序的指称语义?
一些背景:我是哈斯克尔和函数式编程的粉丝,我希望在我的研究继续下去的时候更多地了解这两者。 例如,我明白参照透明度的好处。 这个问题的动机是我是一名研究生,我将给一个编程语言类2个1小时的演示文稿,其中一个涵盖特定的Haskell,另一个涵盖函数式编程。 我怀疑这个班的大部分人对函数式编程不熟悉,可能看过一些方案。 我希望能够(合理地)清楚地解释monad如何解决纯度问题,而不必进入类别理论和单子的理论基础,这是我没有时间来讨论的,无论如何我不完全了解自己 - 当然不够好,不足以呈现。
我想知道这种情况下的“纯度”是否真的不明确?
由于“纯粹”不是特别明确的,所以很难在任何一个方向上进行彻底的争论。 当然,有些东西使得Haskell从根本上与其他语言有所不同,它与管理副作用和IO
¹有着深刻的关系,但并不清楚究竟是什么。 给定一个具体的定义来指代我们可以检查它是否适用,但这并不容易:这样的定义往往不符合所有人的期望或者过于宽泛而无法使用。
那么,Haskell特别的是什么? 在我看来, 评估和执行是分开的。
基础语言 - 与λ-caluclus密切相关 - 全部关于前者。 你使用表达式来评估其他表达式, 1 + 1
到2
。 这里没有任何副作用,不是因为它们被压制或移除,而仅仅是因为它们一开始就没有意义。 它们不是模型的一部分2,比如,回溯搜索是Java模型的一部分(与Prolog相对)。
如果我们坚持使用这种基本语言而没有增加IO
,我认为将其称为“纯粹”是相当无争议的。 它或许可以作为Mathematica的替代品。 你会将你的程序写成一个表达式,然后得到在REPL处评估表达式的结果。 没有什么比一个花哨的计算器,没有人指责你用在计算器中的表达语言是不纯的!
但是,当然,这太局限了。 我们希望使用我们的语言来读取文件并提供网页,绘制图片和控制机器人,并与用户进行交互。 那么问题是如何保留我们喜欢的关于评估表达式的所有内容,同时扩展我们的语言以完成我们想要的任何事情。
我们提出的答案是? IO
。 一种特殊类型的表达方式,我们的类似计算器的语言可以评估哪种表达方式适合做一些有效的操作。 至关重要的是,评估仍然像以前一样工作,即使是在IO
。 该效果按照由生成的IO
值指定的顺序执行,而不是基于如何评估它。 IO
是我们用来将效果引入和管理到其他纯粹的表达式语言中的。
我认为这足以描述Haskell“纯粹”的意义。
脚注
¹请注意,我如何说IO
而不是monad:一个monad的概念对于许多与输入和输出无关的事情非常有用,并且IO
类型必须不仅仅是一个有用的monad。 我觉得两者在共同话语中联系得太紧密。
²这就是为什么unsafePerformIO
如此不安全:它打破了语言的核心抽象。 这与在C语言中使用特定寄存器的方式相同:它可能会导致奇怪的行为并阻止代码的可移植性,因为它低于C的抽象级别。
³大部分情况下,只要我们忽略诸如产生随机数的事情。
例如, a -> IO b
类型为a -> IO b
的函数总是在给定相同的输入时返回相同的IO操作; 它是纯粹的,因为它不可能检查环境,并且服从纯粹功能的所有通常规则。 这意味着,编译器可以将其所有通常的优化规则应用于具有其类型中的IO
函数,因为它知道它们仍然是纯函数。
现在,返回的IO操作可能在运行时查看环境,读取文件,修改全局状态,无论如何,一旦您执行操作,所有投注都将关闭。 但是你并不一定要采取行动; 您可以将其中的五个放入列表中,然后按照您创建它们的顺序反向运行它们,或者根本不运行它们中的一些; 如果IO操作在创建时隐式运行,则无法执行此操作。
考虑这个愚蠢的计划:
main :: IO ()
main = do
inputs <- take 5 . lines <$> getContents
let [line1,line2,line3,line4,line5] = map print inputs
line3
line1
line2
line5
如果你运行这个,然后输入5行,你会看到它们打印回给你,但是按照不同的顺序,并且省略了一个,即使我们的haskell程序按照它们接收的顺序在它们上面执行map print
。 你不能用C的printf
做到这一点,因为它在调用时立即执行它的IO; haskell的版本只是返回一个IO动作,你仍然可以作为一流的值操作,并做任何你想做的事情。
我在这里看到两个主要区别:
1)在haskell中,你可以做一些不在IO monad中的东西。 为什么这很好? 因为如果你有一个函数definitelyDoesntLaunchNukes :: Int -> IO Int
你不知道生成的IO操作不会启动核心,它可能是你所知道的。 cantLaunchNukes :: Int -> Int
绝对不会发射任何核武器(除了几乎所有情况下都应该避免的任何难看的黑客攻击)。
2)在haskell中,它不仅仅是一个可爱的类比:IO操作是一流的值。 你可以把它们放在列表中,只要你想要就放在那里,除非它们成为main
行动的一部分,否则它们不会做任何事情。 与C最接近的是函数指针,它使用起来相当麻烦。 在C ++(实际上是大多数现代命令式语言)中,你有技术上可以用于此目的的闭包,但很少 - 因为Haskell是纯粹的,而不是。
为什么这个区别在这里很重要? 那么,你将从哪里获得你的其他IO动作/关闭? 可能是一些描述的功能/方法。 用不纯的语言来说,它们本身可能有副作用,使得用这些语言将它们孤立起来的尝试毫无意义。
链接地址: http://www.djcxy.com/p/43201.html上一篇: Monads in Haskell and Purity
下一篇: Monadic fold with State monad in constant space (heap and stack)?