【问题标题】:How are Type-Errors handled in Haskell's Either Monad?Haskell 的 Either Monad 中如何处理类型错误?
【发布时间】:2020-09-11 13:52:07
【问题描述】:

我目前在理解 Haskell 中的 Either-Monad 时遇到问题。 要么是 Monad 数据类型的一个实例。因此,我可以像这样在 Do 块中使用 Either ...

type MyAccount = Integer

transaction :: Integer -> MyAccount -> Either String MyAccount
transaction value account | value + account < 0 = Left "Error: insufficient funds!" 
                          | otherwise = return $ account + value

Test1 :: Either String MyAccount
Test1 = do 
          account_state <- return 1000                       
          r1 <- transaction (-200) account_state             
          r2 <- transaction (-1000) r1                    
          return r2                                       

这是一段代码:account_state、r1 和 r2 是整数类型。因此函数 transaction 可以正常工作。不过..

Test2 :: Either String MyAccount
Test2 = do 
          account_state <- return 1000
          r1 <- transaction (-2000) account_state
          r2 <- transaction (-1000) r1
          return r2

在 Test2 中,我将交易价值从 -200 更改为 -2000。因此 r1 应该是字符串“错误:资金不足!”。由于 transaction 不适用于字符串,因此应该存在类型错误。但是没有... Haskell 在这里做什么?

事务 (-1000)(“错误:资金不足!”)不应该工作。

【问题讨论】:

  • 我认为您可能错误地发布了错误的 sn-p:您的 Test2 看起来几乎与 Test1 相同,除了 -200-2000 替换。另请注意,这些都不应该编译,因为函数名不能大写。最后,没有“Monad 数据类型”之类的东西:EitherMonad type class 的一个实例。
  • @FyodorSoikin 用-2000 替换-200 正是重点:第一个不会透支帐户,第二个会。不过,您评论的其他部分仍然有效。
  • 严格来说,Either 不能是 monad,因为它有一种 Type -&gt; Type -&gt; TypeEither String 可以是(并且是),因为它有一种Type -&gt; Type

标签: haskell monads either


【解决方案1】:

...但是 r1String

不是。 r1Integer,它总是一个整数。

这就是Either monad 的工作原理:

r1 <- transaction (-2000) account_state

当您运行此代码时,右侧的transaction ... 是一个单子动作,类型为Either String Integer。但是,一元绑定&lt;- 将一元操作的result(始终为Integer)分配给r1

如果一元动作实际上是Left,那么就不可能给r1赋值,事实上它不会发生……函数会立即返回。

您可以将代码翻译成不使用 monads 的代码(或至少不使用 Haskell 中的 Monad 类型):

test2 =
    let account_state = 1000
    in case transaction (-2000) account_state of
         Left err -> Left err # Returns immediately
         Right r1 -> transaction (-1000) r1

我希望当我们剥离 monad 时,函数的行为会更加明显。

一些注意事项

这很奇怪,

x <- return y

你可以用,替换它,

let x = y

更简单易懂。

也不用写了:

x <- y
return x

相反,只需写:

y

【讨论】:

    【解决方案2】:

    欢迎来到 Haskell 中享受单子的乐趣!

    首先,您的代码经过轻微编辑。

    type MyAccount = Integer
    
    transaction :: Integer -> MyAccount -> Either String MyAccount
    transaction value account | value + account < 0 = Left "Error: insufficient funds!" 
                              | otherwise = return $ account + value
    
    Test1 :: Either String MyAccount
    Test1 = do 
              let account_state = 1000                       
              r1 <- transaction (-200) account_state             
              transaction (-1000) r1
    
    Test2 :: Either String MyAccount
    Test2 = do 
              let account_state = 1000
              r1 <- transaction (-2000) account_state
              transaction (-1000) r1
    

    Either 是一个“错误单子”。它的工作原理如下:

    每个动作都会被评估。

    如果它产生 Right(通常为非错误值),则该值被解包并评估以下操作。

    如果最终操作产生Right,则从do 块返回Right

    如果任何动作评估为Left值,评估不会继续下一个动作,而是从do块返回立即 带有 Left 值。

    因此,在您的情况下,r1 只会收到一个值,并且只有在分配给r1 的操作产生Right 时,才会评估可以读取r1 的后续操作。 r1 将永远是 Integer

    其他错误单子,例如Maybe,工作方式类似。

    【讨论】:

      【解决方案3】:

      感谢 Dietrich EppSilver Rampart。您的两个答案都是对问题的完美解释。 这里只是一个补充

      do 
            account_state <- return 1000
            r1 <- transaction (-2000) account_state
            r2 <- transaction (-1000) r1
            return r2
      

      可以改写为:

      do 
          (return 1000) >>= (transaction (-2000)) >>= (transaction (-1000))
      

      其中 (>>=) 定义为

      instance Monad (Either e) where
          return = Right
          Right m >>= k = k m
          Left e  >>= _ = Left e
      

      因此,如果第一个结果是 Left,Left e &gt;&gt;= _ = Left e 会使 Haskell 忽略与 (>>=) 运算符相关的所有内容。

      【讨论】:

      • Haskell 是有道理的......但是对于初学者来说需要很多时间......
      • 坚持这种态度,很快这将成为第二天性!
      猜你喜欢
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-09
      • 1970-01-01
      • 2015-12-28
      • 2015-01-26
      相关资源
      最近更新 更多