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.