August 6, 2009 por Carlos Gomez
Comentarios (2)
get, set, Haskell, label datatype, records en Haskell, setter, getter
Nota.- Para su mejor comprencion he hecho varios cambios aqui, uno de ellos es el nombre del contructor de datos, de Persona a Per.
Ahora, Como definimos los setters y getters de una estructura de datos Persona?
Responderemos a esta pregunta, en el resto de este blog, asi nuestra estructura de datos es Persona con los datos de ci, nombre, apellido y edad.
data Persona = Per CI Name LastName Edad
deriving Show
type CI = Int
type Name = String
type LastName = String
type Edad = Int
Esto define una funcion constructora de datos de tipo:
Per ::CI -> Name -> LastName -> Edad -> Persona
Veamos algunos ejemplos:
p1 :: Persona
p1 = Per 1212 "Juan" "Block" 35p2 :: Persona
p2 = Persona 2323 "Pedro" "Ramos" 25
Ahora definamos algunas funciones set y get para Persona
setCI :: Persona -> CI -> Persona
setCI (Persona ci nm ap ed) ci' = Per ci' nm ap ed
setNombre :: Persona -> Name -> Persona
setNombre (Persona ci nm ap ed) nm' = Per ci nm' ap edsetApellido :: Persona -> LastName -> Persona
setApellido (Persona ci nm ap ed) ap' = Per ci nm ap' edsetEdad :: Persona -> Edad -> Persona
setEdad (Persona ci nm ap ed) ed' = Per ci nm ap ed'
getCI :: Persona -> CI
getCI (Persona ci _ _ _) = cigetNombre :: Persona -> Name
getNombre (Persona _ nm _ _) = nmgetApellido :: Persona -> LastName
getApellido (Persona _ _ ap _) = apgetEdad :: Persona -> Edad
getEdad (Persona _ _ _ ed) = ed
Si hacemos algunas pruebas obtenemos:
gt;*Main> getCI p1
nos retornaria el CI de 'Juan', tambien podemos obtener nombre, apellido o edad.
Para setear un valor, podemos hacer
*Main> p1 {nombre = "juanes"}
Persona {ci = 1212, nombre = "juanes", apellido = "Block", edad = 35}
Y listo, mision cumplida, aunque un poco tedioso.
Bueno, esto no termina aqui, resulta que Haskell provee una forma diferente de definir los datatypes, esto es nombrando los elementos de cada constructor de datos. Llamado tambien records de Haskell.
Veamos como nos ayuda esto, en nuestra definicion de setters y getters, y como cambia a lo anterior ya hecho.
data Persona = Per { ci :: CI
, nombre :: Name
, apellido :: LastName
, edad :: Edad
} deriving Show
type CI = Int
type Name = String
type LastName = String
type Edad = Int
Bueno, eso es todo lo que tenemos que hacer, hagamos algunas pruebas para ver algo de teoria.
p3 = Per 2525 "Teo" "Flores" 43 -- creamos un instancia
* Main> ci p3
2525
* Main> p3{nombre = "Maria"}
Persona {ci = 2525, nombre = "Maria", apellido = "Flores", edad = 43}
Como veras, hicimos las operaciones de set y get.
Algo de teoria, el nombramiento de campos en los datatypes no afecta al anterior version, por ejemplo el tipo contructor de datos es el mismo. Pero hay funcionalidades que se aumentan. Entre ellos tenemos la seleccion de campos usando labels, construccion usando labels y actualizacion de campos usando labels.
La seleccion de campos usando labels, es practicamente la operacion seter de un objeto. Y lo que Haskell hace es definir una funcion con el nombre del label, el cual esta disponible para su uso. Ejemplo "ci p3".
Tambien podemos contruir instancias usando labels (p4 = Per {edad=12, ci=3434, apellido="Perez", nombre="Pedro"} ), y no solo de la forma comun (p4 = Per 3434 12 "Pedro" "Perez"). Intensionalmente he cambiado las posiciones de los labels, pues esto no afecta. Se debe cuidar de que ningun label se repita mas de una ves, pero si no especificamos algun label, este deberia inicializar como indefinido o infito (GHC lo muestra como un warning).
Para la actualizacion de campos usando labels, se tiene una sintaxis: instancia_obj {label=value, ...}, asi como en el ejemplo p3{ci=3454}. Otra ves, no se debe mencionar mas de una ves una label, y los labels deben pertenecer al tipo contructor.
Al actualizar un valor de la instancia, se genera una nueva instancia modificando asi en la nueva instancia los valores enviados. Es una caracteristica de Persistencia de Haskell. Veamos un ejemplo:
*Main> p1 {edad = 0}
Persona {ci = 1212, nombre = "Juan", apellido = "Block", edad = 0}
*Main> p1
Persona {ci = 1212, nombre = "Juan", apellido = "Block", edad = 35}
Tambien podemos mesclar los distintos contructores de datos con esta sintxis o sin ella. Y respecto a los nombres de los labels, podemos repetirlas entre las funciones contructoras de datos, siempre y cuando se tenga un tipo consistente, pero no podemos repetirnas entre los datatypes, porque generaria una incosistencia de tipos. Veamos un ejemplo sacado del reporte de Haskell 98:
data S = S1 {x :: Int}
| S2 {x :: Int} -- ejemplo buenodata T = T1 {y :: Int}
| T2 {y :: Bool} -- ejemplo malo, tipo inconsistente
En conclusion, esta forma de creacion de datatypes, añade funcionalidad y sintaxis extra para manipular los elementos del datatype.
Algo interesante seria averiguar como definir mucha mas similitud de funcionalidad a OOP. Me refiero a definir solo funciones set o get para algunos elementos, o declarar elementos como publicos, protegidos o privados.
Hay otras extenciones de GHC que nos facilitan la manipulacion de records en Haskell. Pero los que hemos visto aqui pertenece al Haskell 98.