¿Hay aplicaciones útiles para la clase de Tipo Divisible?
Últimamente he estado trabajando en una API en Elm donde uno de los tipos principales es contravariant. Por lo tanto, he buscado en Google para ver qué se puede hacer con los tipos contravariantes y he encontrado que el paquete Contravariante en Haskell define la clase de tipo Divisible.
Se define como sigue:
class Contravariant f => Divisible f where
divide :: (a -> (b, c)) -> f b -> f c -> f a
conquer :: f a
Resulta que mi tipo particular se adapta a la definición de la clase de tipo Divisible. Si bien Elm no admite clases de tipo, miro Haskell de vez en cuando para algo de inspiración.
Mi pregunta: ¿Hay algún uso práctico para esta clase de tipo? ¿Existen API conocidas en Haskell (u otros idiomas) que se beneficien de este patrón de divide y vencerás? ¿Hay algún truco que deba tener en cuenta?
Muchas Gracias por su ayuda.
3 answers
Examinaré el ejemplo de los tipos de datos principales en las técnicas de ordenación radix generalizada de Fritz Henglein implementadas por Edward Kmett en el paquete discrimination.
Si bien hay mucho que hacer allí, en gran medida se centra en un tipo como este{[58]]}
data Group a = Group (forall b . [(a, b)] -> [[b]])
Si tienes un valor de tipo Group a
esencialmente debes tener una relación de equivalencia en a
porque si te doy una asociación entre a
s y algún tipo b
completamente desconocido para ti entonces puedes darme "agrupaciones" de b
.
groupId :: Group a -> [a] -> [[a]]
groupId (Group grouper) = grouper . map (\a -> (a, a))
Puede ver esto como un tipo de núcleo para escribir una biblioteca de utilidades de agrupaciones. Por ejemplo, podríamos querer saber que si podemos Group a
y Group b
luego podemos Group (a, b)
(más sobre esto en un segundo). La idea central de Henglein es que si puedes comenzar con algunas Group
s básicas en enteros - podemos escribir implementaciones muy rápidas Group Int32
a través de radix sort-y luego usar combinadores para extenderlas sobre todos los tipos, entonces tendrás una tipos de datos algebraicos.
Entonces, ¿cómo podríamos construir nuestra biblioteca combinadora?
Bueno, f :: Group a -> Group b -> Group (a, b)
es bastante importante ya que nos permite hacer grupos de tipos similares a productos. Normalmente, obtendríamos esto de Applicative
y liftA2
pero Group
, te darás cuenta, es Contravaiant
, no un Functor
.
Así que en su lugar usamos Divisible
divided :: Group a -> Group b -> Group (a, b)
Observe que esto surge de una manera extraña de{[58]]}
divide :: (a -> (b, c)) -> Group b -> Group c -> Group a
Ya que tiene el típico carácter de "flecha invertida" de las cosas contravariantes. Ahora podemos entender cosas como divide
y conquer
en términos de su interpretación en Group
.
Divide dice que si quiero construir una estrategia para igualar a
s usando estrategias para igualar b
s y c
s, puedo hacer lo siguiente para cualquier tipo x
Toma tu relación parcial
[(a, x)]
y mapéala con una funciónf :: a -> (b, c)
, y una pequeña manipulación de tupla, para obtener una nueva relación[(b, (c, x))]
.Usa mi
Group b
para discriminar[(b, (c, x))]
en[[(c, x)]]
Usa mi
Group c
para discriminar cada[(c, x)]
en[[x]]
dándome[[[x]]]
-
Aplanar las capas internas para obtener
[[x]]
como necesitamosinstance Divisible Group where conquer = Group $ return . fmap snd divide k (Group l) (Group r) = Group $ \xs -> -- a bit more cleverly done here... l [ (b, (c, d)) | (a,d) <- xs, let (b, c) = k a] >>= r
También tenemos interpretaciones de lo más complicado Decidable
refinamiento de Divisible
class Divisible f => Decidable f where
lose :: (a -> Void) -> f a
choose :: (a -> Either b c) -> f b -> f c -> f a
instance Decidable Group where
lose :: (a -> Void) -> Group a
choose :: (a -> Either b c) -> Group b -> Group c -> Group a
Estos se leen como diciendo que para cualquier tipo a
del cual podemos garantizar que no hay valores (no podemos producir valores de Void
por ningún medio, una función a -> Void
es un medio de produciendo Void
dado a
, por lo tanto no debemos ser capaces de producir valores de a
por ningún medio tampoco!) entonces inmediatamente obtenemos una agrupación de valores cero
lose _ = Group (\_ -> [])
También podemos ir a un juego similar a divide
anterior, excepto que en lugar de secuenciar nuestro uso de los discriminadores de entrada, alternamos.
Usando estas técnicas construimos una biblioteca de cosas "Group
capaces", a saber Grouping
class Grouping a where
grouping :: Group a
Y tenga en cuenta que casi todas las definiciones surgen de la definición básica atop groupingNat
que utiliza manipulaciones vectoriales monádicas rápidas para lograr una clasificación radix eficiente.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-19 14:57:52
Un ejemplo:
Aplicativo es útil para el análisis, porque puede convertir los analizadores aplicativos de partes en un analizador de enteros, necesitando solo una función pura para combinar las partes en un todo.
Divisible es útil para serializar (¿deberíamos llamar a esto coparsing ahora?), porque puede convertir los serializadores divisibles de partes en un serializador de conjuntos, necesitando solo una función pura para dividir el conjunto en partes.
En realidad no he visto un proyecto que funcione de esta manera, pero estoy (lentamente) trabajando en una implementación de Avro para Haskell que lo hace.
Cuando me encontré por primera vez con Divisible lo quería para divide
, y no tenía idea de qué uso posible conquer
podría ser otro que hacer trampa (un f a
de la nada, para cualquier a
?). Pero para hacer las leyes Divisibles echa un vistazo a mis serializers conquer
se convirtió en un "serializador" que codifica cualquier cosa a cero bytes, lo que tiene mucho sentido.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-17 22:23:54
Aquí hay un posible caso de uso.
En las bibliotecas de streaming, uno puede tener construcciones tipo fold como las del paquete foldl, que se alimentan de una secuencia de entradas y devuelven un valor de resumen cuando la secuencia se agota.
Estos pliegues son contravariantes en sus entradas, y se pueden hacer Divisible
. Esto significa que si tienes un flujo de elementos donde cada elemento puede descomponerse de alguna manera en b
y c
partes, y también sucede que tiene un pliegue que consume b
s y otro pliegue que consume c
s, entonces puede construir un pliegue que consume el flujo original.
Los pliegues reales de foldl
no implementan Divisible
, pero podrían, usando un contenedor newtype. En mi paquete process-streaming
tengo un tipo de plegado que implementa Divisible
.
divide
requiere que los valores devueltos de los pliegues constituyentes sean del mismo tipo, y ese tipo debe ser una instancia de Monoid
. Si los pliegues devuelven monoides diferentes, no relacionados, un la solución es poner cada valor devuelto en un campo separado de una tupla, dejando el otro campo como mempty
. Esto funciona porque una tupla de monoides es en sí misma un Monoid
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-17 22:16:04