2012-06-09 16 views
10

(+) y (++) son solo especializaciones de mappend; estoy en lo cierto? ¿Por qué son necesarios? Esta es una duplicación inútil ya que Haskell tiene estas potentes clases de tipos e inferencias de tipo. Digamos que eliminamos (+) y (++) y cambiamos el nombre a mappend(+) por comodidad visual y aumento de tipeo. codificación sería más intuitiva, más corto y más comprensible para los principiantes:Haskell: funciones duplicadas (+) y (++), mappend

--old and new 
1 + 2 
--result 
3 

--old 
"Hello" ++ " " ++ "World" 
--new 
"Hello" + " " + "World" 
--result 
"Hello World" 

--old 
Just [1, 2, 3] `mappend` Just [4..6] 
--new 
Just [1, 2, 3] + Just [4..6] 
--result 
Just [1, 2, 3, 4, 5, 6] 

(Se me hace soñar.). Tres, y tal vez más, funciones para la misma cosa no son buenas para un lenguaje bello que insiste en la abstracción y cosas como Haskell. También vi el mismo tipo de repeticiones con mónadas: fmap es el mismo, o casi, como map, (.), liftM, mapM, forM, ... Sé que hay razones para fmap Histórica, pero ¿qué pasa monoides? ¿El comité de Haskell está planeando algo sobre esto? Sería romper algunos códigos, pero escuché, aunque no estoy seguro, hay una versión entrante que tendrá grandes cambios, que es una gran ocasión. Es una lástima ... Al menos, ¿es asequible un tenedor?

EDITAR En las respuestas que he leído, no es el hecho de que para los números, ya sea (*) o (+) podrían caber en mappend. De hecho, creo que (*) debería ser parte de Monoid! Mire:

Actualmente, olvidando las funciones mempty y mconcat, solo tenemos mappend.

class Monoid m where 
    mappend :: m -> m -> m 

Pero podríamos hacer eso:

class Monoid m where 
    mappend :: m -> m -> m 
    mmultiply :: m -> m -> m 

Sería (tal vez, no tienen suficiente, aunque en ello todavía) se comportan de la siguiente manera:

3 * 3 
mempty + 3 + 3 + 3 
0 + 3 + 3 + 3 
9 

Just 3 * Just 4 
Just (3 * 4) 
Just (3 + 3 + 3 +3) 
Just 12 

[1, 2, 3] * [10, 20, 30] 
[1 * 10, 2 * 10, 3 * 10, ...] 
[10, 20, 30, 20, 40, 60, ...] 

realidad 'mmultiply' simplemente se definiría solo en términos de 'mappend', por lo que para casos de Monoid no hay necesidad de redefinirlo. Entonces Monoid está más cerca de las matemáticas; ¡quizás también podríamos agregar (-) y (/) a la clase! Si esto funciona, creo que resolvería el caso de Sum y Product, así como la duplicación de funciones: mappend se convierte en (+) y el nuevo mmultiply es solo (*). Básicamente sugiero refactorizar el código con un "pull up". Oh, necesitaríamos también un nuevo mempty para (*). Podríamos abstracta estos operadores en una clase MonoidOperator y definir Monoid de la siguiente manera:

class (Monoid m) => MonoidOperator mo m where 
    mempty :: m 
    mappend :: m -> m -> m 

instance MonoidOperator (+) m where 
    mempty = 0 
    mappend = --definition of (+) 

instance MonoidOperator (*) where 
    --... 

class Monoid m where 
    -... 

Bueno, yo no sé cómo hacer esto todavía, pero creo que hay una solución fresca para todo esto.

+0

