在 Haskell 中,我如何将一个 Free monad 嵌入另一个?
In Haskell, how could I embed one Free monad in another one?
我有两个免费的单子,用于不同上下文中的不同操作。但是,一个(major
)DSL需要包含另一个(action
)如果特定操作在上下文中:
import Control.Monad.Free
data ActionFunctor next = Wait Timeout next
| Read URI next
instance Functor ActionFunctor where
fmap f (Wait timeout next) = Wait timeout (f next)
fmap f (Read uri next) = Read uri (f next)
type Action = Free ActionFunctor
data MajorFunctor next = Log LogText next
| Act Action next
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action next) = Act action (f next)
fmap f (Send message) = Send message
type Major = Free MajorFunctor
问题是,GHC 会抱怨 MajorFunctor
Act Action next
中的 Action
是一种 (* -> *)
,而不仅仅是一种类型。这是因为在 data ActionFunctor
定义中它应该接受一个 next
作为类型参数,而在 Act Action
行中它不包含这样的参数。但即使消息对我来说很清楚,我也不确定是否应该在 Major
仿函数中声明这样的额外类型参数:
data MajorFunctor actionNext next = ...
这看起来很奇怪,因为只有一个数据构造函数会使用该参数,而这样的暴露会将每个 MajorFunctor
变成 MajorFunctor actionNext
并且看起来完全暴露了太多细节。所以我看了一下FreeT
transformer,看看是不是我想要的。但是,在我的例子中,当 DSL 程序有这样的操作时,我只需要调用 Action
解释器,而不是 monadic 程序中的每个 bind
,所以我也不确定转换器是否是一个好的解决方案.
在您的 Act
构造函数中,您可以嵌入 Action a
,然后根据 a
继续执行 next
步骤。您可以通过使用存在主义将两者联系在一起来做到这一点:
{-# LANGUAGE ExistentialQuantification #-}
data MajorFunctor next = Log LogText next
| forall a. Act (Action a) (a -> next)
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action next) = Act action (fmap f next)
fmap f (Send message) = Send message
一个不需要存在性的更简单的解决方案是将 next
参数嵌入 Action
.
data MajorFunctor next = Log LogText next
| Act (Action next)
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action) = Act (fmap f action)
fmap f (Send message) = Send message
这表示 "When you execute the Action
it'll return a next
",而@Cactus 的解决方案表示 "When you execute the Action
it'll return something (of unknown (existential) type) which can (only) be turned into a next
"。
The co-Yoneda lemma表示这两个解是同构的。我的版本更简单,但 Cactus 对于某些操作可能更快,例如在大 Action
s 上重复 fmap
s。
我有两个免费的单子,用于不同上下文中的不同操作。但是,一个(major
)DSL需要包含另一个(action
)如果特定操作在上下文中:
import Control.Monad.Free
data ActionFunctor next = Wait Timeout next
| Read URI next
instance Functor ActionFunctor where
fmap f (Wait timeout next) = Wait timeout (f next)
fmap f (Read uri next) = Read uri (f next)
type Action = Free ActionFunctor
data MajorFunctor next = Log LogText next
| Act Action next
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action next) = Act action (f next)
fmap f (Send message) = Send message
type Major = Free MajorFunctor
问题是,GHC 会抱怨 MajorFunctor
Act Action next
中的 Action
是一种 (* -> *)
,而不仅仅是一种类型。这是因为在 data ActionFunctor
定义中它应该接受一个 next
作为类型参数,而在 Act Action
行中它不包含这样的参数。但即使消息对我来说很清楚,我也不确定是否应该在 Major
仿函数中声明这样的额外类型参数:
data MajorFunctor actionNext next = ...
这看起来很奇怪,因为只有一个数据构造函数会使用该参数,而这样的暴露会将每个 MajorFunctor
变成 MajorFunctor actionNext
并且看起来完全暴露了太多细节。所以我看了一下FreeT
transformer,看看是不是我想要的。但是,在我的例子中,当 DSL 程序有这样的操作时,我只需要调用 Action
解释器,而不是 monadic 程序中的每个 bind
,所以我也不确定转换器是否是一个好的解决方案.
在您的 Act
构造函数中,您可以嵌入 Action a
,然后根据 a
继续执行 next
步骤。您可以通过使用存在主义将两者联系在一起来做到这一点:
{-# LANGUAGE ExistentialQuantification #-}
data MajorFunctor next = Log LogText next
| forall a. Act (Action a) (a -> next)
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action next) = Act action (fmap f next)
fmap f (Send message) = Send message
一个不需要存在性的更简单的解决方案是将 next
参数嵌入 Action
.
data MajorFunctor next = Log LogText next
| Act (Action next)
| Send Message
instance Functor MajorFunctor where
fmap f (Log text next) = Log text (f next)
fmap f (Act action) = Act (fmap f action)
fmap f (Send message) = Send message
这表示 "When you execute the Action
it'll return a next
",而@Cactus 的解决方案表示 "When you execute the Action
it'll return something (of unknown (existential) type) which can (only) be turned into a next
"。
The co-Yoneda lemma表示这两个解是同构的。我的版本更简单,但 Cactus 对于某些操作可能更快,例如在大 Action
s 上重复 fmap
s。