Haskell
Haskell es un lenguaje de programación funcional y de propósito general. Su nombre hace referencia al lógico estadounidense Haskell Curry por sus contribuciones al cálculo lambda, este es un sistema formal que es la base teórica de los lenguajes que adoptan un paradigma funcional. Su primer lanzamiento data del año 1990 bajo la necesidad de dar un estándar a los distintos lenguajes funcionales de la época. Actualmente Haskell es el lenguaje de programación con el que se escribe la red de Cardano.
Cálculo Lambda: Computación con funciones
El cálculo lambda es un sistema formal que fue diseñado por Alonzo Church para estudiar las funciones desde un punto de vista computacional. Este sistema es el fundamento teórico computacional detrás de Haskell y los demás lenguajes de programación funcional, por lo que es importante tener una comprensión sobre qué es:
¿Qué significa Cálculo?
Puede llamarse cálculo a cualquier sistema de reglas que te permite inferir un valor a partir de otros valores, por ejemplo, las reglas que me permiten saber la cantidad resultante de "1 + 2 * 3"; o la lógica que ocupamos para razonar si es verdadera o no una afirmación; o bien las instrucciones que utiliza un computador moderno para resolver un algoritmo. En todas estas actividades se ocupan reglas u operaciones que al ser combinadas pueden deducir ciertos valores a partir de otros valores, es decir, se está realizando un cálculo o computación. Ahora bien, no todos los sistemas de cálculo tienen las mismas capacidades y pueden lidiar con los mismos problemas, por ejemplo, las operaciones presentes en una calculadora numérica simple sea mecánica o electrónica solo puede resolver un conjunto limitado de problemas frente a una computadora moderna. En la calculadora simple solo pueden efectuarse operaciones aritméticas como la suma, resta, multiplicación y división, en cambio, en una computadora moderna, aparte de esas operaciones aritméticas existen además condicionales, bucles y otros tipos de operaciones que permiten expresar algoritmos de mayor complejidad. Según lo demuestra la teoría, el grado mas alto de computación que puede lograr un sistema es ser Turing completo, es decir, un sistema en el que se poseen las operaciones necesarias para expresar todos los algoritmos posibles, en este basta solo con encontrar el orden correcto de las reglas para reproducir cualquier algoritmo.
Ahora... ¿Qué es Cálculo Lambda?
El cálculo lambda es un sistema Turing completo que permite hacer un proceso de cálculo o computación a través de funciones. Similar a cómo se entiende en matemáticas, una función básicamente es cualquier tipo de operación que se aplica a un dato de entrada (o argumento) y como resultado tiene un dato de salida. En las funciones existe una relación determinista entre el dato de entrada y el dato de salida, un valor inicial siempre tendrá el mismo resultado final dada una misma función. El cálculo lambda consiste en tres cosas: Definición de variables, construcción de funciones y un modo de aplicar funciones con argumentos o funciones entre sí . Un ejemplo de este sistema con su notación puede verse así:
λxy.x -- Una función que toma dos valores como argumento y escoge el primer valor "x".
Una función se declara con la letra griega Lambda (λ) seguido de sus argumentos y luego del punto se determina el comportamiento o resultado que se espera de la función. Solamente esas tres propiedades antes mencionadas son capaces de expresar cualquier algoritmo posible al igual que una máquina Turing.
Cálculo lambda y máquinas de Turing ( Paradigma funcional frente imperativo)
Si bien la máquina de Turing es computacionalmente equivalente al sistema del cálculo lambda, ambos poseen diferentes formas de expresar los algoritmos. Por un lado, una máquina Turing realiza cálculos a través de una serie de instrucciones, las cuales modifican símbolos a lo largo de una memoria (en una cinta). Básicamente desplaza los datos de un lado para otro a través de las celdas de memoria. Por ejemplo, una máquina Turing que tenga las instrucciones para contar hasta 100 irá alternadamente cambiando los valores desde las celdas de las unidades, luego a las decenas y centenas hasta llegar al número. La computación es concretada mediante el desplazamiento de símbolos de acuerdo a instrucciones. Por otro lado, el cálculo lambda siendo la base del paradigma funcional, efectúa cálculos a través de una forma modular conectando la entrada y salidas de cada función. La computación no se realiza mediante instrucciones, sino, en evaluar el significado de una función y sus argumentos para luego retornar el resultado. Cabe señalar aquí, que las funciones son como cajas negras pues su operación y estados internos son irrelevantes; desde el punto de vista teórico, solo se declara el resultado que se espera de una función sin considerar su implementación física. Por ello, en la práctica el cálculo lambda supone ciertas abstracciones físicas que dificultan su implementación directa en hardware (mas bien se encuentra mas del lado del software). Contrariamente, una maquina Turing es una caja blanca, al ser mucho mas representativa de los puntos intermedios que ocurren cuando se transforma un valor en otro (estados internos, haciendo que sea mucho mas fácil de replicar físicamente. Entender estas diferencias entre estos sistemas formales nos servirá mas adelante, observar lo distinto que es Haskell frente a lenguajes de orden imperativo que son simplemente versiones mas complejas de las máquinas Turing.
Características del lenguaje Haskell
Puramente funcional
Haskell es un lenguaje que adopta completamente el paradigma funcional. En Haskell las funciones son tratadas como cualquier otro tipo de dato, pueden servir como argumento o resultado de otras funciones; además, en estricto rigor no existen variables, sino, funciones sin argumentos que se comportan como variables. Tal como se mencionó antes, en contraste con un paradigma imperativo donde hay una secuencia de instrucciones que cambian el estado global con el desplazamiento de valores, en un paradigma funcional los programas son ejecutados evaluando expresiones y cómo se aplican los elementos de la expresión. Dentro de un paradigma funcional no existe el desplazamiento de un valor de una variable a otra, sino, en analizar qué funciones son argumentos de otras funciones y luego ir aplicando las funciones en orden. Por ejemplo, si quisiera un programa que encuentre el valor negativo del sucesor de un número se vería así en Haskell:
sucesor a = a + 1 -- Se define una función que retorne el sucesor de un número. negativo a = a * (-1) -- Se define una función que retorne el negativo de un número. negativo ( sucesor 1 ) -- Aquí comienza el programa evaluando cómo está compuesta la expresión. negativo ( 2 )
Ahí el compilador de Haskell hace un análisis del orden sintáctico de la expresión determinando que "1" es argumento de "sucesor" y a su vez la función "sucesor" es argumento de "negativo". Últimamente cualquier expresión en Haskell se reduce a funciones que son argumento de otras. Esto hace que el trabajo con los datos tengo un curso bien definido donde basta entender donde se conectan las entradas y salidas de las funciones. Hay que destacar que esto es muy importante pues en un lenguaje funcional no existen efectos colaterales, una función o un conjunto de ellas siempre devuelven el mismo resultado dada una entrada; lo cual es muy distinto a los lenguajes imperativos, donde los efectos colaterales pueden suceder porque se trabaja con variables globales que se pueden invocar en cualquier momento, y convertirse en entradas escondidas que para el programador son fáciles de perder de vista (mas aún si hablamos de programas extensos). En Haskell algo así como variables, datos o un estado global no existen, los datos siguen un flujo bien definido en el cual las funciones solo están limitadas a recibir datos a través de un argumento.
Estáticamente tipado
Para explicar este punto, primero debemos distinguir la diferencia entre un tipado dinámico y uno estático con la siguiente pregunta: ¿En un lenguaje de programación puede reasignarse a una variable un valor de distinto tipo? Cuando un lenguaje no permite una reasignación de tipo se dice que es estático y cuando si lo permite se dice que es dinámico. Haskell es un lenguaje de programación con un tipado estático. En un tipado estático el compilador hace una verificación del uso correcto de los tipos de datos, donde en general se debe declarar que tipo de dato corresponde a la variable y luego ese tipo queda fijado a esa variable. Cuando se compila un programa en Haskell, el compilador tiene certeza de qué tipo de datos corresponden con ciertas operaciones y cuales no, entonces muchos de los errores son previstos en el mismo momento de compilar. Por ejemplo, si se suma un número y una cadena el compilador indicará el error. En cambio, un sistema dinámico no hace tal verificación pues se permite cambios en los tipos de datos de las variables, es válido que una variable que en un momento tenía un valor numérico tenga en otro momento una cadena como valor. Por otro lado, a diferencia de otros lenguajes, Haskell evita fuertemente operaciones que permitan una conversión implícita de los datos, por ejemplo, en Javascript podemos ver conversiones implícitas que en Haskell no son posibles:
2 + "2" = "22" // Convertir el numero 2 en una cadena para concatenarla con "2"
Como vemos tales operaciones implícitas junto a otros comportamientos propios de un tipado dinámico suponen formas muy flexibles de trabajar con los datos, de modo que, pueden introducirse diversos errores donde el programa en ejecución no procesa los datos como es esperado. El beneficio de un sistema dinámico es la flexibilidad como brevedad a la hora de escribir código, lo que por lo pronto puede hacer a un programador mas productivo pero traer también complicaciones a la hora de identificar los bugs del código, a menudo en lenguajes de tipado dinámico la prueba del software se hace mas larga y requiere mas casos de prueba. Haskell promueve una programación estricta con los tipos de datos pero mejora la experiencia a la hora de arreglar errores.
Nota: Revisando distintas fuentes uno podrá observar que no existe un consenso en el término estaticamente tipado, también podrá verse otro tipo de términos describiendo un comportamiento parecido como tipado fuerte, pero independiente de aquello lo importante es saber que el lenguaje Haskell es estricto con los datos.
Inmutable
Si en el punto anterior nos preguntábamos si se podía cambiar el tipo de dato, ahora nos preguntamos algo mas básico aún: ¿Se puede cambiar el valor de una variable? Y la respuesta es no, en Haskell los valores de los datos no se pueden cambiar pues son inmutables. Si queremos realizar algo similar con el lenguaje podría ser copiar el valor, cambiarlo y guardarlo en una nueva variable. Por otra parte, algo que suele confundir a los nuevos programadores y pareciere contradecir lo anterior, es que los nombres de las funciones pueden reutilizarse con distintos valores pero esto implica en realidad una reasignación de valor sino crear en memoria otra variable distinta.
Perezoso (Lazy)
Un lenguaje perezoso significa que solo se ejecutarán funciones y calcularán cosas cuando sea necesario, el compilador de Haskell si encuentra que ciertos cálculos son irrelevantes para el resultado que se solicita se omitirán dichos cálculos. Específicamente, un valor o expresión solo será calculado tanto como la función lo requiera. Por ejemplo, esto permite que en Haskell existan listas infinitas y que se puedan hacer operaciones con ellas, una función puede tomar una lista infinita y evaluarla hasta que se consigue el resultado esperado. Esto puede traer ganancias en términos de procesamiento y uso de CPU, pero una incertidumbre respecto a cuantos datos en memoria RAM se utilizarán para tal proceso.
¿Por qué Haskell es importante para Cardano?
Los nodos de Cardano están escritos en Haskell y los contratos inteligentes de la red se encuentran escritos en una extensión del lenguaje llamada Plutus. Las razones que hicieron a Haskell el lenguaje idóneo para programar la red de Cardano se basan al menos en tres argumentos principales:
- Haskell es un lenguaje apto para situaciones de alto riesgo: La naturaleza funcional del lenguaje favorece la implementación de un código que sea apto para situaciones de alto riesgo, esto quiere decir que Haskell es una buena opción para trabajar con sistemas informáticos donde un error en el código significa la perdida de vidas o grandes cantidades de dinero. En particular, esto es cierto tanto porque Haskell es estricto con los errores relacionados con los tipos de datos, como también con la disminución de efectos colaterales que supone un lenguaje funcional.
- Correspondencia entre código y formalización matemática: La sintaxis de Haskell en la medida que está inspirada en un lenguaje matemático hace que sea mas fácil de implementar correctamente los modelos matemáticos en código. Hay una cercana correspondencia entre los modelos matemáticos y el código de Haskell. En lenguajes que siguen un paradigma imperativo tal correspondencia entre modelo y sintaxis es mas débil. Esta discrepancia entre modelo y codificación es una dificultad habitual en la práctica científica, donde habiéndose demostrado correcto un modelo este falle en sus resultados por una pobre implementación, Haskell debido a su sintaxis disminuye esa brecha.
- Refactorización eficiente del código: Por un lado, Haskell permite con pocas líneas de código tener una alta expresividad, lo cual desde el punto de vista del programador supone leer menos código a la hora de encontrar errores. Por otra parte, como las funciones fuerzan al programador a escribir de manera modular existe la posibilidad de reemplazar ciertos puntos intermedios del código con facilidad, a veces basta con cambiar solamente una parte específica del código para corregir un programa.
Historia
La historia de Haskell nace con el intentos progresivos de implementar el cálculo lambda por parte de distintos científicos de la computación. En el año 1958, John Mccarthy desarrolla el primer lenguaje de programación funcional llamado Lisp, sin embargo, este lenguaje contenía ciertas propiedades de otros lenguajes imperativos como la reasignación de variable. Mas tarde, en la década de 1960, surge ISWIM el primer lenguaje puramente funcional creado por Peter Landin. Luego en la década de 1970 surgieron otros tipos de lenguaje (ML, OCAM y Miranda) que incorporaban mas funcionalidades que aumentaran la capacidad de trabajar con datos como inferencia de tipos, evaluación perezosa y funciones de alto orden. Sin embargo, aún en 1987, existían compitiendo entre ellos más de una docena de lenguajes de programación puros funcionales no estrictos. Durante la conferencia sobre Lenguajes de Programación Funcional y Arquitecturas de Ordenador (FPCA '87) en Portland, Oregón, se mantuvo un encuentro durante el cual se alcanzó un fuerte consenso entre sus participantes para formar un comité que definiese un estándar abierto para tales lenguajes. Este comité fue el que finalmente desarrolló una primera aproximación que sería el lenguaje Haskell en la década de 1990 y que se consolidó con su primer versión estable 2003. Posteriormente, otro avance se hizo en el año 2010 ya con mejores estándares, una actualización del compilador y soporte mayor de librerías.
Entorno de trabajo
Compilador GHC (Great Glasgow Compiler)
Es un compilador nativo de código abierto para el lenguaje Haskell. Este provee un entorno multiplataforma para la escritura y prueba de código de código Haskell, además soporta numerosas extensiones, librerías y optimizaciones que optimizan la generación de un código ejecutable. El recurso para descargarlo pueden encontrarse en este link.
Cabal
Cabal es un sistema para construir y empaquetar tanto librerías como programas de Haskell. Define una interfaz común para los autores de los paquetes y distribuidores facilitando así la construcción de aplicaciones que sean portables. Mas información puede encontrar en este enlace.
Recursos para aprender Haskell
Español
- ¡Aprende Haskell por el bien de todos!: Es un libro muy didáctico pensado para principiantes en programación funcional pero que ya tienen una experiencia en programación con otros lenguajes.
- David Giordana - Aprende Haskell desde cero: Este es un video tutorial creador para iniciarse en la programación funcional con Haskell, sin embargo, el curso se encuentra completo en Udemy.
- Martín Kindall - Aprende Haskell, el lenguaje que me obligó a programar funcionalmente: Video tutorial que a través de ejercicios explora conceptos clave en Haskell (no apto para principiantes).
Inglés
- Graham Button's lecture - Introduction to programming in Haskell: Este es un curso introductorio dictado en la universidad de Notttingham por el doctor en ciencias de la computación Graham Button.
- Graham Button's lecture - Advanced programming in Haskell: Esta es la continuación del curso anterior pero ya profundizando en conocimientos mas avanzados de Haskell.
- Learn Haskell for Plutus (Cardano ADA Contracts) Day 1: Video tutorial introductorio a Haskell pero con una orientación también hacia Plutus.
- Wiki Haskell: Learn Haskell in five steps: Guía de la enciclopedia de Haskell para comenzar con Haskell.
- Distrotube Getting started on Haskell: Video tutorial introductorio a Haskell por parte del divulgador de código abierto Derek Taylor.
- Tsoding - HaskellRank: Video tutorial por parte del youtuber Tsoding donde a través de ejercicio prácticos enseña la programación en Haskell.
Bibliografía
Cálculo Lambda: Computación con funciones
- Lambda Calculus - Computerphile. https://www.youtube.com/watch?v=eis11j_iGMs
- Lambda Calculus vs. Turing Machines (heory of Computation). https://www.youtube.com/watch?v=ruOnPmI_40g
- ¿Qué es el cálculo lambda? (ft. Church Encodings). https://www.youtube.com/watch?v=d0yEXKas8xE
- Wikipedia: Cálculo Lambda. https://en.wikipedia.org/wiki/Lambda_calculus
- Wikipedia: Turing Machine. https://en.wikipedia.org/wiki/Turing_machine
Características del lenguaje Haskell
- Why is Haskell so hard to learn. https://www.youtube.com/watch?v=RvRVn8jXoNY
- Functional Programming & Haskell - Computerphile. https://www.youtube.com/watch?v=LnX3B9oaKzw
- Wikipedia: Haskell. https://es.wikipedia.org/wiki/Haskell
- Graham Button's lecture - Functional programming introduction. https://www.youtube.com/watch?v=rIprO6zoujM&list=PLF1Z-APd9zK7usPMx3LGMZEHrECUGodd3&index=3
- Wiki Haskell: Introduction. https://wiki.haskell.org/Introduction
- Wiki Haskell: Why Haskell Matters?. https://wiki.haskell.org/Why_Haskell_matters
- Wiki Haskell: Lazy Evaluation. https://wiki.haskell.org/Lazy_evaluation
- Wiki Haskell: Functional programming. https://wiki.haskell.org/Functional_programming
- Learn you a Haskell for great good: Introduction http://learnyouahaskell.com/introduction#about-this-tutorial
- Real World Haskell: Introduction - Chapter I. https://www.amazon.com/gp/product/0596514980/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=whatpixel-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=0596514980&linkId=8ab7948167a4db4ab54bec1dd1444eee
- Haskell monday morning: Inmutability is awesome. https://mmhaskell.com/blog/2017/1/9/immutability-is-awesome
- Stack Over Flow Site: Does Haskell have variables? https://stackoverflow.com/questions/993124/does-haskell-have-variables
- Stack Over Flow Site: What does inmutable variable in Haskell mean ? https://stackoverflow.com/questions/38040812/what-does-immutable-variable-in-haskell-mean
¿Por qué Haskell es importante para Cardano?
- Charles Hoskinson: Cardano | Lex Fridman Podcast #192. https://www.youtube.com/watch?v=FKh8hjJNhWc
Historia
- Wikipedia: Haskell. https://es.wikipedia.org/wiki/Haskell
- Graham Button's lecture - Functional programming introduction. https://www.youtube.com/watch?v=rIprO6zoujM&list=PLF1Z-APd9zK7usPMx3LGMZEHrECUGodd3&index=3
- Real World Haskell: Introduction - Chapter I. https://www.amazon.com/gp/product/0596514980/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&tag=whatpixel-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=0596514980&linkId=8ab7948167a4db4ab54bec1dd1444eee