Teóricamente, '+' y '*' son especializaciones de 'mappend' pero en la práctica no lo son: esta idea se implementa como [envoltorios delgados alrededor de' Num'] (http://www.haskell.org /ghc/docs/latest/html/libraries/base/Data-Monoid.html#g:3). (Y no se puede implementar de manera sensata de otra manera, para uno, tanto '+' como '*' son válidos como la operación monoide y no habría manera de especificar cuál usar). – huon

+2

Los números forman un 'Monoid 'instancia de dos maneras diferentes: suma y producto. Realmente hay funciones duplicadas, que deberían fusionarse juntas ('map',' fmap', 'liftM',' liftA'; '(<*>)' 'ap' y muchas otras), pero no creo que' mappend' (o '(<>)' en versiones más nuevas) es una de las funciones que necesitan una combinación. – Vitus

+0

¿Qué opinas de la edición de la primera publicación? – L01man

Respuesta

10

Está intentando mezclar conceptos algo separados aquí.

La concatenación aritmética y de listas son operaciones directas muy prácticas. Si se escribe:

[1, 2] ++ [3, 4] 

... usted sabe que obtendrá [1, 2, 3, 4] como el resultado.


Un Monoid es un concepto algebraico matemático que está en un nivel más abstracto. Esto significa que mappend no tiene que significar literalmente "añadir esto a eso"; puede tener muchos otros significados. Cuando se escribe:

[1, 2] `mappend` [3, 4] 

... Estos son algunos resultados válidos que esa operación podría producir:

[1, 2, 3, 4] -- concatenation, mempty is [] 

[4, 6]  -- vector addition with truncation, mempty is [0,0..] 

[3, 6, 4, 8] -- some inner product, mempty is [1] 

[3, 4, 6, 8] -- the cartesian product, mempty is [1] 

[3, 4, 1, 2] -- flipped concatenation, mempty is [] 

[]   -- treating lists like `Maybe a`, and letting lists that 
      -- begin with positive numbers be `Just`s and other lists 
      -- be `Nothing`s, mempty is [] 

¿Por qué mappend para las listas simplemente concatenar las listas? Porque esa es simplemente la definición de monoides que los tipos que escribieron el Informe Haskell eligieron como implementación predeterminada, probablemente porque tiene sentido para todos los tipos de elementos de una lista. Y, de hecho, puede usar una instancia alternativa de Monoid para las listas envolviéndolas en varios tipos nuevos; existe, por ejemplo, una instancia alternativa de Monoid para las listas que realiza el producto cartesiano en ellas.

El concepto de "Monoid" tiene un significado fijo y una larga historia en matemáticas, y cambiar su definición en Haskell significaría divergir del concepto matemático, lo que no debería ocurrir. Un Monoid no es simplemente una descripción de un elemento vacío y una operación (literal) de adición/concatenación; es la base de una amplia gama de conceptos que se adhieren a la interfaz que ofrece Monoid.


El concepto de que usted está buscando es específico a los números (porque no se podía definir algo como mmultiply o tal vez mproduce/mproduct para todas las instancias de Maybe a por ejemplo), un concepto que ya existe y se llama Semiring en matemáticas (Bueno, realmente no cubriste la asociatividad en tu pregunta, pero de todos modos estás saltando entre diferentes conceptos en tus ejemplos, a veces adhiriéndote a la asociatividad, a veces no, pero la idea general es la misma).

Ya hay implementaciones de Semirings en Haskell, por ejemplo, en el paquete algebra.

Sin embargo, un Monoid no es generalmente un Semiring, y también hay múltiples implementaciones de Semirings para números reales además de la suma y la multiplicación, en particular.Agregar ampliaciones generales generalizadas a clases de tipos muy bien definidas como Monoid no debería hacerse solo porque "sería limpio" o "salvaría algunas pulsaciones de teclas"; Hay una razón por la cual tenemos (++), (+) y mappend como conceptos separados, ya que representan ideas computacionales completamente diferentes.

+0

Gracias, entiendo mejor todo esto ahora. Creo que volveré después de haber aprendido un poco más de matemáticas:}. – L01man

+0

Es desafortunado que todas las operaciones en 'Num' estén en la misma clase de letra. ¡Por un lado, significa que las operaciones que deberían estar bien definidas para "Entero complejo" no se pueden usar! –

+0

