簡體   English   中英

Monad Stack Penetration Classes with Free / Operational Monad Transformers?

[英]Monad Stack Penetration Classes with Free/Operational Monad Transformers?

可以為FreeT / ProgramT創建的monad變換器提供類似mtl的機制嗎?

我對歷史的理解如下。 曾幾何時,monad變壓器被發明了。 然后人們開始在另一個上堆疊monad變壓器,然后發現在任何地方插入lift都很煩人。 然后有幾個人發明了monad類,所以我們可以在任何monad m ask :: mr ,以便MonadReader rm 這可以通過讓每個monad類穿透每個monad變換器來實現

(Monoid w, MonadState sm) => MonadState s (WriterT wm)
MonadWriter wm => MonadWriter w (StateT sm)

你需要為每對monad變換器提供這樣的實例聲明對,所以當有n個 monad變換器時,你需要n ^ 2個成本。 然而,這不是一個大問題,因為人們將主要使用預定義的monad並且很少創建自己的monad。 到目前為止,我理解這個故事,並且在下面的問答中也詳細說明:

使用Monad變形金剛避免升降機

然后我的問題是新的免費monad http://hackage.haskell.org/package/free和操作monads http://hackage.haskell.org/package/operational 它們允許我們編寫自己的DSL並將其用作monad,只需將語言定義為某種代數data類型(Operational甚至不需要Functor實例)。 好消息是我們可以免費獲得monad和monad變換器; 那么monad課怎么樣? 壞消息是“我們很少定義我們自己的monad變換器”的假設不再成立。

為了解這個問題,我制作了兩個ProgramT並使它們相互滲透;

https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs

operational包不支持monad類,所以我采用了另一個minioperational實現並將其修改為我需要的工作; https://github.com/nushio3/minioperational

不過,我需要專門的實例聲明

instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where

因為以下形式的一般聲明會導致不可判定的實例。

instance (Monad m, Operational fm) => Operational f (ProgramT gm) where

我的問題是,我們怎樣才能讓我們的運營單子更容易相互滲透。 或者,我是否希望能夠對任何可操作的Monad進行滲透。

我也想知道正確的滲透技術術語:)

我嘗試了一些不同的方法,至少給出了部分答案。 由於堆疊monad有時會出現問題,並且我們知道所有monad都是從某種數據類型構造的,所以我嘗試組合數據類型。

我對MonadFree感覺更舒服,所以我使用它,但我想類似的方法也可用於Operational

讓我們從數據類型的定義開始:

{-# LANGUAGE DeriveFunctor, FlexibleContexts,
             FlexibleInstances, FunctionalDependencies #-}
import Control.Monad
import Control.Monad.Free

data SLang x = ReadStr (String -> x) | WriteStr String x
  deriving Functor
data ILang x = ReadInt (Int -> x) | WriteInt Int x
  deriving Functor

為了將兩個仿函數組合在一起以在免費monad中使用它們,讓我們定義它們的副產品:

data EitherF f g a = LeftF (f a) | RightF (g a)
  deriving Functor

如果我們在EitherF fg創建一個免費的monad,我們可以調用它們的命令。 為了使這個過程透明,我們可以使用MPTC允許從每個仿函數轉換到目標仿函數:

class Lift f g where
    lift :: f a -> g a
instance Lift f f where
    lift = id

instance Lift f (EitherF f g) where
    lift = LeftF
instance Lift g (EitherF f g) where
    lift = RightF

現在我們可以調用lift並將其中任何一部分轉換為副產品。

具有輔助功能

wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a
wrapLift = wrap . lift . fmap return

我們最終可以創建通用函數,允許我們從任何可以提升到仿函數的命令中調用命令:

readStr :: (Lift SLang f, MonadFree f m) => m String
readStr = wrapLift $ ReadStr id

writeStr :: (Lift SLang f, MonadFree f m) => String -> m ()
writeStr x = wrapLift $ WriteStr x ()

readInt :: (Lift ILang f, MonadFree f m) => m Int
readInt = wrapLift $ ReadInt id

writeInt :: (Lift ILang f, MonadFree f m) => Int -> m ()
writeInt x = wrapLift $ WriteInt x ()

然后,程序可以表示為

myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m ()
myProgram = do
  str <- readStr
  writeStr "Length of that str is"
  writeInt $ length str
  n <- readInt
  writeStr "you wanna have it n times; here we go:"
  writeStr $ replicate n 'H'

沒有定義任何進一步的實例


雖然以上所有都很好用,但問題是如何一般地運行這樣的組合免費monad。 我不知道是否有可能擁有一個完全通用的,可組合的解決方案。

如果我們只有一個基本仿函數,我們就可以運行它

runSLang :: Free SLang x -> String -> (String, x)
runSLang = f
  where
    f (Pure x)              s  = (s, x)
    f (Free (ReadStr g))    s  = f (g s) s
    f (Free (WriteStr s' x)) _ = f x s'

如果我們有兩個,我們需要線程化它們的狀態:

runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a)
runBoth = f
  where
    f (Pure x)                       s i  = ((s, i), x)
    f (Free (LeftF  (ReadStr g)))     s i = f (g s) s i
    f (Free (LeftF  (WriteStr s' x))) _ i = f x s' i
    f (Free (RightF (ReadInt g)))     s i = f (g i) s i
    f (Free (RightF (WriteInt i' x))) s _ = f x s i'

我想一種可能性就是使用iter :: Functor f => (fa -> a) -> Free fa -> a來表達運行仿函數iter :: Functor f => (fa -> a) -> Free fa -> a from free然后創建一個類似的組合函數

iter2 :: (Functor f, Functor g)
      => (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a

但我沒有時間嘗試一下。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM