generics haskell arrows

generics - Operaciones útiles en flechas libres



haskell arrows (1)

Sabemos que las mónadas libres son útiles, y paquetes como Operational facilitan la definición de nuevas mónadas al preocuparse solo por los efectos específicos de la aplicación, no por la estructura monádica en sí misma.

Podemos definir fácilmente "flechas libres" análogas a cómo se definen las mónadas libres:

{-# 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

Mi pregunta es, ¿cuáles serían las operaciones genéricas más útiles en las flechas libres? Para mi aplicación particular, necesitaba casos especiales de estos dos:

{-# 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

pero no tengo ningún argumento teórico sobre por qué estos (y no otros) serían los útiles.


Un functor gratuito se deja adjunto a un functor olvidadizo. Para la adjunción necesitas tener el isomorfismo (natural en y ):

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

¿En qué categoría debería ser esto? El functor olvidadizo olvida la instancia de Arrow , por lo que pasa de la categoría de instancias de Arrow a la categoría de todos los bifunctores. Y el functor libre va en dirección opuesta, convierte cualquier bifunctor en una instancia libre de Arrow .

El tipo de flechas de haskell en la categoría de bifuncionantes es:

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

Es lo mismo para las flechas en la categoría de instancias de Arrow , pero con la adición de restricciones de Arrow . Como el functor olvidadizo solo olvida la restricción, no es necesario que lo representemos en Haskell. Esto convierte el isomorfismo anterior en dos funciones:

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

leftAdjunct también debería tener una restricción Arrow y , pero resulta que nunca es necesaria en la implementación. De hecho, hay una implementación muy simple en términos de la unit más útil:

unit :: x :~> FreeA x leftAdjunct f = f . unit

unit es tu effect y rightAdjunct es tu evalA . ¡Así que tienes exactamente las funciones necesarias para la adjunción! leftAdjunct mostrar que leftAdjunct y rightAdjunct son isomorfos. La forma más fácil de hacerlo es probar que rightAdjunct unit = id , en su caso evalA effect = id , que es sencillo.

¿Qué hay de analyze ? Eso es evalA especializado para la flecha constante, con la restricción de Monoid resultante especializada para el monoide aplicativo. Es decir

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

con

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

y Ap del paquete de reductores .

Editar: ¡Casi lo olvido, FreeA debería ser un functor de orden superior! Edit2: Lo cual, pensándolo bien, también se puede implementar con rightAdjunct y unit .

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

Por cierto: hay otra manera de definir funtores gratuitos, para lo cual puse un paquete recientemente en Hackage . No admite kind * -> * -> * (Edit: it now now!), Pero el código se puede adaptar a flechas gratuitas:

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

Si no necesita la introspección que ofrece su FreeA , este FreeA es probablemente más rápido.