haskell - preguntas - ¿Cuál es la relación entre profunctores y flechas?
tag de las 20 preguntas (2)
Al parecer, cada Arrow
es un profunctor Strong
. De hecho, ^>>
y >>^
corresponden a lmap
y rmap
. Y first''
y second''
son lo mismo que first
y second
. Del mismo modo, cada ArrowChoice
es también una Choice
.
Lo que les falta a los profesores en comparación con las flechas es la capacidad de componerlas. Si añadimos composición, ¿obtendremos una flecha? En otras palabras, si un profesor (fuerte) también es una category , ¿ya es una flecha? Si no, ¿qué falta?
Lo que les falta a los profesores en comparación con las flechas es la capacidad de componerlas. Si añadimos composición, ¿obtendremos una flecha?
MONOIDES
Esta es exactamente la pregunta abordada en la sección 6 de " Nociones de computación como monoides ", que muestra un resultado de la semántica categórica para flechas (bastante densa). Las "nociones" son un gran artículo porque, aunque profundiza en la teoría de categorías, (1) no asume que el lector tiene más que un conocimiento superficial de álgebra abstracta y (2) ilustra la mayoría de las matemáticas que inducen la migraña con el código Haskell. Podemos resumir brevemente la sección 6 del documento aquí:
Digamos que tenemos
class Profunctor p where
dimap :: (contra'' -> contra) -> (co -> co'') -> p contra co -> p contra'' co''
Su codificación estándar de dividendos, negativos y positivos de los profunctores en Haskell. Ahora este tipo de datos,
data (⊗) f g contra co = forall x. (f contra x) ⊗ (g x co)
tal como se implementó en hackage.haskell.org/package/profunctors-5.2/docs/… , actúa como composición para profunctor. Podemos, por ejemplo, demostrar una instancia legítima de Profunctor
:
instance (Profunctor f, Profunctor g) => Profunctor (f ⊗ g) where
dimap contra co (f ⊗ g) = (dimap contra id f) ⊗ (dimap id co g)
Agitaremos la prueba de que es legal por razones de tiempo y espacio.
DE ACUERDO. Ahora la parte divertida. Digamos que esta clase de tipos:
class Profunctor p => ProfunctorMonoid p where
e :: (a -> b) -> p a b
m :: (p ⊗ p) a b -> p a b
Esta es, con mucho más movimiento de la mano, una forma de codificar la noción de monoides profunctores en Haskell. Específicamente, se trata de un monoide en la categoría monoidal Pro
, que es una estructura monoidal para la categoría de funtor [C^op x C, Set]
con ⊗
como tensor y Hom
como su unidad. Por lo tanto, hay una gran cantidad de dicción matemática ultraespecífica para descomprimir aquí, pero para eso solo debes leer el documento.
Entonces vemos que ProfunctorMonoid
es isomorfo a Arrow
... casi.
instance ProfunctorMonoid p => Category p where
id = dimap id id
(.) pbc pab = m (pab ⊗ pbc)
instance ProfunctorMonoid p => Arrow p where
arr = e
first = undefined
instance Arrow p => Profunctor p where
lmap = (^>>)
rmap = (>>^)
instance Arrow p => ProfunctorMonoid p where
e = arr
m (pax ⊗ pxb) = pax >> pxb
Por supuesto, estamos ignorando las leyes de clase de tipos aquí, pero, como muestra el documento, funcionan de manera fantástica.
Ahora dije casi porque crucialmente no pudimos implementar first
. Lo que realmente hemos hecho es demostrar un isomorfismo entre ProfunctorMonoid
y flechas previas . El artículo llama Arrow
sin una flecha previa . Luego pasa a mostrar que
class Profunctor p => StrongProfunctor p where
first :: p x y -> p (x, z) (y, z)
class StrongProfunctor p => StrongProfunctorMonoid p where
e :: (a -> b) -> p a b
m :: (p ⊗ p) a b -> p a b
Es necesario y suficiente para el isomorfismo deseado a la Arrow
. La palabra "fuerte" proviene de una noción específica en la teoría de categorías y se describe en el artículo con una mejor redacción y un detalle más rico del que jamás podría reunir.
Así que para resumir:
Un monoide en la categoría de profunctores es una flecha previa y viceversa. (Una versión anterior del artículo usaba el término "flechas débiles" en lugar de flechas previas, y supongo que también está bien).
Un monoide en la categoría de profunctores fuertes es una flecha, y viceversa.
Como la mónada es un monoide en la categoría de endofunctores, podemos pensar en la analogía SAT
Functor : Profunctor :: Monad : Arrow
. Este es el verdadero impulso de las nociones de computación-como-monoides.Las categorías de monoides y monoides son criaturas marinas suaves que aparecen en todas partes, y es una pena que algunos estudiantes pasen por la educación en informática o ingeniería de software sin que se les enseñe monoides.
La teoría de categorías es divertida.
Haskell es divertido.
La respuesta de @haoformayor (y el artículo al que se hace referencia) es una gran idea de la teoría de categorías subyacentes: ¡las categorías monoidales son bastante hermosas! - pero pensé que algún código que muestre cómo convertir una Arrow
en una Strong Category
y viceversa, tal como aparecen en sus respectivas bibliotecas, podría ser un apéndice útil.
import Control.Arrow
import Control.Category
import Data.Profunctor
import Data.Profunctor.Strong
import Prelude hiding (id, (.))
De una sola mano...
newtype WrapP p a b = WrapP { unwrapP :: p a b }
instance Category p => Category (WrapP p) where
id = WrapP id
WrapP p . WrapP q = WrapP (p . q)
instance (Category p, Strong p) => Arrow (WrapP p) where
first = WrapP . first'' . unwrapP
second = WrapP . second'' . unwrapP
-- NB. the first usage of id comes from (->)''s Category instance (id :: a -> a)
-- but the second uses p''s instance (id :: p a a)
arr f = WrapP $ dimap f id id
... y los demás ...
newtype WrapA p a b = WrapA { unwrapA :: p a b }
instance Arrow p => Profunctor (WrapA p) where
dimap f g p = WrapA $ arr f >>> unwrapA p >>> arr g
instance Arrow p => Strong (WrapA p) where
first'' = WrapA . first . unwrapA
second'' = WrapA . second . unwrapA