gpt4 book ai didi

haskell - 为什么在 Haskell 中将副作用建模为 monad?

转载 作者:行者123 更新时间:2023-12-02 10:19:28 25 4
gpt4 key购买 nike

有人可以指出为什么 Haskell 中的不纯计算被建模为 monad 吗?

我的意思是 monad 只是一个包含 4 个操作的接口(interface),那么在其中建模副作用的原因是什么?

最佳答案

假设一个函数有副作用。如果我们将它产生的所有效果作为输入和输出参数,那么这个函数对于外界来说是纯粹的。

因此,对于不纯函数

f' :: Int -> Int

我们将现实世界添加到考虑因素中

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
...

所以我们可以定义一个运算符~~~来绑定(bind)它们:

(~~~) :: (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 。大写,我们可以写

printUpperCaseFile = 
getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(注意:通常我们写 getLine ~~~ getContents ~~~ (putStrLn . upperCase))

我们一直在使用 monad

现在让我们看看我们做了什么:

  1. 我们定义了一个运算符(~~~)::IO b -> (b -> IO c) -> IO c,它将两个不纯函数链接在一起
  2. 我们定义了一个函数impurify::a -> IO a,它将纯值转换为不纯值。

现在我们进行识别(>>=) = (~~~)return = impurify,看到了吗?我们有一个单子(monad)。

<小时/>

技术说明

为了确保它确实是一个 monad,还需要检查一些公理:

  1. 返回 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
  2. 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
  3. f >>= (\x -> g x >>= h) = (f >>= g) >>= h

    留作练习。

关于haskell - 为什么在 Haskell 中将副作用建模为 monad?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2488646/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com