Enero 18, 2018, 08:12:27 am

Autor Tema: [Guia] Haskell  (Leído 7630 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
[Guia] Haskell
« en: Abril 27, 2012, 04:29:45 am »
Intro.
Revisando un seminario antiguo que realicé con un compañero de clases para el curso de Lenguajes de Programación de mi facultad, decidí prepararlo y ofrecerlo para todo aquel que quiera iniciarse en este lenguaje.

Sumario:
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login
 You are not allowed to view links. Register or Login

Saludos
« Última modificación: Abril 30, 2012, 10:56:03 am por kicasta »

“When all you have is a hammer, every problem looks like a nail.”

You are not allowed to view links. Register or Login: You are not allowed to view links. Register or Login - You are not allowed to view links. Register or Login

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #1 en: Abril 27, 2012, 04:32:37 am »
Historia

En la conferencia de FPCA ’87 se discutió la situación en la comunidad funcional: Existencia de más de una docena de lenguajes puramente funcionales con semántica semejante e igual poder expresivo.
El objetivo de este encuentro: creación de un lenguaje común, Haskell (en honor al lógico Haskell Brooks Curry, cuyo trabajo proporciono las bases lógicas para el trabajo venidero).

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #2 en: Abril 27, 2012, 04:38:39 am »
Pattern Matching

En Haskell los patrones están dados por:
1.   Constantes enteras, caracteres y booleanos: -2, ‘1’, True.
2.   Variables: x, varNumber17.
3.   Tuplas de patrones: por ejemplo (p1,…, pk), donde cada pi es un patrón en sí mismo.
4.   Patrones de lista: [] y (p1: p2) donde p1 y p2 son patrones.
5.   Wild Card: _.
6.   Patrones Constructores, que se explicarán más adelante.

Como se ha visto los patrones pueden estar anidados. Una tupla o un patrón de lista pueden ser construidos a partir de otros patrones.

Un patrón de macheo hace dos cosas:
1.   Chequea si el argumento tiene la forma correcta.
2.   Asocia los valores con variables dentro del patrón para que puedan ser usados dentro de la definición.

Por ejemplo [2, 3, 4] machea con (a: x) porque la lista tiene asociada una cabeza (head) y una cola (tail). Es más, 2 se asocia con a y [3, 4] se asocia con x. Si se usa este patrón en la definición siguiente:

Código: (haskell) You are not allowed to view links. Register or Login
sumaLista (a:x) = a + sumaLista x
Tendremos:
sumaLista [2,3,4] = 2 + sumaLista [3,4]

¿Cuándo un argumento a machea con un patrón p?
•   Si p es constante, ocurre cuando a es igual a p.
•   Si p es variable, digamos x, a machea con p y su valor se asocia con x en la definición.
•   Si p es una tupla de patrones, digamos (p1,…, pk), a machea con p si a es una tupla (a1,…, ak) y cada ai machea con cada pi.
•   Si p es un patrón de lista (p1: p2), a machea con p si es una lista no vacía y si su cabeza machea con p1 y su cola con p2.
•   Si p es un wild card _, a machea con p, pero no se hace asociación alguna.

Hasta ahora solo hemos aplicado los patrones a los argumentos de funciones, a veces queremos aplicar patrones sobre otros valores.

Código: (haskell) You are not allowed to view links. Register or Login
primerDigito :: String -> Char
primerDigito st =
case (digits st) of
[] -> ’\0’
(a: _) -> a

La función digits devuelve un String, equivalente a [Char], con los dígitos de st. Ejemplo:

digits “1nfi1tra2” = “112”

Las expresiones case tienen el efecto de distinguir entre varias alternativas, en este caso con una lista vacía o con una no-vacía, extrayendo partes de ella, asociando los valores a las variables del patrón. En el caso que machee e con (a:_) se asocia el valor de la cabeza de e con a, y la cola de e machea con _ pero no se asocia. Una expresión case en general tiene la forma:

case e of
   p1 -> e1
   p2 -> e2
   …
   pk -> ek

Donde e es una expresión que macheara con p1, p2, …, pk.  Si  pi es el primer patrón que machea con e, el resultado será ei donde las variables en pi estarán asociadas a las partes de e.

Ejemplo: Sumando listas de pares.
Veremos que hay varias formas de definir esta función usando patrones.

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares [(Int, Int)] -> Int
sumaPares [] = 0

Hay numerosas posibilidades para el caso de listas no vacías.

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares (a: x) = fst a + snd a + sumaPares x (1)
Una lista no vacía macheara con (a: x). Los componentes de la cabeza serán accedidos con las funciones incorporadas fst y snd. Ahora usaremos un simple patrón de lista, pero usando la cláusula where para definir la primera y a la segunda parte del par a.
 
Código: (haskell) You are not allowed to view links. Register or Login
sumaPares (a: x) = c + d + sumaPares x (2)
where
c = fst a
d = snd a

Alternativamente, usamos un patrón conformado para extraer las componentes de a en una cláusula where.

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares (a: x) = c + d + sumaPares x (3)
where
(c, d) = a

Un patrón conformado p = e solo tendrá éxito si e machea con p. Como una alternativa a las definiciones de la (1) a la (3), daremos otra, usando un patrón anidado.

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares ((c, d): x) = c + d + sumaPares x (4)
En este caso una lista no vacía macheará, y tendremos como su primer elemento al par (c, d). Las tres componentes son usadas en la parte derecha de la definición. 

La última definición usa una estrategia diferente. Se define de forma separada una función auxiliar que calcula la suma del par.

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares (a: x) = sumaPar a + sumaPares x (5)
sumaPar (c, d) = c + d

El patrón que machea la suma del par está definido en sumaPar mientras que el patrón que machea la suma de la lista está definido en sumaPares. Cada forma de definición tiene sus ventajas. Las primeras tres usan un simple patrón top-level, que extraen las componentes del par de diferentes maneras. La cuarta usa un patrón anidado, y la última separa el proceso de la lista del proceso de los elementos. Esta separación hace a las funciones potencialmente fáciles de modificar: podemos cambiar la función para sumar listas de triplos o pares de pares de números sin tener que modificar la función sumaPares.

La excepción.
Existe una excepción de la regla que se puede aplicar patrones de macheo con constructores. Son conocidos como los patones n+k. Es válido escribir algo como esto:

Código: (haskell) You are not allowed to view links. Register or Login
pred :: Int -> Int
pred (n+1) = n

Esto se considera una mala forma de programación, debido a lo cual muchos la evitan.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #3 en: Abril 27, 2012, 04:48:45 am »
Higher-Order functions HOF’s / Currying

Higher-Order Functions. Funciones de orden superior.

Una función es de orden superior si recibe una función como parámetro o retorna una función.

Veremos algunas de las funciones de orden superior (higher-order functions HOFs) map, filter, fold, foldr, en algunos ejemplos, en particular, como usarlas en combinación.

Ejemplo: Biblioteca.

Código: (haskell) You are not allowed to view links. Register or Login
libros :: BaseDatos -> Persona -> [Libros]
Se toma BaseDatos y una persona per, se obtiene como resultado los libros pedidos por per. El algoritmo consta de dos fases:
•   Encontrar todos los pares tal que la primera parte es per – filter.
•   Por cada par, tomar la segunda parte – map.

Entonces podemos decir:

Código: (haskell) You are not allowed to view links. Register or Login
libros bd per = map snd (filter EsPer bd)
where
EsPer (p, l) = (p == per)

Hay dos cosas sin valor acerca de esta definición.
1.   La función EsPer chequea si la primera mitad del par es per. Como está definida en la cláusula where puede usar per en su definición, y desde las variables de la parte izquierda de la ecuación.
2.   No hay patrón de macheo en esta definición. El uso de map y filter significa que la definición es de alto orden, y substancialmente fácil de leer. Por supuesto los patrones están por algún lado, precisamente en las definiciones de map y de filter.

Como segundo ejemplo considere la función:

Código: (haskell) You are not allowed to view links. Register or Login
retPrestamo :: BaseDatos -> Persona -> Libros -> BaseDatos
Para la devolución del préstamo del libro l de la persona p.

Código: (haskell) You are not allowed to view links. Register or Login
devPrestamo bd p l =
filter noPL bd
where
noPL pr = (pr /= (p, l))
Podemos hacer esto si queremos eliminar todos los pares del tipo (p, l).

Currying.

Es la técnica de transformar una función que toma múltiples parámetros de tal forma que pueda ser llamada como una cadena de funciones de un solo parámetro, cada una:

f (x, y) = x * y

Entonces:

g x = (\y -> f (x, y))

Es una versión curried de f. Una vez más este nombre se debe al lógico Haskell Curry.

Dada una función f :: x -> y -> z, entonces hacerle currying a f crea una función g :: x -> (y -> z). Esto es curry(f) = g toma un parámetro de tipo x y retorna una función del tipo y -> z. Uncurrying es el proceso inverso.

Intuitivamente, currying dice que si se pasan algunos parámetros, se obtiene una función de los restantes. Por ejemplo si tenemos la función div de forma curried para la operación de la división x/y; entonces llamando a div donde x tiene valor 1, nos daría otra función: la misma que la función inv que retorna la multipicacion inversa de su parámetro. inv(y) = 1/y.

Las funciones de múltiples parámetros en Haskell son simplemente azúcar-sintáctico; TODAS las funciones tienen exactamente un parámetro.

Hay dos funciones de orden superior (HOFs) que convierten entre curried y uncurried las funciones:

Código: (haskell) You are not allowed to view links. Register or Login
curry :: ((t, u) -> v) -> (t -> u -> v)
curry g a b = g (a, b)

uncurry :: (t -> u -> v) -> ((t, u) -> v)
uncurry f (a, b) = f a b

Estas dos funciones, cada una es la inversa de la otra. La aplicación parcial de funciones se hace en sus argumentos de izquierda a derecha, para que una función no pueda aplicarse directamente solo a su segundo parámetro. Este efecto solo puede ser logrado transformando la forma en que la función toma sus parámetros,

Código: (haskell) You are not allowed to view links. Register or Login
flip :: (t -> u -> v) -> (u -> t -> v)
flip f a b = f b a


Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #4 en: Abril 27, 2012, 04:53:14 am »
List Comprehension.

Nos provee de una forma alternativa de escribir listas y funciones que las manipulan. Son útiles para construir listas a partir de otras.

Varios ejemplos:
Si la lista ex es [2, 4, 7] y hacemos:

[2 * a | a <- ex]                           (1)

El resultado es [4, 8, 14]. Esta lista contiene cada elemento de la lista ex multiplicado por 2. La expresión (1) se lee: Todos los 2 * a tal que a pertenece a ex, donde el símbolo <- tiene significado parecido al símbolo matemático  . En la comprensión de listas a <- ex es llamado generador, porque genera los datos desde donde se construye el resultado. Podemos combinar un generador en una o más pruebas (tests), que sean expresiones boleanas.

[2 * a | a <- ex, EsPar a, a > 3]                  (2)

Se lee (2): Todos los 2 * a tal que a pertenece a ex, a es par y mayor que 3. El resultado de esta expresión es [8] puesto que 4 es el único elemento par de la lista ex que es mayor que 3. Así como pusimos una variable en la parte izquierda de <- podemos poner cualquier patrón:

Código: (haskell) You are not allowed to view links. Register or Login
sumaPares :: [(Int, Int)] -> [Int]
sumaPares lista_pares = [ a+b | (a, b) <- lista_pares]

Aquí tomamos todos los pares en la lista y sumamos sus componentes creando una nueva lista con elementos simples.

sumaPares [(2, 3), (2, 1), (7, 8)] = [5, 3, 15]

Podemos añadir tests en esta situación también.

Código: (haskell) You are not allowed to view links. Register or Login
nueva_sumaPares :: [(Int, Int)] -> [Int]
nueva_sumaPares lista_pares = [a + b | (a, b) <- lista_pares, a < b]

En este ejemplo:

nueva_sumaPares [(2, 3), (2, 1), (7, 8)] = [5, 15]

Este es el resultado porque el segundo par (2, 1) falló al test.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #5 en: Abril 27, 2012, 04:57:45 am »
Guards

Los guardias pueden ser imaginados como una extensión a las facilidades de los patrones. Con ellos se permite definir funciones por partes que serán tomadas de acuerdo a expresiones booleanas arbitrarias. Los guardias aparecen después de todos los parámetros de una función pero antes del signo igual, y comienzan con una barra vertical. Podemos usar guardias para definir una función sencilla que devuelva un String diciéndonos el resultado de comparar dos elementos.

Código: (haskell) You are not allowed to view links. Register or Login
compara :: Int -> Int -> String
compara x y
| x < y = "El primero es menor"
| x > y = "El segundo es menor"
| otherwise = "Son iguales"

Podemos leer la barra vertical como “tal que”. Entonces decimos que compara x y “tal que” x es menor que y es “El primero es menor”; el valor “tal que” x es mayor que y es “El segundo es menor”; el valor en otro caso es “Son iguales”. La palabra clave otherwise es simplemente definida para ser igual a True y machea cualquier cosa que llegue hasta él. Entonces podemos ver que esto funciona:

Prelude> compara 5 10
"El primero es menor"
Prelude> compara 10 5
"El segundo es menor"
Prelude> compara 7 7
"Son iguales"

Una cosa a notar en los guardias es que ellos son testeados después del patrón de macheo, y no junto a él. Esto significa que una vez que el patrón machee, si ninguno de los guardias tiene éxito, no se intentarán patrones de macheo más allá de este punto. En cambio, si hubiéramos tenido esta definición:

Código: (haskell) You are not allowed to view links. Register or Login
compara2 :: Int -> Int -> String
compara2 x y
| x < y = "El primero es menor"
| x > y = "El segundo es menor"
compara2 _ _ = "Son iguales"

La intención sería que si ambos guardias fallan, la comparación “vaya” al último macheo y diga “Son iguales”. Sin embargo, esto NO pasa.

Prelude> compara2 7 7
*** Exception: Non-exhaustive patterns in function compara2

Si nos ponemos a pensar que está pasando en el compilador, esto tiene sentido. Cuando aplicamos compara2 7 7, estos son macheados por x e y, lo cual sucede exitosamente y los valores están limitados. Entonces el macheo con patrones se detiene completamente. Los guardias se activan, x e y son comparados. Ningún guardia tiene éxito, entonces se produce un error. Una bondad de los guardias es que la cláusula where es común para todos los guardias. Una definición de la función  EsBrillante usando guardias:

Código: (haskell) You are not allowed to view links. Register or Login
EsBrillante c
| r == 255 = True
| g == 255 = True
| b == 255 = True
| otherwise = False
where (r, g, b) = ColorRGB c

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #6 en: Abril 27, 2012, 04:59:31 am »
Definable Operators

Existen dos formas de nombrar funciones: mediante un identificador (sum , prod) o mediante un símbolo de operador (+, *).

Identificador: c = CHAR (CHAR | DIGIT | ‘ | _)*

Si c es minúscula esto significa que el identificador representa una función o variable, si por el contrario es mayúscula, representa una función constructora.

Ejemplos: sum, f, f’’, elem_2, mi’func

Las palabras claves NO se pueden usar como identificadores.

Un símbolo de operador (: ! # $ % & * + . / < = > ? @ \ ^ | -): ~ (opcional) seguido de uno o más de estos símbolos.

Los operadores que comienzan con ( : ) son utilizados para funciones constructoras.

Mecanismos para utilizar un identificador como símbolo de operador y viceversa:
(operator): se usa como identificador
`identifier`: se usa como operador
(+) 5 9 == 5 `suma` 9 == suma 5 9 == 5 + 9

Al trabajar con operadores es necesario tener en cuenta:
1.   Precedencia: valor asignado entre 0 y 9:            2 + 3 * 4 = 14
2.   Asociatividad: para resolver la ambigüedad     1 - 2 - 3 se define una regla de asociatividad: A la derecha (1 - (2 - 3)), izquierda ((1 - 2) - 3), no asociativo (1 - 2 - 3: error) (infixr, infixl, infix).

Todos los símbolos de operadores se toman por defecto: precedencia 9 y no asociativos.
Para modificar estos valores:

infixl    DIGIT opers
infixr    DIGIT opers
infix    DIGIT opers

Estas declaraciones solo pueden aparecer en ficheros de definición de funciones cargados por el sistema y no puede haber más de una declaración por operador.

Las funciones tienen mayor valor de precedencia  que cualquier símbolo de operador.

f x + g y = (f x) + (g y)
f x + 1 = (f x) + 1; no como f (x + 1)

Código: (haskell) You are not allowed to view links. Register or Login
infixl 9 !!
infixr 9 .
infixr 8 ^
infixl 7 *
infix 7 /, `div`, `rem`, `mod`
infixl 6 +, -
infix 5 \\
infixr 5 ++, :
infix 4 ==, /=, <, <=, >=, >
infix 4 `elem`, `notElem`
infixr 3 &&
infixr 2 ||

« Última modificación: Abril 27, 2012, 03:45:42 pm por kicasta »

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #7 en: Abril 27, 2012, 03:53:33 pm »
Single Assignment

En haskell no podemos modificar el valor de una variable dentro de un mismo scope. En otras palabras la variable ha sido inicializada con un valor al mismo tiempo que se creó. Esto evita algunos tipos de efectos colaterales con lo que se pretende reducir los errores en el software y simplificar la depuración.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #8 en: Abril 27, 2012, 03:59:12 pm »
Recursive Functions

En la parte izquiera de la definición de las funciones no puede haber repeticiones de variables u ocurrencia alguna de término funcional.

Ejemplo:

Código: (haskell) You are not allowed to view links. Register or Login
fact :: Int -> Int
fact 0 = 1
fact n 
| n > 0  = n * fact (n-1)

Código: (haskell) You are not allowed to view links. Register or Login
data Arbol a = Hoja a | Rama (Arbol a) (Arbol a)

hojas :: Arbol t -> [t]
hojas (Hoja a) = [a]
hojas (Rama izq der) = hojas izq ++ hojas der

Las funciones recursivas tiene un número de casos base no recursivo y un número de casos recursivos. En el caso de factorial y el de hojas tienen uno de cada tipo.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #9 en: Abril 27, 2012, 04:12:48 pm »
Algebraic Data Types

Una definición de tipo de dato comienza con la palabra clave data, seguida del nombre del tipo, el signo igual (=) y los constructores del tipo que se define. El nombre del tipo y el de los constructores comienza con letra mayúscula.

Ejemplo: Tipos enumerados.
La clase más simple de los tipos algebraicos se define enumerando los elementos del tipo. Por ejemplo:

Código: (haskell) You are not allowed to view links. Register or Login
data Temp = Frio | Calor
data Estaciones = Invierno | Primavera | Verano | Otoño

El tipo Temp tiene dos miembros, Frio y Calor, y Estaciones tiene 4 miembros. Frio y Calor son constructores del tipo Temp. Para definir funciones sobre estos tipos usamos patrones: podemos machear contra una constante o una variable. Para definir el tiempo (en Cuba).

Código: (haskell) You are not allowed to view links. Register or Login
tiempo :: Estaciones -> Temp
tiempo Invierno = Frio
tiempo _ = Calor

El macheo con patrones es secuencial; el primer patrón que machea un parámetro es el que se usará. Esto significa que el tiempo es frío solo en invierno, y caluroso el resto del año. El tipo integrado Bool puede definirse:
Código: (haskell) You are not allowed to view links. Register or Login
data Bool = True | False
Como hemos visto los patrones son usados para definir funciones sobre tipos algebraicos. Podemos hacer lo siguiente para poner a Temp dentro de la clase Eq.
Código: (haskell) You are not allowed to view links. Register or Login
Frio == Frio = True
Calor == Calor = True
_ == _ = False



Types synonyms.
Los tipos sinónimos existen simplemente por conveniencia. Su eliminación no hace a Haskell menos poderoso. Considere el caso cuando se está constantemente tratando con listas de puntos tridimensionales. Por ejemplo, pudiera tener una función con tipo [(Double), (Double), (Double)] -> Double -> [(Double), (Double), (Double)]. Desde que se es buen ingeniero del software se quiere poner las signaturas del tipo en todas las funciones top-level. Sin embargo, teclear [(Double), (Double), (Double)] todo el tiempo es muy tedioso. Para evitar esto, podemos definir un tipo sinónimo:

Código: (haskell) You are not allowed to view links. Register or Login
type List3D = [(Double), (Double), (Double)]
Ahora, la signatura de tipo para nuestras funciones podrán ser escritas List3D -> Double -> List3D. Debemos notar que un tipo sinónimo no puede ser auto-referenciado. Esto es, no podemos tener:
Código: (haskell) You are not allowed to view links. Register or Login
type TipoMalo = Int -> TipoMalo
Esto es porque esto es un “tipo infinito”. Los tipos sinónimos también pueden ser parametrizados. Por ejemplo, podemos querer ser capaces de cambiar los tipos de los puntos en la lista de puntos 3D. Para esto, podemos definir:

Código: (haskell) You are not allowed to view links. Register or Login
type List3D = [a, a, a]
Entonces nuestras referencias a [(Double), (Double), (Double)] se convertirán en List3D Double.

Ejemplo: Tipos productos.
En vez de usar una tupla, podemos definir tipos con un número de componentes, que a menudo son llamados tipos productos.

Código: (haskell) You are not allowed to view links. Register or Login
data Gente = Persona Nombre Edad (1)
Donde Nombre es sinónimo de String y Edad de Int:

Código: (haskell) You are not allowed to view links. Register or Login
type Nombre = String
type Edad = Int

La definición de Gente debe leerse como: “Para construir un elemento de tipo Gente, se necesita suplir un objeto, digamos n, de tipo Nombre, y otro e, de tipo Edad. El elemento formado por ellos será una Persona n e”.
Ejemplo de valores de este tipo:

Persona “Arthur Pendragon” 21
Persona “Harvey Milk” 48

Las funciones se definen usando patrones. Un elemento del tipo Gente tendrá la forma Persona n e, y podremos usar este patrón en la parte izquierda de la definición.

Código: (haskell) You are not allowed to view links. Register or Login
muestraPersona :: Gente -> String
muestraPersona (Persona n e) = n ++ “--” ++ show e

El llamado a show dentro de la función muestra en forma textual un Int, pues Int es instancia de Show.

muestraPersona (Persona “Harvey Milk” 48)
   = “Harvey Milk -- 48”;

En este ejemplo, el tipo tiene un solo constructor, que es binario porque toma dos elementos para formar un valor del tipo Gente. Para los tipos enumerados los constructores se llaman nularios, porque no toman argumentos. Los constructores introducidos por las definiciones de tipos algebraicos pueden usarse además como funciones, así que Persona n e es el resultado de aplicar la función Persona a los parámetros n y e.

Código: (haskell) You are not allowed to view links. Register or Login
Persona :: Nombre -> Edad -> Gente
Una definición alternativa del tipo Gente se da a continuación.

Código: (haskell) You are not allowed to view links. Register or Login
type Gente = (Nombre, Edad)
Ventajas de usar un tipo algebraico:
•   Cada objeto del tipo lleva una etiqueta explícita del propósito del elemento. En este caso ella representa una persona.
•   No es posible, ni por accidente, tratar un par del tipo (String, Int) como una persona; una persona debe ser creada usando el constructor Persona.
•   El tipo aparecerá en cualquier mensaje de error debido a pérdida de tipo; un tipo sinónimo podría extenderse fuera y desaparecer de cualquier tipo de mensajes de error.

También existen ventajas al usar el tipo tupla, con una declaración sinónima:
•   Los elementos son más compactos, por lo que las definiciones serán más cortas.
•   Usando tuplas, especialmente un par, permite el re-uso de muchas funciones polimórficas como: fst, snd, zip.

En cada sistema que modelemos tendremos que elegir entre estas alternativas: nuestra decisión dependerá exactamente en cómo usaremos los productos, y en la complejidad del sistema.

Si hiciéramos:

Código: (haskell) You are not allowed to view links. Register or Login
data Edad = Años Int
cuyos elementos son Años 45, y así sucesivamente. Queda claro desde una definición que 45 está siendo usado como una edad en años. La desventaja es que no podemos usar funciones definidas sobre Int, directamente sobre Edad. Podemos usar el mismo nombre, por ejemplo Persona, para ambos, el tipo y el constructor del tipo, como en:

Código: (haskell) You are not allowed to view links. Register or Login
data Persona = Persona Nombre Edad
Escogemos no hacerlo, usar el mismo nombre para dos objetos relacionados pero diferentes puede fácilmente guiarnos hacia una confusión.

Ejemplo: Alternativas.
Una figura en un simple programa geométrico es un círculo o un rectángulo. Estas alternativas están dadas por:

Código: (haskell) You are not allowed to view links. Register or Login
data Figura = Circulo Float | Rectangulo Float Float
Lo cual nos dice que hay dos formas de construir un elemento de Figura. Una forma es proveer de radio a Circulo; la otra es darle los lados a Rectangulo.

Circulo 3.0
Rectangulo 45.9 87.6

Macheo con patrones nos permite definir funciones por casos:

Código: (haskell) You are not allowed to view links. Register or Login
esRedondo :: Figura -> Bool
esRedondo (Circulo _) = True
esRedondo (Rectangulo _ _) = False

y nos permite usar las componentes de los elementos:

Código: (haskell) You are not allowed to view links. Register or Login
area :: Figura -> Float
area (Circulo r) = pi*r*r
area (Rectangulo h b) = h*b

La forma general.
data Nombre_Tipo =
     Cons1 t11 … t1k1
   | Cons2 t21 … t2k2
     …
   | Consn tn1 … tnkn

Cada Consi es un constructor, seguido por ki tipos, donde ki es un entero no negativo que puede ser cero.

•   Los tipos pueden ser recursivos, podemos usar el tipo en definición, Nombre_Tipo, como (parte de) uno de los tij. Con esto tendremos listas, árboles y muchas otras estructuras de datos.
•   Seguido de Nombre_Tipo puede ir una o más variables de tipo que usaremos en la parte derecha, convirtiendo la definición en polimórfica.

Los tipos polimórficos recursivos combinan estas dos ideas, y esta potente mezcla nos provee de tipos que podremos re-usar en disímiles situaciones. El tipo integrado de lista es un ejemplo de lo que hemos visto.

Derivando instancias de clases.
Haskell tiene un número de clases integradas, incluyendo:
•   Eq, una clase que nos da igualdad y desigualdad.
•   Ord, creada sobre Eq, nos da una ordenación sobre elementos de un tipo.
•   Enum, permite el tipo para ser enumerado, nos da expresiones al estilo [n..m] sobre el tipo.
•   Show, permite a los elementos del tipo ser convertidos a forma textual.
•   Read, permite elementos para ser leídos desde cadenas de caracteres.

Cuando introducimos un nuevo tipo algebraico, como Estaciones o Figura, podríamos esperar que tuvieran igualdad, enumeración, etc. Esto puede ser proporcionado por el sistema si preguntamos por ellos. Así:

Código: (haskell) You are not allowed to view links. Register or Login
data Estaciones = Invierno | Primavera | Verano | Otoño
      deriving (Eq, Ord, Enum, Show)

data Figura = Circulo Float | Rectangulo Float Float
  deriving (Eq, Ord, Show)
Podemos comparar así estaciones por igualdad y por orden, escribir expresiones de la forma:

Código: (haskell) You are not allowed to view links. Register or Login
[Primavera..Otoño]
Denotar la lista:

Código: (haskell) You are not allowed to view links. Register or Login
[Primavera, Verano, Otoño]
Y mostrar valores del tipo (Show). Lo mismo se aplica a Figura, excepto que no podemos enumerar figuras; de Enum solo pueden derivar tipos enumerados como por ejemplo Estaciones.

No estamos forzados a usar las definiciones de derivación; podemos dar nuestras propias definiciones. La definición de muestraPersona anterior podría también formar parte de hacer de Gente una instancia de Show.

Newtypes.
Consideremos el problema en que necesitamos un tipo muy parecido a Int, pero que su ordenamiento está definido de forma diferente. Quizás, deseamos ordenar enteros por números pares y después por impares (esto es, un número par es mayor que cualquier impar, y los subconjuntos pares/impares se ordenan de la forma estándar). Desgraciadamente, no podemos definir una nueva instancia de Ord para Int, porque entonces Haskell no sabría cual las dos usar. Lo que se quiere es definir un tipo isomórfico a Int.

Nota: Isomórfico es un término común en matemáticas que significa básicamente “estructura idéntica”. Por ejemplo, en teoría de grafos, si se tiene dos grafos que son idénticos excepto por las etiquetas de sus nodos, entonces son isomorfos. En nuestro contexto, dos tipos son isomorfos si tienen la misma estructura subyacente.

Una forma de hacer esto será definir un nuevo tipo de dato:

Código: (haskell) You are not allowed to view links. Register or Login
data Mi_Int = Mi_Int Int
Podemos entonces escribir código apropiado para este tipo de dato. El problema (y esto es muy sutil) es que este tipo no es verdaderamente isomórfico a Int: el tiene un valor más. Cuando pensamos en el tipo Int, generalmente pensamos que el toma todos los valores enteros, pero realmente tiene un valor más: _|_ (se pronuncia: “bottom”), que es usado para representar cómputos indefinidos o erróneos. Así, Mi_Int no tiene solo los valores Mi_Int 0, Mi_Int 1, sino también Mi_Int _|_ Sin embargo, desde que los tipos de dato pueden ser indefinidos, el tiene un valor adicional: _|_ que difiere de  Mi_Int _|_ y esto hace los tipos no-isomórfico. Si se desconoce esta sutileza, podrían existir problemas de eficacia con esta representación: ahora, en lugar de almacenar simplemente un entero, debemos almacenar un puntero a un entero y seguir a ese puntero cada vez que necesitemos el valor de Mi_Int.
Para evitar esos problemas, Haskell tiene la construcción newtype. Un newtype es un cruce entre un tipo de dato y un tipo sinónimo: tiene constructor como un tipo de dato, pero solo puede tener uno y el mismo puede tener solo un parámetro. Por ejemplo, podemos definir:

Código: (haskell) You are not allowed to view links. Register or Login
newtype Mi_Int = Mi_Int Int
Pero no podemos definir alguno de:
Código: (haskell) You are not allowed to view links. Register or Login
newtype Malo1 = Malo1a Int | Malo1b Double
newtype Malo2 = Malo2 Int Double

Por supuesto, el hecho de que no podamos definir Malo2 como anteriormente no es gran problema: podemos simplemente definir lo siguiente apareando los tipos.

Código: (haskell) You are not allowed to view links. Register or Login
newtype Bueno2 = Bueno2 (Int, Double)
Ahora, suponga que hemos definido Mi_Int como newtype. Esto habilita el uso de escribir nuestra deseada instancia de Ord como:

Código: (haskell) You are not allowed to view links. Register or Login
instance Ord Mi_Int where
Mi_Int i < Mi_Int j
| par i && par j = i < j
| impar i && impar j = i < j
| impar i = True
| otherwise = False
where par x = (x ‘mod‘ 2) == 0
impar = not . par

Como los tipos de dato, podemos seguir derivando clases como Show y Eq sobre newtypes. Además, a los newtypes, se les permite derivar de cualquier clase de la cual el tipo base (en este caso Int) sea instancia. Por ejemplo, podemos derivar Num en Mi_Int para proveer funciones aritméticas sobre él.
El patrón de macheo sobre newtypes es exactamente como en los tipos de dato. Podemos escribir funciones constructor y destructor para Mi_Int como:

Código: (haskell) You are not allowed to view links. Register or Login
mkMi_Int i = Mi_Int i
unMi_Int (Mi_Int i) = i

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #10 en: Abril 30, 2012, 10:13:20 am »
Lazy Evaluation

Un evaluador perezoso solo evalúa un parámetro de una función si el valor de este parámetro es necesitado para el cómputo del conjunto de resultados. Además, si un parámetro es estructurado, digamos una tupla, solo se examinarán las partes de esta que se necesiten. Una de las consecuencias de la evaluación perezosa es que es posible por el lenguaje describir estructuras infinitas. Esto requiere de una infinita cantidad de tiempo para evaluarse completamente, pero bajo la evaluación perezosa, solo las partes que se necesiten serán examinadas. La evaluación en Haskell se centra en la aplicación de funciones. La idea básica que está por detrás es simple. Para evaluar una función f aplicada a los parámetros a1, a2,…, ak, simplemente se sustituyen las expresiones ai por las variables correspondientes en la definición de la función.

Código: (haskell) You are not allowed to view links. Register or Login
fun1 :: Int -> Int -> Int
fun1 a b = a+b

Entonces:

fun1 (9-3) (fun1 34 3)
   = (9-3) + (fun1 34 3)

Las expresiones (9-3) y (fun1 34 3) no son evaluadas antes de pasárselas a la función. En este caso para que la evaluación continúe necesitamos evaluar los parámetros de la suma (+), obteniendo:

= 6 + (34 + 3)
= 6 + 37
= 43

En este ejemplo ambos parámetros son evaluados, pero esto no siempre es el caso. Si definimos:

Código: (haskell) You are not allowed to view links. Register or Login
fun2 :: Int -> Int -> Int
fun2 a b = a+12

Entonces:

fun2 (9-3) (fun2 34 3)
   = (9-3) + 12
   = 6 + 12
   = 18
         
Aquí (9-3) se sustituye por a, pero como b no aparece en la parte derecha de la ecuación el argumento (fun2 34 3) no aparece en el resultado, y no es evaluado. Aquí vemos la primera ventaja de la evaluación perezosa, un parámetro que no se necesite nunca será evaluado. Un ejemplo más realista:

Código: (haskell) You are not allowed to view links. Register or Login
switch :: Int -> t -> t -> t
switch n x y
| n > 0 = x
| otherwise = y

Si n es positiva, el resultado es el valor de x, en otro caso el valor de y. Ambos parámetros son usados, pero en el primer caso y no es evaluado y en el segundo x no es evaluado. Otro ejemplo:

Código: (haskell) You are not allowed to view links. Register or Login
fun3 :: Int -> Int -> Int
fun3 a b = a+a

Así que:

fun3 (9-3) (fun3 34 3)
   = (9-3) + (9-3)

Aquí parece que el parámetro (9-3) tiene que ser evaluado dos veces, por su duplicación en la sustitución. La evaluación perezosa asegura que un parámetro duplicado no es evaluado más de una vez. En la implementación, no hay evaluación duplicada puesto que los cálculos se realizan sobre grafos (en lugar de usar árboles), para representar las expresiones que deben ser evaluadas.

Lo que no se hace:

          +
        /   \
      /       \   
 (9-3)   (9-3)


Lo que se hace:

         +
       \     /
         \ /
        (9-3)

Un último ejemplo, dado por patones de macheo:

Código: (haskell) You are not allowed to view links. Register or Login
fun4 :: (Int, Int) -> Int
fun4 (a, b) = a + 1

Aplicado al par (3+2, 4-17):

fun4 (3+2, 4-17)
 = (3+2) + 1
 = 6

El parámetro es examinado y parte de él se evalúa. Su segunda parte permanece sin evaluar, porque no se necesita en el cálculo.

Resumiendo:
•   Los parámetros de funciones solo son evaluados cuando su valor es necesario para que la evaluación continué.
•   Un parámetro no es necesariamente evaluado en su totalidad: solo las partes necesitadas se examinan.
•   Un parámetro solo se evalúa una vez, en absoluto. Esto se hace en la implementación reemplazando expresiones por grafos y haciendo el cálculo sobre ellos.


Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #11 en: Abril 30, 2012, 10:22:26 am »
Monads

El concepto más difícil, mientras aprendemos Haskell, es la comprensión y uso de mónadas. Se pueden distinguir dos sub-componentes: (1) Cómo se usan los mónadas existentes y (2) Cómo escribir nuevos. Si quieres usar Haskell, debes aprender a usar los mónadas existentes. Si puede aprender a escribir sus propios mónadas, la programación en Haskell será más agradable.

Código: (haskell) You are not allowed to view links. Register or Login
class Computation c where
success :: a -> c a
failure :: String -> c a
augment :: c a -> (a -> c b) -> c b
combine :: c a -> c a -> c a

Veremos si esta definición nos permitirá que realicemos IO (input / output). Esencialmente, necesitamos una forma de representar la toma de un valor fuera de una acción y realizar alguna nueva operación sobre el.

Código: (haskell) You are not allowed to view links. Register or Login
main = do
s <- readFile "somefile"
putStrLn (show (f s))

Pero esto es exactamente lo que hace augment. Usando augment podemos escribir el código anterior.
Código: (haskell) You are not allowed to view links. Register or Login
main = -- note the la falta del "do"
(readFile "somefile") ‘augment‘ \s -> putStrLn (show (f s))

Esto ciertamente parece ser suficiente. Y, de hecho, resulta ser más que suficiente. La definición de un mónada es una versión ligeramente trimmed-down de la clase Computation. La clase Monad tiene cuatro métodos (el cuarto método puede ser definido en términos de tercero).

Código: (haskell) You are not allowed to view links. Register or Login
class Monad m where
return :: a -> m a
fail :: String -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b

En esta definición return es equivalente a success; fail es equivalente a failure; y >>= (se lee: “ligar”) es equivalente a augment. El método >> (se lee: “entonces”) es simplemente una versión de >>= que ignora a. Esto resultará ser útil: aunque puede ser definido en función de >>=.

a >> x = a >>= \_ -> x

Notación do.
Hemos indicado que hay una conexión entre mónadas y la notación do. Aquí, hacemos concreta esta relación. No hay nada mágico acerca de la notación do, es simplemente “azúcar sintáctico” para las operaciones monódicas.

Usando la clase Computation podemos definir nuestro programa anterior como:

Código: (haskell) You are not allowed to view links. Register or Login
main =
readFile "somefile" ‘augment‘ \s -> putStrLn (show (f s))

Pero sabemos que augment es llamado >>= en el mundo monódico. Así, realmente el programa se lee:
Código: (haskell) You are not allowed to view links. Register or Login
main =
readFile "somefile" >>= \s -> putStrLn (show (f s))

A estas alturas esto es completamente valido en Haskell: Si definimos una función

Código: (haskell) You are not allowed to view links. Register or Login
f :: Show a => String -> a
podemos compilar y ejecutar el programa. Esto sugiere que podamos traducir

x <- f
g x

en f >>= \x -> g x. Esto es exactamente lo que hace el compilador.

Existen cuatro reglas de traducción.

1.   do {e} → {e}
2.   do {e; es} → e >> do {es}
3.   do {let decl; es} → let decl in do {es}
4.   do {p <- e; es} → let ok p = do {es} ; ok _ = fail “…” in e >>= ok

Regla 1
La primera regla do {e} → {e} declara que cuando se realiza una acción simple, la existencia del do es irrelevante. Esto es esencialmente el caso base para una definición inductiva del do. El caso base tiene una sola acción (llamada e). Las restantes reglas manejan los casos que tienen más de una acción.

Regla 2
Esta regla declara do {e; es} → e >> do {es}. Esto nos dice qué hacer cuando tenemos una acción (e) seguida por una lista de acciones (es). Aquí, hacemos uso de la función >>, previamente definida. Esta regla declara que primero debemos realizar la acción e, desechar el resultado y entonces do es.
Por ejemplo, si e es putStrLn s para alguna cadena s, entonces la traducción de do {e; es} es realizar e (escribir la cadena) y luego do es. Esto es claramente lo que queremos.

Regla 3
Esta regla declara do {let decl; es} → let decl in do {es}. Nos dice cómo lidiar con lets dentro de una declaración do. Dejamos la declaración dentro del let, fuera, y hacemos cualquier cosa que venga después.

Regla 4
Esta regla declara do {p <- e; es} → let ok p = do {es} ; ok _ = fail “…” in e >>= ok. Lo que sucede aquí no es exactamente obvio. Sin embargo, una formulación alterna de esta regla, que es aproximadamente equivalente, es do {p <- e; es} → e >>= \p -> es. Aquí, es claro lo que sucede. Hacemos la acción e, y luego enviamos el resultado hacia es, pero primero le asignamos el nombre p al resultado.

La razón por la que la definición es compleja es que p no necesariamente es una variable, puede ser algún patrón complejo. Por ejemplo, lo siguiente es un código válido:
Código: (haskell) You are not allowed to view links. Register or Login
foo = do (‘a’:’b’:’c’:x:xs) <- getLine
putStrLn (x:xs)

Aquí, estamos asumiendo que el resultado de la acción getline comenzará con la cadena “abc” y tendrá al menos un caracter más. La pregunta es: ¿qué ocurrirá si el patrón de macheo falla? Sin embargo, desde que estamos dentro de un mónada, tenemos acceso a la función especial fail, y preferimos fallar usando esta función en vez de la función error “captura todo”. Así, la traducción, como definida, permite al compilador rellenar … con un mensaje de error apropiado acerca del fallo del patrón de macheo. Aparte de esto, las dos definiciones son equivalentes.

Definición.
Existen tres reglas que todos los mónadas deben obedecer, son llamadas las “Leyes de los Mónadas” (y depende de nosotros que nuestros mónadas las obedezcan):

1.   return a >>= f tres líneas f a   
2.   f  >>= return tres líneas f
3.   f >>= (\x -> g x >>= h) tres líneas (f >>= g) >>= h

Observemos, a cada uno individualmente:

Ley 1.
Esta regla declara return a >>= f tres líneas f a.Suponga que pendamos en los mónadas como cómpu...s. Esto significa que si creamos un compu... trivial que simplemente retorna el valor a indiferente a cualquier cosa (esta es la parte return a); y lo ligamos juntos con algún otro cómpu... f, entonces esto es equivalente a realizar simplemente el cómpu... de f directamente.
Por ejemplo, suponga que f es la función putStrLn y a es la cadena “Hola Mundo”. Esta regla declara que ligando un cómpu... cuyo resultado es “Hola Mundo” a putStrLn es lo mismo que simplemente imprimirlo en la pantalla. Esto parece tener sentido.
En la notación do, esta regla declara que los siguientes programas son equivalentes:

ley1a = do
x <- return a
f x
ley1b = do f a

Ley2
La segunda ley mónada declara que f  >>= return tres líneas f. Esto es, f es algún cómpu..., la ley declara que si realizamos el cómpu... de f y pasamos el resultado a la función return, entonces todo lo que hemos hecho es realizar el cómpu....
Que esta ley debe sostener, es obvio. Para ver esto, piense en f como getLine (lee una cadena desde el teclado). Esta ley declara que leyendo una cadena y después retornando el valor leído, es exactamente lo mismo que leer la cadena simplemente.
En la notación do, la regla declara que los siguientes programas son equivalentes:

ley2a = do
x <- f
return x
ley2b = do f

Ley3
Esta regla declara  f >>= (\x -> g x >>= h) tres líneas (f >>= g) >>= h. A primera vista, esta ley no es tan fácil de entender como las otras dos. Es esencialmente una ley de asociatividad para los mónadas.
Nota: Fuera del mundo de los mónadas, una función (.) es asociativa si (f . g) . h = f . (g . h). Por ejemplo, la + y la * son asociativas, los anaqueles no marcan diferencias. Por otra parte – y / no son asociativas, 5 – (3 - 1) ≠ (5 - 3) – 1.
Si desechamos el desorden de los lambdas, vemos que lo que esta ley declara: f >>= (g >>= f) tres líneas (f >>= g) >>= h. La intuición detrás de esta ley es eso, cuando enlazamos acciones conjuntas, no importa como las agrupemos.
Para un ejemplo concreto, tome a f como getLine. Tome a g como una acción que toma un valor ingresado, lo imprime en la pantalla, lee otra cadena vía getLine, y entonces retorna la cadena recientemente leída. Tome a h como putStrLn.
Consideremos que hace (\x -> g x >>= h). toma el valor llamado x, y le aplica g, pasando el resultado hacia h. En este ejemplo, esto significa que va a tomar un valor, lo imprime, lee otro valor y lo imprime. Así, completamente la parte izquierda de la ley primero lee una cadena y entonces hace lo que nosotros hemos descrito.
Por otra parte, considere (f >>= g). Esta acción lee una cadena del teclado, la imprime, lee otra, retornando el valor más reciente. Cuando ligamos esto con h como en la parte derecha de la ley, tenemos una acción que realiza la acción descrita por (f >>= g), que entonces imprime el resultado.
Claramente, estas dos acciones son la misma.
Mientras esta explicación es bastante complicada, y el texto de la ley es también bastante complicado, el significado actual es simple: si tenemos tres acciones, y las disponemos en el mismo orden, no importa donde pongamos los paréntesis. El resto es solo notación.
En la notación do, los siguientes programas son equivalentes:

Código: (haskell) You are not allowed to view links. Register or Login
ley3a = do
x <- f
do g <- x
h y
ley3b = do
y <- do x <- f
g x
h y

Common Monads.
Resulta que muchos de nuestros favoritos tipos de datos son realmente mónadas. Considere, por ejemplo, las listas. Hay una definición mónada parecida a:

Código: (haskell) You are not allowed to view links. Register or Login
instance Monad [] where
return x = [x]
l >>= f = concatMap f l
fail _ = []

Esto nos permite usar listas en la notacion do. Por ejemplo, dada la definición:

Código: (haskell) You are not allowed to view links. Register or Login
cruza l1 l2 = do
x <- l1
y <- l2
return (x, y)

Tenemos una función producto cruzado:

Prelude> cruza “ab” “def”
[(’a’,’d’),(’a’,’e’),(’a’,’f’),(’b’,’d’),(’b’,’e’),(’b’,’f’)]


No es coincidencia que esto tenga gran parecido con la forma de comprensión de listas:

Prelude> [(x,y) | x <- "ab", y <- "def"]
[(’a’,’d’),(’a’,’e’),(’a’,’f’),(’b’,’d’),(’b’,’e’),(’b’,’f’)]


Comprension de listas es simplemente una forma abreviada de una declaración monódica. De hecho, en versiones anteriores de Haskell, las formas de comprensión de listas podían ser usadas por cualquier mónada - no solo listas. En versiones recientes esto no es permitido.

El tipo Maybe también es mónada, con failure representado por Nothing y success por Just. Tenemos la siguiente declaración de instancia:

Código: (haskell) You are not allowed to view links. Register or Login
instance Monad Maybe where
return a = Just a
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing

Podemos usar el mismo producto cruzado que usamos para listas en Maybes. Esto es porque la notación do funciona para cualquier mónada, y no hay nada específico de listas en la función cruza.

Prelude> cruza (Just ’a’) (Just ’b’)
Just (’a’,’b’)
Prelude > cruza (Nothing :: Maybe Char) (Just ’b’)
Nothing
Prelude > cruza (Just ’a’) (Nothing :: Maybe Char)
Nothing
Prelude > cruza (Nothing :: Maybe Char) (Nothing :: Maybe Char)
Nothing


Monadic Combinator.
Las librería Monad/Control.Monad contiene algunos combinadores monadicos utiles, que aun no se han discutido completamente. Algunos de ellos son:

•   (=<<)    :: (a -> m b) -> m a -> m b
•   mapM       :: (a -> m x) -> [a] -> m

•   mapM_      :: (a -> m b) -> [a] -> m ()
•   filterM    :: (a -> m Bool) -> [a] -> m [a]
•   foldM    :: (a -> x -> m a) -> a ->
  • -> m a

•   sequence    :: [m a] -> m [a]
•   sequence_   :: [m a] -> m ()
•   liftM    :: (a -> b) -> m a -> m b
•   when       :: Bool -> m () -> m ()
•   join       :: m (m a) -> m a

En lo anterior se aume siempre que m es una instancia de Monad.
En general, las funciones que tienen underscore al final son rquivalentes a las que no tienen, excepto que no tiene valor de retorno.
La función =<< es exactamente igual a >>=, excepto que toma los parámetros en orden opuesto. Por ejemplo, en el mónada IO, podemos escribir de cualquiera de estas formas.

Prelude> writeFile "foo" "hello world!" >>
(readFile "foo" >>= putStrLn)
hello world!

Prelude > writeFile "foo" "hello world!" >>
(putStrLn =<< readFile "foo")
hello world!


Las funciones mapM, filterM y foldM son map, filter y fold envueltas dentro de los mónadas. Estas funciones son increíblemente utiles (particularmente foldM) cuando se trabaja con mónadas. Podemos usar mapM, por ejemplo, para imprimir una lista de cosas en la pantalla.

Prelude> mapM_ print [1,2,3,4,5]
1
2
3
4
5


Podemos usar foldM para sumar una lista e imprimir la suma intermedia en cada paso.

Prelude> foldM (\a b -> putStrLn
(show a ++ "+" ++ show b ++ "=" ++ show (a+b))
    >> return (a+b)) 0 [1..5]
0+1=1
1+2=3
3+3=6
6+4=10
10+5=15

Prelude > it
15

Las funciones sequence y sequence_ simplemente ejecutan una lista de acciones. Por ejemplo:

Prelude> sequence [print 1, print 2, print ’a’]
1
2
’a’
* Prelude > it
[(),(),()]

* Prelude > sequence_ [print 1, print 2, print ’a’]
1
2
’a’

* Prelude > it
()

Podemos ver que la versión con underscore no retorna valor, mientras que la versión sin underscore retorna la lista de valores retornados.

La funcion liftM “eleva”(lift) una función no-monadica a una función monadica. Esto es útil para acortar el código, entre otras cosas. Por ejemplo, podemos querer escribir una función que (prepends) cada línea en un archivo con su número de línea. Podemos hacerlo con:

Código: (haskell) You are not allowed to view links. Register or Login
numberFile :: FilePath -> IO ()
numberFile fp = do
text <- readFile fp
let l = lines text {-devuelve la cantidad de lineas del archivo-}
let n = zipWith (\n t -> show n ++ ’ ’ : t) [1..] l
mapM_ putStrLn n
« Última modificación: Abril 30, 2012, 10:24:39 am por kicasta »

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #12 en: Abril 30, 2012, 10:28:37 am »
Type Classes

Una clase de tipo es una construcción de un sistema de tipo que soporta polimorfismo ad-hoc. Esto se logra añadiendo restricciones a las variables de tipo en tipos polimórficos parametrizados. Una restricción típicamente involucra una clase de tipo T y una variable a, y significa que a solo puede ser instanciada por un tipo, cuyos miembros soporten las operaciones sobrecargadas asociadas con T. La primera aparición de las clases de tipo fue en Haskell, y se concibió originalmente como una forma de implementar aritmética sobrecargada y operadores de igualdad. En contraste con los “eqtypes” de ML Estándar, sobrecargar el operador de igualdad para su uso en las clases de tipo en Haskell no requirió una modificación extensiva del compilador o del sistema de tipo subyacente.

Desde su creación, se han descubierto muchas otras aplicaciones de las clases de tipo. El programador define una clase de tipo especificando un conjunto de funciones o nombres de constantes, junto con sus respectivos tipos, que tienen que existir para todo tipo que pertenezca a la clase. En Haskell, una clase Eq que pretenda contraer tipos que admitan igualdad debiera ser declarada de la manera siguiente:

Código: (haskell) You are not allowed to view links. Register or Login
Class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

Usando esta declaración se puede escribir la función miembro.

Código: (haskell) You are not allowed to view links. Register or Login
miembro :: (Eq a) => a -> [a] -> Bool
miembro y [] = False
miembro y (x:xs) = (x == y) || miembro y xs
La función miembro tiene el tipo a -> [a] -> Bool en el contexto (Eq a), que limita los tipos a en un rango sobre el cual pertenezcan a la clase Eq. Podemos hacer que cualquier tipo t sea miembro de una clase dada c usando la declaración instance que define la implementación de todos los métodos de c para el tipo particular t. Por ejemplo, si definimos un nuevo tipo de dato t, podemos hacer de t un tipo de Eq suministrándole la función de igualdad sobre los valores del tipo. Una vez hecho esto podemos usar la función miembro en una lista de elementos de tipo t.
data MiTipo = CtorMiTipo Int

Código: (haskell) You are not allowed to view links. Register or Login
instance Eq MiTipo where
CtorMiTipo i == CtorMiTipo j = i == j
CtorMiTipo i /= CtorMiTipo j = i /= j

Nota: las clases de tipo son diferentes de las clases en los LPOO. En particular Eq no es un tipo: no existe tal cosa como un valor de tipo Eq.

Desconectado kicasta

  • Yo vivo en CPH
  • ***
  • Mensajes: 578
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #13 en: Abril 30, 2012, 10:55:30 am »
Ejercicios de Ejemplo

A continuación una pequeña colección de ejercicios que creo resultan esclarecedores y útiles desde el punto de vista docente/educativo.

You are not allowed to view links. Register or Login

Código: (haskell) You are not allowed to view links. Register or Login
criba :: Int -> [Int]
criba n = cribar [2..n]
where
cribar [] = []
cribar (x:xs) = [x] ++ cribar [a | a <- xs, mod a x /= 0]

You are not allowed to view links. Register or Login
Código: (haskell) You are not allowed to view links. Register or Login
fib = fibr 0 1
where
fibr a b = a : fibr b (a+b)

You are not allowed to view links. Register or Login
Código: (haskell) You are not allowed to view links. Register or Login
pascal = pascalr [[1]]
         where
pascalr [(xs)] = [xs] ++ pascalr [(zipWith (+) (0:xs) (xs++[0]))]

You are not allowed to view links. Register or Login

Código: (haskell) You are not allowed to view links. Register or Login
hanoi :: Int -> String
hanoi k = hanoir k "A" "B" "C"
where
hanoir 1 x y z = "Mueve de " ++ x ++ " a " ++ z ++ "  "
hanoir n x y z = hanoir (n-1) x z y
           ++ "Mueve de " ++ x ++ " a " ++  z  ++ "  "
               ++ hanoir (n-1) y x z


Saludos

Desconectado TheobZ

  • CPQUE??
  • *
  • Mensajes: 1
  • Sexo: Masculino
  • Yo AMO a pOrtal HAcker!
    • Ver Perfil
Re:[Guia] Haskell
« Respuesta #14 en: Junio 24, 2013, 04:04:21 pm »
Muchas Gracias por el aporte, me sirve muchisimo  :koolCPH:


question
Sintaxis de Haskell

Iniciado por Norst

2 Respuestas
1384 Vistas
Último mensaje Marzo 09, 2012, 09:02:44 am
por Norst
xx
[Haskell] Potencial

Iniciado por dresuer

0 Respuestas
694 Vistas
Último mensaje Agosto 01, 2015, 03:32:02 am
por dresuer
exclamation
Algunas cositas en Haskell

Iniciado por kicasta

1 Respuestas
846 Vistas
Último mensaje Octubre 11, 2011, 07:35:22 pm
por JaAViEr
question
Opinion sobre haskell

Iniciado por _ANTRAX_

2 Respuestas
1508 Vistas
Último mensaje Marzo 17, 2012, 08:32:06 am
por kicasta
xx
Problema con el compilador de Haskell

Iniciado por karlospv94

0 Respuestas
687 Vistas
Último mensaje Octubre 27, 2013, 05:51:23 am
por karlospv94
xx
Lenguaje de programacion funcional HASKELL

Iniciado por diosstewie

0 Respuestas
1430 Vistas
Último mensaje Junio 19, 2007, 04:52:56 am
por diosstewie
exclamation
[Código-Haskell]Calculadora - JaAViEr

Iniciado por JaAViEr

1 Respuestas
843 Vistas
Último mensaje Octubre 08, 2011, 03:32:05 pm
por kicasta
exclamation
[Código-Haskell]Calculadora V2 - JaAViEr

Iniciado por JaAViEr

5 Respuestas
2074 Vistas
Último mensaje Marzo 01, 2013, 08:03:46 pm
por _ANTRAX_
question
[Duda]Errores Haskell - Ayuda ???

Iniciado por JaAViEr

0 Respuestas
608 Vistas
Último mensaje Febrero 13, 2011, 02:08:12 am
por JaAViEr
exclamation
[Código-Haskell]Combinaciones - JaAViEr

Iniciado por JaAViEr

0 Respuestas
787 Vistas
Último mensaje Octubre 08, 2011, 03:03:20 pm
por JaAViEr