@deflemstr Después de leer su respuesta, tengo la impresión de que deja la pregunta en el aire sobre lo que hace que la instancia '' Num'' sea tan diferente de '' List''. Quiero decir, dijiste que hay muchos '' mappend'' posibles para '' List'', lo mismo se aplica para '' Num'', sin embargo, uno fue elegido para '' List'' como predeterminado, mientras que no se eligió ningún valor predeterminado para ' 'Num''. Entonces, ¿qué tiene de malo tener '' + '' como '' mappend'' para '' Num''? Aún puede tener Multiplicación con un nuevo tipo. – Gustavo

3

Bueno, hay dos Monoids para los números - Product y Sum, ¿cómo lidiarías con eso?

Tres, y tal vez más, funciones para la misma cosa no son buenas para un lenguaje hermoso que insiste en la abstracción y cosas como Haskell.

Las abstracciones no se tratan de eliminar la duplicación de código. Las operaciones aritméticas y monótona son dos ideas diferentes, y a pesar de tener casos en los que comparten la misma semántica, no se gana nada al fusionarlos.

+0

Edité la pregunta para sugerir una forma de resolver esto. – L01man

+3

... y 'Producto' y' Suma' tampoco son los dos únicos monoides posibles sobre los números. Simplemente resultan ser los dos monoides más comúnmente utilizados. –

+0

Acabo de ver que Num no es una instancia de Monoid. Me resulta extraño tener monoides para "operaciones" y no para los números mismos. – L01man

8

En el cambio de nombre mappend a (+)/(*)

Mientras (+) y (*) son ambos monoides que tienen distributivity leyes adicionales, relacionadas las dos operaciones, así como cancellative leyes por ejemplo 0 * x = 0. Esencialmente, (+) y (*) forman un ring. Dos monoides de algún otro tipo pueden no satisfacer estas propiedades de anillo (o incluso de semianillo más débil). La denominación de los operadores (+) y (*) es sugestiva de sus propiedades adicionales (interrelacionadas). Por lo tanto, evitaría subvertir las intuiciones matemáticas tradicionales al renombrar mappend a + o * ya que estos nombres sugieren propiedades adicionales que pueden no ser válidas. A veces, una sobrecarga excesiva (es decir, demasiada generalización) conduce a una pérdida de la intuición y, por lo tanto, a una pérdida de usabilidad.

Si sucede que tiene dos monoides que forman una especie de anillo a continuación, que le gustaría obtener una instancia de Num de estos, como los nombres "+" y "*" sugieren las propiedades adicionales.

En confundir (++) y mappend

el cambio de nombre a mappend(++) podría ser más apropiado ya que hay menos equipaje mental adicional (++). De hecho, dado que las listas son monoid (es decir, un monoide sin propiedades adicionales), utilizar el operador tradicional de concatenación de listas (++) para indicar el funcionamiento binario de un monoide no parece una idea terrible.

En la definición de múltiples monoides para un tipo

Como usted señala, tanto (+) son (*) son monoides pero ambos no se puede hacer una instancia de Monoid para el mismo tipo t. Una solución, que se proporciona a medias, es tener un parámetro de tipo adicional a la clase Monoid para distinguir dos monoides. Tenga en cuenta que las clases de tipos solo se pueden parametrizar por tipos, no por expresión como se muestra en su pregunta. Una definición adecuada sería algo así como:

class Monoid m variant where 
mappend :: variant -> m -> m -> m 
mempty :: variant -> m 

data Plus = Plus 
data Times = Times 

instance Monoid Int Plus where 
    mappend Plus x y = x `intPlus` y 
    mempty = 0 

instance Monoid Int Times where 
    mappend Times x y = x `intTimes` y 
    mempty = 1 

(+) = mappend Plus 
(*) = mappend Times 

Para que las aplicaciones de mappend/mempty ser resueltos a una operación en particular, cada uno debe tener un valor de presenciar el tipo que indica el monoide especial "variante".

Aparte: el título de su pregunta menciona mconcat.Esta es una operación completamente diferente a mappend - mconcat es el homomorfismo monoid de la monoid libre a algún otro monoid es decir, reemplazar contras con mappend y nil con mempty.

+0

Gracias, está muy claro. Cambié el título. – L01man

Cuestiones relacionadas