Conjuntos, Funtores y Eq confusión


Una discusión surgió recientemente en el trabajo sobre los Conjuntos, que en Scala soportan el método zip y cómo esto puede conducir a errores, por ejemplo,

scala> val words = Set("one", "two", "three")
scala> words zip (words map (_.length))
res1: Set[(java.lang.String, Int)] = Set((one,3), (two,5))

Creo que está bastante claro que Set s no debería soportar una operación zip, ya que los elementos no están ordenados. Sin embargo, se sugirió que el problema es que Set no es realmente un funtor, y no debería tener un método map. Ciertamente, usted puede meterse en problemas mediante el mapeo de un conjunto. Cambiar a Haskell ahora,

data AlwaysEqual a = Wrap { unWrap :: a }

instance Eq (AlwaysEqual a) where
    _ == _ = True

instance Ord (AlwaysEqual a) where
    compare _ _ = EQ

Y ahora en ghci

ghci> import Data.Set as Set
ghci> let nums = Set.fromList [1, 2, 3]
ghci> Set.map unWrap $ Set.map Wrap $ nums
fromList [3]
ghci> Set.map (unWrap . Wrap) nums
fromList [1, 2, 3]

Así que Set no satisface la ley del funtor

    fmap f . fmap g = fmap (f . g)

Se puede argumentar que esto no es un fallo de la operación map en Sets, sino un fallo de la instancia Eq que definimos, porque no respeta la ley de sustitución, es decir, que para dos instancias de Eq en A y B y una asignación f : A -> B entonces

    if x == y (on A) then f x == f y (on B)

Que no se sostiene para AlwaysEqual (por ejemplo, considere f = unWrap).

Es la ley de sustitución a ¿ley sensata para el tipo Eq que debemos tratar de respetar? Ciertamente, otras leyes de igualdad son respetadas por nuestro tipo AlwaysEqual (la simetría, la transitividad y la reflexividad están trivialmente satisfechas) por lo que la sustitución es el único lugar en el que podemos meternos en problemas.

Para mí, la substición parece una propiedad muy deseable para la clase Eq. Por otro lado, algunos comentarios sobre una discusión reciente de Reddit incluyen

" La sustitución parece más fuerte de lo necesario, y es básicamente cociente el tipo, poniendo requisitos en cada función usando el tipo."

-- godofpumpkins

"También realmente no quiero sustitución/congruencia ya que hay muchos usos legítimos para valores que queremos equiparar pero que de alguna manera son distinguibles."

-- sclv

"La sustitución solo es válida para la igualdad estructural, pero nada insiste en que Eq es estructural."

-- edwardkmett

Estos tres son todos bastante conocidos en la comunidad Haskell, así que dudaría en ir en contra de ellos e insistir en la sustitución de mis Eq tipos!

Otro argumento en contra de que Set sea un Functor - es ampliamente aceptado que ser un Functor le permite transformar los "elementos" de una "colección" mientras preserva la forma. Por ejemplo, esta cita en el wiki de Haskell (tenga en cuenta que Traversable es una generalización de Functor)

"Donde Foldable le da la capacidad de ir a través de la estructura procesando los elementos pero desechando la forma, Traversable le permite hacer eso mientras preserva la forma y, por ejemplo, poniendo nuevos valores."

"Traversable se trata de preservar la estructura exactamente como está."

Y en el Mundo Real Haskell

"...[Un] funtor debe preservar la forma. La estructura de una colección no debe verse afectada por un funtor; solo los valores que contiene debería cambiar."

Claramente, cualquier instancia de funtor para Set tiene la posibilidad de cambiar la forma, reduciendo el número de elementos en el conjunto.

Pero parece como si Set s realmente deberían ser funtores (ignorando el requisito Ord por el momento - lo veo como una restricción artificial impuesta por nuestro deseo de trabajar eficientemente con conjuntos, no un requisito absoluto para cualquier conjunto. Por ejemplo, los conjuntos de funciones son una cosa perfectamente sensata a considerar. En cualquier case, Oleg ha mostrado cómo escribir instancias eficientes de Funtor y Mónada para Set que no requieren una restricción Ord). Hay demasiados usos agradables para ellos (lo mismo es cierto para la instancia no existente Monad).

¿Puede alguien aclarar este desastre? ¿Debería Set ser un Functor? Si es así, ¿qué hace uno sobre el potencial para romper las leyes Funtor? ¿Cuáles deberían ser las leyes para Eq, y cómo interactúan con las leyes para Functor y la instancia Set en particular?

