【问题标题】:Is it possible to have a state-either hybrid monad?是否有可能有一个状态或混合单子?
【发布时间】:2019-12-09 01:58:33
【问题描述】:

目标

我正在尝试编写解释器的内部结构,出于人体工程学的目的,我想我想要一个可以像 state 和任何一个 monad 一样工作的 monad。

例如,我想用任何一种风格做一些事情:

checkedAddress :: Integer -> Interpreter Int
checkedAddress n = if (n < toInteger (minBound :: Int))
                      then fail $ "Address " ++ show n ++ " is too low"
                   else if (n > toInteger (maxBound :: Int))
                      then fail $ "Address " ++ show n ++ " is too high"
                   else return $ fromInteger n

我想用状态样式做其他事情:

setInstructionPointer :: Int -> Interpreter ()
setInstructionPointer ip (Machine _ mem) = ((), Machine ip mem)

getInstructionPointer :: Interpreter Int
getInstructionPointer m@(Machine ip mem) = (ip, m)

问题

是否可以像这样创建一个 state-either 混合 monad?

如果不可能,为什么不可能?是否有替代方案具有良好的人体工程学设计,并且我认为提前终止(例如通过Left m &gt;&gt;= _ = Left m 停止进一步处理)这种方法的效率?

如果可能的话,我该如何为该类型编写 monad 实例?我试过了,但是在写(&gt;&gt;=) 的时候卡住了,因为我看不到在不知道运行时Machine 值的情况下知道要生成什么构造函数的方法。

data Interpreter a = Running      (Machine -> (a, Machine))
                   | Halted       (Machine -> Machine)
                   | Error String (Machine -> Machine)

instance Monad Interpreter where
  return = Running . (,)
  Running f     >>= g = DontKnowWhich $ \ m -> let (a, m') = f m
                                               in  case g a of
                                                        Running h ->
                                                        Halted  h ->
                                                        Error s h ->
  h@(Halted  _) >>= _ = h
  e@(Error _ _) >>= _ = e

【问题讨论】:

  • 你需要的是一个单子转换器。查看 mtl 包中的 StateT 和 EitherT。

标签: haskell monads state-monad either


【解决方案1】:

组合的 monad 应该如下所示:

newtype Interpreter a
  = Interpreter { runInterpreter :: Machine -> (Machine, Either String a) }

它接受一个状态,Machine,返回一个修改后的状态,并返回成功或失败。

deriving instance Functor Interpreter
instance Monad Interpreter where
    return x = _exercise
    Interpreter x >>= f = _exercise
instance Applicative Interpreter where pure = return; (<*>) = ap
liftEither :: Either String a -> Interpreter a
liftState :: State Machine a -> Interpreter a

一般来说,为了将 monad 组合在一起,您将一个“放在”另一个“内部”:

Interpreter a <~> State Machine (Either String a)

你可以用另一种方式,s -&gt; Either String (s, a),但是你不会因为错误而恢复状态。 (注意Either String (State Machine a)不起作用:你是否失败将不被允许取决于状态。它只是一个Applicative。)

您不必自己编写Monad Interpreter 实例。 transformers 包(与 GHC 一起提供)提供了“monad 转换器”,用于组合地构造 monad。 monad 转换器是一个T :: (Type -&gt; Type) -&gt; (Type -&gt; Type),它接受一个 monad 作为参数并返回一个新的 monad。

type Interpreter = @987654322@ String (@987654323@ Machine)
liftEither = @987654324@
liftState = @987654325@

ExceptT String是一个monad转换器,State Machine是一个monad,所以Interpreter = ExceptT String (State Machine)也是一个monad。我之前提到的另一种方式是StateT Machine (Either String)

下一步是使用mtl。这个库在transformer 类型之上提供了类,例如throwErrorget 等特定于monad 的操作被重载,以根据需要自动提升自己通过尽可能多的monad 转换器。使用 mtl,您可以在 monad 堆栈中保留自己的函数多态:

checkedAddress :: MonadExcept String m => Integer -> m Int
checkedAddress n = do
  -- you don't need to branch, failure short-circuits!
  when (n < toInteger (minBound :: Int)) $ @987654327@ _
  when (n > toInteger (maxBound :: Int)) $ throwError _
  pure (fromInteger n)

setInstructionPointer :: MonadState Machine m => Int -> m ()
setInstructionPointer ip = @987654328@ \(Machine _ mem) -> (Machine ip mem)

getInstructionPointer :: MonadState Machine m => m Int
getInstructionPointer = @987654329@ \(Machine i _) -> i

-- combined:
checkedOffsetJump :: (MonadState Machine m, MonadExcept String m) => Integer -> m ()
checkedOffsetJump off = setInstructionPointer =<< checkedAddress =<< (off +) <$> toInteger <$> getInstructionPointer
-- read: setInstructionPointer(checkedAddress(off + toInteger(getInstructionPointer())))

您可以稍后确定它们,通常在最后:

@987654330@ $ @987654331@ $ checkedOffsetJump 0x8000 :: Machine -&gt; (Either String (), Machine)

【讨论】:

  • 以另一种方式堆叠它们也是合理的;哪个更好取决于上下文。
  • @dfeuer 我确实说过,但 OP 的尝试似乎暗示他们想要在失败时恢复 Machine
  • 对不起,我错过了。
  • 谢谢! State Machine (Either String a) 类型为我提供了从头开始制作混合 monad 的 (&gt;&gt;=) 所需的线索,关于 monad 转换器的讨论是对它们的一个很好的预览。
猜你喜欢
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 2022-12-19
  • 2011-03-16
  • 2020-08-14
  • 2019-11-26
  • 1970-01-01
相关资源
最近更新 更多