为什么在Haskell中将副作用建模为monad? [英] Why are side-effects modeled as monads in Haskell?

查看:86
本文介绍了为什么在Haskell中将副作用建模为monad?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我的意思是monad只是一个有4个操作的接口,所以什么是推理建模的副作用?

解决方案

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



所以对于不纯的函数

  f':: Int  - > Int 

我们将RealWorld添加到考虑范围中

  f :: Int  - > RealWorld  - > (Int,RealWorld)
- 输入整个世界的一些状态,
- 由于副作用而改变整个世界,
- 然后返回新世界。

然后 f 再次为纯。我们定义一个参数化数据类型 IO a = RealWorld - > (a,RealWorld),所以我们不需要多次输入RealWorld

  f :: 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(文件名,world1)= getLine world0
(contents,world2)=(getContents文件名)world1 $ b $ in(putStrLn内容)world2 - 结果in((),world3)

我们在这里看到一个模式:函数被这样调用: / b>

  ... 
(< f的结果,worldY)= f worldX
( < g的结果,worldZ)= g< f的结果> worldY
...

所以我们可以定义一个运算符〜 ~~ 绑定它们:

 (~~~)::(IO b) - > ; (b→IOc)→> 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 

不触及真实世界。




现在假设我们也想让文件内容为大写。 Uppercasing是一个纯函数

  upperCase :: String  - >字符串

但是为了进入真实世界,它必须返回一个 IO字符串。很容易解除这样的功能:

  impureUpperCase :: String  - > RealWorld  - > (String,RealWorld)
impureUpperCase str world =(upperCase str,world)

推广:

  impurityify :: a  - > IO a 

sludgeify :: a - > RealWorld - > (a,RealWorld)
污染世界=(a,world)

code> impureUpperCase =杂质。 upperCase ,我们可以写成:

  printUpperCaseFile = 
getLine ~~~ getContents ~~ (注意:通常我们写<$($)) c $ c> getLine ~~~ getContents ~~~(putStrLn。upperCase)






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


  1. 我们定义了一个运算符(~~~ ):: IO b - > (b→IOc)→> IO c 将两个不纯的函数链接在一起

  2. 我们定义了一个函数 impurityify :: a - > IO a 将纯价值转换为不纯。

现在我们将(>> =)=(~~~) return = contaminify 我们有一个单子。




(要检查它是否真的是单子,应该满足几个公理: (1)返回a>> = f = fa

  contaminify a =(\ world  - >(a,world))
(污染~~~ f)worldX = let(resF,worldY)= (\ world - >(a,world))worldX
in f resF worldY
= let(resF,worldY)=(a,worldX))
in f resF worldY
= fa worldX

(2) f>> =返回= f

 (f ~~~杂质)a worldX = let(resF,worldY)=在f f世界y 
= let(resF,worldY)=(a,worldX)
in f f世界y
= faw orldX



<3> <3 c $ c> f>> =(\ x - > gx>> = h)=(f>> = g)>> = h



练习。 / p>

Could anyone give some pointers on why the impure computations in Haskell are modelled as monads?

I mean monad is just an interface with 4 operations, so what was the reasoning to modelling side-effects in it?

解决方案

Suppose a function has side effects. If we take all the effects it produces as the input and output parameters, then the function is pure to the outside world.

So for an impure function

f' :: Int -> Int

we add the RealWorld to the consideration

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.

then f is pure again. We define a parametrized data type IO a = RealWorld -> (a, RealWorld), so we don't need to type RealWorld so many times

f :: Int -> IO Int

To the programmer, handling a RealWorld directly is too dangerous—in particular, if a programmer gets their hands on a value of type RealWorld, they might try to copy it, which is basically impossible. (Think of trying to copy the entire filesystem, for example. Where would you put it?) Therefore, our definition of IO encapsulates the states of the whole world as well.

These impure functions are useless if we can't chain them together. Consider

getLine :: IO String               = RealWorld -> (String, RealWorld)
getContents :: String -> IO String = String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO ()        = String -> RealWorld -> ((), RealWorld)

We want to get a filename from the console, read that file, then print the content out. How would we do it if we can access the real world states?

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

We see a pattern here: the functions are called like this:

...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

So we could define an operator ~~~ to bind them:

(~~~) :: (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

then we could simply write

printFile = getLine ~~~ getContents ~~~ putStrLn

without touching the real world.


Now suppose we want to make the file content uppercase as well. Uppercasing is a pure function

upperCase :: String -> String

But to make it into the real world, it has to return an IO String. It is easy to lift such a function:

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

this can be generalized:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

so that impureUpperCase = impurify . upperCase, and we can write

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

(Note: Normally we write getLine ~~~ getContents ~~~ (putStrLn . upperCase))


Now let's see what we've done:

  1. We defined an operator (~~~) :: IO b -> (b -> IO c) -> IO c which chains two impure functions together
  2. We defined a function impurify :: a -> IO a which converts a pure value to impure.

Now we make the identification (>>=) = (~~~) and return = impurify, and see? We've got a monad.


(To check whether it's really a monad there's few axioms should be satisfied:

(1) 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

(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 -> g x >>= h) = (f >>= g) >>= h

Exercise.)

这篇关于为什么在Haskell中将副作用建模为monad?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