variable tuplas tipos simbolos programa imprimir funciones ejemplos basicas anonima haskell types type-signature

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.