自由箭头的有用操作

问题描述 投票:0回答:1

我们知道免费的 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

但我没有任何理论论据来解释为什么这些(而不是其他)会是有用的。

haskell generics arrow-abstraction
1个回答
30
投票

自由函子与健忘函子相邻。对于附加词,您需要具有同构(在

x
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
是您的
effect
rightAdjunct
是您的
evalA
。所以您完全具备了附加所需的功能!您需要证明
leftAdjunct
rightAdjunct
是同构的。最简单的方法是证明
rightAdjunct unit = id
,就您而言
evalA effect = id
,这很简单。

analyze
呢?这是
evalA
专用于常量箭头,而生成的
Monoid
约束专用于应用幺半群。即

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

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

Ap
来自减速器包。 (编辑:自 GHC 8.6 以来,它也位于
Data.Monoid
的基础中)

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

rightAdjunct
unit
来实现。

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

顺便说一句:还有另一种方法来定义自由函子,我最近在 Hackage 上放了一个包。它不支持 kind * -> * -> *

(编辑:现在支持!),但代码可以适应自由箭头:

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
 可能会更快。

© www.soinside.com 2019 - 2024. All rights reserved.