Is there an inject equivalent for Haskell in the context of free monads

大城市里の小女人 提交于 2019-12-13 12:29:05

问题


I'm trying to translate this Scala's cats example about composing free monads.

The gist of the example seems to be the decomposition of separate concerns into separate data types:

data Interact a = Ask (String -> a) | Tell String a deriving (Functor)

data DataOp = AddCat String | GetAllCats [String] deriving (Functor)

type CatsApp = Sum Interact DataOp

Without having these two separate concerns, I would build the "language" for Interact operations as follows:

ask :: Free Interact String
ask = liftF $ Ask id

tell :: String -> Free Interact ()
tell str = liftF $ Tell str ()

However, if I want to use ask and tell in a program that also uses DataOp I cannot define them with the types above, since such a program will have type:

program :: Free CatsApp a

In cats, for the definition of the tell and ask operations they use an InjectK class, and an inject method from 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))
}

What puzzles me is that somehow the positional information of the functors (DataOp is on the left, Interact is on the right) seems to be irrelevant (which is quite nice).

Are there similar type-classes and functions that could be used to solve this problem in an elegant manner using Haskell?


回答1:


This is covered in Data Types à la Carte. The Scala library you demonstrated looks like a fairly faithful translation of the original Haskell. The gist of it is, you write a class representing a relationship between a functor sup which "contains" a functor sub:

class (Functor sub, Functor sup) => sub :-<: sup where
    inj :: sub a -> sup a

(These days you might use a Prism, because they can both inject and project.) Then you can use the usual technique of composing the base functors of your free monads using the functor coproduct, implement :-<: for the two cases of 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

and make instruction sets composable by abstracting over the base functor.

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

Now you can write a program which uses both Interact and DataOp without making reference to a particular sum type.

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

None of this is particularly better than the standard MTL approach, though.



来源:https://stackoverflow.com/questions/47367696/is-there-an-inject-equivalent-for-haskell-in-the-context-of-free-monads

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!