tuplas - tipos de variables haskell
Entendiendo las firmas de tipo Haskell (3)
Bueno, como ya se dijo, $ se puede entender fácilmente si simplemente se olvida de leer y verlo como, por ejemplo, en C ++
template<typename A, typename B>
B dollar(std::function<B(A)> f, A x) {
return f(x);
}
¡Pero en realidad, hay más en esto que solo aplicar una función a un valor! La aparente similitud entre las firmas de $ y el map tiene, de hecho, un significado bastante profundo de la teoría de categorías: ¡ambos son ejemplos de la acción-morfismo de un functor!
En la categoría Hask con la que trabajamos todo el tiempo, los objetos son tipos. ( Eso es un poco confuso , pero no te preocupes). Los morfismos son funciones.
Los funtores (endo) más conocidos son aquellos que tienen una instancia de la clase de tipos del mismo nombre . Pero en realidad, matemáticamente, un funtor es solo algo que mapea objetos a objetos y morfismos a morfismos 1 . map (juego de palabras, ¡supongo!) es un ejemplo: toma un objeto (es decir, el tipo) A y lo asigna a un tipo [A] . Y, para cualquiera de los dos tipos A y B , toma un morfismo (es decir, función) A -> B , y lo asigna a la función de lista correspondiente de tipo [A] -> [B] .
Este es solo un caso especial de la operación de firma de la clase functor:
fmap :: Functor f => (a->b) -> (f a->f b)
Sin embargo, las matemáticas no requieren que este fmap tenga un nombre. Y así puede haber también el functor de identidad , que simplemente se asigna cualquier tipo a sí mismo. Y, todo morfismo a sí mismo:
($) :: (a->b) -> (a->b)
La "identidad" existe, obviamente, de manera más general, también puede asignar valores de cualquier tipo a ellos mismos.
id :: a -> a
id x = x
Y por supuesto, una posible implementación es entonces
($) = id
1 La mente, no todo lo que mapea objetos y morfismos es un funtor ... necesita satisfacer las leyes de los funtores .
Estoy en el proceso de enseñarme a mí mismo Haskell y me preguntaba sobre el siguiente tipo de firmas:
Prelude> :t ($)
($) :: (a -> b) -> a -> b
Prelude>
¿Cómo debo interpretar (sin juego de palabras) que?
Un resultado semi similar también está demostrando ser desconcertante:
Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude>
Voy a empezar con el map . La función de map aplica una operación a cada elemento de una lista. Si tuviera
add3 :: Int -> Int
add3 x = x + 3
Entonces podría aplicar esto a una lista completa de Int s usando el map :
> map add3 [1, 2, 3, 4]
[4, 5, 6, 7]
Así que si nos fijamos en el tipo de firma
map :: (a -> b) -> [a] -> [b]
Verá que el primer argumento es (a -> b) , que es solo una función que toma a y devuelve a b . El segundo argumento es [a] , que es una lista de valores de tipo a , y el tipo de retorno [b] , una lista de valores de tipo b . Por lo tanto, en inglés normal, la función de map aplica una función a cada elemento en una lista de valores, luego devuelve esos valores como una lista.
Esto es lo que hace que el map sea una función de orden superior , toma una función como argumento y hace cosas con ella. Otra forma de ver el map es agregar algunos paréntesis a la firma de tipo para hacerlo
map :: (a -> b) -> ([a] -> [b])
Así que también puedes pensar en ella como una función que transforma una función de a a b en una función de [a] a [b] .
La función ($) tiene el tipo
($) :: (a -> b) -> a -> b
Y se usa como
> add3 $ 1 + 1
5
Todo lo que hace es tomar lo que está a la derecha , en este caso 1 + 1 , y add3 a la función de la izquierda , aquí add3 . ¿Porque es esto importante? Tiene una fijación práctica, o precedencia del operador, que lo hace equivalente a
> add3 (1 + 1)
Entonces, lo que sea a la derecha se envuelve esencialmente entre paréntesis antes de ser pasado a la izquierda. Esto solo lo hace útil para encadenar varias funciones juntas:
> add3 $ add3 $ add3 $ add3 $ 1 + 1
es mejor que
> add3 (add3 (add3 (add3 (1 + 1))))
Porque no tienes que cerrar paréntesis.
($) es solo una aplicación de función. Obtiene una función de tipo a->b , un argumento de tipo a , aplica la función y devuelve un valor de tipo b .
map es un maravilloso ejemplo de cómo leer la firma de un tipo de función ayuda a entenderlo El primer argumento del map es una función que toma b devuelve b , y su segundo argumento es una lista de tipo [a] . Entonces, el map aplica una función de tipo a->b a una lista de valores. Y el tipo de resultado es de hecho [b] : ¡una lista de valores b !
(a->b)->[a]->[b] puede interpretarse como "Acepta una función y una lista y devuelve otra lista", y también como "Acepta una función de tipo a->b y devuelve otra función de tipo [a]->[b] ". Cuando lo ve de esta manera, el map "upgrade" f (el término "levantar" se usa a menudo en este contexto) para trabajar en listas: si double es una función que duplica un número entero, entonces map double es una función que duplica cada entero en una lista.