partes - ¿Cuál es el objetivo del mapa en Haskell, cuando hay fmap?
mapa de atributo (3)
En todas partes que he intentado usar map
, fmap
ha funcionado. ¿Por qué los creadores de Haskell sintieron la necesidad de una función de map
? ¿No podría ser simplemente lo que actualmente se conoce como fmap
y fmap
podría eliminarse del lenguaje?
Citando de la documentación de Functor
en https://wiki.haskell.org/Typeclassopedia#Functor
Puede preguntar por qué necesitamos una función de
map
separado. ¿Por qué no acaba con la función demap
actual de solo lista y cambia el nombre defmap
almap
? Bueno, esa es una buena pregunta. El argumento habitual es que alguien que acaba de enterarse de Haskell, al usar elmap
incorrectamente, preferiría ver un error sobre listas que sobreFunctor
.
Me gustaría hacer una respuesta para llamar la atención sobre el comentario de augustss :
De hecho, no es así como sucede. Lo que sucedió fue que el tipo de mapa se generalizó para cubrir a Functor en Haskell 1.3. Es decir, en Haskell 1.3 fmap se llamaba mapa. Este cambio se revirtió en Haskell 1.4 y se introdujo fmap. La razón de este cambio fue pedagógica; al enseñar Haskell a los principiantes, el tipo muy general de mapa hacía que los mensajes de error fueran más difíciles de entender. En mi opinión, esta no era la forma correcta de resolver el problema.
Haskeller 98 es visto como un paso atrás por algunos Haskellers (incluyéndome a mí), las versiones anteriores habían definido una biblioteca más abstracta y consistente. Oh bien.
Se ven iguales en el sitio de la aplicación, pero son diferentes, por supuesto. Cuando aplique cualquiera de esas dos funciones, map
o fmap
, a una lista de valores, producirán el mismo resultado, pero eso no significa que sean para el mismo propósito.
Ejecute una sesión GHCI (el Compilador de Haskell de Glasgow interactivo) para consultar información sobre esas dos funciones, luego eche un vistazo a sus implementaciones y descubrirá muchas diferencias.
mapa
Consultar GHCI para obtener información sobre el map
Prelude> :info map
map :: (a -> b) -> [a] -> [b] -- Defined in ‘GHC.Base’
y verá que se define como una función de orden superior aplicable a una lista de valores de cualquier tipo que arroje una lista de valores de cualquier tipo b
. Aunque polimórfica (la a
y la b
en la definición anterior representan cualquier tipo), la función del map
está destinada a ser aplicada a una lista de valores que es solo un tipo de datos posible entre muchos otros en Haskell. La función de map
no se pudo aplicar a algo que no es una lista de valores.
Como puede leer en el código fuente de GHC.Base , la función de map
se implementa de la siguiente manera
map _ [] = []
map f (x:xs) = f x : map f xs
que hace uso de la coincidencia de patrones para sacar la cabeza (la x
) de la cola (la xs
) de la lista, luego construye una nueva lista usando el constructor de valores :
(cons) para anteponer a fx
( fx
como "f aplicado x ) a la recursión del map
sobre la cola hasta que la lista esté vacía. Vale la pena observar que la implementación de la función de map
no se basa en ninguna otra función, sino solo en sí misma.
fmap
Ahora intenta buscar información sobre fmap
y verás algo bastante diferente.
Prelude> :info fmap
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
...
-- Defined in ‘GHC.Base’
Esta vez, fmap
se define como una de las funciones cuyas implementaciones deben proporcionar los tipos de datos que desean pertenecer a la clase de tipo Functor
. Eso significa que puede haber más de un tipo de datos, no solo el tipo de datos "lista de valores" , que pueden proporcionar una implementación para la función fmap
. Eso hace que fmap
sea aplicable a un conjunto mucho mayor de tipos de datos: ¡los funtores de verdad!
Como puede leer en el código fuente de GHC.Base , una posible implementación de la función fmap
es la proporcionada por el tipo de datos Maybe
:
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just a) = Just (f a)
y otra implementación posible es la proporcionada por el tipo de datos de 2 tuplas
instance Functor ((,) a) where
fmap f (x,y) = (x, f y)
y otra posible implementación es la proporcionada por el tipo de datos de lista (¡por supuesto!):
instance Functor [] where
fmap f xs = map f xs
que se basa en la función de map
(tenga en cuenta la notación sin puntos allí ... pero eso está fuera del alcance de su pregunta original).
Conclusión
La función de map
se puede aplicar a nada más que una lista de valores (donde los valores son de cualquier tipo) mientras que la función fmap
se puede aplicar a muchos más tipos de datos: todos los que pertenecen a la clase de functor (por ejemplo, maybes, tuplas, listas, etc.). Dado que el tipo de datos "lista de valores" también es un funtor (porque proporciona una implementación para él), a continuación, puede aplicarse fmap
y produce el mismo resultado que el map
.
map (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)