¿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.

Author: TheSeamau5, 2015-08-18

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 Groups 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

  1. Toma tu relación parcial [(a, x)] y mapéala con una función f :: a -> (b, c), y una pequeña manipulación de tupla, para obtener una nueva relación [(b, (c, x))].

  2. Usa mi Group b para discriminar [(b, (c, x))] en [[(c, x)]]

  3. Usa mi Group c para discriminar cada [(c, x)] en [[x]] dándome [[[x]]]

  4. Aplanar las capas internas para obtener [[x]] como necesitamos

    instance 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.

 9
Author: J. Abrahamson,
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.

 20
Author: Ben,
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 bs 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-streamingtengo 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.

 16
Author: danidiaz,
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