在自由单子的上下文中是否有 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 的程序中使用 asktell,我不能用上面的类型定义它们,因为这样的程序将具有类型:

program :: Free CatsApp a

cats 中,对于 tellask 操作的定义,他们使用 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

现在您可以编写同时使用 InteractDataOp 的程序,而无需引用特定的求和类型。

myProgram :: (Interact :-< f, DataOp :-< f) => Free f ()
myProgram = ask >>= addCat
不过,

None 这比标准的 MTL 方法特别好。