如何在Haskell模块化应用程序中管理内部状态?

问题描述 投票:2回答:1

我正在按照on this video by Ben Kolera提出的建议来构建模块化Haskell应用程序。

建议使用多个monad转换器以使应用程序模块化和组织化。建议使用自定义liftModule函数将这些单子组合在一起。

例如,我们有一个主App模块和一个Logic模块。

newtype App a = App
  { unApp :: ExceptT AppError (ReaderT AppEnv IO) a }

newtype Logic a = Logic
  { unLogic :: ExceptT LogicError (Reader LogicEnv) a }

liftLogic定义如下。

runLogic :: LogicEnv -> Logic a -> Either LogicError a

liftLogic :: Logic a -> App a
liftLogic l = do 
  c <- asks appEnvLogic
  either (throwError . AppLogicError) pure $ runLogic c l

通过这种方法,我如何赋予模块内部状态?如果我将StateT LogicState放在Logic变压器中,那么liftMonad不会完全运行monad从而解开它并破坏其内部状态吗?

[我看到的唯一方法是将Logic的内部状态泄漏到App,我认为这是反模块化的,因为它迫使App负责Logic的状态。

haskell monads monad-transformers
1个回答
0
投票

在您的示例中,App已经“照顾”了LogicEnvLogicError,所以我认为反模块化飞船已经航行了。

无论如何,要在Logic中运行有状态的App,您需要从某处获取初始的LogicState,并确定完成Logic操作后应如何处理。特别是,如果您希望能够将两个Logic动作分别提起到App中并使它们穿过LogicState,那么App也将需要保持状态,而LogicState会埋在该位置中。 AppState

因此,答案可能类似于:

newtype App a = App
  { unApp :: ExceptT AppError (StateT AppState (ReaderT AppEnv IO)) a }
  deriving (Functor, Applicative, Monad, MonadReader AppEnv, 
            MonadState AppState, MonadError AppError)

newtype Logic a = Logic
  { unLogic :: ExceptT LogicError (StateT LogicState (Reader LogicEnv)) a }

runLogic :: Logic a -> LogicEnv -> LogicState -> (Either LogicError a, LogicState)
runLogic l c s = runReader (runStateT (runExceptT (unLogic l)) s) c

liftLogic :: Logic a -> App a
liftLogic l = do
  c <- asks appEnvLogic
  s <- get
  let (ea, ls') = runLogic l c (logicState s)
  put s { logicState = ls' }
  either (throwError . AppLogicError) return ea

请注意,App可以不了解LogicState内部。只是不要导出LogicState的构造函数。这样,除了代表App维护LogicState之外,Logic不能对其进行任何操作。当然,您必须安排从Logic导出某些功能以获得初始LogicState

© www.soinside.com 2019 - 2024. All rights reserved.