在自由单子的上下文中是否有 Haskell 的注入等价物
Is there an inject equivalent for Haskell in the context of free monads
我正在尝试翻译 this Scala's cats
example 关于编写自由 monad 的内容。
该示例的要点似乎是将单独的关注点分解为单独的数据类型:
data Interact a = Ask (String -> a) | Tell String a deriving (Functor)
data DataOp = AddCat String | GetAllCats [String] deriving (Functor)
type CatsApp = Sum Interact DataOp
如果没有这两个单独的问题,我会为 Interact
操作构建 "language",如下所示:
ask :: Free Interact String
ask = liftF $ Ask id
tell :: String -> Free Interact ()
tell str = liftF $ Tell str ()
但是,如果我想在也使用 DataOp
的程序中使用 ask
和 tell
,我不能用上面的类型定义它们,因为这样的程序将具有类型:
program :: Free CatsApp a
在 cats
中,对于 tell
和 ask
操作的定义,他们使用 InjectK
class 和 inject
来自 Free
的方法:
class Interacts[F[_]](implicit I: InjectK[Interact, F]) {
def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg))
def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt))
}
令我困惑的是,函子的位置信息(DataOp
在左边,Interact
在右边)似乎无关紧要(这很好)。
是否有类似的类型-classes 和函数可用于使用 Haskell 以优雅的方式解决此问题?
Data Types à la Carte 中对此进行了介绍。您演示的 Scala 库看起来像是对原始 Haskell 的相当忠实的翻译。它的要点是,你写一个 class 表示一个函子 sup
和 "contains" 一个函子 sub
:
之间的关系
class (Functor sub, Functor sup) => sub :-<: sup where
inj :: sub a -> sup a
(现在你可能会使用 Prism
,因为它们既可以注入又可以投射。)然后你可以使用常用的技术,使用仿函数余积来组合你的自由 monad 的基仿函数,实现 :-<:
对于 Sum
,
两种情况
instance Functor f => f :-<: f where
inj = id
instance (Functor f, Functor g) => f :-<: (Sum f g) where
inj = InL
instance (Functor f, Functor g, Functor h, f :-<: g) => f :-<: (Sum h g) where
inj = InR . inj
并通过对基本仿函数进行抽象使指令集可组合。
ask :: Interact :-<: f => Free f String
ask = liftF $ inj $ Ask id
tell :: Interact :-<: f => String -> Free f ()
tell str = liftF $ inj $ Tell str ()
addCat :: DataOp :-<: f => String -> Free f ()
addCat cat = liftF $ inj $ AddCat cat ()
getCats :: DataOp :-<: f => Free f [String]
getCats = liftF $ inj $ GetCats id
现在您可以编写同时使用 Interact
和 DataOp
的程序,而无需引用特定的求和类型。
myProgram :: (Interact :-< f, DataOp :-< f) => Free f ()
myProgram = ask >>= addCat
不过,None 这比标准的 MTL 方法特别好。
我正在尝试翻译 this Scala's cats
example 关于编写自由 monad 的内容。
该示例的要点似乎是将单独的关注点分解为单独的数据类型:
data Interact a = Ask (String -> a) | Tell String a deriving (Functor)
data DataOp = AddCat String | GetAllCats [String] deriving (Functor)
type CatsApp = Sum Interact DataOp
如果没有这两个单独的问题,我会为 Interact
操作构建 "language",如下所示:
ask :: Free Interact String
ask = liftF $ Ask id
tell :: String -> Free Interact ()
tell str = liftF $ Tell str ()
但是,如果我想在也使用 DataOp
的程序中使用 ask
和 tell
,我不能用上面的类型定义它们,因为这样的程序将具有类型:
program :: Free CatsApp a
在 cats
中,对于 tell
和 ask
操作的定义,他们使用 InjectK
class 和 inject
来自 Free
的方法:
class Interacts[F[_]](implicit I: InjectK[Interact, F]) {
def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg))
def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt))
}
令我困惑的是,函子的位置信息(DataOp
在左边,Interact
在右边)似乎无关紧要(这很好)。
是否有类似的类型-classes 和函数可用于使用 Haskell 以优雅的方式解决此问题?
Data Types à la Carte 中对此进行了介绍。您演示的 Scala 库看起来像是对原始 Haskell 的相当忠实的翻译。它的要点是,你写一个 class 表示一个函子 sup
和 "contains" 一个函子 sub
:
class (Functor sub, Functor sup) => sub :-<: sup where
inj :: sub a -> sup a
(现在你可能会使用 Prism
,因为它们既可以注入又可以投射。)然后你可以使用常用的技术,使用仿函数余积来组合你的自由 monad 的基仿函数,实现 :-<:
对于 Sum
,
instance Functor f => f :-<: f where
inj = id
instance (Functor f, Functor g) => f :-<: (Sum f g) where
inj = InL
instance (Functor f, Functor g, Functor h, f :-<: g) => f :-<: (Sum h g) where
inj = InR . inj
并通过对基本仿函数进行抽象使指令集可组合。
ask :: Interact :-<: f => Free f String
ask = liftF $ inj $ Ask id
tell :: Interact :-<: f => String -> Free f ()
tell str = liftF $ inj $ Tell str ()
addCat :: DataOp :-<: f => String -> Free f ()
addCat cat = liftF $ inj $ AddCat cat ()
getCats :: DataOp :-<: f => Free f [String]
getCats = liftF $ inj $ GetCats id
现在您可以编写同时使用 Interact
和 DataOp
的程序,而无需引用特定的求和类型。
myProgram :: (Interact :-< f, DataOp :-< f) => Free f ()
myProgram = ask >>= addCat
不过,None 这比标准的 MTL 方法特别好。