效果在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

    上一篇: effects modeled as monads in Haskell?

    下一篇: Functional, Declarative, and Imperative Programming