结合自由类型 [英] Combining Free types

查看:26
本文介绍了结合自由类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近自学了 free 中的 Free monad包,但我遇到了一个问题.我想为不同的库提供不同的免费 monad,本质上我想为不同的上下文构建 DSL,但我也希望能够将它们组合在一起.举个例子:

I've been recently teaching myself about the Free monad from the free package, but I've come across a problem with it. I would like to have different free monads for different libraries, essentially I would like to build DSLs for different contexts, but I would also like to be able to combine them together. As an example:

{-# LANGUAGE DeriveFunctor #-}
module TestingFree where

import Control.Monad.Free

data BellsF x
    = Ring x
    | Chime x
    deriving (Functor, Show)

type Bells = Free BellsF

data WhistlesF x
    = PeaWhistle x
    | SteamWhistle x
    deriving (Functor, Show)

type Whistles = Free WhistlesF

ring :: Bells ()
ring = liftF $ Ring ()

chime :: Bells ()
chime = liftF $ Chime ()

peaWhistle :: Whistles ()
peaWhistle = liftF $ PeaWhistle ()

steamWhistle :: Whistles ()
steamWhistle = liftF $ SteamWhistle ()


playBells :: Bells r -> IO r
playBells (Pure r)         = return r
playBells (Free (Ring x))  = putStrLn "RingRing!" >> playBells x
playBells (Free (Chime x)) = putStr "Ding-dong!" >> playBells x

playWhistles :: Whistles () -> IO ()
playWhistles (Pure _)                = return ()
playWhistles (Free (PeaWhistle x))   = putStrLn "Preeeet!" >> playWhistles x
playWhistles (Free (SteamWhistle x)) = putStrLn "Choo-choo!" >> playWhistles x

现在,我希望能够创建一种 BellsAndWhistles 类型,它允许我结合 BellsWhistles 的功能而无需很努力.

Now, I would like to be able to create a type BellsAndWhistles that allows me to combine the functionality of both Bells and Whistles without much effort.

由于问题是结合 monad,我的第一个想法是查看 Control.Monad.Trans.Free 模块以获得快速简便的解决方案.不幸的是,有稀疏的例子,没有一个显示我想要做什么.此外,似乎堆叠两个或多个自由 monad 不起作用,因为 MonadFree 具有 m ->f.本质上,我希望能够编写如下代码:

Since the problem is combining monads, my first thought was to look at the Control.Monad.Trans.Free module for a quick and easy solution. Unfortunately, there are sparse examples and none showing what I want to do. Also, it seems that stacking two or more free monads doesn't work, since MonadFree has a functional dependency of m -> f. Essentially, I'd like the ability to write code like:

newtype BellsAndWhistles m a = BellsAndWhistles
    { unBellsAndWhistles :: ???
    } deriving
        ( Functor
        , Monad
        -- Whatever else needed
        )

noisy :: Monad m => BellsAndWhistles m ()
noisy = do
    lift ring
    lift peaWhistle
    lift chime
    lift steamWhistle

play :: BellsAndWhistles IO () -> IO ()
play bellsNwhistles = undefined

但是这样BellsWhistles 可以存在于单独的模块中,并且不必了解彼此的实现.这个想法是我可以为不同的任务编写独立的模块,每个模块实现自己的 DSL,然后有办法根据需要将它们组合成一个更大的"DSL.有没有简单的方法可以做到这一点?

But in such a way that Bells and Whistles can exist in separate modules and don't have to know about each others implementations. The idea is that I can write stand alone modules for different tasks, each implementing its own DSL, and then having a way to combine them into a "larger" DSL as needed. Is there an easy way to do this?

作为奖励,能够利用已经编写的不同 play* 函数会很棒,这样我就可以将它们换掉.我希望能够使用一个免费的解释器进行调试,而在生产中使用另一个,并且能够选择单独调试哪个 DSL 显然很有用.

As a bonus it'd be great to be able to leverage the different play* functions that are already written, in such a way that I can swap them out. I want to be able to use one free interpreter for debug and another in production, and it'd obviously be useful to be able to choose which DSL was being debugged individually.

推荐答案

这是基于论文的答案 点菜的数据类型,除了没有类型类.我建议阅读那篇论文.

This is an answer based off of the paper Data types à la carte, except without type classes. I recommend reading that paper.

诀窍在于,不是为 BellsWhistles 编写解释器,而是为它们的单个函子步骤定义解释器,BellsFWhistlesF,像这样:

The trick is that instead of writing interpreters for Bells and Whistles, you define interpreters for their single functor steps, BellsF and WhistlesF, like this:

playBellsF :: BellsF (IO a) -> IO a
playBellsF (Ring  io) = putStrLn "RingRing!"  >> io
playBellsF (Chime io) = putStr   "Ding-dong!" >> io

playWhistlesF :: WhistelsF (IO a) -> IO a
playWhistlesF (PeaWhistle   io) = putStrLn "Preeeet!"   >> io
playWhistlesF (SteamWhistle io) = putStrLn "choo-choo!" >> io

如果您选择不组合它们,您可以将它们传递给 Control.Monad.Free.iterM 以恢复您原来的播放功能:

If you choose not to combine them, you can just pass them to Control.Monad.Free.iterM to get back your original play functions:

playBells    :: Bells a    -> IO a
playBells    = iterM playBell

playWhistles :: Whistles a -> IO a
playWhistles = iterM playWhistlesF

... 然而,因为它们处理单个步骤,所以它们可以更容易地组合起来.你可以像这样定义一个新的组合自由 monad:

... however because they deal with single steps they can be combined more easily. You can define a new combined free monad like this:

data BellsAndWhistlesF a = L (BellsF a) | R (WhistlesF a)

然后把它变成一个自由的单子:

Then turn that into a free monad:

type BellsAndWhistles = Free BellsAndWhistlesF

然后根据两个子解释器为 BellsAndWhistlesF 的单个步骤编写一个解释器:

Then you write an interpreter for a single step of BellsAndWhistlesF in terms of the two sub-interpreters:

playBellsAndWhistlesF :: BellsAndWhistlesF (IO a) -> IO a
playBellsAndWhistlesF (L bs) = playBellsF    bs
playBellsAndWhistlesF (R ws) = playWhistlesF ws

... 然后你通过将它传递给 iterM 来获得自由 monad 的解释器:

... and then you get the interpreter for the free monad by just passing that to iterM:

playBellsAndWhistles :: BellsAndWhistles a -> IO a
playBellsAndWhistles = iterM playBellsAndWhistlesF

因此,您的问题的答案是,组合自由 monad 的技巧是通过为各个函子步骤(代数")定义中间解释器来保留更多信息.这些代数"比自由单子的解释器更易于组合.

So the answer to your question is that the trick to combining free monads is to preserve more information by defining intermediate interpreters for individual functor steps ("algebras"). These "algebras" are much more amenable to combination than interpreters for free monads.

这篇关于结合自由类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