Author: Chris Taylor, 2013-10-04

3 answers

Otro argumento en contra de que Set sea un Functor - es ampliamente aceptado que ser un Functor le permite transformar los "elementos" de una "colección" mientras preserva la forma. [...] Claramente, cualquier instancia de funtor para Set tiene la posibilidad de cambiar la forma, reduciendo el número de elementos en el set.

Me temo que este es un caso de tomar la analogía de la "forma" como una condición definitoria cuando no lo es. Matemáticamente hablando, hay tal cosa como el poder establecer functor. De Wikipedia :

Conjuntos de potencia: El funtor de conjunto de potencia P: Set → Set asigna cada conjunto a su conjunto de potencia y cada función f: X → Y al mapa que envía U X X a su imagen f (U) {Y.

La función P(f) (fmap f en el funtor de conjunto de potencia) no conserva el tamaño de su conjunto de argumentos, sin embargo, este es un funtor.

Si quieres una analogía intuitiva mal considerada, podríamos decir esto: en un estructura como una lista, cada elemento " se preocupa "por su relación con los otros elementos, y se" ofendería " si un funtor falso rompiera esa relación. Pero un conjunto es el caso límite: una estructura cuyos elementos son indiferentes entre sí, por lo que hay muy poco que se puede hacer para "ofenderlos"; lo único es si un funtor falso mapeara un conjunto que contiene ese elemento a un resultado que no incluye su "voz"."

(Ok, me callaré ahora...)


EDIT: I trunca los siguientes bits cuando me citó en la parte superior de mi respuesta:

Por ejemplo, esta cita en el wiki de Haskell (tenga en cuenta que Traversable es una generalización de Functor)

"Donde Foldable le da la capacidad de ir a través de la estructura procesando los elementos pero desechando la forma, Traversable le permite hacer eso mientras preserva la forma y, por ejemplo, poniendo nuevos valores."

"Traversable es sobre la preservación de la estructura exactamente como está."

Aquí hay que señalar que Traversable es una especie de especializada Functor, no es una "generalización". Uno de los hechos clave sobre cualquier Traversable (o, en realidad, sobre Foldable, que Traversable se extiende) es que requiere que los elementos de cualquier estructura tengan un orden lineal: puede convertir cualquier Traversable en una lista de sus elementos (con Foldable.toList).

Otro hecho menos obvio sobre Traversable es que existen las siguientes funciones (adaptado de Gibbons & Oliveira, "La Esencia del Patrón Iterador"):

-- | A "shape" is a Traversable structure with "no content," 
-- i.e., () at all locations.
type Shape t = t ()

-- | "Contents" without a shape are lists of elements.
type Contents a = [a]

shape :: Traversable t => t a -> Shape t
shape = fmap (const ())

contents :: Traversable t => t a -> Contents a
contents = Foldable.toList

-- | This function reconstructs any Traversable from its Shape and
-- Contents.  Law:
--
-- > reassemble (shape xs) (contents xs) == Just xs
--
-- See Gibbons & Oliveira for implementation.  Or do it as an exercise.
-- Hint: use the State monad...
--
reassemble :: Traversable t => Shape t -> Contents a -> Maybe (t a)

Una instancia Traversable para conjuntos violaría la ley propuesta, porque todos los conjuntos no vacíos tendrían el mismo Shape-el conjunto cuyo Contents es [()]. A partir de esto, debería ser fácil demostrar que cada vez que intente reassemble un conjunto, solo recuperaría el conjunto vacío o un singleton.

Lección? Traversable "conserva la forma" en un sentido muy específico y más fuerte que Functor.

 25
Author: Luis Casillas,
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
2013-10-07 05:36:13

Set es "solo" un funtor (no un Functor) de la subcategoría de Hask donde Eq es "agradable" (es decir, la subcategoría donde congruencia, sustitución, sostiene). Si los tipos de restricción estaban alrededor de desde entonces tal vez set sería un Functor de algún tipo.

 8
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
2013-10-05 04:01:55

Bien, Set puede ser tratado como un funtor covariante, y como un funtor contravariante; usualmente es un funtor covariante. Y para que se comporte con respecto a la igualdad uno tiene que asegurarse de que sea cual sea la implementación, lo hace.

Con respecto al Conjunto.zip - es una tontería. Así como Set.cabeza (lo tienes en Scala). No debería existir.

 1
Author: Vlad Patryshev,
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
2013-10-11 22:29:09