结合Free类型 [英] Combining Free types

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

问题描述

我最近在 Free monad的情况。 >免费套餐,但我遇到了问题。我想为不同的图书馆提供不同的免费monad,本质上我想为不同的上下文构建DSL,但我也希望能够将它们结合在一起。例如:
$ b

{ - #LANGUAGE DeriveFunctor# - }
模块TestingFree where

import Control.Monad.Free

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

类型Bells =免费BellsF

数据WhistlesF x
= PeaWhistle x
| SteamWhistle x
派生(Functor,Show)

类型Whistles =免费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)=返回r
playBells(免费(Ring x))= putStrLnRingRing! >> playBells x
playBells(Free(Chime x))= putStr定东! >> playBells x

playWhistles :: Whistles() - > IO()
playWhistles(Pure _)= return()
playWhistles(Free(PeaWhistle x))= putStrLnPreeeet! >> playWhistles x
playWhistles(免费(SteamWhistle x))= putStrLnChoo-choo! >> playWhistles x

现在,我希望能够创建一个类型的BellsAndWhistles ,使我可以毫不费力地将 Bells Whistles 的功能结合起来。



由于问题在于组合monads,我的第一个想法是查看 Control.Monad.Trans.Free 模块为快速和简单的解决方案。不幸的是,有稀疏的例子,没有显示我想要做什么。此外,由于 MonadFree 的函数依赖性为 m - >,所以似乎堆叠两个或更多空闲monad不起作用。 ˚F。基本上,我希望能够编写如下代码:

  newtype BellsAndWhistles ma = BellsAndWhistles 
{unBellsAndWhistles :: ???
}派生
(Functor
,Monad
- 无论需要什么


noisy :: Monad m => BellsAndWhistles m()
吵吵闹闹=做
举重环
举豌豆胡哨
举电铛
举steamWhistle

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

但是用这样的方式 Bells Whistles 可以存在于不同的模块中,并且不必知道每个其他实现。我的想法是,我可以为不同的任务编写独立的模块,每个模块都实现自己的DSL,然后根据需要将它们组合成一个更大的DSL。有没有简单的方法来做到这一点?



作为一种奖励,能够利用不同的 play * 已经写好的函数,以便我可以将它们交换出来。我希望能够使用一个免费的解释器进行调试,而另一个正在生产中,并且显然能够选择单独调试哪个DSL。

解决方案

这是根据数据类型单点菜单,除了没有类型的类。我推荐阅读那篇文章。



诀窍是,不要为 Bells Whistles ,你可以为他们的单个函子步骤定义解释器, BellsF WhistlesF ,就像这:

  playBellsF :: BellsF(IO a) - > IO a 
playBellsF(Ring io)= putStrLnRingRing! >> io
playBellsF(Chime io)= putStr定东! >> io

playWhistlesF :: WhistelsF(IO a) - > IO a
playWhistlesF(PeaWhistle io)= putStrLnPreeeet! >> io
playWhistlesF(SteamWhistle io)= putStrLnchoo-choo! >> io

如果您选择不合并它们,您可以将它们传递给 Control.Monad.Free.iterM 取回您的原始播放功能:

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

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

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

$ p $ data $ BellsAndWhistlesF a = L(BellsF a) R(WhistlesF a)

然后把它变成一个免费的monad:

 类型BellsAndWhistles =免费BellsAndWhistlesF 

然后你就这两个子解释器编写一个解释器,用于 BellsAndWhistlesF 的单个步骤:

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

...然后你通过将它传递给 iterM



来获得免费monad的解释器

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

所以你的问题的答案是,结合免费monads是通过定义单个函数步骤(代数)的中间解释器来保存更多的信息。这些代数比免费单子的口译更适合组合。


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

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.

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

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?

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.

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

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

... 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

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

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

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

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.

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

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