34

我们知道免费的 monad 很有用,像Operational这样的包通过只关心特定于应用程序的效果而不是 monadic 结构本身来轻松定义新的 monad。

我们可以很容易地定义“自由箭头”,类似于定义自由单子的方式:

{-# LANGUAGE GADTs #-}
module FreeA
       ( FreeA, effect
       ) where

import Prelude hiding ((.), id)
import Control.Category
import Control.Arrow
import Control.Applicative
import Data.Monoid

data FreeA eff a b where
    Pure :: (a -> b) -> FreeA eff a b
    Effect :: eff a b -> FreeA eff a b
    Seq :: FreeA eff a b -> FreeA eff b c -> FreeA eff a c
    Par :: FreeA eff a₁ b₁ -> FreeA eff a₂ b₂ -> FreeA eff (a₁, a₂) (b₁, b₂)

effect :: eff a b -> FreeA eff a b
effect = Effect

instance Category (FreeA eff) where
    id = Pure id
    (.) = flip Seq

instance Arrow (FreeA eff) where
    arr = Pure
    first f = Par f id
    second f = Par id f
    (***) = Par

我的问题是,对自由箭头最有用的通用操作是什么?对于我的特定应用程序,我需要这两种情况的特殊情况:

{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
analyze :: forall f eff a₀ b₀ r. (Applicative f, Monoid r)
        => (forall a b. eff a b -> f r)
        -> FreeA eff a₀ b₀ -> f r
analyze visit = go
  where
    go :: forall a b. FreeA eff a b -> f r
    go arr = case arr of
        Pure _ -> pure mempty
        Seq f₁ f₂ -> mappend <$> go f₁ <*> go f₂
        Par f₁ f₂ -> mappend <$> go f₁ <*> go f₂
        Effect eff -> visit eff

evalA :: forall eff arr a₀ b₀. (Arrow arr) => (forall a b. eff a b -> arr a b) -> FreeA eff a₀ b₀ -> arr a₀ b₀
evalA exec = go
  where
    go :: forall a b. FreeA eff a b -> arr a b
    go freeA = case freeA of
        Pure f -> arr f
        Seq f₁ f₂ -> go f₂ . go f₁
        Par f₁ f₂ -> go f₁ *** go f₂
        Effect eff -> exec eff

但是对于为什么这些(而不是其他)有用的,我没有任何理论论据。

4

1 回答 1

29

一个自由函子与一个健忘函子相伴。对于附加词,您需要具有同构(自然在xand中y):

(Free y :~> x) <-> (y :~> Forget x)

这应该属于什么类别?健忘函子忘记了Arrow实例,因此它从Arrow实例的类别转到所有双函子的类别。而自由函子则相反,它将任何双函子变成自由Arrow实例。

双函子类别中的 haskell 箭头类型是:

type x :~> y = forall a b. x a b -> y a b

实例类别中的箭头也是Arrow如此,但增加了Arrow约束。由于健忘函子只忘记约束,我们不需要在 Haskell 中表示它。这将上述同构变成了两个函数:

leftAdjunct :: (FreeA x :~> y) -> x :~> y
rightAdjunct :: Arrow y => (x :~> y) -> FreeA x :~> y

leftAdjunct也应该有一个Arrow y约束,但事实证明它在实现中从来不需要。就更有用的方面而言,实际上有一个非常简单的实现unit

unit :: x :~> FreeA x

leftAdjunct f = f . unit

unit是你的effectrightAdjunct是你的evalA。所以你完全有附件所需的功能!你需要证明这一点leftAdjunct并且rightAdjunct是同构的。最简单的方法是证明rightAdjunct unit = id,在你的情况下evalA effect = id,这是简单的。

怎么样analyze?这是evalA专门针对常量箭头的,结果Monoid约束专门针对应用类幺半群。IE

analyze visit = getApp . getConstArr . evalA (ConstArr . Ap . visit)

newtype ConstArr m a b = ConstArr { getConstArr :: m }

Ap来自减速器包。(编辑:从 GHC 8.6 开始,它也在基础中Data.Monoid

编辑:我差点忘了,FreeA 应该是高阶函子!Edit2:再想一想,也可以用rightAdjunctand来实现unit

hfmap :: (x :~> y) -> FreeA x :~> FreeA y
hfmap f = evalA (effect . f)

顺便说一句:还有另一种定义自由函子的方法,我最近在 Hackage 上放了一个包。它不支持种类* -> * -> *(编辑:现在支持!),但代码可以适应自由箭头:

newtype FreeA eff a b = FreeA { runFreeA :: forall arr. Arrow arr => (eff :~> arr) -> arr a b }
evalA f a = runFreeA a f
effect a = FreeA $ \k -> k a

instance Category (FreeA f) where
  id = FreeA $ const id
  FreeA f . FreeA g = FreeA $ \k -> f k . g k

instance Arrow (FreeA f) where
  arr f = FreeA $ const (arr f)
  first (FreeA f) = FreeA $ \k -> first (f k)
  second (FreeA f) = FreeA $ \k -> second (f k)
  FreeA f *** FreeA g = FreeA $ \k -> f k *** g k
  FreeA f &&& FreeA g = FreeA $ \k -> f k &&& g k

如果您不需要自省您的FreeA报价,这FreeA可能会更快。

于 2012-08-24T22:26:35.393 回答