任何人都可以对为什么Haskell中的不纯计算被建模为monad给出一些指示吗?
我的意思是monad只是一个具有4种操作的接口,所以在其中建模副作用的原因是什么?
任何人都可以对为什么Haskell中的不纯计算被建模为monad给出一些指示吗?
我的意思是monad只是一个具有4种操作的接口,所以在其中建模副作用的原因是什么?
return
和(>>=)
。x >> y
与x >>= \\_ -> y
(即忽略第一个参数的结果)相同。我们不谈论fail
。
fail
而Monad
上课;它确实属于MonadPlus
。请注意,其默认定义是不安全的。
Answers:
假设一个函数有副作用。如果我们将其产生的所有效果作为输入和输出参数,则该函数对外界是纯净的。
因此,对于不纯净的功能
f' :: Int -> Int
我们将RealWorld加入考虑范围
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.
然后又f
是纯净的。我们定义了一个参数化的数据类型type IO a = RealWorld -> (a, RealWorld)
,因此我们不需要多次键入RealWorld,而只需编写
f :: Int -> IO Int
对于程序员来说,直接处理RealWorld太危险了-特别是,如果程序员接触到RealWorld类型的值,他们可能会尝试复制它,这基本上是不可能的。(例如,想尝试复制整个文件系统。您将它放在哪里?)因此,我们对IO的定义也封装了整个世界的状态。
如果我们不能将它们链接在一起,那么这些不纯函数就没有用了。考虑
getLine :: IO String ~ RealWorld -> (String, RealWorld)
getContents :: String -> IO String ~ String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO () ~ String -> RealWorld -> ((), RealWorld)
我们想
如果我们可以进入现实世界中的国家,该怎么办?
printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
(contents, world2) = (getContents filename) world1
in (putStrLn contents) world2 -- results in ((), world3)
我们在这里看到一个模式。这些函数的调用方式如下:
...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...
因此,我们可以定义一个运算符~~~
来绑定它们:
(~~~) :: (IO b) -> (b -> IO c) -> IO c
(~~~) :: (RealWorld -> (b, RealWorld))
-> (b -> RealWorld -> (c, RealWorld))
-> (RealWorld -> (c, RealWorld))
(f ~~~ g) worldX = let (resF, worldY) = f worldX
in g resF worldY
然后我们可以简单地写
printFile = getLine ~~~ getContents ~~~ putStrLn
而不接触现实世界。
现在假设我们也要使文件内容大写。大写是一个纯粹的功能
upperCase :: String -> String
但是要使其进入现实世界,它必须返回IO String
。提升这样的功能很容易:
impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)
可以概括为:
impurify :: a -> IO a
impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)
这样impureUpperCase = impurify . upperCase
,我们可以写
printUpperCaseFile =
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn
(注意:通常我们写getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)
现在让我们看看我们做了什么:
(~~~) :: IO b -> (b -> IO c) -> IO c
将两个不纯函数链接在一起的运算符impurify :: a -> IO a
将纯值转换为不纯值的函数。现在我们做的鉴定(>>=) = (~~~)
和return = impurify
,并看到了吗?我们有一个单子。
为了确保它确实是monad,仍然需要检查一些公理:
return a >>= f = f a
impurify a = (\world -> (a, world))
(impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world )) worldX
in f resF worldY
= let (resF, worldY) = (a, worldX)
in f resF worldY
= f a worldX
f >>= return = f
(f ~~~ impurify) worldX = let (resF, worldY) = f worldX
in impurify resF worldY
= let (resF, worldY) = f worldX
in (resF, worldY)
= f worldX
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
留做运动。
RealWorld
为...好吧,您将看到。
IO
,因为后者支持交互,并发和不确定性。请参阅我对这个问题的回答以获得更多指示。
IO
这种方式实现,但RealWorld
实际上并不代表现实世界,它只是使操作井然有序的一个标记(“魔术”RealWorld
是GHC Haskell唯一的唯一性类型)
IO
通过将此表示形式与非标准的编译器魔术结合起来实现的(使人联想到Ken Thompson著名的C编译器病毒)。对于其他类型,事实与通常的Haskell语义一起存在于源代码中。
任何人都可以对为什么Haskell中的不纯计算被建模为monad给出一些指示吗?
这个问题存在广泛的误解。杂质和Monad是独立的概念。杂质不是Monad的模型。相反,有几种数据类型,例如IO
,表示命令式计算。对于其中一些类型,其接口的一小部分对应于称为“ Monad”的接口模式。此外,尽管存在关于的含义的通常被称为故事,但尚无已知的纯粹的/功能性的/解释性的解释IO
(考虑到的“罪恶箱”的目的,不可能有一个IO
)。这个故事不能如实描述,因为World -> (a, World)
IO a
IO
IO
支持并发和不确定性。当进行确定性计算以允许与计算机进行中间交互时,该故事甚至不起作用。
有关更多说明,请参见此答案。
编辑:在重新阅读问题时,我认为我的答案不太正确。正如问题所表明的那样,命令式计算模型的确经常成为单子。质问者可能不会真正假设单调性能够实现命令式计算的建模。
RealWorld
,正如文档所说,这“非常神奇”。它是表示运行时系统正在执行的操作的令牌,实际上对现实世界没有任何意义。您甚至不能想出一个新的线程来做“线程”,而不必做额外的欺骗。天真的方法只会创建一个单一的阻止动作,并且在何时运行会产生很大的不确定性。
Monad
总的来说确实如此。
Monad
一般”是指forall m. Monad m => ...
,即在任意实例上工作。您可以使用任意monad执行的操作几乎与您执行的操作完全相同IO
:接收不透明的基元(分别作为函数参数,或从库中获取),使用构造无操作return
,或使用不可逆方式转换值(>>=)
。在任意monad中进行编程的本质是生成一系列不可撤销的动作:“先执行X,然后执行Y,然后...”。听起来对我来说非常重要!
m
存在性可能会更有帮助。此外,我的“解释”是对法律的重新表述;“ do X”语句的列表恰好是通过创建的未知结构上的自由monoid (>>=)
;而monad法则只是关于endofunctor组成的monoid法则。
IO
这是一个病理情况,恰恰是因为它几乎没有提供任何其他功能。在特定情况下,类型可能揭示更多的结构,因此具有实际意义;但是在其他方面,基于法律,一元论的基本特性与原样清晰地相反IO
。如果不导出构造函数,穷举枚举原始操作或类似操作,这种情况就绝望了。
据我了解,一个叫Eugenio Moggi的人首先注意到,以前模糊的数学结构“ monad”可用于对计算机语言中的副作用进行建模,从而使用Lambda演算来指定其语义。在开发Haskell时,可以采用多种方法对不正确的计算进行建模(有关更多详细信息,请参见Simon Peyton Jones的“头发衬衫”文章),但是当Phil Wadler介绍monads时,很快就很明显这就是答案。剩下的就是历史。
任何人都可以对为什么Haskell中的不纯计算被建模为monad给出一些指示吗?
好吧,因为Haskell很纯正。您需要一个数学概念来区分类型级别上的纯计算和纯计算,并分别对程序流进行建模。
这意味着您将不得不使用某种IO a
模型来模拟不纯的计算。然后,您需要了解将这些计算组合在一起的方法,这些方法最明显,最基本,它们依次应用于(>>=
)和提升值(return
)。
通过这两个,您已经定义了一个monad(甚至都没有想到);)
此外,单子提供非常笼统,抽象厉害,这么多种类的控制流可以方便地推广在一元函数一样sequence
,liftM
或特殊的语法,使得unpureness没有这样一个特例。
正如您所说的,这Monad
是一个非常简单的结构。答案的一半是:Monad
是我们可能赋予副作用功能并能够使用它们的最简单的结构。通过Monad
这样做,我们可以做两件事:我们可以将纯值视为副作用值(return
),并且可以将副作用函数应用于副作用值以获得新的副作用值(>>=
)。失去执行上述任何一项功能的能力将使人瘫痪,因此我们的副作用类型必须至少为“” Monad
,事实证明Monad
足以实现到目前为止所需的一切。
另一半是:我们可以为“可能的副作用”赋予的最详细的结构是什么?我们当然可以将所有可能的副作用的空间视为一个集合(唯一需要的操作是成员资格)。我们可以将两个副作用依次进行组合,这将产生不同的副作用(或者可能是相同的副作用-如果第一个是“关闭计算机”,第二个是“写入文件”,那么结果这些只是“关机计算机”)。
好的,那么我们能说一下这个操作吗?它是关联的;也就是说,如果我们合并三个副作用,则按合并顺序无关紧要。如果执行(先写入文件然后读取套接字)然后关闭计算机,则与先写入文件然后(先读取套接字然后关闭)相同电脑)。但这不是可交换的:(“写入文件”然后“删除文件”)与(“删除文件”然后“写入文件”)有不同的副作用。而且我们有一个身份:特殊的副作用“无副作用”有效(“无副作用”然后“删除文件”与“删除文件”具有相同的副作用),此时任何数学家都在考虑“分组!”。但是,小组有相反的观点,通常没有办法消除副作用。“删除文件” 是不可逆的。因此,我们剩下的结构是一个monoid的结构,这意味着我们的副作用函数应该是monad。
有没有更复杂的结构?当然!我们可以将可能的副作用分为基于文件系统的影响,基于网络的影响等,并且我们可以提出更详尽的组成规则来保留这些细节。但这又归结为:Monad
非常简单,但功能强大到足以表达我们关心的大多数属性。(特别是,关联性和其他公理使我们可以在小块中测试我们的应用程序,并确信组合应用程序的副作用将与这些块的副作用组合相同)。
实际上,从功能上考虑I / O的方式非常干净。
在大多数编程语言中,您都进行输入/输出操作。在Haskell中,假设编写代码不是执行操作,而是生成您想执行的操作的列表。
Monad只是用于此目的的漂亮语法。
如果您想知道为什么monad而不是其他东西,我想答案是它们是代表人们在制作Haskell时可能想到的I / O的最佳功能方式。
AFAIK,原因是能够在类型系统中包括副作用检查。如果您想了解更多信息,请收听那些SE-Radio剧集:第108集:Simon Peyton Jones,函数编程和Haskell第72集:Erik Meijer,LINQ
上面有很好的具有理论背景的详细答案。但是我想对IO monad发表看法。我不是经验丰富的haskell程序员,所以可能是天真的,甚至是错误的。但是我在某种程度上帮助我处理了IO monad(请注意,它与其他monad没有关系)。
首先,我想说的是,关于“真实世界”的示例对我来说还不太清楚,因为我们无法访问其(真实世界)的先前状态。也许它根本不涉及monad计算,但从参照透明性的角度来看是合乎需要的,通常在haskell代码中存在。
因此,我们希望我们的语言(haskell)是纯净的。但是我们需要输入/输出操作,因为没有它们,我们的程序将无法使用。而且,这些操作本质上不可能是纯粹的。因此,解决此问题的唯一方法是必须将不纯运算与其余代码分开。
monad来了。实际上,我不确定是否不存在其他具有类似所需属性的构造,但是重点是monad具有这些属性,因此可以使用它(并且可以成功使用)。主要属性是我们无法摆脱它。Monad接口没有可摆脱我们价值附近的monad的操作。其他(非IO)monad提供此类操作并允许模式匹配(例如Maybe),但这些操作不在monad接口中。另一个必需的属性是链接操作的能力。
如果我们考虑类型系统方面的需求,就会发现我们需要带构造函数的类型,该构造函数可以包装在任何变量上。构造函数必须是私有的,因为我们禁止对其进行转义(即,模式匹配)。但是我们需要函数将值放入此构造函数中(这里想到了返回)。我们需要连锁经营的方式。如果我们考虑一段时间,就会发现事实是,链接操作必须具有>> =的类型。因此,我们得出与monad非常相似的东西。我认为,如果现在使用这种结构分析可能存在的矛盾情况,我们将得出monad公理。
注意,已开发的构造与杂质没有任何共同之处。它仅具有一些属性,而我们希望这些属性必须能够处理不纯净的操作,即无转义,链接和进入方式。
现在,此选定的monad IO中的语言预定义了一些不纯运算。我们可以将这些操作结合起来以创建新的不纯操作。所有这些操作都必须具有其类型的IO。但是请注意,某些功能类型中IO的存在并不会使该功能不纯。但是据我了解,用IO类型编写纯函数是个坏主意,因为最初将纯函数和不纯函数分开是我们的想法。
最后,我想说的是,monad不会将不纯的操作变成纯操作。它仅允许有效地将它们分开。(我重复,这只是我的理解)