Haskells 绑定算子和>>算子及其关系

Haskells bind operator and the >> operator and their relation

提问人:Piskator 提问时间:7/16/2023 最后编辑:Piskator 更新时间:11/11/2023 访问量:143

问:

我最近发布了一个关于操作员的问题,因为即使我已经阅读了 LYAH walk the linee 部分,我的理解仍然存在一些差距。以下是我偶然发现的一些代码/MVE,从那时起,它引发了以下思考。为什么我可以得到代码后面的输出?难道不是没有向 Bind 运算符提供任何参数,因此没有参数可以连接,否则它们将使用 bind,如定义所示,并且其结果不应与下面提到的预期结果相似:>>str>>=

import Control.Monad
import Data.List

data Value =
    NoneVal
  | IntVal Int
  | ListVal [Value]
  deriving (Eq, Show, Read)

data RErr = EBVar String  | EBInt Int
  deriving (Eq, Show)

newtype Compuptaton a = Computation {runComputation :: [(String, Value)] -> (Either RErr a, [String]) }

instance Monad Computation where
  return a = Computation( \_ -> (Right a, []))
  m >>= f = Computation(\env -> case runComputation m env of
        (Left e, str) -> (Left e, str)
        (Right a, str) -> let (a', str') = runComputation (f a) env in (a', str++str'))

showValue :: Value -> String
showValue (IntVal int) = show int
showValue (ListVal values) = "[" ++ intercalate ", " [showValue x| x<-values] ++ "]"

output :: String -> Computation ()
output s = Computation (\_ -> (Right (), [s]))

apply :: String-> [Value] -> Computation Value
apply "print" values = output (unwords [showValue x| x<-values]) >> return NoneVal

输出:

ghci> runComputation (apply "print" [(IntVal 1), (IntVal 2)]) [("x", (IntVal 4)), ("y", (IntVal 3))]
(Right NoneVal,["1 2"])

预期输出

(Right NoneVal, [])

此外,为什么会用额外的串联来扩展绑定运算符定义:str'

        (Left e, str) -> (Left e, str)
        (Right a, str) -> let (a', str') = runComputation (f a) env in (a', str++str'++str'))

而另一个像这样: ,不会导致以下结果:apply>>apply "print" values = output (unwords [showValue x| x<-values]) >> output (unwords [showValue x| x<-values]) >> return NoneVal

ghci> runComputation (apply "print" [(IntVal 1), (IntVal 2)]) [("x", (IntVal 4)), ("y", (IntVal 3))]
(Right NoneVal,["1 2","1 2","1 2", "1 2"])

而不是实际的:

ghci> runComputation (apply "print" [(IntVal 1), (IntVal 2)]) [("x", (IntVal 4)), ("y", (IntVal 3))]
(Right NoneVal,["1 2","1 2","1 2"])

只有 3 个输入元素。

哈斯克尔 函数式编程 单子 副作用

评论

4赞 leftaroundabout 7/16/2023
你知道 - 你的这些问题对阅读/回答来说相当没有吸引力,因为有相当多的代码需要通过,这些代码似乎与问题表面上的单子运算符没有任何关系。如果你把你的问题减少到实际的最小值(StackOverflow 称之为最小可重复的例子),那么它会更容易回答,也许你甚至不需要问,因为它已经让自己更清楚了。
0赞 Piskator 7/16/2023
@leftaroundabout,我很遗憾听到您觉得阅读/回答没有吸引力。你提到的MRE正是我试图创造的。我只是称它为 MVE(最小可行示例),但我试图删除所有我认为不完全必要的代码,并提供了一些输出示例,以更清楚地说明预期的反实际行为是什么。这样其他人会更容易阅读/回答。如前所述,我很遗憾听到你发现我没有成功,这与我的意图完全相反。
2赞 leftaroundabout 7/16/2023
这不仅涉及削减未使用的代码,还涉及保持数据结构尽可能简单。 看起来相当于 3 个 Monad 变形金刚的堆叠。这样写,你可以 a) 省略手写的实例,这样就没有必要在那里搜索错误 b) 查看 transformer 堆栈的哪些部分实际上导致了你觉得不直观的行为,或者 c) 如果你真的得到了不同的行为,那么问题就集中在为什么存在差异上。你可以问一下区别,或者你可以问一下如何重写变压器。CompMonad
0赞 leftaroundabout 7/16/2023
Comp a = ReaderT [(String, Value)] (ExceptT RError (WriterT [String] a))如果我没记错的话。
2赞 Li-yao Xia 7/16/2023
尝试手动评估您的程序,然后您要么自己弄清楚发生了什么,要么您将在问题中指出更具体的内容。

答:

5赞 duplode 7/16/2023 #1

难道不是没有向 Bind 运算符提供任何参数的情况,因此没有参数可以连接起来,否则它们会使用 bind,如定义所示str>>=

没有。,根据定义,是 ,因此您可以通过在定义中用常量函数替换来查看它的作用:m >> nm >>= \_ -> nf\_ -> n

m >> n = Comp(\env -> case runComp m env of
      (Left e, str) -> (Left e, str)
      (Right a, str) -> let (a', str') = runComp ((\_ -> n) a) env in (a', str++str'))

-- Applying (\_ -> n)
m >> n = Comp(\env -> case runComp m env of
      (Left e, str) -> (Left e, str)
      (Right _, str) -> let (a', str') = runComp n env in (a', str++str'))

所以唯一被忽略的是中间结果。输出的生成照常进行。(使用一些行话,我们可以说它是一元计算效果的一部分,与从中获得的任何类型的结果形成鲜明对比。astra

在输出复制绑定示例中,您得到三个而不是四个字符串,因为该绑定仅复制第二个计算的输出(但不是 )。str'str

(顺便说一句,即使你只是为了说明而故意这样做,但值得注意的是,复制绑定是不合法的:不会成立,因为会有重复的输出,而不会,重复的不对称性也会使关联定律失效。return a >>= f = f areturn a >>= ff a