什么monad可以表示为free over some functor?

时间:2013-02-01 07:50:59

标签: haskell monads functor free-monad

documentation for Free说:

  

许多常见的单子出现为免费单子,

     
      
  • 鉴于data Empty aFree EmptyIdentity monad同构。
  •   
  • 免费Maybe可用于建模偏好monad,其中每个图层表示运行计算的时间更长。
  •   

使用Free可以表达哪些其他monad?

我只想到一个:我相信Free (Const e)Either e同构。

修改:使用Free 表达的monad是什么?为什么?

4 个答案:

答案 0 :(得分:28)

几乎所有这些(直到涉及循环和mfix的问题),而不是Cont

考虑State monad

newtype State s a = State (s -> (a,s))

看起来像任何就像一个免费的monad ......但是就你如何使用来考虑State

get :: m s --or equivalently (s -> m a) -> m a
set :: s -> m () --or (s,m a) -> m a
runState :: m a -> s -> (a,s)

我们可以通过将操作列为构造函数

来设计具有此接口的免费monad
data StateF s a
  = Get (s -> a) | Set s a deriving Functor

然后我们

type State s a = Free (StateF s) a

get = Impure (Get Pure)
set x = Impure (Set x (Pure ())

我们只需要一种方法来使用它

runState (Pure a) s = (a,s)
runState (Impure (Get f)) s = runState (f s) s
runState (Impure (Set s next)) _ = runState next s

你可以用大多数monad做这个结构。像may / partiality monad由

定义
stop :: m a
maybe :: b -> (a -> b) -> m a -> b

规则是,我们将m x中以x结尾的每个函数视为仿函数中的构造函数,其他函数是运行生成的自由monad的方法。在这种情况下

data StopF a = StopF deriving Functor

maybe _ f (Pure a)      = f a
maybe b _ (Impure Stop) = b

为什么这很酷?好吧一些事情

  1. 免费monad为您提供了一段数据,您可以将其视为monadic代码的AST。您可以编写对此数据进行操作的函数,这对DSL非常有用
  2. Functors组合,这意味着分解你的monad就像这样使它们半可组合。特别是,给定两个共享代数的仿函数(当f a -> a是一个仿函数时,代数对于某些a基本上只是一个函数f),该组合也具有该代数。
  3. Functor组合只是我们可以通过多种方式组合仿函数,其中大部分都保留了代数。在这种情况下,我们不需要组成的仿函数 (f (g (x))),而是仿函数coproduct 。函数添加

    data f :+: g a = Inl (f a) | Inr (g a) 
    
    instance (Functor f, Functor g) => Functor (f :+: g) where
      fmap f (Inl x) = Inl (fmap f x)
      fmap f (Inr x) = Inr (fmap f x)
    
    compAlg :: (f a -> a) -> (g a -> a) -> f :+: g a -> a
    compAlg f _ (Inl x) = f x
    compAlf _ g (Inr x) = g x
    

    也免费monad保留代数

    freeAlg :: (f a -> a) -> Free f a -> a
    freeAlg _ (Pure a) = a
    freeAlg f (Impure x) = f $ fmap (freeAlg f) x
    

    在Wouter Swierstra的着名论文Data Types A La Carte中,这被用来产生很大的效果。该论文的一个简单例子是计算器。我们将对这篇文章采用monadic take new。鉴于代数

    class Calculator f where
     eval :: f Integer -> Integer
    

    我们可以想到各种情况

    data Mult a = Mult a a deriving Functor
    instance Calculator Mult where
      eval (Mult a b) = a*b
    
    data Add a = Add a a deriving Functor
    instance Calculator Add where
      eval (Add a b) = a+b
    
    data Neg a = Neg a deriving Functor
    instance Calculator Neg where
      eval (Neg a) = negate a
    
    instance Calculator (Const Integer) where
      eval (Const a) = a
    
    data Signum a = Signum a deriving Functor
    instance Calculator Signum where
      eval (Signum a) = signum a
    
    data Abs a = Abs a deriving Functor
    instance Calculator Abs where
      eval (Abs a) = abs a
    

    和最重要的

    instance (Calculator f, Calculator g) => Calculator (f :+: g) where
       eval = compAlg eval
    

    您可以定义数字monad

    newtype Numerical a = Numerical (
            Free (Mult 
            :+: Add 
            :+: Neg 
            :+: Const Integer 
            :+: Signum
            :+: Abs) a deriving (Functor, Monad)
    

    然后你可以定义

     instance Num (Numerical a)
    

    这可能完全没用,但我觉得很酷。它确实允许您定义其他内容,如

     class Pretty f where
        pretty :: f String -> String
    
     instance Pretty Mult where
        pretty (Mult a b) = a ++ "*" ++ b
    

    和其他所有人类似。

    这是一个有用的设计策略:列出你希望monad做的事情==>为每个操作定义仿函数==>弄清楚它的一些代数应该是什么==>为每个操作定义那些仿函数==>快点。

    快速加速很难,但我们有一些技巧。诀窍1是将你的免费monad包裹在Codensity(“更快的按钮”)中,但是当这不起作用时,你想要摆脱自由表示。记得我们什么时候

    runState (Pure a) s = (a,s)
    runState (Impure (Get f)) s = runState (f s) s
    runState (Impure (Set s next)) _ = runState next s
    

    好吧,这是一个从Free StateF as -> (a,s)的函数,仅使用结果类型,因为我们对状态的定义似乎合理......但我们如何定义操作?在这种情况下,你知道答案,但推导它的一种方法是根据Conal Elliott所谓的类型态射来考虑。你想要

    runState (return a) = return a
    runState (x >>= f) = (runState x) >>= (runState f)
    runState (set x) = set x
    runState get = get
    

    这很容易

    runState (return a) = (Pure a) = \s -> (a,s)
    
    runState (set x) 
       = runState (Impure (Set x (Pure ()))) 
       = \_ -> runState (Pure ()) x
       = \_ -> (\s -> (a,s)) x
       = \_ -> (a,x)
    
    runState get
      = runState (Impure (Get Pure))
      = \s -> runState (Pure s) s
      = \s -> (s,s)
    

    非常有帮助。以这种方式得出>>=可能很难,我不会在这里包含它,但其他的正是你期望的定义。

答案 1 :(得分:7)

如上所述,要回答这个问题,你在问题中没有提到的大多数熟悉的单子都不是自由的单子。菲利普JF的答案提到你如何将一个给定的monad与一个新的,免费的monad联系起来,但是新的monad将会更大"它具有比原始monad更明显的价值。例如,真正的State s monad满足get >>= put = return (),但StateF上的免费monad不满足Reader r。作为一个自由的monad,它不能满足额外的方程式;这就是自由的概念。

除非在Writer w / State s / {的特殊情况下,我才会显示rws monad不是免费的{1}}。

让我介绍一些术语。如果m是monad,那么我们会调用类型m a和(m - )操作的任何值。如果某个m等于return x,我们会将x - 行动称为行动。否则我们称之为不平凡。

如果m = Free f是仿函数f上的任意免费monad,那么m会允许monad同态到Maybe。这是因为Free在其参数fMaybe = Free (Const ())中是函数的,其中Const ()是仿函数类别中的终端对象。具体地说,这种同态可以写成

toMaybe :: Free f a -> Maybe a
toMaybe (Pure a) = Just a
toMaybe (Impure _) = Nothing

由于toMaybe是monad同态,因此对于任何toMaybe (v >> w) = toMaybe v >> toMaybe w - 动作mv,它尤其满足w。现在观察toMaybe发送琐碎的m - 操作到琐碎的Maybe - 操作和不平凡的m - 操作到不平凡的Maybe - 操作Nothing 。现在,Maybe具有以下任一操作>>执行任何操作的非常重要操作的属性,产生一个非常重要的操作(Nothing >> w = v >> Nothing = Nothing);因此m也是如此,因为toMaybe是一个保留(非)平凡性的monad同态。

(如果您愿意,也可以直接从>>的公式中为免费monad验证这一点。)

要显示特定的monad m与任何免费monad不同构,那么,只需查找m - 动作vw即可vw中的一个不是微不足道的,但v >> w是微不足道的。

Reader r满足v >> w = w,因此我们只需选择w = return ()以及任何非常重要的行动v,只要r至少有两个,就会存在值(然后ask是非常数的,即非平凡的。)

对于Writer w,假设除了身份之外还有一个可逆元素k :: w。让kinv :: w反其道而行之。然后是tell k >> tell kinv = return (),但tell ktell kinv并不重要。任何非平凡的组(例如,添加下的整数)都具有这样的元素k。我认为Writer w形式的免费monad只是幺半群w本身自由的那些,即w = [a]Writer w ~ Free ((,) a)

对于State s,同样如果s允许任何非平凡的自同构f :: s -> s反向finv :: s -> s,那么modify f >> modify finv = return ()。具有至少两个元素和可判定的相等性的任何类型s都具有这样的自同构。

答案 2 :(得分:2)

根据Reid的回答中的见解,我写了一个证据,证明monad在posting to the haskell-cafe mailing list中不是免费的:

回想一下,对于任何仿函数f,我们可以为f:

构建免费monad
data Free f a = Pure a | Roll (f (Free f a))

直观地,Free f a是具有a型叶子的f形树的类型。 连接操作仅将树木移植到一起并且不执行任何操作 进一步的计算。应调用表格(Roll _)的值 "非平凡"在这篇文章中。

一些通常的monad实际上是一些仿函数的免费monad:

  • Tree monad是仿函数Pair上的免费monad,其中 data Pair a = MkPair a a。直觉就像双形树 在这个例子中是最强大的。每个父节点都有一对 孩子。

  • Maybe monad是仿函数Unit上的免费monad,其中 data Unit a = MkUnit。在图形图片中,父节点有一个 Unit孩子,所以根本没有孩子。所以恰好有两个 Unit - 形状的树:仅由叶子组成的树(对应的 到Just _)以及由无子节点父节点组成的三个节点 (对应Nothing)。

  • Identity monad是仿函数Void上的免费monad, 其中Void a是没有构造函数的类型。

  • Partiality monad是Maybe上的免费monad。

  • Writer [r] monad是仿函数(r,)上的免费monad。 在(r,)形状的树中,父节点用值装饰 类型r并且只有一个子节点。穿越这样的道路 生成r的列表和类型a的叶值,总计如此 类型为([r],a) = Writer r a的值。

    (特别是Free (r,) ()[r]同构。这个观察结果 导致名单Monad是免费的。但这种说法是错误的 并且实际上并没有从观察中得出结论。显示monad m 是免费的,必须表现出monad同构forall a. m a -> Free f a 对于一些仿函数f。相反,表现出m a的同构 Free (f a) ()既不充分也不必要。)

更多的monad是免费monad的商。例如,State sFree (StateF s)的商,其中

data StateF s a = Put s a | Get (s -> a).
-- NB: This functor is itself a free functor over a type constructor
-- which is sometimes called StateI [1], rendering Free (StateF s) what
-- Oleg and Hiromi Ishii call a "freer monad" [2].

该商由以下monad态射展示,其给出 语义学对于自由monad的纯粹语法价值。

run :: Free (StateF s) a -> State s a
run (Pure x)         = return x
run (Roll (Put s m)) = put s >> run m
run (Roll (Get k))   = get >>= run . k

这一观点是apfelmus [1]运作方案的基础 并且还在StackOverflow线程[3]中讨论过。它是主要的 免费monad在编程环境中有用的原因。

那么,monad列表是免费的monad吗?直觉上,它不是,因为 列表monad(连接)的连接操作并不仅仅是移植 表达在一起,但使它们变得扁平[4]。

这是一个证明列表monad不是免费的。我记不起来了 我已经有很长一段时间对证据感兴趣,但正在寻找 它没有产生任何结果。然而,线程[3]和[5]接近了。

在任何仿函数的免费monad中,绑定一个非常重要的结果 任何函数的动作总是非常重要,即

(Roll _ >>= _) = Roll _

可以直接从(>>=)的定义中免费查看 monad或者Reid Barton利用monad的伎俩 对于Maybe的态射,见[3]。

如果列表monad对于自由monad是同构的a-monad而不是某些 仿函数,同构只会将单例列表[x]映射到值 形式(Pure _)和所有其他列表到非平凡值。这是 因为monad isomorphisms必须与" return"和return x 列表monad中为[x],免费monad中为Pure x

这两个事实相互矛盾,如下所示 例如:

do
    b <- [False,True]  -- not of the form (return _)
    if b
        then return 47
        else []
-- The result is the singleton list [47], so of the form (return _).

对一些自由monad应用假设的同构 仿函数,我们有一个非平凡的价值(图像 在同构下的[False,True]和一些函数导致a 平凡的价值([47]的图像,即return 47)。

干杯, INGO

[1] http://projects.haskell.org/operational/Documentation.html

[2] http://okmij.org/ftp/Haskell/extensible/more.pdf

[3] What monads can be expressed as Free over some functor?

[4] https://hackage.haskell.org/package/free-4.12.4/docs/Control-Monad-Free.html

[5] https://www.reddit.com/r/haskell/comments/50zvyb/why_is_liststate_not_a_free_monad/

答案 3 :(得分:0)

所有monad都可以表示为Free Monad。他们的附加属性(如MonadFix和Cont)可能会被剥夺,因为Free Monad不是免费的MonadFix或Free Cont。

一般的方法是根据liftM定义Functor的fmap,然后在Functor周围自由包装。 通过指定函数returnjoin(纯净和不纯)必须如何映射到实际的Monad,可以将生成的Monad缩减为前一个/实际Monad。