效果在Haskell中建模为单子?
任何人都可以提供一些关于为什么Haskell中的不纯计算被建模为monads的指示吗?
我的意思是monad仅仅是一个包含4个操作的接口,那么为它建模的副作用是什么原因呢?
假设一个函数有副作用。 如果我们将所产生的所有效果作为输入和输出参数,那么该函数对外界来说是纯粹的。
所以对于不纯的功能
f' :: Int -> Int
我们将RealWorld添加到考虑中
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the a side effects,
-- then return the new world.
那么f
是纯粹的。 我们定义了参数化数据类型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
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。
(要检查它是否真的是单子,应该满足几个公理:
(1) return a >>= f = fa
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
(2) f >>= return = f
(f ~~~ impurify) a worldX = let (resF, worldY) = impuify a worldX
in f resF worldY
= let (resF, worldY) = (a, worldX)
in f resF worldY
= f a worldX
(3) f >>= (x -> gx >>= h) = (f >>= g) >>= h
行使。)
任何人都可以指出为什么Haskell中的不确定计算被建模为monads?
这个问题蕴含着广泛的误解。 杂质和Monad是独立的概念。 杂质不是由Monad建模的。 相反,有一些数据类型,例如IO
,代表了命令式计算。 对于其中一些类型,其接口的一小部分对应于称为“Monad”的接口模式。 此外,对于IO
没有已知的纯粹的/功能的/外延的解释(并且考虑到IO
的“罪恶目的”目的,不可能有一个),尽管通常讲述了关于World -> (a, World)
是IO a
的意思。 这个故事不能如实描述IO
,因为IO
支持并发和非确定性。 当确定性计算允许与世界进行中等计算交互时,故事甚至不起作用。
有关更多解释,请参阅此答案。
编辑:在重新阅读这个问题时,我认为我的回答不太理想。 正如问题所述,命令式计算的模型常常变成单子。 提问者可能不会真的假定单调性以任何方式支持命令式计算的建模。
据我所知,有人叫欧金尼奥Moggi首先注意到一个以前晦涩难懂的数学结构称为“monad”可用于模拟计算机语言的副作用,因此使用Lambda演算指定其语义。 当Haskell开发出来时,有不同的方法模拟了不纯的计算(参见Simon Peyton Jones的“发衬衫”文章以获取更多细节),但是当Phil Wadler引入单子时,它很快就变得明显,这就是The Answer。 剩下的就是历史。
链接地址: http://www.djcxy.com/p/42959.html