gpt4 book ai didi

haskell - 为什么是单子(monad)?它如何解决副作用?

转载 作者:行者123 更新时间:2023-12-03 06:13:46 25 4
gpt4 key购买 nike

我正在学习 Haskell 并试图理解 Monads。我有两个问题:

  • 据我所知,Monad 只是另一个类型类,它声明了与“容器”内的数据交互的方式,包括 Maybe , List , 和 IO .用一个概念实现这 3 件事似乎既聪明又干净,但实际上,重点是可以在一系列函数、容器和副作用中进行干净的错误处理。这是正确的解释吗?
  • 副作用的问题究竟是如何解决的?有了容器的这个概念,语言本质上说容器内的任何东西都是非确定性的(例如 i/o)。因为列表和 IO 都是容器,所以列表与 IO 等价分类,即使列表中的值对我来说似乎非常确定。那么什么是确定性的,什么有副作用呢?我无法理解基本值是确定性的这个想法,直到你把它放在一个容器中(这与旁边有一些其他值的相同值没什么特别,例如 Nothing ),现在它可以随机。

  • 有人可以直观地解释 Haskell 如何通过输入和输出改变状态吗?我没有看到这里的魔法。

    最佳答案

    The point is so there can be clean error handling in a chain of functions, containers, and side effects. Is this a correct interpretation?



    并不真地。你提到了人们在试图解释 monad 时引用的很多概念,包括副作用、错误处理和非确定性,但听起来你有一种错误的感觉,即所有这些概念都适用于所有 monad。但是你提到的一个概念确实如此: 链接 .

    这个有两种不同的口味,所以我会用两种不同的方式来解释它:一种没有副作用,一种有副作用。

    无副作用:

    以下面的例子为例:
    addM :: (Monad m, Num a) => m a -> m a -> m a
    addM ma mb = do
    a <- ma
    b <- mb
    return (a + b)

    这个函数将两个数字相加,但它们被包裹在一些 monad 中。哪个单子(monad)?没关系!在所有情况下,特别 do语法去糖如下:
    addM ma mb =
    ma >>= \a ->
    mb >>= \b ->
    return (a + b)

    ... 或者,使用明确的运算符优先级:
    ma >>= (\a -> mb >>= (\b -> return (a + b)))

    现在你真的可以看到这是一个小函数链,全部组合在一起,它的行为将取决于 >>=return为每个 monad 定义。如果您熟悉面向对象语言中的多态性,那么这本质上是一回事:一个具有多个实现的通用接口(interface)。它比一般的 OOP 界面更令人费解,因为该界面代表一种计算策略,而不是动物或形状或其他东西。

    好的,让我们看一些例子 addM在不同的 monad 中表现。 Identity monad 是一个不错的起点,因为它的定义很简单:
    instance Monad Identity where
    return a = Identity a -- create an Identity value
    (Identity a) >>= f = f a -- apply f to a

    那么当我们说:
    addM (Identity 1) (Identity 2)

    逐步展开这个:
    (Identity 1) >>= (\a -> (Identity 2) >>= (\b -> return (a + b)))
    (\a -> (Identity 2) >>= (\b -> return (a + b)) 1
    (Identity 2) >>= (\b -> return (1 + b))
    (\b -> return (1 + b)) 2
    return (1 + 2)
    Identity 3

    伟大的。现在,既然你提到了干净的错误处理,让我们看看 Maybe单子(monad)。它的定义只是比 Identity 稍微复杂一点。 :
    instance Monad Maybe where
    return a = Just a -- same as Identity monad!
    (Just a) >>= f = f a -- same as Identity monad again!
    Nothing >>= _ = Nothing -- the only real difference from Identity

    所以你可以想象如果我们说 addM (Just 1) (Just 2)我们会得到 Just 3 .但是为了咧嘴笑,让我们扩展 addM Nothing (Just 1)反而:
    Nothing >>= (\a -> (Just 1) >>= (\b -> return (a + b)))
    Nothing

    或者反过来, addM (Just 1) Nothing :
    (Just 1) >>= (\a -> Nothing >>= (\b -> return (a + b)))
    (\a -> Nothing >>= (\b -> return (a + b)) 1
    Nothing >>= (\b -> return (1 + b))
    Nothing

    所以 Maybe monad 的定义 >>=被调整以解释失败。当函数应用于 Maybe值使用 >>= ,你会得到你所期望的。

    好的,所以你提到了非确定性。是的,list monad 在某种意义上可以被认为是对不确定性的建模......这有点奇怪,但可以将列表视为表示替代可能值: [1, 2, 3]不是一个集合,它是一个单一的非确定性数字,可以是一、二或三。这听起来很愚蠢,但是当您考虑如何 >>= 时,它开始变得有意义了。为列表定义:它将给定的函数应用于每个可能的值。所以 addM [1, 2] [3, 4]实际上要计算这两个非确定性值的所有可能总和: [4, 5, 5, 6] .

    好的,现在来解决你的第二个问题......

    副作用:

    假设您申请 addMIO 中的两个值monad,比如:
    addM (return 1 :: IO Int) (return 2 :: IO Int)

    你没有得到任何特别的东西,只有 3 个在 IO单子(monad)。 addM不读取或写入任何可变状态,所以它有点不好玩。 State 也是如此或 ST单子(monad)。没有乐趣。所以让我们使用一个不同的函数:
    fireTheMissiles :: IO Int  -- returns the number of casualties

    显然,每次发射导弹,世界都会不同。清楚地。现在假设您正在尝试编写一些完全无害、无副作用、不发射导弹的代码。也许您再次尝试将两个数字相加,但这一次没有任何 monad 四处乱飞:
    add :: Num a => a -> a -> a
    add a b = a + b

    突然间你的手滑了,你不小心打错了:
    add a b = a + b + fireTheMissiles

    一个诚实的错误,真的。 key 靠得很近。幸运的是,因为 fireTheMissiles类型为 IO Int而不是简单的 Int ,编译器能够避免灾难。

    好的,完全人为的例子,但重点是在 IO 的情况下, ST和 friend 们,类型系统将效果隔离到某些特定的上下文中。它并没有神奇地消除副作用,使代码引用透明,这不应该是透明的,但它确实在编译时明确了影响限制在什么范围内。

    所以回到原点:这与函数的链接或组合有什么关系?嗯,在这种情况下,它只是表达一系列效果的一种方便的方式:
    fireTheMissilesTwice :: IO ()
    fireTheMissilesTwice = do
    a <- fireTheMissiles
    print a
    b <- fireTheMissiles
    print b

    概括:

    monad 代表一些用于链接计算的策略。 Identity的策略是纯函数组合, Maybe的策略是具有失败传播的函数组合, IO的策略是不纯函数组合等等。

    关于haskell - 为什么是单子(monad)?它如何解决副作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7840126/

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