diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..1e20b3a9
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,6 @@
+FROM ghcr.io/jamesdbrock/ihaskell-notebook:master
+# FROM ghcr.io/ihaskell/ihaskell-notebook:master
+
+RUN pip install RISE
+
+# RUN stack install Decimal http-conduit ansi-terminal
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..a1447f19
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,3 @@
+{
+ "build": { "dockerfile": "Dockerfile" }
+}
diff --git a/ES-translation/Lecciones/02-Funciones-TipoDeDatos-y-Firmas.ipynb b/ES-translation/Lecciones/02-Funciones-TipoDeDatos-y-Firmas.ipynb
new file mode 100644
index 00000000..ba516d31
--- /dev/null
+++ b/ES-translation/Lecciones/02-Funciones-TipoDeDatos-y-Firmas.ipynb
@@ -0,0 +1,5053 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Tipos de Datos, Firmas y Polimorfismo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "\n",
+ "## Indice\n",
+ "\n",
+ "- Introducción pragmática a tipos\n",
+ "- Firmas de funciones\n",
+ "- Trabajando con funciones\n",
+ " - Variables en Haskell\n",
+ " - Funciones infijas y prefijas \n",
+ "- Tipos de Datos comunes\n",
+ "- Valores polimórficos y variables de tipo\n",
+ "- Diversión con listas!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Introducción pragmática a tipos"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### El `::`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Un tipo es una etiqueta que cada expresión tiene y restringe su uso."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Utilizamos el símbolo *dos puntos duplicado* para mostrar o asignar el tipo de una expresión. Por ejemplo, el código\n",
+ "\n",
+ "``` haskell\n",
+ "miexpresion :: MiTipo\n",
+ "```\n",
+ "\n",
+ "nos indica que la expresión `miexpresión` tiene tipo `MiTipo`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tipos usados frecuentemente"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Existen tipos estándar que son utilizados frecuentemente en Haskell:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* `Int` y `Integer` para números enteros.\n",
+ "* `Float` y `Double` para números de punto flotante y reales.\n",
+ "* `Bool` para `True` y `False`.\n",
+ "* `Char` para caracteres.\n",
+ "* `String` para cadenas de texto."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Cómo chequear un tipo?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "\n",
+ "El comando `:type` (o en forma abreviada `:t`) utilizado en el intérprete GHCI, seguido por una expresión válida , nos indicará su tipo.\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "True :: Bool"
+ ],
+ "text/plain": [
+ "True :: Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "False :: Bool"
+ ],
+ "text/plain": [
+ "False :: Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(3 < 5) :: Bool"
+ ],
+ "text/plain": [
+ "(3 < 5) :: Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "'A' :: Char"
+ ],
+ "text/plain": [
+ "'A' :: Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\"Hello world!\" :: [Char]"
+ ],
+ "text/plain": [
+ "\"Hello world!\" :: [Char]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":type True\n",
+ "\n",
+ ":type False\n",
+ "\n",
+ ":t (3 < 5)\n",
+ "\n",
+ ":t 'A'\n",
+ "\n",
+ ":t \"Hello world!\" "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Firma de una función"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "El símbolo `::` debe ser leído como \"es del tipo\", e indica el tipo de la *firma*. Expliquemos que es el el tipo de la firma con el siguiente ejemplo. En Haskell, una función que eleva al cuadrado su argumento se define de la siguiente forma:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "cuadrado :: Int -> Int\n",
+ "cuadrado v = v * v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "La primera línea contiene la **firma**, la segunda línea contiene la **definición** de la función `cuadrado`.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "* La **firma** de una función es un mensaje a todo elmundo de la existencia de dicha función, este es su nombre y estos son los tipos con los cuales trabaja. \n",
+ "\n",
+ "* La **definición** de una función es información respecto a lo que exactamente hace dicha función"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "En la firma"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "cuadrado :: Int -> Int\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "dos partes están *separadas* por doble dos puntos en "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ "* el **nombre** de la función a la izquierda\n",
+ "\n",
+ "* el **tipo de función** a la derecha."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**Todo dato en un programa Haskell es de un tipo específico.** Y considerando que las funciones trabajan con datos, su **firma contiene los tipos de sus entradas y salidas, separadas por flechas `->`**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "La firma de la función para elevar al cuadrado `cuadrado` nos indica que acepta un *único* argumento del tipo `Int` y retorna un valor del mismo tipo `Int`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Si hay más de un argumento, la firma se adapta. Por ejemplo, la firma de la función `producto` que retorna el producto de dos enteros recibidos como argumentos, puede verse así: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "producto :: Int -> Int -> Int\n",
+ "producto x y = x * y"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Tiene dos argumentos de tipo `Int` , y su salida también es de tipo `Int`. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "En la **definición** de una función, el signo de `=` separa el código en dos partes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ "* **Cabezal** es el código a la izquierda de `=` y consiste en el **nombre de una función** y **nombres de argumentos** (nombres, no tipos!), separados por espacios.\n",
+ " \n",
+ "* **Cuerpo** es el código a la derecha de `=` expresa la esencia de la función, su contenido."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "### Que nos indica la firma de una función?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Haskell es un lenguaje de programación *funcional* y cada programa consiste en *funciones*. Cada función toma un número fijo de parámetros de ciertos tipos y retorna un valor que también tiene un tipo determinado. Por ejemplo, la función"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "``` haskell\n",
+ "not :: Bool -> Bool\n",
+ "``` "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "toma un parámtero de tipo `Bool` y retorna su negación, la cual también es de tipo `Bool`. \n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Observando la *flecha más a la derecha `->`* en la firma, se entiende que\n",
+ "* todo lo que está a la izquierda de ella son **tipos de argumentos**, los cuales a su vez también pueden estar separados por flechas `->`,\n",
+ "* todo lo que está a la derecha de ella es **el tipo del valor calculado**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Trabajando con funciones"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Variables en Haskell (nombres/definiciones)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Observa la siguiente función:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "nombre = \"Bob\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Si no tenemos parámetros, tenemos una función que siempre retorna el mismo valor -un `String`-"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "O sea, tenemos una expresión de tipo:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "nombre :: String\n",
+ "nombre = \"Bob\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**Este tipo de funciones que no toman parámetros son llamadas usualmente definición o nombre**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "De todas formas, también pueden ser llamadas variables, ya que así son llamadas en la mayoría de los lenguajes de programación. Pero \"variable\" no siempre significa lo mismo."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Considerando que no podemos cambiar el valor de una definición (la expresión a la derecha de `=` siempre evaluará al mismo resultado), `nombre`y `Bob` son esencialmente la misma cosa. Y los podemos utilizar indistintamente."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Cuando hablamos de los lenguajes de programación en general, una variable es como una caja que contiene un valor. El nombre de la variable se encuentra escrito al costado de la caja. Se pueden poner valores adentro de la caja, y -en la mayoría de los lenguajes de programación- se podrá cambiar el valor dentro de la caja."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- ESTO NO ES CODIGO HASKELL VALIDO!!!\n",
+ "x = 3\n",
+ "7 + x -- 10\n",
+ "x = 5\n",
+ "7 + x -- 12\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "OOOOOOOOhhhhhh pero no con Haskell, no, no, no! Una vez se le ha indicado a Haskell que `x` significa `3`, el `3` se mantendrá para siempre!\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "En términos técnicos: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Las variables en Haskell son **inmutables.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "En Haskell el concepto de variable es diferente. Haskell tiene variables, pero en un sentido matemático. En el sentido de cuando decimos: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```Haskell\n",
+ "x = 3\n",
+ "pais = \"Paris\"\n",
+ "letra = 'a'\n",
+ "esVerdadero = True\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Estamos estableciendo que los términos a la izquierda del signo `=` son **intercambiables** con los términos a la derecha. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Y esto también aplica a los parámetros de la función:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "volumenCilindro r h = pi * r^2 * h "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "En este caso, una vez que le pasamos los valores a los parámetros de `volumenCilindro`, no podemos cambiarlos en el cuerpo de la función. Podemos utilizar la función nuevamente y pasarle parámetros diferentes, pero no los podemos *cambiar* una vez que los hemos pasado."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Notación Prefija e Infija"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Las funciones pueden ser aplicadas (utilizadas) en dos notaciones diferentes: prefija e infija."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Prefija"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Observemos la siguiente expresión: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "20"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "prod x y = x * y\n",
+ "prod 4 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "`prod` es utilizada en **forma prefija**, o sea **antes de sus argumentos**. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Infija\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Observemos la siguiente expresión: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "1 + 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "`+` es de hecho una función! Y está siendo utilizada en su **forma infija**, o sea **entre sus argumentos**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Las funciones definidas para una forma de aplicación infija son llamadas **operadores.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Y como sabemos si una función es infija o prefija? Bueno..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Las funciones definidas **solamente con símbolos** serán automáticamente consideradas como **funciones infijas**, sino serán funciones prefijas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "De todas formas una función infija puede ser utilizada en su forma prefija y visceversa."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Infija a Prefija y visceversa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Utilizaremos paréntesis en torno a una función infija para utilizarla en forma prefija: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(+) 1 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Para chequear el tipo de una función infija, también debemos rodearla con paréntesis:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(+) :: forall a. Num a => a -> a -> a"
+ ],
+ "text/plain": [
+ "(+) :: forall a. Num a => a -> a -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t (+)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Estoy seguro que habrás notado que el tipo en la firma de `+` luce diferente a otros previos. Esto se debe a que utiliza tipos polimórficos y tipos de clases. Estudiaremos los tipos polimórficos en el día de hoy y tipos de clasees en lecciones futuras. Por ahora, no te preocupes demasiado.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Utilizamos el tilde invertido \\` rodeando una función prefija para utilizarla como infija:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "20"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "4 `prod` 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Tipos de datos comunes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "\n",
+ "### Tipos de datos Enteros: `Int` y `Integer`\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- `Integer` es un tipo con precisión arbitraria: podrá contener cualquier entero -sin importar cuan grande sea- hasta el límite de la memoria de la máquina."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Esto significa que nunca tendrás desbordamientos (overflows) aritméticos, pero eso también implica que la aritmética es relativamente lenta."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- En cambio los valores de tipo `Int`, encuentran acotados en el rango $±2^{63}$ *(para CPUs de 64-bit)*."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Esto limita los valores que `Int` puede contener, pero lo hace más eficiente."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Veamos esto en la práctica:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "4611686018427387904"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "2^62 :: Int -- Todo bien"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "2^64 :: Int -- Oh no!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "170141183460469231731687303715884105728"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "2^127 :: Integer -- Todo bien nuevamente"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Pero y que pasa con los números reales? Números con lugares decimales?. Para ello tenemos `Float` y `Double`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tipos de número con punto flotante: `Float` y ` Double`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "`Float` es un tipo real de punto flotante con precisión simple (32 bits), mientras `Double` es un tipo real de punto flotante con precisión doble (64 bits)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Veamos que ocurre si queremos mostrar los primeros 20 dígitos de pi (π) en ambos casos: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3.1415927"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "3.141592653589793"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "3.14159265358979323846 :: Float\n",
+ "\n",
+ "3.14159265358979323846 :: Double"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Puedes decir que `Double` es muuuuucho más preciso que `Float`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Teóricamente, las razones para utilizar un tipo u otro son de alguna forma análogas a los casos `Int` y `Integer`.\n",
+ "`Double` tiene doble precisión, pero consume más memoria ya que utiliza el doble de bits para representar los números. \n",
+ "\n",
+ "PERO!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Recomendación basada en usos reales:\n",
+ "\n",
+ "- **Aunque no te importen especialmente los valores exactos, utiliza `Double.`** En las computadoras modernas, rara vez hay una desventaja respecto a la velocidad, y con `Double`, tienes mucha menos probabilidad de pegarte un tiro en el pie por errores de redondeo."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "- Si te encuentras en una configuración en la cual **las cantidades exactas son críticas** (p.ej: finanzas y contabilidad), una buena idea puede ser **utiliza los tipos de datos `Rational` o `Decimal`**. Estos tipos evitan por completo los errores de redondeo. Los veremos en futuras lecciones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tipo de datos Booleano `Bool`\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "El tipo de datos `Bool` contiene únicamente dos valores: `True` y `False`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Números, caracteres y cadenas de caracteres pueden ser comparados utilizando los usuales **operadores de comparación** para producir un valor `Bool`: $$==,~~/=,~~<=,~~>=,~~<,~~>$$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "\n",
+ "5 /= 0 -- True\n",
+ "\n",
+ "3 >= 0 -- True\n",
+ "\n",
+ "7.2 < 6.1 -- False\n",
+ "\n",
+ "pi > 3.14 -- True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "También tenemos los operadores `&&` (**AND**) y `||` (**OR**) que nos permiten la combinación de valores:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "- El operador `&&` (AND) returna `True` si ambos booleanos a su izquierda y derecha son `True`.\n",
+ "- El operador `||` (OR) returna `True` si alguno de ellos es `True`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(&&) :: Bool -> Bool -> Bool"
+ ],
+ "text/plain": [
+ "(&&) :: Bool -> Bool -> Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(||) :: Bool -> Bool -> Bool"
+ ],
+ "text/plain": [
+ "(||) :: Bool -> Bool -> Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t (&&)\n",
+ ":t (||)\n",
+ "\n",
+ "True && False\n",
+ "True || False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tipo de datos Carácter `Char`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "`Char` es el tipo que usamos para representar un caráctere *Unicode*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "\n",
+ "
\n",
+ "El estándar Unicode (Unicode) es un conjunto de reglas que imponen una forma de tratar y expresar texto. Esto es necesario debido a que las computadoras piensan en números (ceros y unos), y debemos decidir colectivamente que números representan que caracteres.\n",
+ "
\n",
+ "
\n",
+ "Actualmente es un poco complicado (ver: Character encoding). Pero para nuestro propósito, sólo nos interesa saber que podemos utilizar casi cualquier símbolo que alguna vez necesitaremos utilizando caracteres Unicode. Letras, números y más 140K símbolos.\n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Escribimos valores del tipo Char (caracteres Unicode) entre tildes simples. Así: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'@'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'7'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "'a'\n",
+ "'@'\n",
+ "'7'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notar que si tu escribes un número rodeado de tildes simpoles (como en la última expresión), Haskell no lo tratará como un número. Lo tratará como cualquier otro carácter. O sea no se puede realizar matemática con `'7'` (con tildes simples), pero sí se puede con `7` (sin tildes simples)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "\n",
+ "Importante: Puedes escribir caracteres simples uno por vez! Algo como 'hi'
no es un Char
valido! \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Entonces, como puedes escribir oraciones enteras? Ya te lo diré, pero antes de eso debes aprender acerca de listas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Listas"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "En Haskell, **las listas son una estructura de datos homogénea**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Esta simplemente es una forma elegante de decir que las listas almacenan elementos del mismo tipo. O sea, podemos tener un lista de `Int` o una lista de `Char`, pero no una lista de una mezcla de ellos."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "* Las listas se indican mediante paréntesis rectos `[1,5,3,-4,0]` y los valores dentro de las listas **se separan con comas**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "* El tipo de una lista se expresa como el tipo de los elementos que la misma contiene, redeados de corchetes rectos. Una lista de tipo `[Int]` contiene números de tipo `Int`. Una lista de tipo `[Char]` contiene elementos de tipo `Char`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "['a', 'b', 'c', 'd'] :: [Char]"
+ ],
+ "text/plain": [
+ "['a', 'b', 'c', 'd'] :: [Char]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "[True,False, 3 > 2, 'a' == 'b'] :: [Bool]"
+ ],
+ "text/plain": [
+ "[True,False, 3 > 2, 'a' == 'b'] :: [Bool]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t ['a', 'b', 'c', 'd']\n",
+ "\n",
+ ":t [True,False, 3 > 2, 'a' == 'b']"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Strings"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**Strings respresenta lista de caracteres.** Puedes utilizar el tipo `String` para escribir mensajes, valores alfanuméricos, símbolos, etc. A diferencia de `Char`s, los `String`s deben ser escritos entre **tildes dobles** así:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"Hellooooooo!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "\"Hellooooooo!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Lo cual siginifica que los siguientes dos valores son lo mismo!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "['H','i','!'] == \"Hi!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Y también que `String` y `[Char]` son el mismo tipo! Más específicamente, `String` es azúcar sintáctico (sintaxis diseñada para facilitar la lectura y expresión) para `[Char]`! Por lo tanto pueden ser utilizados indistintamente!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Lo que no es intercambiable en Haskell son los tildes simples con los dobles. `String` (escrito entre tildes dobles) son listas de `Char` (escrito entre tildes simples). No son lo mismo!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\"A\" :: [Char]"
+ ],
+ "text/plain": [
+ "\"A\" :: [Char]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "'A' :: Char"
+ ],
+ "text/plain": [
+ "'A' :: Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t \"A\"\n",
+ ":t 'A'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Todo codificador sabe que las listas son extremadamente útiles. Pero y si queremos colocar juntos valores de tipos diferentes? Ahí es cuando las tuples son útiles!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tuplas"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Las Tuplas son estructuras utilizadas para almacenar **elementos heterogéneos** como un solo valor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Representamos las tuplas comenzando con un paréntesis de apertura, escribiendo todos los elementos separadso por una coma y finalizamos con un paréntesis de cierre. Este es un ejemplo de una tupla con 3 elementos:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('a',3,True)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "('a', 3, True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Se parecen mucho a las listas, pero son hay dos diferencias esenciales: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* **Las tuplas pueden almacenar elementos de tipos diferentes:** como puedes ver en el ejemplo previo, las tuplas pueden almacenar elementos de diferentes tipos, mientras que las listas no."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* **Las tuplas tienen un tamaño fijo:**. Tu puedes incrementar el tamaño de una lista mediante la concatenación o de otras formas, pero el tamaño de una tupla no puede ser incrementado o disminuído. Una vez se ha indicado que una tupla tiene N elementos, permanece siempre teniendo N elementos."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "**Y estas diferencias esenciales se reflejan en el tipo de las tuplas**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "El tipo tupla depende de: \n",
+ "- El tipo de sus elementos\n",
+ "- El orden de los elementos\n",
+ "- La cantidad de elementos"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Por ejemplo:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "('a', True) :: (Char, Bool)"
+ ],
+ "text/plain": [
+ "('a', True) :: (Char, Bool)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(True, 'a') :: (Bool, Char)"
+ ],
+ "text/plain": [
+ "(True, 'a') :: (Bool, Char)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(True, 'a', 'b') :: (Bool, Char, Char)"
+ ],
+ "text/plain": [
+ "(True, 'a', 'b') :: (Bool, Char, Char)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(True) :: Bool"
+ ],
+ "text/plain": [
+ "(True) :: Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t ('a', True)\n",
+ "\n",
+ ":t (True, 'a')\n",
+ "\n",
+ ":t (True, 'a', 'b')\n",
+ "\n",
+ ":t (True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Como puedes ver `('a', True) :: (Char, Bool)`, `(True, 'a') :: (Bool, Char)`, y `('a', True, True) :: (Char, Bool, Bool)` todos tiene diferentes tipos. En lo que respecta al compilador, esas tres tuplas son tan diferentes entre ellas como lo son `Float` y `Char`.\n",
+ "\n",
+ "Te has dado cuenta que si tratas de crear una tupla con un solo elemneto, GHCi retorna solamente el elemento? (Ver las últimas dos expresiones del bloque de código anterior). Esto se debe a que no hay tuplas de un solo elmento! Tener una tupla de un solo elemento no aportaría ningún valor extra. Por lo tanto, Haskell ignora la tupla y evalúa sólo el elemento."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Valores Polimórficos y variables de Tipo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Lo grandeza de los tipos es que nos protegen de nosotros mismos! Si decimos que una función toma como tipo de entrada `[Char]`, Haskell chequeará que cumplamos con dicho requerimiento cada vez que usemos dicha función. Si le pasamos un `Double` el compilador nos gritará para que corrijamos el error!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Pero ahora tenemos un problema! Imagina que creamos la función `prod`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "prod :: Int -> Int -> Int\n",
+ "prod x y = x * y"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "La misma funciona perfectamente para valores de tipo `Int`. ¿Pero que pasa si la precisamos para valores de tipo `Double`? Sabemos que funcionará debido a siguen siendo números y la fórmula proveerá la respuesta correcta."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "*Podríamos* crear una nueva función que haga lo mismo pero especificando un tipo diferente:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "prodForDoubles :: Double -> Double -> Double\n",
+ "prodForDoubles x y = x * y"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Técnicamente funciona. Pero y que pasa con los tipos `Float`y `Integer`? Si tenemos que duplicar las funciones para cada caso, eso se vuelve rápidamente insostenible!\n",
+ "\n",
+ "**Tipos Polimórficos al rescate!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Polimórfico significa que algo tiene muchas formas. Y **un valor polimórfico es un valor que puede tener múltiples tipos** (Por ejemplo, `4` puede ser `Int`, `Integer`, `Float`, ...)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Por ejemplo, imagina que queremos crear una función que toma una tupla con dos valores (también llamada par) y retorna el primer valor. Como esto: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "first (x,y) = x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "¿Que tipo debe tener? No me preocupan particularmente los tipos de los elementos, ya que no hacemos nada con ellos! No hago operaciones aritméticas, manipulación de texto o alguna otra cosa! Es más, solamente retorno el primer elemento y listo!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "En estos casos, especificamos una firma con variables de Tipo!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "first :: (a, b) -> a\n",
+ "first (x,y) = x\n",
+ "\n",
+ "first ('a', \"hi!\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Esta firma se le de la siguiente forma: \"La función `first` recibe un par de tipo (a,b) y retorna un valor del tipo `a`.\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "\n",
+ "Importante: Tipos específicos (p.ej., Char
, Bool
, Int
) comienzan con mayúsculas. Pero los tipos polimórficos comienzan con minúsculas. Podemos utilizar nombres largos para los tipos polimórficos, pero lo usual es utilizar una letra.(p.ej., a
, b
, c
).\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Esta función \"`first`\" que hemos creado actualemente ya viene con Haskell, pero su nombre es `fst`! Y viene con su contraparte `snd`!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "fst :: forall a b. (a, b) -> a"
+ ],
+ "text/plain": [
+ "fst :: forall a b. (a, b) -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "snd :: forall a b. (a, b) -> b"
+ ],
+ "text/plain": [
+ "snd :: forall a b. (a, b) -> b"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "2"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t fst\n",
+ ":t snd\n",
+ "\n",
+ "fst (1,2)\n",
+ "snd (1,2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`a` y `b` son variables de tipo, lo que significa que pueden ser de cualquier tipos. Independientemente del tipo, el valor retornado por `first` tiene que ser del mismo tipo que el primer elemento del par (ya que los dos son de tipo `a`).\n",
+ "\n",
+ "Al utilizar variables de tipo, podemos utilizar la función `first` con pares de cualquier tipo (valores polimórficos)!\n",
+ "\n",
+ "Observa que `a` y `b` ambos PUEDEN ser de cualquier tipo Y de tipos diferentes. Pero no TIENEN que serlo. Puedes utilizar `first` en una tupla con valores del mismo tipo: `('a','b') :: (Char, Char)`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Otro ejemplo de funciones polimórficas son las funciones `head` y `tail`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Puedes utilizar `head` para obtener el primer elemento de una lista y `tail` para obtener todos los elementos de una lista *excepto* el primero."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[1,2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "head :: forall a. [a] -> a"
+ ],
+ "text/plain": [
+ "head :: forall a. [a] -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "tail :: forall a. [a] -> [a]"
+ ],
+ "text/plain": [
+ "tail :: forall a. [a] -> [a]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "list = [1,2,3,4]\n",
+ "list\n",
+ "\n",
+ ":t head\n",
+ "head list\n",
+ "\n",
+ ":t tail\n",
+ "tail list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "No nos importan los tipos específicos. Estamos simplemente extrayendo un elemento. Entonces, el parámetro es una lista polimórfica (una lista de cualquier tipo, llamémosla `[a]`). El resultado tiene que ser un elemento del mismo tipo que los elementos de la lista. Por ese motivo tiene que ser `a`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ahora que nos hemos familiarizado con todos esetos tipos, tengamos un poco de diversión con listas! (Dejaremos la diversión con tuplas una vez que estudiemos concordancia de patrones (pattern matching). De esa forma será más divertido.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Diversión con listas!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ " Cada elemento tiene un índice determinado por su posición en la lista - comenzando en 0 (cero)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Utilizaremos el operador `!!` para acceder a un elemento específico adentro de la lista utiliando su índice:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(!!) :: forall a. [a] -> Int -> a"
+ ],
+ "text/plain": [
+ "(!!) :: forall a. [a] -> Int -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'b'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "18"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t (!!)\n",
+ "\"abc\" !! 1 \n",
+ "[12,13,16,18] !! 3 "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Las tuplas no tienen índices, o sea que no es posible extraer elementos de una tupla de esa forma. Pero podemos utilizar `fst` y `snd` para pares y concordancia de patrones para tuplas con más elementos. (Ver lección concordancia de patrones.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Las listas pueden ser definidas por un rango: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "[3..22]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "También podemos especificar el paso de incremento entre los elementos del rango:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[3,5,7,9,11,13,15,17,19,21]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"acegikmoqsuwy\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "[3,5..22]\n",
+ "['a','c'..'z']"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "El resultado de la primer expresión contendrá todos los elementos comenzando en `3` con un paso `2=5-3` que no exceden el `22` (si el último elemento no se ajusa al patrón del paso definido, será omitido del resultado final).\n",
+ "\n",
+ "El resultado de la segunda expresión contendrá las minúsculas del alfabeto, correspondientes a los índices pares.\n",
+ "\n",
+ "Es importante destacar que sólo es posible especificar un tamaño de paso!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Si el incremento es negativo, los elementos serán en orden decreciente:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[17,14,11,8,5]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "[17,14..3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "También puedes utilizar rangos para crear listas infinitas, simplemente no indicando el límite superior."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "* `[1..]` is the infinite list $[1,2,3,4,5,...]$.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* `[1,3..]` is the infinite list $[1,3,5,7,9,...]$. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ahora, si simplemente evaluamos la lista por si misma, el programa se ejecutará para siempre (o hasta que aborte). Entonces, las listas infinitas usualmente son utilizadas como parte de expresiones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "También tenemos la función `take` que retorna una lista conteniendo los primeros `n` elementos de una lista `l` (potencialmente infinita)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "take :: forall a. Int -> [a] -> [a]"
+ ],
+ "text/plain": [
+ "take :: forall a. Int -> [a] -> [a]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"xyz\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[5,3,1,-1,-3,-5,-7]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t take\n",
+ "\n",
+ "take 3 ['x'..]\n",
+ "\n",
+ "take 20 [1,3..]\n",
+ "\n",
+ "take 7 [5,3..] "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Utilizamos el operador *cons* (notación `:`) para añadir un elemento al inicio de la lista:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(:) :: forall a. a -> [a] -> [a]"
+ ],
+ "text/plain": [
+ "(:) :: forall a. a -> [a] -> [a]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,3,4,5]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t (:)\n",
+ "2 : [3,4,5]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ " Y utilizamos el operador `++` para la **concatenación** de dos listas:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(++) :: forall a. [a] -> [a] -> [a]"
+ ],
+ "text/plain": [
+ "(++) :: forall a. [a] -> [a] -> [a]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[1,3,7,9,3,3,1]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t (++)\n",
+ "[1,3,7,9] ++ [3,3,1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Observar que `++` es una función que tomas dos listas, y `:` es una función que toma un elemento y una lista. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "**Atención** El uso repetido del operador `++` en listas largas (independientemente que estés anexando un solo elemento a una lista, (p.ej: `[1,2,3] ++ [4]`), fuerza a Haskell a **recorrer toda la lista** de la parte izquierad de `++`. Entonces, anexar algo al final de una lista con cincuenta millones de elementos, tomará un tiempo! Sin embargo, añadir algo al comienzo de una lista utilizando el operador cons `:` es instantáneo!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Entre las varias funciones definidas sobre listas, mencionamos brevemente las siguientes:\n",
+ "\n",
+ "* `length` recibe una lista y retorna su largo;\n",
+ "\n",
+ "* `null` chequea si una lista es vacía;\n",
+ "\n",
+ "* `sum` recibe una lista de númerso y retorna su suma;\n",
+ "\n",
+ "* `elem` recibe un elemento `x` y una lista de elementos `l` del mismo tipo y chequea si `x` es un elemento de la lista `l`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "length [2,4,5,6,7]\n",
+ "\n",
+ "null [2]\n",
+ "\n",
+ "sum [-1,0,1,6,-5,-1]\n",
+ "\n",
+ "5 `elem` [6,3,5,7,5]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Esto es todo sobre listas por ahora. Durante el curso seguiremos aprendiendo más acerca de ellas!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Juntando y separando texto"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Existen situaciones en las cuales lo que quieres hacer con tu lista son cosas específicamente relacionadas con texto. Haskell tiene funciones específicas para ese tipo de situaciones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Por ejemplo:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* `words :: String -> [String]` separa a `String` en una lisa de palabras, delimitadas por espacio en blanco.\n",
+ "\n",
+ "* `unwords :: [String] -> String` es la operación inversa a words. Junta las palabras delimitándolas con un espacio en blanco.\n",
+ "\n",
+ "* `lines :: String -> [String]` divide el argumento en una lista de líneas, utilizando el carácter nueva línea (`\\n`) como separador.\n",
+ "\n",
+ "* `unlines :: [String] -> String` crea un `String` a partir de una lista de strings, agregando el carácter nueva línea (`\\n`) entre las líneas originales."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"Ser\",\"o\",\"no\",\"ser?\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[\"Como has estado? \",\" Estoy bien, y tu?\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "words \"Ser o no ser?\"\n",
+ "\n",
+ "lines \"Como has estado? \\n Estoy bien, y tu?\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ },
+ "rise": {
+ "enable_chalkboard": true,
+ "header": "
"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/ES-translation/Tarea/Tarea03/Tarea03.hs b/ES-translation/Tarea/Tarea03/Tarea03.hs
new file mode 100644
index 00000000..5a7b2a03
--- /dev/null
+++ b/ES-translation/Tarea/Tarea03/Tarea03.hs
@@ -0,0 +1,29 @@
+-- Pregunta 1
+-- Escribe una función que chequea si el consumo eléctrico mensual de un dispositivo es mayor, igual o menor que un máximo permitido y
+-- retornar un mensaje acorde.
+-- La función debe recibir el consumo eléctrico por hora del dispositivo, cantidad de horas de uso diarias y el máximo consumo mensual permitido.
+-- Uso mensual = consumo (kW) * horas de uso diario (h) * 30 días.
+
+
+-- Pregunta 2
+-- Preludio:
+-- Nosotros utilizamos la función `show :: a -> String` para transformar cualquier tipo en String.
+-- Entonces `show 3` producirá `"3"` y `show (3 > 2)` producirá `"True"`.
+
+-- En la función previa, retornar el exceso/ahorro de consumo como parte del mensaje.
+
+
+-- Pregunta 3
+-- Escribe una función que muestre las ventajas del uso de expresiones que dividen expresiones grandes en expresiones más chicas
+-- Luego, compartela con otros estudiantes en Canvas.
+
+
+-- Pregunta 4
+-- Escribe una función que toma dos números y retorna su cociente tal que no sea mayor que 1.
+-- Retornar el número como un string (cadena de caracteres), y en caso que el divisor sea 0, retornar un mensaje indicando porque la división no es posible.
+-- Para implementar esta función utilizar guardas y sentencias if-then-else.
+
+
+-- Pregunta 5
+-- Escribe una fución que toma dos númerso y calcula la suma de sus raíces cuadradas, para el producto y el cociente de dichos números.
+-- Escribe la función utilizando un bloque where, adentro de una expresión let y una expresión let adentro de un bloque where.
diff --git a/ES-translation/Tarea/Tarea04/Tarea04.hs b/ES-translation/Tarea/Tarea04/Tarea04.hs
new file mode 100644
index 00000000..e5f41e89
--- /dev/null
+++ b/ES-translation/Tarea/Tarea04/Tarea04.hs
@@ -0,0 +1,29 @@
+-- Pregunta 1
+-- Consideremos los valores anidados indicados más abajo. ¿Cómo obtendrías el valor 4 utilizando en la función únicamente concordancia de patrones?
+
+anidado :: [([Int], [Int])]
+anidado = [([1,2],[3,4]), ([5,6],[7,8])]
+
+-- Pregunta 2
+-- Escribe una función que toma una lista de elementos de cualquier tipo y, si la lista tiene 3 o más elementos, los remueve.
+-- En caso contrario no hace nada.
+-- Escribela dos veces, una con múltiples definiciones de funciones y otra con expresiones case.
+
+
+-- Pregunta 3
+-- Crea una función que toma una tupla de 3 elemnentos (todos de tipo Integer) y los suma
+
+
+-- Pregunta 4
+-- Implementa una función que retorna True si una lista es vacía y False en caso contrario.
+
+
+-- Pregunta 5
+-- Escribe una implementación de la función tail utilizando concordancia de patrones.
+-- Pero en lugar de fallar en el caso de la lista vacía, retornar una lista vacía.
+
+
+-- Pregunta 6
+-- Escribe una expresión case contenida en una función que toma un Int y le suma uno (1) en el caso de que sea impar.
+-- En caso contrario no hace nada. (Utilizar la función `even` para chequear si el parámentro de entrada es impar).
+
diff --git a/ES-translation/Tarea/Tarea05/Tarea05.hs b/ES-translation/Tarea/Tarea05/Tarea05.hs
new file mode 100644
index 00000000..2e72f351
--- /dev/null
+++ b/ES-translation/Tarea/Tarea05/Tarea05.hs
@@ -0,0 +1,27 @@
+-- Crea una función de orden superior que reciba 3 parámetros: Una función y los dos parámentros de dicha función, e
+-- invertir el orden de los parámetros.
+-- Por ejemplo: `(/) 6 2` retorna `3`. Pero: `flip' (/) 6 2` retorna `0.3333333333`
+
+
+-- Crea la función `uncurry'` que convierte una función currificada en una función sobre pares.
+-- O sea, esto: `(+) 1 2` que retorna `3` puede ser escrito como `uncurry' (+) (1,2)` (con los dos argumentos adentro del par).
+
+
+-- Crear la función `curry` que convierte una función no currificada en una currificada.
+-- O sea, esto: `fst (1,2)` que retorna `1` puede ser escrito como `curry' fst 1 2` (con la tupla convertida en dos argumentos diferentes).
+
+
+-- Utiliza funciones de orden superior, aplicación parcial y estilo de punto-libre para crear una fucnión que chequea si una palabra contiene una letra mayúscula.
+-- Comienza utilizando simplemente una función de orden superior y construye desde allí.
+
+
+-- Crea la función `contar`que recibe un equipo ("Rojo", "Azul", o "Verde") y devuelve la cantidad de votos que cada equipo tiene en `votos`.
+
+votos :: [String]
+votos = ["Rojo", "Azul", "Verde", "Azul", "Azul", "Rojo"]
+
+
+-- Crea una función de una línea, que filtre `autos` por marca e indique si queda alguno disponible.
+
+cars :: [(String,Int)]
+cars = [("Toyota",0), ("Nissan",3), ("Ford",1)]
diff --git a/Homework/Homework03/Homework03.hs b/Homework/Homework03/Homework03.hs
index 41035315..6c681882 100644
--- a/Homework/Homework03/Homework03.hs
+++ b/Homework/Homework03/Homework03.hs
@@ -25,6 +25,6 @@
-- Question 5
--- Write a function that takes in two numbers and calculates the sum of squares for the product and quotient
+-- Write a function that takes in two numbers and calculates the sum of square roots for the product and quotient
-- of those numbers. Write the function such that you use a where block inside a let expression and a
-- let expression inside a where block.
diff --git a/Homework/Homework07/Homework07.hs b/Homework/Homework07/Homework07.hs
index a52c0de9..69c49b90 100644
--- a/Homework/Homework07/Homework07.hs
+++ b/Homework/Homework07/Homework07.hs
@@ -12,9 +12,13 @@
-- Question 4
--- Add type signatures to the functions below and use type variables and type classes.
+-- Add the most general type signatures possible to the functions below.
-- Then uncomment the functions and try to compile.
+--f1 x y z = show (x / y) ++ z
+
+--f2 x = if x == maxBound then minBound else succ x
+
-- Question 5
-- Investigate the numeric type classes to figure out which behaviors they provide to change between numeric types.
diff --git a/Homework/Homework09/1-Maze.hs b/Homework/Homework09/1-Maze.hs
new file mode 100644
index 00000000..a66c9dda
--- /dev/null
+++ b/Homework/Homework09/1-Maze.hs
@@ -0,0 +1,55 @@
+{-
+
+**************************** IMPORTANT ****************************
+
+This week is a two-step homework. First, you have to solve the
+"Maze" challenge, and then the "Forest" challenge. The challenges
+are in two separate files in both the homework and solution, so
+you can check the solution for the first "Maze" challenge without
+spoilers of the "Forest" one. Make sure to check the solution for
+"Maze" (and only "Maze," I see you 🥸👀) before starting with the
+"Forest" challenge!
+
+*******************************************************************
+
+Today, you'll build the simplest and most basic game imaginable.
+It'll be a maze game where the player has to write a list of moves, and the game will perform them
+and tell the player where it ends up. Then, the player can change the moves and check again until it
+finds the exit.
+
+To play the game, the player will open GHCi, load this file, and run a "solveMaze" function that
+takes a maze and a list of moves and returns a String with the resulting state.
+
+It should look like this:
+
+*Main> solveMaze testMaze []
+"You're still inside the maze. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+*Main> solveMaze testMaze [GoLeft]
+"You've hit a wall!"
+*Main> solveMaze testMaze [GoForward]
+"You're still inside the maze. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+*Main> solveMaze testMaze [GoForward, GoRight]
+"You've hit a wall!"
+*Main> solveMaze testMaze [GoForward, GoLeft]
+"YOU'VE FOUND THE EXIT!!"
+
+How are you going to achieve this? You can try it on your own, but here you have a
+step-by-step just in case:
+
+1. Write two data types. One for the moves (Move) you can make, and another for the maze (Maze).
+(Use the example above to figure them out.)
+
+2. Write a function called "move" that takes a maze and a move and returns the maze after the move.
+
+3. Write a "testMaze" value of type "Maze" and test the "move" function in GHCi.
+
+4. Write the "solveMaze" function that will take a maze and a list of moves and returns the maze
+after making those moves.
+
+5. If you test the "solveMaze" function, you'll see that each time you try to solve the maze,
+it'll print the whole maze for the player to see. To avoid that, write a "showCurrentChoice" function
+that takes a maze and returns a different string depending on if you hit a wall, found the exit, or
+still need to make another choice.
+
+6. Adapt adapt "solveMaze" function to use "showCurrentChoice" and play with your new game using GHCi! :D
+-}
diff --git a/Homework/Homework09/2-Forest.hs b/Homework/Homework09/2-Forest.hs
new file mode 100644
index 00000000..c6ee2482
--- /dev/null
+++ b/Homework/Homework09/2-Forest.hs
@@ -0,0 +1,44 @@
+{-
+
+**************************** IMPORTANT ****************************
+
+Solve this homework after completing and checking the "Maze" one.
+
+*******************************************************************
+
+We're going to build on top of the "Maze" challenge by coding a similar
+but a bit more complicated game.
+
+It works the same as the "Maze" game, with the difference that the player
+is now in a forest. Because we're in a forest, there are no walls. And,
+if you walk long enough, you're guaranteed to find the exit.
+
+So, what's the challenge in playing this game? The challenge lies in that
+now we have "stamina." Stamina is a number (we start with 10). And, each
+time the player makes a move, its stamina gets reduced by the amount of work
+needed to cross the current trail (represented by a number contained in the
+value constructor).
+
+The data types and functions are pretty much the same, with a few caveats:
+
+- We don't have walls.
+- We don't want to choose a specific numeric type, but we want to make sure
+we can do basic numeric operations regardless of the type we pass to the functions.
+- Because now we have to keep track of the player's stamina, we'll need to
+move it around with our current forest. This would be an awesome use case
+for monads, but because we don't know how to use them yet, a "(stamina, forest)"
+pair will have to do.
+
+Using GHCi, like the "Maze" game, this game should look like this:
+
+*Main> solveForest testForest []
+"You have 10 stamina, and you're still inside the Forest. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+*Main> solveForest testForest [GoForward ]
+"You have 7 stamina, and you're still inside the Forest. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+*Main> solveForest testForest [GoForward, GoForward]
+"You have 4 stamina, and you're still inside the Forest. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+*Main> solveForest testForest [GoForward, GoForward, GoLeft ]
+"You ran out of stamina and died -.-!"
+*Main> solveForest testForest [GoForward, GoLeft , GoRight]
+"YOU'VE FOUND THE EXIT!!"
+-}
diff --git a/Homework/Homework10/Homework.hs b/Homework/Homework10/Homework.hs
new file mode 100644
index 00000000..cb3367a2
--- /dev/null
+++ b/Homework/Homework10/Homework.hs
@@ -0,0 +1,60 @@
+{-
+-- Question 1 --
+Continuing with the logistics software of the lesson:
+ 1. After using the `Container` type class for a while, you realize that it might need a few adjustments:
+ - First, write down the `Container` type class and its instances, same as we did in the lesson
+ (try to do it without looking and check at the end or if you get stuck).
+ - Then, add a function called `unwrap` that gives you back the value inside a container.
+ 2. Create an instance for the `MailedBox` data type.
+ - The MailedBox data type represents a box sent through the mail.
+ - The parameter `t` is a tag with a person's identifier
+ - The parameter `d` is the person's details (address,etc).
+ - The parameter `a` is the content of the MailedBox
+-}
+
+data MailedBox t d a = EmptyMailBox t d | MailBoxTo t d a
+
+-- Question 2 --
+-- Create instances for Show, Eq, and Ord for these three data types (use
+-- automatic deriving whenever possible):
+
+data Position = Intern | Junior | Senior | Manager | Chief
+
+data Experience = Programming | Managing | Leading
+
+type Address = String
+
+data Salary = USD Double | EUR Double
+
+data Relationship
+ = Contractor Position Experience Salary Address
+ | Employee Position Experience Salary Address
+
+data Pokemon = Pokemon
+ { pName :: String,
+ pType :: [String],
+ pGeneration :: Int,
+ pPokeDexNum :: Int
+ }
+
+charizard = Pokemon "Charizard" ["Fire", "Flying"] 1 6
+
+venusaur = Pokemon "Venusaur" ["Grass", "Poison"] 1 3
+
+-- Question 3 -- EXTRA CREDITS
+-- Uncomment the next code and make it work (Google what you don't know).
+
+-- -- Team memeber experience in years
+-- newtype Exp = Exp Double
+--
+-- -- Team memeber data
+-- type TeamMember = (String, Exp)
+--
+-- -- List of memeber of the team
+-- team :: [TeamMember]
+-- team = [("John", Exp 5), ("Rick", Exp 2), ("Mary", Exp 6)]
+--
+-- -- Function to check the combined experience of the team
+-- -- This function applied to `team` using GHCi should work
+-- combineExp :: [TeamMember] -> Exp
+-- combineExp = foldr ((+) . snd) 0
diff --git a/Homework/Homework11/Homework.hs b/Homework/Homework11/Homework.hs
new file mode 100644
index 00000000..77c4d7df
--- /dev/null
+++ b/Homework/Homework11/Homework.hs
@@ -0,0 +1,105 @@
+import Data.List
+import System.CPUTime (getCPUTime)
+import System.Directory (doesFileExist, listDirectory)
+import Text.XHtml (thead)
+
+{-
+We imported some functions that you'll need to complete the homework.
+FilePath is just a synonym for String. Although, make sure to follow the standard path
+representation when using them (https://en.wikipedia.org/wiki/Path_(computing).
+getCPUTime :: IO Integer
+doesFileExist :: FilePath -> IO Bool
+listDirectory :: FilePath -> IO [FilePath]
+You can hover over the functions to know what they do.
+-}
+
+{-
+-- Question 1 --
+Define an IO action that counts the number of all enteries in the current directory
+and prints it to the terminal inside a string message.
+(hidden files are not included)
+-}
+
+-- listFiles :: IO ()
+
+{-
+-- Question 2 --
+Write an IO action that asks the user to type something, then writes the message
+to a file called msg.txt, and after that, it reads the text from the msg.txt
+file and prints it back. Use the writeFile and readFile functions.
+-}
+
+-- createMsg :: IO ()
+
+
+{-
+-- Context for Questions 3 and 4 --
+In cryptography, prime numbers (positive integers only divisible by themselves and 1) play a fundamental
+role in providing unbreakable mathematical structures. These structures, in turn, are leveraged to
+establish secure encryption.
+But, generating primes is a computational straining problem, as we will measure in the following exercise.
+This is because, to know whether a number is a prime number, you first need to know all the previous primes
+and then check that they are not a divisor of this number. So, this problem gets bigger and bigger!
+Our lead cryptographer provided us with 3 different algorithms (primes1, primes2, and primes3). All three
+correctly produce a list of all the prime numbers until a limit (that we provide as a parameter).
+Our job is not to understand these algorithms but to measure which is the fastest and print the largest
+prime number below our limit. Do it step by step, starting with question 3.
+-}
+
+primes1 :: Integer -> [Integer]
+primes1 m = sieve [2 .. m]
+ where
+ sieve [] = []
+ sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
+
+primes2 :: Integer -> [Integer]
+primes2 m = sieve [2 .. m]
+ where
+ sieve (x : xs) = x : sieve (xs \\ [x, x + x .. m])
+ sieve [] = []
+
+primes3 :: Integer -> [Integer]
+primes3 m = turner [2 .. m]
+ where
+ turner [] = []
+ turner (p : xs) = p : turner [x | x <- xs, x < p * p || rem x p /= 0]
+
+{-
+-- Question 3 --
+Define an IO action that takes an IO action as input and calculates the time it takes to execute.
+Use the getCPUTime :: IO Integer function to get the CPU time before and after the IO action.
+The CPU time here is given in picoseconds (which is 1/1000000000000th of a second).
+-}
+
+-- timeIO :: IO a -> IO ()
+
+
+{-
+-- Question 4 --
+Write an action that retrieves a value from the standard input, parses it as an integer,
+and compares the time all three algorithms take to produce the largest prime before the
+limit. Print the number and time to the standard output.
+-}
+
+-- benchmark :: IO ()
+
+{-
+ -- Question 5 -- EXTRA CREDITS -- (In case the previous ones were too easy)
+Write a program that prints the directory tree structure from the current folder.
+Below you can see an example output of how such a structure looks like:
+.
+├── foo1.hs
+├── foo2.hs
+├── bar1
+ ├── bar2
+ ├── foo3.hs
+ ├── foo4.hs
+ └── bar3
+ └── foo5.hs
+└── bar5
+ ├── bar6
+ ├── foo6.hs
+ └── foo7.hs
+HINT: You can use the function doesFileExist, which takes in a FilePath and returns
+True if the argument file exists and is not a directory, and False otherwise.
+-}
diff --git a/Homework/Homework14/ForestGameHWSolution/CHANGELOG.md b/Homework/Homework14/ForestGameHWSolution/CHANGELOG.md
new file mode 100644
index 00000000..a76db143
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for ForestModule
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/Homework/Homework14/ForestGameHWSolution/ForestGameHWSolution.cabal b/Homework/Homework14/ForestGameHWSolution/ForestGameHWSolution.cabal
new file mode 100644
index 00000000..bd0c923f
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/ForestGameHWSolution.cabal
@@ -0,0 +1,21 @@
+cabal-version: 3.0
+name: ForestGame
+version: 0.1.0.0
+license: MIT
+license-file: LICENSE
+author: Robertino Martinez
+maintainer: robertino.martinez@iohk.io
+category: Game
+build-type: Simple
+extra-doc-files: CHANGELOG.md
+
+
+executable ForestModule
+ import: warnings
+ main-is: Main.hs
+ other-modules: Forest.Level1
+ , User.Actions.Move
+ build-depends: base ^>=4.16.4.0
+ , random
+ hs-source-dirs: app
+ default-language: Haskell2010
diff --git a/Homework/Homework14/ForestGameHWSolution/LICENSE b/Homework/Homework14/ForestGameHWSolution/LICENSE
new file mode 100644
index 00000000..ecfc812d
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2023 Robertino Martinez
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Homework/Homework14/ForestGameHWSolution/app/Main.hs b/Homework/Homework14/ForestGameHWSolution/app/Main.hs
new file mode 100644
index 00000000..4084cb01
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/app/Main.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE TypeApplications #-}
+
+module Main where
+
+import Forest.Level1 (Forest (..), level1forest)
+import System.Random (randomRIO)
+import User.Actions.Move (AvailableMoves, move)
+import User.Actions.Battle (battle)
+
+main :: IO ()
+main = do
+ startingStamina <- randomRIO @Int (10_000, 20_000)
+ putStrLn "\nYou're traped in a Forest, try to scape! Remember that you loose stamina with each step you take."
+ gameLoop (startingStamina, level1forest)
+ where
+ gameLoop (_, FoundExit) = putStrLn "YOU'VE FOUND THE EXIT!!"
+ gameLoop (s, _) | s <= 0 = putStrLn "You ran out of stamina and died -.-!"
+ gameLoop (s, forest) = do
+ let continueLoop = do
+ putStrLn $ "\nYou have " ++ show s ++ " stamina, and you're still inside the Forest. Choose a path, brave adventurer: GoLeft, GoRight, or GoForward."
+ selectedMove <- getLine
+ gameLoop $ move (s, forest) (read @AvailableMoves selectedMove)
+ battleDice <- randomRIO @Int (0, 3)
+ case battleDice of
+ 2 -> do
+ r <- battle
+ if r then continueLoop else return ()
+ _ -> continueLoop
\ No newline at end of file
diff --git a/Homework/Homework14/ForestGameHWSolution/src/Forest/Level1.hs b/Homework/Homework14/ForestGameHWSolution/src/Forest/Level1.hs
new file mode 100644
index 00000000..bd68ba8f
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/src/Forest/Level1.hs
@@ -0,0 +1,26 @@
+module Forest.Level1 (Forest (..), level1forest) where
+
+data Forest a = FoundExit | Trail a (Forest a) (Forest a) (Forest a) deriving (Show)
+
+level1forest :: (Num a, Ord a) => Forest a
+level1forest =
+ Trail
+ 3_000
+ ( Trail
+ 7_000
+ (Trail 3_000 FoundExit FoundExit FoundExit)
+ (Trail 4_000 FoundExit FoundExit FoundExit)
+ (Trail 5_000 FoundExit FoundExit FoundExit)
+ )
+ ( Trail
+ 3_000
+ (Trail 3_000 FoundExit FoundExit FoundExit)
+ (Trail 9_000 FoundExit FoundExit FoundExit)
+ (Trail 5_000 FoundExit FoundExit FoundExit)
+ )
+ ( Trail
+ 5_000
+ (Trail 3_000 FoundExit FoundExit FoundExit)
+ (Trail 4_000 FoundExit FoundExit FoundExit)
+ (Trail 1_000 FoundExit FoundExit FoundExit)
+ )
diff --git a/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Battle.hs b/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Battle.hs
new file mode 100644
index 00000000..98e344ed
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Battle.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module User.Actions.Battle where
+
+data Golem = Golem { gAttack :: Int, gHp :: Int } deriving Show
+data Player = Player { pAttack :: Int, pHp :: Int } deriving (Show)
+data Battle = Fight | RunAway deriving (Show, Read)
+
+battle :: IO Bool
+battle = undefined
\ No newline at end of file
diff --git a/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Move.hs b/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Move.hs
new file mode 100644
index 00000000..83c6981f
--- /dev/null
+++ b/Homework/Homework14/ForestGameHWSolution/src/User/Actions/Move.hs
@@ -0,0 +1,11 @@
+module User.Actions.Move (AvailableMoves (..), move) where
+
+import Forest.Level1 (Forest (..))
+
+data AvailableMoves = GoForward | GoLeft | GoRight deriving (Show, Read)
+
+move :: Num a => (a, Forest a) -> AvailableMoves -> (a, Forest a)
+move (s, FoundExit) _ = (s, FoundExit)
+move (s, Trail a x _ _) GoLeft = (s - a, x)
+move (s, Trail a _ x _) GoForward = (s - a, x)
+move (s, Trail a _ _ x) GoRight = (s - a, x)
\ No newline at end of file
diff --git a/Homework/Homework15/CHANGELOG.md b/Homework/Homework15/CHANGELOG.md
new file mode 100644
index 00000000..4644279b
--- /dev/null
+++ b/Homework/Homework15/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for Homework15
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/Homework/Homework15/Homework15.cabal b/Homework/Homework15/Homework15.cabal
new file mode 100644
index 00000000..f921658d
--- /dev/null
+++ b/Homework/Homework15/Homework15.cabal
@@ -0,0 +1,27 @@
+cabal-version: 3.0
+name: Homework15
+version: 0.1.0.0
+license: MIT
+license-file: LICENSE
+author: Robertino Martinez
+maintainer: robertino.martinez@iohk.io
+build-type: Simple
+extra-doc-files: CHANGELOG.md
+
+common warnings
+ ghc-options: -Wall
+
+executable Homework15A
+ import: warnings
+ main-is: Homework15A.hs
+ build-depends: base ^>=4.16.4.0
+ hs-source-dirs: app
+ default-language: Haskell2010
+
+
+library
+ import: warnings
+ build-depends: base ^>=4.16.4.0
+ hs-source-dirs: src
+ exposed-modules: Homework15B
+ default-language: Haskell2010
diff --git a/Homework/Homework15/LICENSE b/Homework/Homework15/LICENSE
new file mode 100644
index 00000000..ecfc812d
--- /dev/null
+++ b/Homework/Homework15/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2023 Robertino Martinez
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Homework/Homework15/README.md b/Homework/Homework15/README.md
new file mode 100644
index 00000000..3ed70eb1
--- /dev/null
+++ b/Homework/Homework15/README.md
@@ -0,0 +1,6 @@
+
+## Homework
+
+1. Read the [Data.Maybe](https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Maybe.html) and [Data.Either](https://hackage.haskell.org/package/base-4.18.0.0/docs/Data-Either.html) modules.
+2. Read the [Control.Exception](https://hackage.haskell.org/package/base-4.18.0.0/docs/Control-Exception.html) module skipping the "Asynchronous Exceptions" section.
+3. Solve Homework15A (inside `app` directory) and Homework15B (inside `src`).
\ No newline at end of file
diff --git a/Homework/Homework15/app/Homework15A.hs b/Homework/Homework15/app/Homework15A.hs
new file mode 100644
index 00000000..b6cae1c7
--- /dev/null
+++ b/Homework/Homework15/app/Homework15A.hs
@@ -0,0 +1,58 @@
+module Main where
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-- IMPORTANT: Read the README.md file before completing the homework.
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+-- This is a CLI application that allows the user to manage a TODO list.
+-- The user can add and remove items from the list and save and load the list
+-- from a file.
+-- It's a working prototype, but it has some bugs. Specifically, it doesn't
+-- handle errors very well. Your mission, should you choose to accept it, is to
+-- fix those bugs and make the application more robust. Hint: Try to interact
+-- with the application in unexpected ways and see what happens! You should be
+-- able to find and fix 3 bugs.
+
+printTodoItem :: (Int, String) -> IO ()
+printTodoItem (n, todo) = putStrLn (show n ++ ": " ++ todo)
+
+prompt :: [String] -> IO ()
+prompt todos = do
+ putStrLn ""
+ putStrLn "Current TODO list:"
+ foldr (\x k -> printTodoItem x >> k) (return ()) (zip [0 ..] todos)
+ command <- getLine
+ interpretCommand command todos
+
+delete :: Int -> [a] -> [a]
+delete 0 (_ : as) = as
+delete _ [] = []
+delete n (a : as) = a : delete (n - 1) as
+
+interpretCommand :: String -> [String] -> IO ()
+interpretCommand cmd todos = case cmd of
+ "q" -> return ()
+ ('+' : ' ' : todo) -> prompt (todo : todos)
+ ('-' : ' ' : num) -> prompt $ delete (read num) todos
+ ('s' : ' ' : fn) ->
+ writeFile fn (show todos)
+ ('l' : ' ' : fn) -> readFile fn >>= prompt . read
+ _ -> do
+ putStrLn ("Invalid command: `" ++ cmd ++ "`")
+ prompt todos
+
+printCommands :: IO ()
+printCommands = do
+ putStrLn "Commands:"
+ putStrLn "+ - - Add a TODO entry"
+ putStrLn "-
- - Delete the numbered entry"
+ putStrLn "s - Save the current list of TODOs"
+ putStrLn "l - Load the saved list of TODOs"
+ putStrLn "q - Quit without saving"
+
+main :: IO ()
+main = do
+ printCommands
+ prompt []
diff --git a/Homework/Homework15/hye.yml b/Homework/Homework15/hye.yml
new file mode 100644
index 00000000..3f64a4eb
--- /dev/null
+++ b/Homework/Homework15/hye.yml
@@ -0,0 +1,3 @@
+cradle:
+ cabal:
+
diff --git a/Homework/Homework15/src/Homework15B.hs b/Homework/Homework15/src/Homework15B.hs
new file mode 100644
index 00000000..2da0464c
--- /dev/null
+++ b/Homework/Homework15/src/Homework15B.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Homework15B where
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-- IMPORTANT: Read the README.md file before completing the homework.
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+-- 1. Write a function that takes a list and returns the head if the list is not empty.
+-- If the list is empty, return Nothing.
+
+headMaybe :: [a] -> Maybe a
+headMaybe = undefined
+
+-- 2. Write a function that takes a list of Maybe values and returns a list of all the Just values.
+-- If there are no Just values, return an empty list.
+
+catMaybes :: [Maybe a] -> [a]
+catMaybes = undefined
+
+-- 3. Write a function that tries to read from a file and returns the contents of the file.
+-- If the file does not exist, return Nothing.
+
+readFileMaybe :: FilePath -> IO (Maybe String)
+readFileMaybe = undefined
+
+-- 4. Write a function that checks all the requirements for a password using the
+-- Either type with a custom data type for errors.
+-- The requirements are:
+-- - The password must be at least 10 characters long.
+-- - The password must contain at least one digit.
+-- - The password must contain at least one uppercase letter.
+-- - The password must contain at least one lowercase letter.
+
+data PasswordError = WrongConstructor
+
+passwordLongEnough :: String -> Either PasswordError String
+passwordLongEnough = undefined
+
+passwordHasDigit :: String -> Either PasswordError String
+passwordHasDigit = undefined
+
+passwordHasUppercase :: String -> Either PasswordError String
+passwordHasUppercase = undefined
+
+passwordHasLowercase :: String -> Either PasswordError String
+passwordHasLowercase = undefined
+
+passwordRequirements :: String -> Either PasswordError String
+passwordRequirements = undefined
diff --git a/Homework/Homework16/README.md b/Homework/Homework16/README.md
new file mode 100644
index 00000000..ffc9f975
--- /dev/null
+++ b/Homework/Homework16/README.md
@@ -0,0 +1,14 @@
+# SECTION'S FINAL PROJECT
+
+Congratulations!! 🥳🎉 you finished the "GAINING INDEPENDENCE" section of the course. 🔥 That means you should be able to build a simple project from start to finish by yourself. So, that's what you're going to do for this homework! 😃
+
+Create and configure a new project using Cabal, and build a Tic-Tac-Toe game that follows these requirements:
+
+## Requirements:
+
+- It has to be a CLI executable with the business logic implemented as a library.
+- There has to be a single-player (against the machine) and a multiplayer (two-player) mode.
+- The machine has to play randomly.
+- The board has to be printed nicely on the console.
+- Use any library you want. However, the provided solution will only use the `random` library and Haskell features explained up until now. So, if you're tempted to use more advanced features, you're likely overcomplicating it.
+
diff --git a/Homework/Homework17/Monoid.hs b/Homework/Homework17/Monoid.hs
new file mode 100644
index 00000000..8c51fc7c
--- /dev/null
+++ b/Homework/Homework17/Monoid.hs
@@ -0,0 +1,174 @@
+--------------------------------------------------------------------------------------------
+----------------------------------------- Boolean ------------------------------------------
+{-
+ - Define all possible Monoid instances for `Bool` same as we did for `Int` and `Ord`
+ - in the lesson.
+ -}
+
+newtype All = All { getAll :: Bool } deriving (Show, Eq)
+newtype Any = Any { getAny :: Bool } deriving (Show, Eq)
+
+-- TODO: Define the Semigroup and Monoid instances for All and Any
+
+-- Test it out
+
+--- >>> All True <> All False <> All True
+-- All {getAll = False}
+
+--- >>> (All False <> All True) <> All True == All False <> (All True <> All True)
+-- True
+
+--- >>> Any True <> Any False <> Any True
+-- Any {getAny = True}
+
+--- >>> (Any False <> Any True) <> Any True == Any False <> (Any True <> Any True)
+-- True
+
+--- >>> All True <> mempty == All True
+-- True
+
+--- >>> mempty <> All True == All True
+-- True
+
+--- >>> Any False <> mempty == Any False
+-- True
+
+--- >>> mempty <> Any False == Any False
+-- True
+
+--- >>> mconcat [All True, All False, All True] == foldr (<>) mempty [All True, All False, All True]
+-- True
+
+--------------------------------------------------------------------------------------------
+-------------------------------------------- Log --------------------------------------------
+{-
+ - Suppose you have a system where you want to collect logs from various parts of your application.
+ - You could define a Log type and make it a Monoid, where mempty is an empty log and mappend
+ - combines two logs.
+ -}
+
+newtype Log = Log [String] deriving (Show, Eq)
+
+-- TODO: Define the Semigroup and Monoid instances for Log
+
+-- Test it out
+log1, log2, log3 :: Log
+log1 = Log ["Hello"]
+log2 = Log ["World"]
+log3 = Log ["!"]
+
+--- >>> log1 <> log2
+-- Log ["Hello","World"]
+
+--- >>> mconcat [log1, log2, log3]
+-- Log ["Hello","World","!"]
+
+--- >>> mconcat [log1, log2, log3] == log1 <> log2 <> log3
+-- True
+
+--- >>> mempty <> log1 == log1
+-- True
+
+--- >>> log1 <> mempty == log1
+-- True
+
+--- >>> (log1 <> log2) <> log3 == log1 <> (log2 <> log3)
+-- True
+
+--- >>> mconcat [log1, log2, log3] == foldr (<>) mempty [log1, log2, log3]
+-- True
+
+--------------------------------------------------------------------------------------------
+------------------------------------------ Config ------------------------------------------
+{-
+- Suppose you have a configuration that can be overridden.
+- You could define a Config type and make it a Monoid, where mempty is a default configuration
+- and mappend overrides the configuration.
+-}
+
+data Config = Config { port :: Int, host :: String } deriving (Show, Eq)
+
+-- TODO: Define the Semigroup and Monoid instances for Config
+
+-- Test it out
+
+c1, c2, c3 :: Config
+c1 = Config { port = 8080, host = "localhost" }
+c2 = Config { port = 3000, host = "localhost" }
+c3 = Config { port = 4000, host = "example.com" }
+
+--- >>> c1 <> c2
+-- Config {port = 3000, host = "localhost"}
+
+--- >>> c1 <> c2 <> c3
+-- Config {port = 4000, host = "example.com"}
+
+--- >>> mconcat [c1, c2, c3]
+-- Config {port = 4000, host = "example.com"}
+
+--- >>> mempty <> c3
+-- Config {port = 4000, host = "example.com"}
+
+--- >>> c3 <> mempty
+-- Config {port = 4000, host = "example.com"}
+
+--- >>> (c1 <> c2) <> c3 == c1 <> (c2 <> c3)
+-- True
+
+--- >>> mconcat [c1, c2, c3] == foldr (<>) mempty [c1, c2, c3]
+-- True
+
+--------------------------------------------------------------------------------------------
+---------------------------------------- Emergency -----------------------------------------
+{-
+ - Suppose you have a system that can raise emergencies of various severities.
+ - We'll use the same `Severity` type as in the lesson, but now it's going to be part of the
+ - `Emergency` type. The `Emergency` type has to contain:
+ - 1. A Severity
+ - 2. A description of the emergency
+ - 3. A flag indicating whether the emergency has been resolved
+ - Once you define the type, make it a Monoid.
+ -}
+
+data Severity = Low | Medium | High | Critical deriving (Show, Eq, Ord)
+
+-- TODO: Define the Semigroup and Monoid instances for Severity
+
+-- TODO: Define the Emergency type
+
+-- TODO: Define the Semigroup and Monoid instances for Emergency
+
+-- Test it out
+
+e1, e2, e3, e4 :: Emergency
+e1 = Emergency Low "You left the lights on; " (All True)
+e2 = Emergency Medium "You might have left the stove on, but you're not sure; " (All False)
+e3 = Emergency High "The building is on fire; " (All True)
+e4 = Emergency Critical "You mother-in-law is coming over!; " (All False)
+
+--- >>> e1 <> e2
+-- Emergency {severity = Medium, description = "You left the lights on; You might have left the stove on, but you're not sure; ", resolved = All {getAll = False}}
+
+--- >>> e1 <> e2 <> e3 <> e4
+-- Emergency {severity = Critical, description = "You left the lights on; You might have left the stove on, but you're not sure; The building is on fire; You mother-in-law is coming over!; ", resolved = All {getAll = False}}
+
+--- >>> e1 <> e2 <> e3 <> e4 == mconcat [e1, e2, e3, e4]
+-- True
+
+--- >>> mempty <> e4
+-- Emergency {severity = Critical, description = "You mother-in-law is coming over!; ", resolved = All {getAll = False}}
+
+--- >>> e4 <> mempty
+-- Emergency {severity = Critical, description = "You mother-in-law is coming over!; ", resolved = All {getAll = False}}
+
+--- >>> e3 <> mempty == e3
+-- True
+
+--- >>> mempty <> e3 == e3
+-- True
+
+--- >>> (e1 <> e2) <> e3 == e1 <> (e2 <> e3)
+-- True
+
+--- >>> mconcat [e1, e2, e3, e4] == foldr (<>) mempty [e1, e2, e3, e4]
+-- True
diff --git a/Homework/Homework17/Semigroup.hs b/Homework/Homework17/Semigroup.hs
new file mode 100644
index 00000000..7d0d2434
--- /dev/null
+++ b/Homework/Homework17/Semigroup.hs
@@ -0,0 +1,41 @@
+import Data.Monoid
+import Data.Semigroup
+
+--------------------------------------------------------------------------------------------
+---------------------------------------- NonEmpty ------------------------------------------
+
+-- This is the type of a NonEmpty list:
+data NonEmpty a = a :| [a] deriving (Show, Eq)
+
+l1, l2, l3 :: NonEmpty Int
+l1 = 1 :| [2, 3]
+l2 = 4 :| [5, 6]
+l3 = 7 :| [8, 9]
+
+l4, l5 :: NonEmpty Char
+l4 = 'h' :| "ello"
+l5 = 'w' :| "orld"
+
+--- As you can see, because we alwas have to provide the first `a`, we can never have an empty list.
+-- Now, define a Semigroup instance for NonEmpty:
+
+-- TODO: Define the Semigroup instance for NonEmpty
+
+-- Test it out
+
+--- >>> l1 <> l2
+-- 1 :| [2,3,4,5,6]
+
+--- >>> l4 <> l5
+-- 'h' :| "elloworld"
+
+--- >>> (l1 <> l2) <> l3 == l1 <> (l2 <> l3)
+-- True
+
+-- Now, try to define a Monoid instance for NonEmpty.
+-- You'll notice that it's not possible to do so because there's
+-- no identity element for NonEmpty.
+
+-- instance Monoid (NonEmpty a) where
+ -- mempty = undefined
+
diff --git a/Homework/Homework18/Homework.hs b/Homework/Homework18/Homework.hs
new file mode 100644
index 00000000..cc190204
--- /dev/null
+++ b/Homework/Homework18/Homework.hs
@@ -0,0 +1,123 @@
+{-# LANGUAGE InstanceSigs #-}
+
+import Data.Functor
+import Data.Semigroup (Sum (..))
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 1 ------------------------------------------------
+{-
+Question 1: Implement the Functor instance for a RoseTree type.
+A RoseTree is a tree where each node can have any number of children.
+It's a bit more complicated than the binary tree we saw in the lecture,
+but here's the trick to solve it: Follow the types!
+-}
+
+data RoseTree a = RoseNode a [RoseTree a] deriving (Eq, Show)
+
+exampleRoseTree :: RoseTree Int
+exampleRoseTree = RoseNode 1 [RoseNode 2 [], RoseNode 3 [RoseNode 4 []]]
+
+-- TODO: Implement the Functor instance for RoseTree
+
+-- Test it out:
+
+--- >>> fmap (+1) exampleRoseTree
+-- RoseNode 2 [RoseNode 3 [],RoseNode 4 [RoseNode 5 []]]
+
+--- >>> (*2) <$> exampleRoseTree
+-- RoseNode 2 [RoseNode 4 [],RoseNode 6 [RoseNode 8 []]]
+
+--- >>> exampleRoseTree $> "Yes!"
+-- RoseNode "Yes!" [RoseNode "Yes!" [],RoseNode "Yes!" [RoseNode "Yes!" []]]
+
+--- >>> (id <$> exampleRoseTree) == id exampleRoseTree
+-- True
+
+----------------------------------------------------------------------------------------------------
+---------------------------------- QUESTION 2 - Introduction ---------------------------------------
+{-
+You're writing an app that queries the database for a list of items (products) and then performs
+several transformation on these items, such as calculating the taxes, adding them to get the
+total value, etc. The database query that returns the list of items might fail, so the result of
+the query is wrapped in a Maybe.
+-}
+
+-- Type representing an item (product)
+newtype Item a = Item {getItem :: (String, Sum a)} deriving (Eq, Show)
+
+exampleItem :: Item Double
+exampleItem = Item ("Phone", Sum 50.0)
+
+-- Type of the result of the database query
+type DBResponse = Maybe [Item Double]
+
+-- Example of a database query result
+dbResult :: DBResponse
+dbResult = Just [Item ("Phone", Sum 50.0), Item ("Glasses", Sum 30.0)]
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 2 A ----------------------------------------------
+{-
+Write the Functor instance for Item.
+-}
+
+-- TODO
+
+-- >>> fmap (*2) exampleItem
+-- Item {getItem = ("Phone",Sum {getSum = 100.0})}
+
+-- >>> fmap id exampleItem == id exampleItem
+-- True
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 2 B ----------------------------------------------
+{-
+Write a function that gives you all the items of the list for free (price = 0.0).
+-}
+
+giveForFree :: DBResponse -> DBResponse
+giveForFree = undefined -- TODO
+
+-- >>> giveForFree dbResult
+-- Just [Item {getItem = ("Phone",Sum {getSum = 0.0})},Item {getItem = ("Glasses",Sum {getSum = 0.0})}]
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 2 C ----------------------------------------------
+{-
+Write a function that changes the products prices by applying a tax of 20%.
+-}
+
+applyTaxes :: DBResponse -> DBResponse
+applyTaxes = undefined -- TODO
+
+-- >>> applyTaxes dbResult
+-- Just [Item {getItem = ("Phone",Sum {getSum = 60.0})},Item {getItem = ("Glasses",Sum {getSum = 36.0})}]
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 2 D ----------------------------------------------
+{-
+Write a function that marks the products as discounted (in the name) and applies the discount (that
+goes from 0.0 to 1.0) to the price.
+-}
+
+markOnSale :: Double -> DBResponse -> DBResponse
+markOnSale perc = undefined -- TODO
+
+-- >>> markOnSale 0.3 dbResult
+-- Just [Item {getItem = ("Phone (30.0% Discount)",Sum {getSum = 35.0})},Item {getItem = ("Glasses (30.0% Discount)",Sum {getSum = 21.0})}]
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- QUESTION 2 E ----------------------------------------------
+{-
+Write a function that returns a pair with the list of items as first argument and the total price
+as second argument.
+-}
+
+listItemsWithFinalPrice :: DBResponse -> Maybe ([String], Double)
+listItemsWithFinalPrice = undefined -- TODO
+
+-- >>> listItemsWithFinalPrice dbResult
+-- Just (["Phone","Glasses"],80.0)
+
+-- >>> listItemsWithFinalPrice . applyTaxes . markOnSale 0.3 $ dbResult
+-- Just (["Phone (30.0% Discount)","Glasses (30.0% Discount)"],67.2)
diff --git a/Homework/Homework19/Homework19.hs b/Homework/Homework19/Homework19.hs
new file mode 100644
index 00000000..a095e0c9
--- /dev/null
+++ b/Homework/Homework19/Homework19.hs
@@ -0,0 +1,52 @@
+----------------------------------------------------------------------------------------------------
+---------------------------------------- Question 1 ------------------------------------------------
+{-
+-- Write the specialized types of all the `Functor` and `Applicative` operators
+-- in the `result` expression.
+-}
+
+eDiv :: Float -> Float -> Either String Float
+eDiv x 0 = Left "division by zero"
+eDiv x y = Right (x / y)
+
+
+
+-- ?
+-- |
+result :: Either String Float -- |
+result = (\x y z -> x * y + z) <$> (3 `eDiv` 2) <*> Right 2 <*> pure 4
+-- | |_______________________________________________
+-- | |
+-- ? |
+-- |
+-- _______________________________________________________|
+-- |
+-- ?
+
+
+
+----------------------------------------------------------------------------------------------------
+---------------------------------------- Question 2 ------------------------------------------------
+{-
+ - Implement all these Applicative functions
+-}
+
+-- Conditional execution of 'Applicative' expressions. For example,
+-- > when debug (putStrLn "Debugging")
+-- will output the string @Debugging@ if the Boolean value @debug@
+-- is 'True', and otherwise do nothing.
+when :: (Applicative f) => Bool -> f () -> f ()
+when p s = undefined
+
+-- The reverse of 'when'.
+unless :: (Applicative f) => Bool -> f () -> f ()
+unless p s = undefined
+
+-- Like 'replicateM', but discards the result.
+replicateM_ :: (Applicative m) => Int -> m a -> m ()
+replicateM_ n action = undefined
+
+
+--------------------------------------------------------------------------------------------------------
+-- NEXT LESSON IS GOING TO BE ABOUT USING APPLICATIVE ON A REAL WORLD PROBLEM. SO, WE'LL CUT IT HERE.
+--------------------------------------------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index d6456956..f433b1a5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -175,28 +175,3 @@
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/README.md b/README.md
index 97f41aec..d2db700b 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
# Haskell Course
-[Versión en 🇪🇸](https://github.com/input-output-hk/haskell-course/tree/main/ES-translation)
+[Versión en 🇪🇸 traducida por la comunidad](https://github.com/input-output-hk/haskell-course/tree/main/ES-translation)
-**This course is designed to teach students Haskell from zero to everything needed to work with Marlowe and Plutus.** The course itself doesn't contain content specific to Marlowe or Plutus. So, if you want to use it to learn Haskell for other purposes, you can! 😃
+> *The easiest way to learn Haskell* - R.M.
+
+**This course is designed to teach non-engineers (e.g., self-taught/bootcamp coders) Haskell from zero to productive in an interactive, easy-to-follow way.** The course doesn't contain content specific to Marlowe or Plutus, but it will cover all the Haskell you'll needed to work with them.
For a more detailed explanation, keep reading or watch the introduction video:
[](https://youtu.be/H1vbUKMKvnM)
@@ -13,11 +15,11 @@ In the [outline](#what-well-cover) below, there are clear stopping points (for b
## How to read/watch the lessons
-To go through the interactive lessons, go to your chosen lesson's outline inside "[What we'll cover](#what-well-cover)" and click on the button that looks like the one below. If the page loads with a "500: Internal Server Error" just refresh it, and it should be fine. At the top, you will see a console that displays the progress of preparing your interactive lesson. During this time, you can scroll down and look at the lesson, that is displayed non-interactively.
+To go through the **interactive lessons**, go to your chosen lesson's outline inside "[What we'll cover](#what-well-cover)" and click on the button that looks like the one below. If the page loads with a "500: Internal Server Error" just refresh it, and it should be fine. At the top, you will see a console that displays the progress of preparing your interactive lesson. During this time, you can scroll down and look at the lesson, that is displayed non-interactively.
[](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F01-Introduction-to-haskell.ipynb)
-And to see the video, click on the button that looks like this:
+And to see the **video lessons**, click on the button that looks like this:
[](https://youtu.be/H1vbUKMKvnM)
@@ -49,7 +51,6 @@ Everything else can be safely ignored
## To hang out and discuss with other students
-- [Canvas](https://iohk.instructure.com/enroll/3BAAGG)
- [IOG's technical community (check out the #ask-haskell channel!)](https://discord.gg/inputoutput)
## FAQ
@@ -68,7 +69,8 @@ Everything else can be safely ignored
---
-### START OF THE BEGINNER SECTION 🐣⟶🐥
+### BEGINNER SECTION - GETTING STARTED WITH HASKELL - 🥚⟶🐣
+In this section, we get familiar with basic concepts and Haskell syntax.
---
@@ -175,156 +177,196 @@ Everything else can be safely ignored
- Value parameters
- Record syntax
-### 9. Creating Parameterized and Recursive Types
+### 9. Creating Parameterized and Recursive Types [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F09-Creating-parameterized-and-recursive-types.ipynb) [](https://www.youtube.com/watch?v=wPV94aZIOGQ)
- Type Parameters
- - Prameteryzing type synonyms
- - Prameteryzing data types
+ - Prameteryzing `type` synonyms
+ - Prameteryzing `data` types
- Recursive data types
- `Tweet` me a river
- A `Sequence` of `Node`s
- A `Tree` of `Node`s
-- The `newType` keyword
- Kinds
+- The `newType` keyword
-### 10. Creating Type Classes and Instances
+### 10. Creating Type Classes and Instances [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F10-Creating-Type-Classes-and-Instances.ipynb) [](https://www.youtube.com/watch?v=I6tmM3wNGEI)
-- Revisiting Type Classes
+- Overloading
+- Steps to create Type Classes and Instances
- The `Eq` type class
- - Defining the `Eq` type class
- - Defining an instance for the `Eq` type class
- - Improving our `Eq` type class (minimal complete definition)
- - Defining an instance for a parameterize type.
-- The `Ord` type class
- - Exploring `Ord` type class (Subclassing)
+ - Defining the Type Class
+ - Defining multiple instances
+ - Improving our `Eq` type class with mutual recursion (and Minimal Complete Definitions)
+ - Defining an instance for a parameterized type.
+- The `WeAccept` Type Class
+- The `Container` Type Class
+- Exploring `Ord` type class (Subclassing)
- Deriving
-- Complete example
-
-### 11. Basic IO
-
-- We need side effects
-- What is IO
-- main + putStrLn + composing other functions
-- `>>`
-- `>>=`
-- do notation
- - `do`
- - `<-`
- - `let`
-- Some examples
-- Read/Write to console
-- Read/Write to file
-
-### 12. Pragmas, Modules, and Cabal
-
-- Prelude
-- pragmas/extensions
-- Overview of base modules
-- Importing base modules
-- A few modules
- - Data.Char
- - Data.Tuple
- - Data.Array
-- Creating our own modules
-- Cabal
- - What is it and why we use it
- - Cabal file
- - Using external libraries with Cabal
+ - Deriving can go wrong
+
+### 11. Basic IO [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F11-Basic-IO.ipynb) [](https://www.youtube.com/watch?v=0xQ8j6h8bNc)
+
+- Pure functions
+- Introduction to IO actions
+- IO actions under the hood
+- IO actions in practice
+ - The `()` type
+- Interacting with the user
+ - `getChar`, `getLine`, and `putStrLn`
+- Actions are first-class values
+- Composing IO actions (`>>` and `>>=` operators)
+- The `do` block
+ - Using `let`, nesting do-blocks, escaping `IO` and `return`
+- The `main` action
+- Concepts and syntax recap
-### 13. Bits and Bytes
+---
-- Grouping bits and bytes
-- Haskell and bytes
-- Lazy and strict byte strings
-- Example
+### BEGINNER SECTION - GAINING INDEPENDENCE - 🐣⟶🐥
+In this section, we learn about Haskell tooling and the necessary concepts to start working on our own projects.
-### 14. Handling Errors
+---
-- TODO
+### 12. Installing Haskell Locally [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F12-Installing-Haskell-and-first-program.ipynb) [](https://www.youtube.com/watch?v=hSN5mxITv0A&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=13)
+
+- Installing Haskell
+ - Installing GHCup
+ - Installing GHC, Cabal, Stack, and HLS with GHCup
+ - Installing VSCode Extensions
+- Creating our first program
+ - Writing the simplest Haskell program
+ - Compiling and running our program
+
+### 13. Modules [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F13-Modules.ipynb) [](https://www.youtube.com/watch?v=R64sCXU0Ru0&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=14)
-### 15. Learning on your own and Map
+- Importing Modules
+ - Controlling environments
+ - Controlling namespaces
+- Creating our own Modules
+- The `Prelude` and Standard Libraries
-- Using GHCi to find out more
-- Hoogle
-- HaskellWiki
-- Walking through while teaching Map module
+### 14. Cabal and language extensions [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F14-Cabal_and_language_extensions.ipynb) [](https://www.youtube.com/watch?v=AvpMOMSSZHs&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=15)
+
+- Cabal
+ - Introduction to Cabal
+ - Creating a new Haskell project
+ - Going over the Cabal file using an external library
+ - Building and running our executable
+- Language extensions and Pragmas
+ - Introduction
+ - `NumericUnderscores`
+ - `TypeApplications`
+
+### 15. Handling Errors [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F15-Handling-Errors.ipynb) [](https://www.youtube.com/watch?v=QkUCHFG1hK8&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=16)
+
+- There're always `Exception`s to the rule
+- Speed-running `Exception`s with a dumb self-driving 🤖 car 🚗
+ - I'm the `Exception` cause I have `class` 😎
+ - `throw` all the `Exception`s you want. I'll `catch` them all!
+- `Maybe` give me a value? 🙏
+ - Benefits of optional values
+- Ok, you `Either` give me a value or a reason why you didn't!
+- From `Exception`s to optional values
+- Tradeoffs
+ - So, what should I use?
+
+### 16. Learning on your own and final section project [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F16-Gaining-your-independence-and-project.ipynb) [](https://www.youtube.com/watch?v=ObZ_1eap5MY&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=17)
+
+- Small tips and tricks 🤹
+ - REPL
+ - Hackage
+ - Hoogle
+ - `undefined`
+ - Type Holes 🕳️
+- Section's Final Project 🥳
---
-### END OF THE BEGINNER SECTION 🐥
+### BEGINNER SECTION - BASIC ABSTRACTIONS & EFFECTS - 🐥⟶🐓
+In this section, we learn about a few of the most useful and talked about Abstractions in Haskell and how we deal with effects in general (not only `IO`).
---
-#### Congratulations! 🥳 You can call yourself a (beginner) Haskell programmer!
+### 17. Semigroup and Monoid [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F17-Semigroup-and-Monoid.ipynb) [](https://www.youtube.com/watch?v=-O1ZApHPvkg&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=18)
-#### YOU'RE READY FOR THE MARLOWE PIONEER PROGRAM! 🥳🎉 (Keep going for Plutus.)
+- What does it mean to abstract a pattern?
+- Why abstracting patterns (in general)?
+- Teaser: Why abstracting `Semigroup` and `Monoid`?
+- The `Semigroup` type class
+- The `Monoid` type class
+- What can I do with `Semigroup` and `Monoid`?
----
+### 18. Functor [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F18-Functor.ipynb) [](https://www.youtube.com/watch?v=Fb4qD5PWEs8&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=19)
-### START OF THE INTERMEDIATE SECTION 🐥⟶🐓
+- Abstracting the `map` function
+- The `Functor` type class
+- Defining `Functor` instances
+- Seemingly unintuitive `Functor` instances
+ - The `Either a` functor 🤔
+ - The `(,) a` functor 🤨
+ - The `(->) r` functor 🤯
+- Defining the `<$>` operator and *lifting* 🏋️ a function
+- `Functor` nesting dolls 🪆
+- Extra functions and `Functor` as defined in `base`
----
-### 16. Monoid
+### 19. Applicative and Effects [](https://mybinder.org/v2/gh/input-output-hk/haskell-course/HEAD?labpath=%2Flessons%2F19-Applicative.ipynb) [](https://www.youtube.com/watch?v=7vxhNfNWP3k&list=PLNEK_Ejlx3x1D9Vq5kqeC3ZDEP7in4dqb&index=20)
-- Basic idea (definition without details)
-- Intuitive examples
-- Extracting the pattern
-- Complete definition (with all the details/laws)
+- Why `Applicative` functors?
+ - Our journey until now
+ - The limits of Functor
+- Function application at the `Functor` level 🥇
+- Being `pure` 😇
+- The `Applicative` type class
+- The `Applicative` laws
+- 🎆 Programming with effects 🎆
+- Extra functions and `Applicative` as defined in `base`
-### 17. Functor
+### 20. Project using `Functor` and `Applicative`
-- Basic idea (definition without details)
-- Intuitive examples
-- Extracting the pattern
-- Complete definition (with all the details/laws)
+- TODO
-### 18. Applicative
+### x. Monad
-- Basic idea (definition without details)
-- Intuitive examples
-- Extracting the pattern
-- Complete definition (with all the details/laws)
+- TODO
-### 19. Aeson
+### x. Reader, Writer, and State Monads
-- Aeson
+- TODO
-### 20. Monad
+### Maybe speed-running other type classes? 🤔
-- Basic idea (definition without details)
-- Intuitive examples
-- Extracting the pattern
-- Complete definition (with all the details/laws)
-- `do` notation in general
+- TODO
-### 21. Reader Monad
+### x. Final project
-- Incentive/Motivation
-- Binding strategy (see [here](https://wiki.haskell.org/All_About_Monads#The_Reader_monad))
-- Definition
-- Examples
+- TODO
-### 22. Writer Monad
+---
+
+#### 🥳 CONGRATULATIONS! 🥳 You can call yourself a (beginner) Haskell developer!
+Thank you for going on this journey with me! Please feel free to give us feedback through issues, email, or Twitter. And share this course if you find it valuable. 😄🙌
-- Incentive/Motivation
-- Binding strategy
-- Definition
-- Examples
+---
-### 23. State Monad
+### Where can you go from now?
-- Incentive/Motivation
-- Binding strategy
-- Definition
-- Examples
+It depends on your preferences. You could:
-### 24. Monadic functions / Operating with Monads
+- If you're interested in Cardano, explore Marlowe, Plutus, Atlas, CardanoAPI, and other Cardano-related tools and libraries.
+- Start your own project and learn what you need on the way (e.g., a server, a compiler, a full-stack app using IHP)
+- Read Haskell books and sources that go into more advanced subjects. See recommended resources here.
+- Learn about specific subjects, for example (organized roughly by difficulty):
+ 1. Explore the `base` library and understand all types and type classes.
+ 1. Learn about Testing (unit vs property testing and QuickCheck).
+ 1. Explore how to deal with Concurrency and Parallelism.
+ 1. Learn about Parsers (Parser combinators, `Alternative` type class, `MonadPlus`, Parsec library).
+ 1. Learn how Haskell deals with data structures under the Hood.
+ 1. Monad Transformers and Free monads.
+ 1. Generic programming.
+ 1. Meta programming with Template Haskell.
+ 1. Type-level programming.
+
+---
-- liftM
-- sequence and sequence\_
-- mapM and mapM\_
-- filterM
-- foldM
-### TODO: It keeps going, but I'm not sure about the outline yet. 😬
diff --git a/images/BST.png b/images/BST.png
new file mode 100644
index 00000000..a59ec7bb
Binary files /dev/null and b/images/BST.png differ
diff --git a/images/diagram_IO_api.png b/images/diagram_IO_api.png
new file mode 100644
index 00000000..d13ddd26
Binary files /dev/null and b/images/diagram_IO_api.png differ
diff --git a/images/diagram_IO_getChar.png b/images/diagram_IO_getChar.png
new file mode 100644
index 00000000..a33b7ac4
Binary files /dev/null and b/images/diagram_IO_getChar.png differ
diff --git a/images/diagram_IO_getLine.png b/images/diagram_IO_getLine.png
new file mode 100644
index 00000000..451b1e94
Binary files /dev/null and b/images/diagram_IO_getLine.png differ
diff --git a/images/diagram_IO_keyboard.png b/images/diagram_IO_keyboard.png
new file mode 100644
index 00000000..979e885f
Binary files /dev/null and b/images/diagram_IO_keyboard.png differ
diff --git a/images/diagram_IO_putStrLn.png b/images/diagram_IO_putStrLn.png
new file mode 100644
index 00000000..0fb31400
Binary files /dev/null and b/images/diagram_IO_putStrLn.png differ
diff --git a/images/diagram_IO_putStrLn_Hi.png b/images/diagram_IO_putStrLn_Hi.png
new file mode 100644
index 00000000..f4bb9e67
Binary files /dev/null and b/images/diagram_IO_putStrLn_Hi.png differ
diff --git a/images/diagram_IO_screen.png b/images/diagram_IO_screen.png
new file mode 100644
index 00000000..556c65cf
Binary files /dev/null and b/images/diagram_IO_screen.png differ
diff --git a/images/diagram_IO_something.png b/images/diagram_IO_something.png
new file mode 100644
index 00000000..452c82cc
Binary files /dev/null and b/images/diagram_IO_something.png differ
diff --git a/images/diagram_purefunc_generic.png b/images/diagram_purefunc_generic.png
new file mode 100644
index 00000000..23d02704
Binary files /dev/null and b/images/diagram_purefunc_generic.png differ
diff --git a/images/diagram_purefunc_lame.png b/images/diagram_purefunc_lame.png
new file mode 100644
index 00000000..606855e4
Binary files /dev/null and b/images/diagram_purefunc_lame.png differ
diff --git a/images/diagram_purefunc_max6.png b/images/diagram_purefunc_max6.png
new file mode 100644
index 00000000..635d9d74
Binary files /dev/null and b/images/diagram_purefunc_max6.png differ
diff --git a/images/diagrams.drawio b/images/diagrams.drawio
new file mode 100644
index 00000000..f98cf43a
--- /dev/null
+++ b/images/diagrams.drawio
@@ -0,0 +1 @@
+7Vnbbts4EP0aAe1DCt1oOY+2U7cFGmzaLLLFvhS0xEjcSKKXonzp1+9QIq0b3XWcOEmLIAgiDkdD8Zy5cBjLm2WbDxwvk0sWkdRy7WhjeReW6zq2M4Y/UrKtJcG5VwtiTiOl1Aiu6Q+i31TSkkak6CgKxlJBl11hyPKchKIjw5yzdVftlqXdVZc4JgPBdYjTofQvGomklo6R3cg/EhonemXHVjMZ1spKUCQ4YuuWyHtveTPOmKifss2MpBI8jUv93nzP7O7DOMnFIS+g+YJffdlubsLLL6tvwfif8yQ7U1ZWOC3VhtXHiq1GgLMyj4g04ljedJ1QQa6XOJSza+AcZInIUjV9y3KhSHR9GBegSfNYDmE0/GS9PuGCbFoitYUPhGVE8C2oqFnPU3Aqf/K0o6wbdpxzJUtazGgZVg4R70w3mMGDgu0eELoGCEcprDqN6KoD5ejfUpJdgXRWVChNQAEQ2VTg6Hl4itXfVOsfaciOsMBntR4POy8nQsgYmsjdu3OpUryLGYtTgpe0eBeyDMRhASrzW5zRVAJ+KbUI51js/9rFg/b8KV+WcrPaGnzDor8COFX+oEXe3Eiy3rbWqE12l1GoGMQVsVraCxfwY9GNCZzSOIfnELyecBBIb6eQYiZqIqNRJF+fcgIfjxeVKRvGS0YBb2kXTS10IW2VgtUbrEwXgrM7MmMpA7sXOcullVuapn1RKyxtNZ5rUr+yBRMMdgHsMq3MSl6FeO0lLpKAIukpaJ+voNpbQFX7CzzujCN3qsw/QhJwfNRNAq4hCYwNScA/VRLw/j+PkjyayIIkPSHFRUHDrps0eXbA0A3hEc5xl0iorNoBdGXyfgYviTqVbghuCzxkwE7LOEmxoKtufTQBqla4kk7c4g71Eviox0lRuZ56q13PeoZ2pO8zJDCPiRgYqvjdbft4yv29eX+RsvBOphzSzx2vFeH+e/6jFL9aSWi+0+AJr/XiyeuFP+6lCntYL9zAkPPGp6oX6LVeHFgvUP/Af2y9COznrRejAeXzMg8FZfnvmhPUhgyd6v3bvqBHnoeGJz73RCc+mm0vVt/Pv20+Xy28aPtnWP599yyd8wAvA6oHd87+U3bORggNnTOaji13BtzOAvj1pcP+psFx6oL5IF9xeuHmmxos7ynD7aU1WL3qeBC4z9Ng+Y/VYA0MPV7BNFJuaLDQFL2mhedLCyjo3ruY0sKprl2MLvLSjtEvNyv0j9FHZ4X+Mfqps8LwGJ3hzUheD8iBDUEog92130DHjTN5yKqa8dHbXy5jOKMjTtkPOyK6vfj2h/Ht2gYn9U4V4IEhwNtXaZqx5kJHcTe44Nl5yaS+CQKiPgEqspLYZztfUXc07dn73PoffsZ3vEPI1c5hwel9LH9MTvoYxPezQ3Bgb3AE8TBs/ulZZ4XmX8fe+/8A7Vddb9sgFP01fpnUyjZ2mjzmo0lfWm2t1O1toobYaNg3wjgf/fUDG2I7eGs7NZo2VZESOFwu5tzDCfbQPN+vBN5kt0Ao90Kf7D208MIw8IOx+tHIoUGuJqgBUsGICWqBB/ZM7UyDVozQshcoAbhkmz6YQFHQRPYwLATs+mFr4P1VNzilDvCQYO6iXxmRWYOOY7/FbyhLM7ty4JuRHNtgA5QZJrDrQOjaQ3MBIJtWvp9TrsmzvDTzlr8YPT6YoIV8zYTt9ttq9eVpzKfL+fX3ZH6Am4sLk2WLeWU2bB5WHiwDAqqCUJ0k8NBslzFJHzY40aM7VXOFZTLnZngNhTRFDCPVL1UkK1LdVT33ke36VEi670BmCysKOZXioELMKPKNoIye0MTQu2urE1gs61TGYtgIIj2mbjlTDUPbGygMHQojh0O1OdknCnOWFqqdKCqoUICmgCndTc1AzgjR02eCluwZP9WpfNXfACtkvYl45sULnauSUDak69SlFPCDzoGDyrsooNBZ1ozzU6hTK9/0lzhnXFN7D08gQe3iFgqwwVCJuu6ZlOr4hTGaqi9Fnv7SAeVlCpByijesvEwgrweSsg5drpvUqnlMHoczk/4dlBFYZzHKiHxXGdGAMKJzCQO9fLZoQabapLQQOC5LlvRV0p49p0CPVBBc4H4dldva+lu3Qr9jl5Ke+7ncdriLB7izmKAcS7bte+YQoWaFz1rDndKF/UMdndakrJVnZnU97iRROHkhkcQipdJJVNf3uO0/L3nklDxwi/5hBuc3g3h0IoQBMxgS9NnMIP4wg1eaQey/kxmMrv6uGYycknOc0/r59BFb6NYn9L/ag9nQwDX17Xe+8OSfPYjdO9/VuU5zHk7uHitMiKpI8ZhMd9X9wLXZlNZTLqiN0L+rcr2wrfQI5/qmnMr6ANb4xRD6j2khQK/Rgp3vhQiN9ee9XgVOZBG7Hh/6A7JAb5eF6rZvao1BtO+76Pon7Vdrb5swFP01SNuHVmBjknzMo2knrdLWaK32aXLBAWsGZ8Zp0v76XYN5BbqmVbdqUpWXfXx9jc85vgEHz9P9uaKb5FJGTDjIjfYOXjgIea43hh+D3JfIaIJLIFY8skENsOIPrJpp0S2PWN4J1FIKzTddMJRZxkLdwahSctcNW0vRXXVDY9YDViEVffSGRzop0TFxG/yC8TipVvZcO5LSKtgCeUIjuWtB+MzBcyWlLlvpfs6EIa/ipZy3fGS0vjDFMn3MhMXS0/R6eeP6i6tLTaJv0fzixGa5o2JrN2wvVt9XDCi5zSJmkngOnu0SrtlqQ0MzugPNAUt0KuzwWmbaioh86OcQybPYdKHXv+RqfaY027cgu4VzJlOm1T2E2FFc8Wv95FdG2TXqeBOLJS1lKoxaQ8R16oYzaFjankEh6lNIZuD6OYHPCD6+QxY9VmG7uksdFTzOoB0COUwBYEjh4MSpHUh5FJnpM8Vy/kBvi1Qu9DeSZ7rYFpmZtSDXVsu8lMGkzrWSP9lcCgl5F5nMTJY1F+IQaqnn2v6SplwYsq/krdQSdnEpM1kFy60qnJBoDQcSETyFL6DTfJmA/DSWMhaMbnh+Gsq0GAjzInS5LlNDs05O0MymfwWveP6BV9CAV/CAV/y/5RX89HFjWTQ1dcs4QdA852HXJs1x7Cl0zVREM9oVEgpwZYCqgOE/0cuiTkHsk9sijwxwV2GKCar5XbeMDhFqV/hiTNzSDh1oFxxokhfWs7PaZe8gUX3wH0ukqYqZ7iUq9K23/XLJ/aHyQN7LwtuVBeKTJ8vC+F9WBfJeFY6sCuTw3/+lVSEYvW1VCHqSp3QPoFt0XDiE5rAj94ODApqamyxh6kLw8b+rGF5wVMWwGxq4q332+UaTg/Pt9883cgdMil/hgG8+fyMiD3/cxN8nKyQuwsnX88G77KAQNOJ3ZkErDKC/tuZ5YGa1q/vQis1v7ZKpeUODzD4BUeafxD2pvRKXU9qj5XJw9cWKgY0Y9tLx9/gePkbcyhwOwnhsXkMmfQXhe88GoyOfDV4gPHSbR7eyKjQPwPjsNw==7VlNc9owEP01nmkPYWzLXxwDhOTQtEkzSTq5dBRb2Gpli5FFgP76SraMbeQ0QClpMnAAa7VeWe+93dUYAwzTxTmD0+SSRogYthktDDAybNsyrUD8SMuytPh9UBpihiPlVBtu8C9U3amsMxyhvOXIKSUcT9vGkGYZCnnLBhmj87bbhJL2qlMYI81wE0KiW+9xxJPSGrhmbb9AOE6qlS1TzaSwclaGPIERnTdM4MwAQ0YpL6/SxRARCV6FS3nf+JnZ1YMxlPFNbph9in98/nb9uDx5uI2If7+IL+GJivIEyUxtWD0sX1YIMDrLIiSDWAYYzBPM0c0UhnJ2LjgXtoSnRE1PaMYVibYjxrnwxFksh2KkP3K1PmIcLRomtYVzRFPE2VK4qFlQCUPpyXHUeF6zY/WVLWkwU9mgEkS8Cl1jJi4UbFtAaGsQfjBsD6YSGSIWH3gfNUzFZnkbOEhwnInrUECDmDBISLDQ4amaSHEUydsHDOX4F3wsQpliPKU448Wm3IHhjmSsGad5SYIMnXNGf6IhJVTEHWU0k1EmmJB1U4M7U43HMMVEQv2VPlJOxS4uaUYrZzpjhQ4SzkU62i44FV8CTPklHfJeTGlMEJzivBfStJgI88J1PClDi8tVcNceqPB7UIoVrCnF1ZXidwjF+VdCAS/nGsqiU1m0pBAIzHMctlVS56JG0B1iEcxgm0dRfSv+q+oF/oQuilrVUMe2gZ3bgV1lY4hAjp/aNbQLULXCldRwgzqwRt168uaF8tRdzZq3FgiYLwTikMWIa4EKflfb3p1yR6Nc5J3Mb9s8Fon/oUi49stFwvIOWSXcY5XYsEpo3O1aJbz+61YJ7/kq8eaKguVvVBTUhjoOsdufCD23zZ6vp3DQoUKwhwweXoSjcfTErq/u774/fJndntvxWzxTtxF0gwOeqTsh1M/UQqLiIYeO1Olby4l30Si9dY0EvcDeKNH20So7VXI8UO/aKj27Zu9vu2VXrP01zE7i3/Wx+hU6qLPGqNVR/51DttCOQ7A7CET9lz3AP/aBV3yr4ra1UvQBq19/dOWAQ/YE/Sx97AnPvGSx+70Gb33f18u6Tuy23cLut1cJtllk5zYihvWL9dK9/nsCnP0G7VjbbuIwEP2aPG6VxAmXRy6l3Uu1q0XaPpvEJFadDHJMgX79jhObkEsXumor7aoSAvvMeByfMzMGHDLL9jeSbtI7iJlwfDfeO2Tu+L7neiP80MihQoZjUgGJ5LFxqoElf2J2pUG3PGZFw1EBCMU3TTCCPGeRamBUStg13dYgmrtuaMI6wDKioove81ilFToK3Rq/ZTxJ7c6eaywZtc4GKFIaw+4EItcOmUkAVY2y/YwJTZ7lpVq3eMZ6fDDJcnXJgsPDwvO+jtX1djlffkmz4eo2/mSiPFKxNQc2D6sOlgEJ2zxmOojnkOku5YotNzTS1h1qjliqMmHMa8iVEdEPcF6gJ88TPcVZ95Ht/kwqtj+BzBFuGGRMyQO6GKs/DKslJp/CwNC7q9XxxgZLT5SxGDUJkRxD15zhwND2Agr9DoVLJfWZ20TiCVWTLSp4kuM4Qj6YREDzwDH5JsaQ8TjWy6eSFfyJrspQLs43wHNVniScOuFcx9oqKCrmdehCSXhgMxCAcec55DrKmgvRhk4Ec818QTMuNL8/YQUK8BR3kIN1hq0sxU+Vwhr0QzLBN2RQv2mH4ioBSASjG15cRZCVhqgoXRfrKjQOj8FDf2rCv0J6hLZv2PQIe9LD7UmP4K3Sg5yvMJbHE92qdCYIWhQ8aqZJXYEdhX4xGdOcNoXEnmsTwPYs8id6WdzogV1yT8gLe7izmGSCKv7Y7Jx9hJodfugkrrUL2qXdLtmiTD2z6rTTtQINgjOBFJUJU51Apb7HY/+95EFHctztG8d6+9dagje8qCWYA/VcSS8uYOK3tBt2C7ivvZO3qt/B82Ii6xP90hfvQGghOQ4SPfj8HVF7D1Q23P1o/g8T4TXu9pb2xA072h+Ffhfxhx/N+8LmTcLWxdvW5NLm3Q4UDN63eY8+JL9U8tEZpS6WfHQmd95Y8nFH8kb7/mjVPdd063t24Pd8z/Zfp1XjtP6VXGle/9dArn8D7VfbjpswEP0aHrsCDCX7mMteKnXVqlHbZy844NZ4qHE2yX59x2DCLW3SbfKw0koRsY/HY3zOzInikHm+vVO0yB4gYcLx3WTrkIXj+57rTfDLILsaia5JDaSKJzaoBZb8mTU7LbrmCSt7gRpAaF70wRikZLHuYVQp2PTDViD6pxY0ZSNgGVMxRr/zRGc1OgndFr9nPM2akz3XruS0CbZAmdEENh2I3DhkrgB0Pcq3cyYMeQ0v9b7bP6zuX0wxqU/ZMF9tI3I/KcjXX75YzNWP6f3snc3yRMXaXti+rN41DChYy4SZJJ5DZpuMa7YsaGxWN6g5YpnOhV1egdRWRD/AeYmRXKZmirPxKzfnM6XZtgPZK9wxyJlWOwyxqySwdNp6mjT0blp1vGuLZR1lGozagkj3qVvOcGBp+wcK/RGFS63MnYdE4g11ny0qeCpxHCMfTCFgeOBYfFO7kPMkMdtnipX8mT5WqVycF8Clrm4SzpxwYXKtNZQ18yZ1qRX8ZHMQgHkXEqTJsuJCDKGOYK6d39KcC8PvF3gEDXiLB5DQBMNaVeJnWmMP+iGZ4gMZNA8TUF6lAKlgtODlVQx5tRCXVejtqk6Nw33y0J/Z9GcojzAK++VBDpSHe6A8gkuVBzneYUwmU2NVphIELUse98uk7cCRQt+YSqikfSHRc5sCaDyL/I1elvQ8cExuh7zwAHcNppigmj/1nfMQofaEz6aIO9oNWzsaaFJWpWd3dZ1ukCjyjiTSVKVMjxJV+u6v/XLJg5HkeNpHjv1mWmxqPr774RM+XqlTeMFJTmEvdOCX6v9tPzzR9sml+jp86+sT+zrwBp7svbCvh4mioWlfuK/fv0l+quTBEaVOljw4UjsXljwaSW58+7XZdXTMrs/g0IE7kNwdO3RwHoPGafu3qVa6/fNJbn4D7VvLcuI4FP2WWVA1s6DLD2zwEkIe0zXp6QpV3VOzE7YwmhiLyOKRfH1LWAJbEuAQYBLiyiL2lXxt6R7do3uiNNyryfKWgOn4HkcwaThWtGy4/Ybj2JbdYb+45Tm3tAM3N8QERaLTxjBAL1A+KawzFMGs1JFinFA0LRtDnKYwpCUbIAQvyt1GOCm/dQpiqBkGIUh0608U0XFu7XjWxn4HUTyWb7Yt0TIBsrMwZGMQ4UXB5F433CuCMc2vJssrmPDJk/OSP3ezpXX9YQSmtMoD377d9zvN/vXIc56/Bv/On+5un5rCyxwkMzHghuMnzF9vhJlbFjdAQZNfNzMSrnr4TzP+yb0xpTwGXf5G54Z3yb7EGMcJBFOUfQnxhJnDjHW5GYEJSjgAHvAQU9xwevc4xRtXPDAghCX3squldvVj/ns6owNK/krl54pPkM1iyumzjCPBszSCfCps1rwYIwoH0/ydC4bc1XgmiWjmngQUnRa7z1hPlMb8du27OPFyFiGhcFkwiUDcQjyBlDyzLqLVlaAQq6LVEfeLDcbsQNjGBXxJGxCwjteuN5FnFyL4rwCCsxUIEZrLWX2A2YzbuoVZLzQbnpCmoTT8/kfh2aHWjagW9Q1KWNl803LsQILilF2HLDqQMAOPCmILuisaJiiK+OM9AjP0AoYrVxaHFEYMw9yv12t4fe5rRnGW44C7zijBj/AKJ5j57ac45V5GKElUUwE+lri/UZdAjmvRGc/ICor5mnI8PsMeX1fetpXl5WuLdZWri12unXtihR0FrJ6ngNX3dLDaBrD6pwKrawCrAgyYRl2e/jkSEpBlKCzDZJMPtAj9gCQCKSgHkvGYBIDkAXfX9MKoxCv65BYmzzPMnbQRmACK5mU2Mk2oeMN3DuJC7JRE47lKTLIV9MRTRfZQHPnBHkcUkBhSzdEqvuthHx5yb3d+suSy3zCFSAAac3w4grP4N/DPsJiB05BjNVkvMOGsJYZkyVExqkpLK0H6zYe4AjN3ZVvTZf425b1//s1aS1k6d6ml5TLdXkS2titla/l8w3HdDv851p7AK6+wVsU9gboSj5Zm/TrNVkyzblCOXat9YJrVHFnnTbPtOuQVQ95y9kSqasg1Ryp2Thzyzm5mLbACCCnC6Zu2/99JPvY1k6GcrUAaARKxSzyjjPk+VY1gt/exzhHYxWmVUeYa2GVtMy2Xo7NLUAV3XRLPJqtRvwF0EmzbMfXBEXQJVabtV6gy3XNWmbKkrclwLxnarSOVma59tjJz+PJf9Pi1Q24H/rz/82H+D/nh1Xpo413oob5fsfY5hh5qBMK59FC93L78Dc8l0JUqivqtM4qiRsTWomhVtlJF0bZ9IFupoqjm6MRs1dqdpCqogLamAr5/JfWgUb03+VX/xFp+teVe49XyawKGMOmB8DFepTDlFXkrJhEkSssxti52ubBuO/rWxbEMyewYsq0xK1QSdOrC+rPsVNTC2rhTOVVhbQSoSfmpdypV6uqDdypqXX3unYpRSyntHvZSpmNg9fdajR9hbPo2ZN14h34r9dy1JTAWjx9YGQgM9HpeZcDerhHVR6U+OdeqqkBg/d+qgG0SsmqyrSILBIf+EV+VBTRHpyZbkxT0Ol3AuUhdwDCqt+gC2wh5oxi8WhbQv7CWBWynCjGc6VRWUPWk9snKe9uk+tUZvcppquDg8klx1GmdOaObjr/WMa9ynEoL1aHnsjTwnDrmpuOXJz+YtZ3U6pNaZzqppdQQbQPjuCa97oCTWux28w9oOXA3/8bnXv8C7Vddb5swFP01SNtDKj4CTR/zuVVatamRNu3RAYd4NZgZk4/++l1jG3BI1rTrtpe+JPj4+mDfe861cIJptv/AUbG5Ywmmju8meyeYOb4fBDfwK4GDAvxoqICUk0RBXgssySPWoKvRiiS4tAIFY1SQwgZjluc4FhaGOGc7O2zNqP3WAqW4ByxjRPvoN5KIjUJHodviHzFJN+bNnqtnMmSCNVBuUMJ2HSiYO8GUMybUU7afYipzZ/Ki1i3OzDYb4zgXlyyYjb9X6V32I8rmxeH69nF+/3AYaJYtopU+sONHFPgmCdnKTYuDzkT0s5I7naxZLgZlXacxBHhRAaWetPMy75SkubUohj1i3g2KUv1PDakUCRJooF7AY4tgI4Qs+Fgez1/IkPIqZSylGBWkvIpZBnBcQshijTJCpdru2YoJ5viTO5Yza4MvOtUaxfYaw+8e86uzFZVYCv4phwBJWvMCQPIUHgYQhbICwnQe3NvP8PPuvcmIPuVxogCuC2NQ3zqNz1mVJ1gW3IPp3YYIvCzUvndgzzqRGdXTkl8bzh/CuIRIuTsYNtxbzAXen1Wc1+gY/I9ZhgU/QIgxf6SlfzDjUI13rZP8ax2z6bjoRmNImzdtqFt9w4OW+DPk7v9e7iqr97isJDY+l/TeCgOtDNAv5qoXy59ZW6iDsAtofKbdFUxktQj0rrGeyEiSyOUTjkHbaFVTuVKbjICDJG84ccKZ5KoEU/qvqUvB2QOeMsqAd5azXLKsCaXHUEdDrh4vjg2oDKKDWcVrPSpH+6FMcyhdHZ7zdaicDaHG2/DYkIfa36+i2Oj6SLGmz3cU63knFBv9LcUGJxR7JAycJ2N500klUFSWJLZl0jaFXoW+Yp6gHNmF9EaNAMyVFzTpxUnvvnwyuZ3khSdyZzCOKRJka9OfSqh+wxcp4k7thnbthl5oU5S19PSq7kV5RDTyniASiKdY9Ijq+jbHfnnJw0ua1JinVVYn/g/alLmOznept57033uS717Qk4J/2ZOit550YU/yRq/Uk5qiv3pPgmH7DaLC2w+5YP4L7VdNc9owFPwtPXimPSTjD2zIEUhI0jbTTJhpjx1hCaOpLBFZBtJfX8mSbGRDgDTppbmAvXpaSW/32c9eNM431xwsF3cMIuKFPtx40aUXhrF/IX8V8KSB6KKvgYxjqKGgAab4NzKgb9ASQ1Q4gYIxIvDSBVNGKUqFgwHO2doNmzPirroEGeoA0xSQLvoDQ7HQ6CD2G/wG4WxhVw58M5IDG2yAYgEgW29B0ZUXjTljQl/lmzEiKnc2L3reZM9ovTGOqDhmwuOMTz+j5Mv4/rY/Ca6jnxP0cGZYVoCU5sBemBDJN4J4pTYtnkwmksdS7XQ0Z1ScFZVOQxkQREsp9agZV3knOKPOpFTuEfHtoCQz/8SSKpMAAc70Ajx1CBZCKMGH6njhRIUU5xljGUFgiYvzlOUSTgsZMpmDHBPltgc2Y4J54eiOUeZs8EWnmoPUnWP5/Ta/PtuyFFPBv1JP6WIHb/AHh1QtV614+03+fPxkE2IO2c6ThCtdLBo6hwk5KylESu9ADq8XWKDpUm97LauzymNOzLDiN/UW9uR9ISMxzdRtzb1CXKDNXsMFtY1l+SOWI8GfZIiZEPZjPcWWfmIqYd0UUnBhsMVWEVkMmNrNaurG3vLCOPwEt4fPu11n9QEVpcKG+5LemWGhmQUcHWedMH6irFIC4WpnK8zUVTRSQmH51BqagRxDqKaPOJKuBrOKyleuZFjWjuKNR158qbhKwbTzK+pCcPYLjRlhkveSMqpY5piQNrRlH9/cT9qlp0vDBLOSV1bUtRzGKsOxqud4X0XHuqZlqK1qeVmTx6ayX8Wssd8yay/umjXYYdbkrcwa7TBryxiIwqF6xyknEFAUOHVt0jwPOgp9RxwCClwhg0FtAPuyi+r0Ith5Ux5M7lby4h25sxhHBAi8cul3JdSscK9M3GjXaz1oekFLk6Kynpm1/YpsESW9A0QC8AyJDlGlb33sl0sev0t+pOSR5Xlq3Z8qeZsoHPxbyZN3yY+VfHBAqaMlHxzwzhtL3j+mC6n6QZAKzOhftSL3XJ99fwuKaWU3QCHgUF6yUsjW9b9qYIL+oQbmFTqMsNVh1B+GW+UTRc/UzwkdhrxtPiy1b5uv8+jqDw==7VdLc9sgEP41nmkPyUgisp2jH0maQ9qO3WlzxQJLTBCrIuRHfn1BQpaRXNvJJL00Fxu+XT6W/ZaHemiSbu4kzpIHIJT3Ao9semjaCwLf84f6zyDbChlcowqIJSPWqQHm7JnWIy1aMEJzx1EBcMUyF4xACBopB8NSwtp1WwJ3Z81wTDvAPMK8i/5iRCUVOgy9Bv9CWZzUM/uetaS4drZAnmAC6z0I3fTQRAKoqpVuJpSb5NV5qcbd/sW6C0xSoc4ZMBP9bPr8mD0+xF/FLEWr+Sy9sCwrzAu7YBus2tYZkFAIQg2J30PjdcIUnWc4Mta11lxjiUq5NS9BKCticKX7ufZkIjZd3euGXM9PpaKbPcgu4Y5CSpXcahdrRcim09ZT/yqs+utGHf/a+iR7ytQYtgUR76ibnOmGTdsLUhgcSGGf61nHhK10M1blyivI5MdJb/93AbXhIi8zN9IO/iDbVMOsvSaa0bzghuJTOeHn0r3i1tFX9O6UGj4QyAFo8ebB/qAbQ6C2ma6fk1F25m9H3ipMZdid6sOcxUK3I11fVGrA1BXTm3lkDSkjxAwfS6rDx4uSytP9DJhQZWWE4144NVyFgmqJJXWuJDzRCXDQvFMBwrAsGedtqNkA/sD2b3HKuKnXGSxAgV7FAwionaGQ5WZKlNJnWhCalIa6Is2PccgvY4CYU5yx/DKCtDREeel6u6yodXNHHgZjS/8G2y08Z7sND2y38L22Gzp9YlFBRuboN5XAcZ6zyC2T5kTz2gr9pJJggVtCDncFUN8B6Fh6KXHulG5y95IXHsmdpBwrtnJvokMJtTN8N0V8RLvr0KXIy9Kzo/ZvjhbRwDtBpLCMqeoQlfrulv16yfsfkp8p+ZUXukqhV0reJgr7/1bywYfk50qOTih1tuToRO28s+TDc95R999MQJFiINyXzwufOPfmgaB5Spm0SoH3RLcLwJL81w+SN3gxoNZlEfrdF8PR/fCCF4PuNt9PVR02X6Ho5g8=7Vdbb9sgGP01lraHVraJc3ls0ssqrbs00ib1ZSI2sdEwn4dxk+zXD2zwNWvdqt3D1JcWDh8H+M7BfHHQKt1fCZwlNxAR5vhutHfQueP7nuvN1T+NHCpktkAVEAsamaAGWNPfxM40aEEjkncCJQCTNOuCIXBOQtnBsBCw64ZtgXVXzXBMBsA6xGyIfqeRTCp0HrgN/oHQOLEre64ZSbENNkCe4Ah2LQhdOGglAGTVSvcrwnTybF6qeZd/Ga03JgiXYyasJn5+/fFu8ePrycHzxJ1Ed/GJYbnHrDAHNpuVB5sBAQWPiCbxHLTcJVSSdYZDPbpTmisskSkzw1vg0ojoT1Q/V5GUx7qresMt2/WJkGTfgswRrgikRIqDCjGjyKbT+Glq+7tGHW9hsKSljMWwMURcUzc5Uw2Ttiek0D+SwilTqy4jeq+asSxPXkE6P530Tn8VYAdO8jJzZyrAm2X7apoZt0S3JC+YpnhXLvi+DK+4N6K/mjpPteAAPrK119htvTULfAKZaD/4bpGTbcFaW9oMJj33PD0DK2fJrksxozFX7VD5kAgFaP9RdenPzEBKo0hPXwqiTok3JZWr+hlQLksHBUsnONdchYQqEyV1LgX8JCtgoHjPOXDNsqWM9aHmongz07/EKWXa17ewAQnqFDfAwQZDIcpLl0ipvn1+oDMfKOfqPzogP40BYkZwRvPTENJyIMzL0MttRa2aNXngLw39C1zLwBtxLedHrmXwWtcSPf5lIzw600+EdgLDeU7Drk2aL5/bV+gbERHmuCfkvDaAfSvQQ+klUeftGSa3lbzggdwJwrCk990X61hCzQpftIlb2vU/qbOeJnlpPTOr/cL0iKaLR4gkFjGRA6JS3/rYz5d88ib5SMnRIugq5T1T8j5RMPm3kgdvko+UfOI/otRYyftEA++8suTTMfXW9We9oVBS4K0KaXThU9ci60QZx3cVi/JUKAjhT65Y/u/S5AVqB4S6z8ZkcaR2mL1M7aC6zS+uypHN71Z08Qc=7VfLctsgFP0azbSLZPSw/Fj6EafpTKadpI/pEktYokVcF6HY6df3IoFlSXbsZJxumk0iDpcDnHOBayeYZptrSVbpLcSUO74bb5xg5vi+53pD/KeRxwoZjIIKSCSLTVAN3LM/1I40aMFimjcCFQBXbNUEIxCCRqqBESlh3QxbAm/OuiIJ7QD3EeFd9DuLVVqhw9Ct8Q+UJamd2XNNT0ZssAHylMSw3oGCKyeYSgBVfWWbKeVaPKtLNW5+oHe7MEmFOmXAV8iG1x9D927Axmz0BaKfP+YXhuWB8MJs2CxWPVoFJBQipprEc4LJOmWK3q9IpHvX6Dliqcq46V6CUMZEv4ftHCOZSHQTW90l2/mpVHSzA5ktXFPIqJKPGGJ6A5sYJp/6Vt517Y43Mli644zFiEmIZEtda4YfRrZnSOjvkbDPcdZJzB7wM1HlzitI69OQt/+7ANtxkZfKjTHAG6w21TDTb4nuaF5wTfGunPB9GV5x4+or+uaUCO9ZSAmdsI7+gXVYnsVelm78jViCo716pg6H5pdUFVJgWh7avLvQWTP+fIM9s/Ppv9233KPzAfkXxyxpnTg8Cqp5rAhnicDvCA8OlQjoA8PwlhqbjozFsR6OuuAmyKKkcrG9AiZUmfLhxAlnmqtQUG20pM6VhF90ChyQdyZAaJYl47wN1SfbG5j2nGSM64N4BwtQ2t9bEGCDoZDlLZEqhZe1H2phQzxq+o8OyC8TgIRTsmL5ZQRZ2RHlZeh8WVHj55Y89CeG/gz3SOgev0f8cM89Er7WPRIcv4qpiMf6TdOZwEmes6iZJvVV7bYd+kZlTARpGTncJoB93IKn5KVx47Hsirsj3lPaScqJYg/NJ3afoGaGzzqJd7xrvwGDlid5mXpm1O6T2CIahEeIFJEJVR2i0t/ttl9uee/N8hMtD4Zh0ynvhZa3icLev7U8fLP8RMt73hGnTrW8TdTJnVe2vH9KgXjzSS8oUgxEs6Q7VLt1yrAKmJJyQUTY4sfFR9x3Z0SRBcnpk+XL/1ennKGQwEekkV290Z5Con+eQgKb9e/FKj3rX93B1V8=7Vdrb5swFP01fFxFcJykH/Nou2mNui1S99nFDlgzvsh2Hu2vnw0mvLIli9pJlSpFBB9fH/A5914gQPNsf6dIni6BMhFEId0HaBFE0SAcTOyfQ55LZHyNSiBRnPqgGljxF1at9OiGU6ZbgQZAGJ63wRikZLFpYUQp2LXD1iDaV81JwnrAKiaij/7k1KQlOsFhjX9mPEmrKw9CP5ORKtgDOiUUdg0I3QRorgBMeZbt50w48SpdynW3f5g93Jhi0pyz4N7kj0vz9fuGytXsPssni7uHT1HJsiVi4zfsb9Y8Vwoo2EjKHMkgQLNdyg1b5SR2szvrucVSkwk/vQZpvInR0I61jeQycUM76t+y38WWKcP2Dchv4Y5Bxox6tiF+FlX6+nwaIT/e1e4Mrj2WNpypMOITIjlQ15rZEy/bP0iIehKSnoZ2c6YtFBE8kfY8tlIwZQEnAbd5N/UTGafULZ8ppvkLeSqoQjvOgUtTbALPArxwXBsDuhTdUWuj4BebgwDLu5AgHcuaC9GFGl6FfnxLMi6ctD/gCQzYXSxBQhUMG1X4nhpjyy/CaGoPVjx3cAH6KgFIBCM511cxZMVErIvQ23VJbU8P5DiaefpXyAw8wu3MGPYzY3gkMYZvlRjD07XFJJ26JuUSQRCtedzOkrr2egY9MkWJJG0fbbet/K+6Ffqbuoy2ul9f24Z2+Ih2FaaYIIZv2z3zmKD+Ct9cDjes6xb1pOOJLjLPr2r2uA7RaHyCyBCVMNMjKvw9bPtyy3HPcu04U9cCXY1N3S8Kvzy4W3p3XWKAz+oSfkNHHlAXdPtOTY+OdPvJkcREb1XUo4+iPrOo0bjjXXRhUXeJMP6/RT3+sPxMy4fhCafOtbxL1MudN7Z80rPctex31q5Pv9S9RofuPHFx5dyJjLvgrcsO66+l0un6mxPd/AY=7VfJbtswEP2WHHxsoNXL0UuWoklTOEF6piVaIkppXIqO7Xx9hxJprY2d1ClQNBebfBw+SvMeh1TPnSbbK0FW8S2ElPccK9z23FnPcWzLHuKfQnYFMhi5BRAJFuqgErhnz9TM1OiahTSrBUoALtmqDgaQpjSQNYwIAZt62BJ4fdUViWgLuA8Ib6PfWSjjAh36VolfUxbFZmXb0iMJMcEayGISwqYCuRc9dyoAZNFKtlPKVfJMXop5l78Z3T+YoKk8ZsL45svQfvRuZvOf118frmbzu/nuk2Z5InytX1g/rNyZDAhYpyFVJHbPnWxiJun9igRqdIOaIxbLhOvhJaRSi+h42M8wkqWR6mKv/chmfSok3VYg/QpXFBIqxQ5DzOhAp1P7yXf9or8p1bFHOiauKGMwog0R7anLnGFDp+0VKXQ6UtjnuOokZE/YjFRzTrO1wsZmDJeqDHfMMNDCANOYiMrsRTOwydiQEfMr61oRzqIU2wGqQQUCSgWG1h/rgYSFoZo+ETRjz2SRU1nYXwFLZZ5Hf9LzZ4prLSErdFfUmRTwg06BA/LOUkgVy5Jx3oQqdrF0/5IkjCt157AACfgWt5CCCYa1yK0XS4kVAOXHjPqon/pRAdl5BBBxSlYsOw8gyQeCLA+9XBbU2NyT+85E05/AnJ7VMKfZ+1Vz2h3m7L+XOd3D+5um4VgVSuUETrKMBXWblPu/pdAjFSFJSV1IrPjGAKZiui+ll4a1CtxObiV5fkfuDCYoJ5I91et2V0L1Ct+UiUvt3GZhGfp1iiy3np5VrbMNIt87QCSJiKhsEeX67l/77ZJ7LclxtaJ4WKoAqR1jfb7DnwL8x+qE3T+qTugX6jglX3/k9P26oP32keNYHc5032tX+x+7+shd7XgN7Zw37uomkef/3V3d/5D8WMkHB5Q6WvLBAe+8s+SDDslb18S8kJNAMkj/6HZ5TZVCO1if4d/DbkUVrfquwjMC6ak4e+nuuRD/3W30BAfLqO4vd9S+LbreaW6L2C2/NAuDlt/r7sUv7Vddb9sgFP0te/DjKtuEpH1M0rSb1GpTs7XaI7GJjYrBwjgf/fUDG2Jje236NWlaXxI4XC5wz7kX7IF5trsUKE+veYypF/rxzgPnXhgGfnCq/jSyr5HJGaiBRJDYGDXAkjxgO9OgJYlx4RhKzqkkuQtGnDEcSQdDQvCta7bm1F01RwnuAcsI0T56R2KZ1ugp9Bv8CyZJalcOfDOSIWtsgCJFMd+2ILDwwFxwLutWtptjqoNn41LPu/jD6GFjAjN5zAS2XIKHYHN7tfh1vy1/wu0omnwOay8bREtzYLNZubcRELxkMdZOAg/MtimReJmjSI9uFecKS2VGzfCaM2lIDEeqXyhLwhLdVb3+ls0pNlhIvGtB5giXmGdYir0yMaPhGaynGD1B3/S3DTvBmQl52mLGYsgIIjm4bmKmGiZszwghGAjhmKpVZzHZqGaimze4KDU2tWNqqdbwwAwLrSywlEJHspm/6pp2fXaIVBGWLluIkoSpdqT4wEIBmgeixD81AxmJYz19JnBBHtCqcuWrfs4Jk1Uk4cyD59pXKXlRM69dF1LwezznlCu/54wz7WVNKO1CLcH4pn+BMkI1vzd8xSVXp7jmjFtjXopKfKmUqgaEUMcUKgb1jzYoThLOE4pRToqTiGfVQFRUphfr2rVqHpzDcGbcv4E8YejKc2Rl15ZnMCDP8XvJc/R0hmMWT3Wp1EqgqChI5MqkqQA9hm6xiBFDLpGq5lsB2JoJHgsvjp0a3A9uK3hwIHYWE5giSTZu5R4KqFnhuxZxw92oW1ogdF0UlfTMrHal7Tgaj59wJJFIsOw5qvg9HPvllMMe5Wq1K6LyTafYtKpC/tdv6sfWlH+sUgTwqEphDjRwU77g2vFdSkH/2gn9AW2C98rr8UdeH5nXYOJyd8jz5+Z1z1H4d/N68kH5saXcf4KpYynvOepq550pPx2gvPdUrEo5iiTh7FUvzB/7XF8Rhd51Wt0LPmKxZojokCx06f/02AN0Jf67J+lb3C2++1wA4/6b8VB6XvlmVN3mi7MWafPdDha/AQ==
\ No newline at end of file
diff --git a/images/distributed_computation.png b/images/distributed_computation.png
new file mode 100644
index 00000000..3c19cb2c
Binary files /dev/null and b/images/distributed_computation.png differ
diff --git a/images/monoid_form_1.png b/images/monoid_form_1.png
new file mode 100644
index 00000000..8ea6062b
Binary files /dev/null and b/images/monoid_form_1.png differ
diff --git a/images/monoid_form_2.png b/images/monoid_form_2.png
new file mode 100644
index 00000000..9d16f88d
Binary files /dev/null and b/images/monoid_form_2.png differ
diff --git a/images/monoid_form_3.png b/images/monoid_form_3.png
new file mode 100644
index 00000000..d84e7b8f
Binary files /dev/null and b/images/monoid_form_3.png differ
diff --git a/images/monoid_form_4.png b/images/monoid_form_4.png
new file mode 100644
index 00000000..4240914d
Binary files /dev/null and b/images/monoid_form_4.png differ
diff --git a/images/monoid_form_5.png b/images/monoid_form_5.png
new file mode 100644
index 00000000..d73b96a8
Binary files /dev/null and b/images/monoid_form_5.png differ
diff --git a/images/monoid_form_6.png b/images/monoid_form_6.png
new file mode 100644
index 00000000..0fa6285b
Binary files /dev/null and b/images/monoid_form_6.png differ
diff --git a/lessons/05-Improving-and-combining-functions.ipynb b/lessons/05-Improving-and-combining-functions.ipynb
index 98484cac..96030632 100644
--- a/lessons/05-Improving-and-combining-functions.ipynb
+++ b/lessons/05-Improving-and-combining-functions.ipynb
@@ -1818,7 +1818,7 @@
}
},
"source": [
- "```haskell\n",
+ "```\n",
"($) :: (a -> b) -> a -> b\n",
"f $ x = f x\n",
"```"
@@ -1868,7 +1868,7 @@
}
},
"source": [
- "```haskell\n",
+ "```\n",
"f g h x = ((f g) h) x\n",
"\n",
"f $ g $ h x = f (g (h x))\n",
diff --git a/lessons/09-Creating-parameterized-and-recursive-types.ipynb b/lessons/09-Creating-parameterized-and-recursive-types.ipynb
new file mode 100644
index 00000000..3cd814e2
--- /dev/null
+++ b/lessons/09-Creating-parameterized-and-recursive-types.ipynb
@@ -0,0 +1,4468 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Creating Parameterized and Recursive Types"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "* Parameterizing Types\n",
+ "\t* Prameteryzing `type` synonyms\n",
+ "\t* Prameteryzing `data` types\n",
+ "* Recursive data types\n",
+ " * `Tweet` me a river\n",
+ " * A `Sequence` of `Node`s\n",
+ " * A `Tree` of `Node`s\n",
+ "* Kinds\n",
+ "* The `newType` keyword"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Paremeterizing Types"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "A **value** constructor **takes values** as parameters and **produces a value**.\n",
+ "\n",
+ " |\n",
+ " v\n",
+ "\n",
+ "A **type** constructor **takes types** as parameters and **produces a type**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can use type constructors with both type synonyms and new types. Let's start with type synonyms."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Parameterizing Type Synonyms"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Going back to our last type synonym, we had:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "type Name = String\n",
+ "type Address = (String, Int)\n",
+ "\n",
+ "type Person = (Name, Address)\n",
+ "\n",
+ "bob = (\"Bob Smith\", (\"Main St.\", 555)) :: Person"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Imagine that, after using it for a while, we find out that we also have to identify companies by their numeric id and providers by their alphanumeric id.\n",
+ "\n",
+ "We could do something like:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "type Address = (String, Int)\n",
+ "type Name = String\n",
+ "type CompanyId = Int\n",
+ "type ProviderId = String\n",
+ "\n",
+ "type Person = (Name, Address)\n",
+ "type Company = (CompanyId, Address)\n",
+ "type Provider = (ProviderId, Address)\n",
+ "\n",
+ "bob = (\"Bob Smith\", (\"Main St.\", 555)) :: Person\n",
+ "io = (584264, (\"Cardano St.\", 999)) :: Company\n",
+ "google = (\"Google LLC\", (\"Amphitheatre Parkway\", 1600)) :: Provider"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this case, we add four more type synonyms. The `CompanyId`, `ProviderId`, `Company`, and `Provider` synonyms.\n",
+ "\n",
+ "We get our desired result, but at the expense of repeating the same structure three times (`Person`, `Company`, and `Provider` are tuples with something and an `Address`). A different approach would be to define a parametric type synonym.\n",
+ "\n",
+ "For example, we could create the `Entity a` type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "type Entity a = (a, Address)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To define a parametric type synonym, we have to indicate the parameter to the left of the `=` sign and use it on the right. Same as with functions.\n",
+ "\n",
+ "And now, every time we use `Entity a`, we can adjust the type of `a` according to our needs. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "type Name = String\n",
+ "type Address = (String, Int)\n",
+ "type CompanyId = Int\n",
+ "type ProviderId = String\n",
+ "\n",
+ "type Entity a = (a, Address)\n",
+ "\n",
+ "bob = (\"Bob Smith\", (\"Main St.\", 555)) :: Entity Name \n",
+ "io = (584264, (\"Cardano St.\", 999)) :: Entity CompanyId\n",
+ "google = (\"Google LLC\", (\"A. Parkway\", 1600)) :: Entity ProviderId\n",
+ "other = (True, (\"Some street\", 0)) :: Entity Bool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This time, we added just three more type synonyms. The `CompanyId`, `ProviderId`, and `Entity a` synonyms.\n",
+ "\n",
+ "And below, we have four different values with four different types. All of them are obtained by passing a different type to the same type constructor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notice that:\n",
+ "* `Entity` by itself is a type constructor, not a type, so **no value can have a type of just `Entity`**.\n",
+ "* `Entity Name`, `Entity CompanyId`, `Entity ProviderId`, and `Entity Bool` are completely different types!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can also use multiple parameters. For example, the `Address` type synonym is also a pair. But one that doesn't have `Address` as the second element. So we could generalize `Entity a` even more and convert the two values into parameterized types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "type Name = String\n",
+ "type Address = Entity String Int\n",
+ "type CompanyId = Int\n",
+ "type ProviderId = String\n",
+ "\n",
+ "type Entity a b = (a, b)\n",
+ "\n",
+ "bob = (\"Bob Smith\", (\"Main St.\", 555)) :: Entity Name Address \n",
+ "io = (584264, (\"Cardano St.\", 999)) :: Entity CompanyId Address\n",
+ "google = (\"Google LLC\", (\"A. Parkway\", 1600)) :: Entity ProviderId Address\n",
+ "other = (True, (\"Some street\", 0)) :: Entity Bool Address"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, now `Entity a b` takes two type parameters. And `Address` is a synonym to a specific case of `Entity a b` where the first parameter is a `String` and the second is an `Int`.\n",
+ "\n",
+ "Of course, now the name `Entity` doesn't make much sense, and our types are starting to get convoluted. I just wanted to show you that you could use more than one type parameter, and it's not a big deal. But that's pretty much it for type synonyms. They are useful to provide extra context when needed, and they provide certain flexibility allowing for type parameters. But other than that, they're pretty boring.\n",
+ "\n",
+ "Let's learn the good stuff! You know what I mean. Let's learn about parameterizing data types!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Parameterizing data types"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To add type parameters while defining new types, you do the same as with function and parameterized type synonyms. Add the parameter to the left of the `=` sign, and (optionally) use it on the right:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Box a = Empty | Has a deriving (Show)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Here, we're defining a brand new type. A type that represents a box that contains values.\n",
+ "\n",
+ "In this case, `Box` is a type constructor that takes one type variable `a`. So we could have values of type `Box Bool`, `Box Char`, `Box Float`, etc.\n",
+ "\n",
+ "And we have two value constructors:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Empty :: forall a. Box a"
+ ],
+ "text/plain": [
+ "Empty :: forall a. Box a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Has :: forall a. a -> Box a"
+ ],
+ "text/plain": [
+ "Has :: forall a. a -> Box a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t Empty\n",
+ ":t Has"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`Empty` for when the box is empty. In this case, `Empty` is of type `Box a`, meaning it is polymorphic. We don't know the type of what it's inside because it's empty!\n",
+ "\n",
+ "And the `Has` value constructor for when the box has a value inside. In this case, we do have a value inside, so the type of `Box a` will be dependent on the type of that value.\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "box1 :: Box [Char]"
+ ],
+ "text/plain": [
+ "box1 :: Box [Char]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "box2 :: forall a. Box a"
+ ],
+ "text/plain": [
+ "box2 :: forall a. Box a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "box1 = Has \"What's in the box?!\"\n",
+ ":t box1\n",
+ "\n",
+ "box2 = Empty\n",
+ ":t box2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can also modify and combine the values inside the boxes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Has 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- data Box a = Empty | Has a\n",
+ "\n",
+ "box = Has (1 :: Int)\n",
+ "\n",
+ "addN :: Num a => a -> Box a -> Box a\n",
+ "addN _ Empty = Empty\n",
+ "addN n (Has a) = Has (a + n)\n",
+ "\n",
+ "addN 3 box"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Has 10"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Empty"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- data Box a = Empty | Has a\n",
+ "\n",
+ "addBoxes :: Num a => Box a -> Box a -> Box a\n",
+ "addBoxes _ Empty = Empty\n",
+ "addBoxes Empty _ = Empty\n",
+ "addBoxes (Has a) (Has b) = Has (a + b)\n",
+ "\n",
+ "addBoxes (Has 3) (Has 7)\n",
+ "addBoxes (Has 5) Empty"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And what if we want to extract the value inside the box? The case of the `Has a` value constructor is easy, we just need to pattern match and return `a`. But what about the case when the box is empty?\n",
+ "\n",
+ "Well, we could ask for a default value to return if the box is empty. That way, we always return a value!\n",
+ "\n",
+ "So, if we translate this to code, we get:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "15"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[1,2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- data Box a = Empty | Has a\n",
+ "\n",
+ "extract :: a -> Box a -> a\n",
+ "extract def Empty = def\n",
+ "extract _ (Has x) = x\n",
+ "\n",
+ "extract 'a' Empty\n",
+ "extract 0 (Has 15)\n",
+ "extract 0 Empty\n",
+ "extract [] (Has [1,2,3,4])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We could keep creating more functions for `Box a`, but there's still a lot to cover, so let's keep going!\n",
+ "\n",
+ "We can also use type constructors with record syntax. Imagine we also want the option of using other ways of representing colors in our shapes. Previously, we used `String` values and wrote down the color's name. But other situations could warrant different formats. Like hexadecimal or RGB values. So, better if we parameterize our type like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Shape a\n",
+ " = Circle\n",
+ " { position :: (Float, Float)\n",
+ " , radius :: Float\n",
+ " , color :: a\n",
+ " }\n",
+ " | Rectangle\n",
+ " { position :: (Float, Float)\n",
+ " , width :: Float\n",
+ " , height :: Float\n",
+ " , color :: a\n",
+ " }\n",
+ " deriving (Show)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, the color field can be of any type, and our shape can be of type `Shape String`, `Shape Int`, etc.\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "circleS :: Shape [Char]"
+ ],
+ "text/plain": [
+ "circleS :: Shape [Char]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "circleRGB :: Shape RGB"
+ ],
+ "text/plain": [
+ "circleRGB :: Shape RGB"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "circleS = Circle { position = (1,2), radius = 6, color = \"Green\"}\n",
+ ":t circleS\n",
+ "\n",
+ "type RGB = (Int,Int,Int)\n",
+ "circleRGB = Circle { position = (1,2), radius = 6, color = (0, 128, 0) :: RGB}\n",
+ ":t circleRGB"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And all the other properties of record types still apply.\n",
+ "\n",
+ "Now that we know all these ways of creating types, we'll go through a few more examples to hone in on the knowledge. But because we're highly efficient students, we'll kill two birds with one stone and learn about recursive while at it!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Recursive data types"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can use type synonyms inside other type synonyms. But for technical reasons, we cannot define recursive type synonyms. We can, though, define recursive data types."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tweet me a river"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Here's the situation. Elon musk wants to rebuild Twitter using Haskell. And you're interviewing for the position. The first question is to define a data type for a tweet. A tweet has its contents, the number of retweets, likes, comments, metadata, etc. That would be a huge data type, but the interviewer doesn't care about the details. He wants you to present the general idea.\n",
+ "\n",
+ "So, you provide this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Tweet :: String -> Int -> [Tweet] -> Tweet"
+ ],
+ "text/plain": [
+ "Tweet :: String -> Int -> [Tweet] -> Tweet"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Tweet = Tweet\n",
+ " { contents :: String\n",
+ " , likes :: Int\n",
+ " , comments :: [Tweet]\n",
+ " }deriving (Show)\n",
+ ":t Tweet -- Type of the Tweet constructor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Just 1 constructor with 3 fields. You used record syntax because you know this type will eventually contain many more fields, and it would be cumbersome to use regular syntax. You also figured out that a comment to a tweet is just another tweet, so you can recursively use `[Tweet]` as the type of the comments inside the `Tweet` data type.\n",
+ "\n",
+ "And to test it out, you create a realistic `Tweet` value: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "tweet :: Tweet\n",
+ "tweet = Tweet \"I'm angry about something! >.<\" 5\n",
+ " [ Tweet \"Me too!\" 0 []\n",
+ " , Tweet \"It makes me angry that you're angry\" 2\n",
+ " [ Tweet \"I have no idea what's happening\" 3 [] ]\n",
+ " ]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The interviewer liked your idea but was skeptical about how easy it would be to work with a type like this. And to prove that it's super easy, you wrote a function to measure the engagement based on the number of likes and responses the tweet and all the tweets that tweet generated had:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "13"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "engagement :: Tweet -> Int\n",
+ "engagement Tweet {likes = l, comments = c} = l + length c + sum (map engagement c)\n",
+ "\n",
+ "engagement tweet"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `engagement` function pattern matched only the fields it needed, then it added the likes and amount of comments of that tweet. And to that, it added the sum of numbers generated by recursively mapping the `engagement` function we're creating to all the tweets on the list of comments.\n",
+ "\n",
+ "The interviewer is so impressed that she stops the interview short and offers you a senior position. But you rejected the offer when you found out that, now, the salaries of all Twitter employees are paid in Dodge coin.\n",
+ "\n",
+ "So, you move on to the next adventure."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### A `Sequence` of `Nodes`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "After successfully completing Twitter's interview process and rejecting their offer, your confidence is skyrocketing, and you decide to give it a go at Google.\n",
+ "\n",
+ "The initial interviews are ok, but it's time for the real deal: The technical interview! You show up on time, and so does the interviewer. We're up for a good start. And here comes the first question:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "\"Write data type that represents a linear sequence of nodes where each node contains a value and points to the rest of the sequence.\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Easy enough! So, you need a data type similar to the `Box a` we created before:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Box a = Empty | Has a"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Empty` constructor represents an empty node, and the `Has` constructor is a node that has a value inside. That's a good start. The problem is that you need to contain a sequence of these boxes. \n",
+ "\n",
+ "Luckily, you know you can pass multiple parameters to a value constructor, so you can simply add another box as the second parameter of the `Has` constructor:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Has :: forall a. a -> Box a -> Box a"
+ ],
+ "text/plain": [
+ "Has :: forall a. a -> Box a -> Box a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Box a = Empty | Has a (Box a)\n",
+ "\n",
+ ":t Has"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That new parameter means that the `Has` value constructor now contains a value and a box that can contain another value and another box, and so on and so forth."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And with that, boom! We have a data type that is a linear sequence of boxes (or nodes) where each box has a value and points to the rest of the boxes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's awesome! But you did all this in your head, and the interviewer started to get worried about your long silence. So you explained the reasoning but changed the word \"Box\" with \"Sequende\" and \"Has\" with \"Node\" because that's the language of the question. So you presented the solution like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Node :: forall a. a -> Sequence a -> Sequence a"
+ ],
+ "text/plain": [
+ "Node :: forall a. a -> Sequence a -> Sequence a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Sequence a = EmptyS | Node a (Sequence a) deriving (Show)\n",
+ "\n",
+ ":t Node"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That data type represents a sequence of nodes that could be either empty or have a node that contains a value and points to the rest of the sequence. It's the same type as before, but changing the names makes you think differently about what's happening. \n",
+ "\n",
+ "And to prove that it works as expected, you create a few values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Sequence a = EmptyS | Node a (Sequence a)\n",
+ "\n",
+ "sequence1 :: Sequence a\n",
+ "sequence1 = EmptyS -- A sequence of just one empty node\n",
+ "\n",
+ "sequence2 :: Sequence Char\n",
+ "sequence2 = Node 'a' EmptyS -- A sequence of 2 nodes\n",
+ "\n",
+ "sequence3 :: Sequence Bool\n",
+ "sequence3 = Node True (Node False EmptyS) -- A sequence of 3 nodes\n",
+ "\n",
+ "sequence4 :: Sequence Integer\n",
+ "sequence4 = Node 1 (Node 2 (Node 3 EmptyS)) -- A sequence of 4 nodes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Right after that, the interviewer looked you dead in the eye and asked:\n",
+ "\n",
+ "\"And how is this useful?\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "You hesitated for a second. And that's when you vaguely remembered a Haskell course you did a long time ago—yes, video lectures can be recursive too. You smiled and said, \"oh, I'll tell you how useful this is.\"\n",
+ "\n",
+ "And proceeded to slightly modify the data type to make a point. This is what you did:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(:->) :: forall a. a -> Sequence a -> Sequence a"
+ ],
+ "text/plain": [
+ "(:->) :: forall a. a -> Sequence a -> Sequence a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "infixr 5 :->\n",
+ "data Sequence a = EmptyS | a :-> (Sequence a) deriving (Show)\n",
+ "\n",
+ ":t (:->)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Since value constructors are just functions, you can also create infix value constructors—with the caveat that they have to start with a colon (`:`).\n",
+ "\n",
+ "In this case, you define the `:->` (weird arrow) value constructor that takes the node's value as a first argument and the rest of the sequence as a second argument.\n",
+ "\n",
+ "So the previous `sequence4` value now looks like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "sequence4 :: Sequence Integer\n",
+ "sequence4 = 1 :-> 2 :-> 3 :-> EmptyS -- A sequence of 3 nodes + empty node"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Looks familiar? Exactly! That's a list!! If we compare the two side by side, it's pretty obvious:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "sequence4' :: [] Integer -- Same as [Integer]\n",
+ "sequence4' = 1 : 2 : 3 : [] -- A list with 3 elements + empty list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And if we compare our type with how lists are defined in Haskell:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data Sequence a = EmptyS | a :-> (Sequence a)\n",
+ "\n",
+ "data [] a = [] | a : [a]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We see that they are virtually the same type, but lists have some special \"extra suggary\" syntax to make them easier to use.\n",
+ "\n",
+ "And that's why you chose the fixity to be `infixr 5`. Because it's the same that the `:` constructor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "After presenting that evidence, the utility of the type is obvious. You just recreated the list type, and lists are everywhere!\n",
+ "\n",
+ "The interviewer was pleased, but he was just starting! And he asked:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "\"Now write a function to check if a specific element is inside this sequence.\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The interviewer was pleased, but he was just starting! And he asked:\n",
+ "\n",
+ "No problem! You had to implement the `elem` function for your new type the same way it's implemented for lists:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Sequence a = EmptyS | a :-> (Sequence a)\n",
+ "\n",
+ "elemSeq :: (Eq a) => a -> Sequence a -> Bool\n",
+ "elemSeq _ EmptyS = False\n",
+ "elemSeq x (y :-> ys) = x == y || elemSeq x ys"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "You define the `elemSeq` function that takes a value of type `a` and a value of type `Sequence a` and returns a `Bool`. Where `a` is an instance of the equality type class (because you'll be chequing for equality).\n",
+ "\n",
+ "You have two constructors, so you start with two equations. One for the `EmptyS` constructor and one for the `:->` constructor.\n",
+ "\n",
+ "If the sequence is empty, you don't care about the other value because you know it won't be inside an empty node.\n",
+ "\n",
+ "And if the sequence has at least one non-empty node, you pattern match to extract the value of the first node (`y`), check if it's equal to the value provided as the first parameter (`x`), and recursively apply the `elemSeq` function to the same initial value and the rest of the list.\n",
+ "\n",
+ "\n",
+ "If at least one element of the list is equal to the value provided, you want to return `True`. So, you use the `||` operator that takes two booleans and returns `True` if either is `True`. That way, as soon as you get one match, you'll get `True` till the end. And you will know that value is inside the sequence."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Using this function, we can check if an element is inside our sequence of nodes like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "seq5 = 'a' :-> 'b' :-> '4' :-> '%' :-> EmptyS\n",
+ "\n",
+ "elemSeq 'c' seq5\n",
+ "elemSeq '%' seq5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "\"Well done.\" - says the interviewer - \"But I have a problem with this. I have tens of thousands of elements, and if we have to check one by one in sequence, it'll take forever!\"\n",
+ "\n",
+ "You saw this coming from a mile away and said: \"No problem! If we have the values ordered, we could use a Binary Search Tree!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### A `Tree` of `Node`s"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The interviewer was right! Imagine you have 10.000 items to go through. If you go one by one, it will take forever! So, what do you do?\n",
+ "\n",
+ "Think about the last time you looked for a word in the dictionary. No, not on the computer. I mean an actual physical dictionary. How did you do it? Did you go to the first page, look for the word there, then to the second page, and so on and so forth? No! You straight up opened the dictionary in the middle! And when you saw that the word wasn't there, you chose one of the halves based on the order of the alphabet, split that half in half, and checked for the word again. That way, every time you checked, you reduced the size of the problem in half. That's called a \"binary search algorithm,\" and it's much better than linear search. How much better, you ask?\n",
+ "\n",
+ "For example, if the dictionary has 10.000 pages, when searching linearly, the worst-case scenario (the word is at the very end) would be to check all 10.000 pages. But if we use the binary search algorithm, the worst-case scenario would be that we need to check 13 pages! 13! That's it! You can see how this is a game changer for efficiency."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, we want to create a data structure that allows us to easily search that way. There are a few we could use. But one of the most famous ones is the Binary Search Tree (also called the Sorted Binary Tree) data structure. And it looks like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In a Binary Tree:\n",
+ "* Each node can have at most two child nodes\n",
+ "* It has only one root, that is, a node without a parent (node 8, in this case).\n",
+ "* And has only one path to get to any node.\n",
+ "\n",
+ "So, the node of value 3 is the child of node 8 and the parent of nodes 1 and 6. And the only way to get to node 7 is through 8, 3, 6, and 7.\n",
+ "\n",
+ "That's a binary tree. Now, what makes this \"Binary Tree\" a \"Binary Search Tree\" is that the value of each node is greater than all the values under the node's left subtree and smaller than the ones under its right subtree. For example, all the values under node 8's left subtree are smaller than 8, and all the values under node 8's right subtree are larger than 8.\n",
+ "\n",
+ "By knowing this, each time we check the value of a node, and it's not the one we're looking for, we know that if the value is smaller, we have to keep looking on the left subtree, and if it's bigger, we have to keep going on the right subtree. Allowing us to discard all the nodes of the other branch and reducing the size of the problem in half. Same as we did in the dictionary example."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, so how do we translate this to code? Well, Haskell makes it surprisingly easy. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Sequence a = EmptyS | Node a (Sequence a) deriving (Show)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In our `Sequence a` type, we had one case where the node was empty and one when the node had a value and pointed to the rest of the sequence.\n",
+ "\n",
+ "To make a BST, we need virtually the same type, except that now we want them to point to up to two sequences that are now trees. So the data type we need is this one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Node :: forall a. a -> Tree a -> Tree a -> Tree a"
+ ],
+ "text/plain": [
+ "Node :: forall a. a -> Tree a -> Tree a -> Tree a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Tree a = EmptyT | Node a (Tree a) (Tree a) deriving (Show)\n",
+ "\n",
+ ":t Node"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's it! The only difference lies in the `Node` constructor, which now contains a value and two different subtrees. \n",
+ "\n",
+ "Let's plant a few trees:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Tree a = EmptyT | Node a (Tree a) (Tree a) \n",
+ "\n",
+ "emptyTree :: Tree a\n",
+ "emptyTree = EmptyT\n",
+ "\n",
+ "oneLevelTree :: Tree Char\n",
+ "oneLevelTree = Node 'a' EmptyT EmptyT\n",
+ "\n",
+ "twoLevelTree :: Tree Integer\n",
+ "twoLevelTree = Node 8\n",
+ " (Node 3 EmptyT EmptyT)\n",
+ " (Node 10 EmptyT EmptyT)\n",
+ "\n",
+ "threeLevelTree :: Tree Integer -- Almost the same as the tree of the image\n",
+ "threeLevelTree = Node 8\n",
+ " (Node 3\n",
+ " (Node 1 EmptyT EmptyT)\n",
+ " (Node 6 EmptyT EmptyT)\n",
+ " )\n",
+ " (Node 10\n",
+ " EmptyT\n",
+ " (Node 14 EmptyT EmptyT)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Awesome. We have our data type ready to rock! We now need to implement the function to check if an element is inside a tree.\n",
+ "\n",
+ "We start, as always, with the type. The function will take a value of type `a` and a tree of values of type `a`. It will check if the value is inside the tree and return a `Bool` of value `True` if it is and `False` if it isn't. So we can start with a type signature like this one:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "elemTree :: a -> Tree a -> Bool\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, because the `Tree` type has two constructors, we know that it's likely we'll need two definitions (one per constructor) as the two cases. One for when the tree is empty, and one for when it's not:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "elemTree :: a -> Tree a -> Bool\n",
+ "elemTree v EmptyT = False\n",
+ "elemTree v (Node x left right) = ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If the tree is empty, the value we provided is obviously not inside the tree, so we return `False`.\n",
+ "\n",
+ "And what if the tree is not empty? Well, we just pattern-matched the node and have its value right there. Might as well check if it's the one we need:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "elemTree :: (Eq a) => a -> Tree a -> Bool\n",
+ "elemTree v EmptyT = False\n",
+ "elemTree v (Node x left right) = if v == x then True else ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because we are checking if the value of the first parameter is equal to the value inside the tree, we know the type `a` has to be an instance of the `Eq` type class. So we add that constraint to the signature.\n",
+ "\n",
+ "If it is equal, we return `True` and end of the story. But if it's not, we have to choose the subtree to keep looking. And that depends if the value is bigger or smaller than the one in the node. So we not only have to check if the value is equal but also greater or smaller than the value of the node."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "elemTree :: (Ord a) => a -> Tree a -> Bool\n",
+ "elemTree v EmptyT = False\n",
+ "elemTree v (Node x left right)\n",
+ " | v == x = True\n",
+ " | v > x = ...\n",
+ " | v < x = ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because we now also have to use the `>` (greater than) and `<` (smaller than) behaviors, the types have to be an instance of the `Ord` type class. And because (like we saw in a previous lesson) to be an instance of the `Ord` type class, you have to previously be an instance of the `Eq` type class, we can remove that constraint and put the `Ord` constraint.\n",
+ "\n",
+ "Also, because we'd need a bunch of nested if-else statements, we switch to guards for a more straightforward code. And now for the final two cases:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "elemTree :: (Ord a) => a -> Tree a -> Bool\n",
+ "elemTree v EmptyT = False\n",
+ "elemTree v (Node x left right)\n",
+ " | v == x = True\n",
+ " | v > x = elemTree v right\n",
+ " | v < x = elemTree v left \n",
+ "-- Examples\n",
+ "elemTree 6 threeLevelTree\n",
+ "elemTree 17 threeLevelTree"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If the value provided is bigger than the value of the node, we know that—if the value is in the tree—it will be in the right branch, where all the values are bigger than the value of the current node. So, the only thing we have to do is to recursively check the right subtree with the same initial value.\n",
+ "\n",
+ "And if the value is smaller than the value for the node, we know that—if the value is in the tree—it will be in the left branch, where all the values are smaller than the value of the current node. So, the only thing we have to do is to recursively check the left subtree with the same initial value.\n",
+ "\n",
+ "And that's it! We have a way to check if a value is in our data structure using the binary search algorithm."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's a great solution. The thing is, while you were thinking about all this, you got so focused on your thoughts that you never noticed 15 minutes had passed without you saying anything! The interviewer got a bit scared and told you that it was great meeting you and they'll communicate to let you know if you passed the interview.\n",
+ "\n",
+ "So, the takeaway is, in your next interview, remember to think out loud while working on the problems. It helps the interviewer know your thought process, and you avoid showing the face you make when binge-watching Haskell videos. Yes, the one you're making right now.\n",
+ "\n",
+ "But don't worry, you'll have more hypothetical opportunities. For now, we still have a few more things to see today. For example, the fact that the shape of the data type directs how you write functions with it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The shape of the data type directs how you write functions with it"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, this is not written in stone, but in general, you have one equation per value constructor. And if a constructor is recursive (one or N times), the equation will be recursive (one or N times).\n",
+ "\n",
+ "A few examples are:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Box a = Empty | Has a\n",
+ "\n",
+ "extract :: a -> Box a -> a\n",
+ "extract def Empty = def\n",
+ "extract _ (Has x) = x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Box a` data type has two constructors (`Empty` and `Has`), and none are recursive.\n",
+ "\n",
+ "So, when you write a function for this data type, it's likely you'll need to write two formulas (meaning two definitions)—one per constructor—and no formula will have a recursive call."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Sequence a = EmptyS | a :-> (Sequence a)\n",
+ "\n",
+ "elemSeq :: (Eq a) => a -> Sequence a -> Bool\n",
+ "elemSeq _ EmptyS = False\n",
+ "elemSeq x (y :-> ys) = x == y || elemSeq x ys"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Sequence a` data type has two constructors (`EmptyS` and `:->`), and one of the constructors (`:->`) is recursive (has `(Sequence a )` as a second parameter).\n",
+ "\n",
+ "So, when you write a function for this data type, it's likely you'll need to write two formulas—one per constructor—and the formula that matches for the `:->` constructor will have a recursive call of the function you're defining."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- data Tree a = EmptyT | Node a (Tree a) (Tree a)\n",
+ "\n",
+ "elemTree :: (Ord a) => a -> Tree a -> Bool\n",
+ "elemTree v EmptyT = False\n",
+ "elemTree v (Node x left right)\n",
+ " | v == x = True\n",
+ " | v > x = elemTree v right\n",
+ " | v < x = elemTree v left"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Tree a` data type has two constructors (`EmptyT` and `Node`), and one of the constructors (`Node`) is two times recursive (`(Tree a )` twice).\n",
+ "\n",
+ "So, when you write a function for this data type, it's likely you'll need to write two formulas—one per constructor—and the formula that matches the `Node` constructor will have two recursive calls of the function you're defining."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Of course, there are cases when this rule of thumb doesn't apply. but you can use it to get started whenever you're unsure how to define a function."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, there's so much going on with types, value constructors, type constructors, etc. that it's hard to keep track of things. Thankfully, Haskell has a trick up its sleeve: **Kinds**! "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Kinds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's go back to the simpler days. Remember the `Box` type? No? Let's see.. it had a value constructor called `Has`. Let's check its type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Has :: forall a. a -> Box a"
+ ],
+ "text/plain": [
+ "Has :: forall a. a -> Box a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t Has"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Cool, so it takes a value of any type and returns a value of type `Box a`. And what's up with that `Box` type? How can I know more about it? If you try to check the type of a type, you get an error:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:1: error: Data constructor not in scope: Box"
+ ]
+ }
+ ],
+ "source": [
+ ":t Box"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "But there's a way to know more about that type. We can use the `:i` (info) command:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {},
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ],
+ "text/plain": [
+ "type Box :: * -> *\n",
+ "data Box a = Empty | Has a\n",
+ " \t-- Defined at :1:1"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":i Box"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The second line is the definition. But what in the world are those stars at the first line? That's the `Box`'s kind. Same as how the type of a value constructor gives you the quantity and the type of the values it takes, the kind of a type constructor gives you the quantity and kind of types it takes.\n",
+ "\n",
+ "Let me say that again:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "The **type** of a **value constructor** gives you the quantity and **type of the values** it takes.\n",
+ "\n",
+ " |\n",
+ " v\n",
+ "\n",
+ "The **kind** of a **type constructor** gives you the quantity and **kind of types** it takes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, **a kind is like the type of a type**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "You can read kinds like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "* `*` means: **\"concrete type\"** (a type that doesn't take any parameters. Like `Float`.)\n",
+ "* `* -> *` means: **\"type constructor that takes a single concrete type and returns another concrete type\"** (Like `Box a`.)\n",
+ "* `* -> (* -> *) -> *` means: **\"type constructor that takes one concrete type, and one single-paramter type constructor, and returns a concrete type\"** (we haven't seen one of these yet.)\n",
+ "* And so on..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "A few examples:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`Int`, `String`, and other like them are concrete types."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Int :: *"
+ ],
+ "text/plain": [
+ "Int :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "String :: *"
+ ],
+ "text/plain": [
+ "String :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Bool :: *"
+ ],
+ "text/plain": [
+ "Bool :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Concrete types\n",
+ "\n",
+ ":k Int \n",
+ ":k String\n",
+ ":k Bool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, you can also check the kind of a type by using the `:k` (`:kind`) command."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`Box`, `Sequence`, and `Tree` all take a concrete type (`String`, `Int`, doesn't matter) and return a concrete type (`Box Int`, `Sequence String`, `Tree Float`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box :: * -> *"
+ ],
+ "text/plain": [
+ "Box :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Sequence :: * -> *"
+ ],
+ "text/plain": [
+ "Sequence :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Tree :: * -> *"
+ ],
+ "text/plain": [
+ "Tree :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Type constructor with one concrete type as parameter\n",
+ "\n",
+ ":k Box \n",
+ ":k Sequence\n",
+ ":k Tree"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Entity` type synonym took two concrete types and returned a concrete type (`Entity String Bool`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Entity :: * -> * -> *"
+ ],
+ "text/plain": [
+ "Entity :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Type constructor with two concrete types as parameters\n",
+ "-- type Entity a b = (a, b)\n",
+ "\n",
+ ":k Entity"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, type synonyms also have kinds. Because they can also have type parameters."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And also, notice that as soon as a type constructor gets all its parameters, it becomes a concrete type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box :: * -> *"
+ ],
+ "text/plain": [
+ "Box :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Box String :: *"
+ ],
+ "text/plain": [
+ "Box String :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Box\n",
+ ":k Box String"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that you can also partially-apply type constructors, the same as with functions or value constructors!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "DoubleBox :: * -> * -> *"
+ ],
+ "text/plain": [
+ "DoubleBox :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "DoubleBox String :: * -> *"
+ ],
+ "text/plain": [
+ "DoubleBox String :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "DoubleBox String Int :: *"
+ ],
+ "text/plain": [
+ "DoubleBox String Int :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data DoubleBox a b = Empty | Has a b deriving (Show)\n",
+ "\n",
+ ":k DoubleBox\n",
+ ":k DoubleBox String\n",
+ ":k DoubleBox String Int"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, next time you need to know a bit more about a type constructor, check its kind!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And now, to finish the lecture, I'll give you one more teeny-tiny piece of information. But don't worry. You don't have to learn anything more than a caveat and a single keyword. And that's the `newType` keyword."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `newType` keyword"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`newType` works essentially the same as the `data` keyword, except for an important caveat:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Types created with `newType` need to have exactly **one constructor** with exactly **one parameter/field**."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ },
+ "vscode": {
+ "languageId": "plaintext"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Like this:\n",
+ "newtype Color a = Color a\n",
+ "-- And this:\n",
+ "newtype Product a = Product { getProduct :: a }"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "But, you can also do that with `data`. So, why use `newType`? "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Short version: **Performance reasons.** So, if you happen to be creating a data type with one constructor and one parameter, you can switch the `data` keyword to the `newtype` and get a performance boost for free."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/10-Creating-Type-Classes-and-Instances.ipynb b/lessons/10-Creating-Type-Classes-and-Instances.ipynb
new file mode 100644
index 00000000..cf6251b6
--- /dev/null
+++ b/lessons/10-Creating-Type-Classes-and-Instances.ipynb
@@ -0,0 +1,3712 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Creating Type Classes and Instances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Overloading\n",
+ "* Steps to create Type Classes and Instances\n",
+ "* The `Eq` type class\n",
+ "\t* Defining the Type Class\n",
+ " * Defining multiple instances\n",
+ "\t* Improving our `Eq` type class with mutual recursion (and MCD)\n",
+ "\t* Defining an instance for a parameterized type.\n",
+ "* The `WeAccept` Type Class\n",
+ "* The `Container` Type Class\n",
+ "* Exploring `Ord` type class (Subclassing)\n",
+ "* Deriving\n",
+ " * Deriving can go wrong\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Overloading"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Before learning what Overloading is, let's learn what the word \"date\" means. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "DATE:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "What does \"date\" mean? If I'd say that you have only one chance to answer, and I'll give you $100 if you answer correctly, the intuitive answer is: \"It depends!\"\n",
+ "\n",
+ "If you're saying: \"What is your date of birth?,\" then it means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "1. The time at which an event occurs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If you're saying: \"Joe took Laura out on a date.\", then it means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "2. A social engagement that often has a romantic character (unless Joe gets friend-zoned)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If you're saying: \"I'll want to date a fossil,\" I want to believe you're not referring to a romantic social engagement but to:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "3. The act of estimating or computing a date or chronology."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And if you look up the word, \"date\" is also the name of a fruit and has even more definitions!\n",
+ "\n",
+ "In programming, we would say that the word \"date\" is overloaded. Because it has multiple definitions for the same name. \n",
+ "\n",
+ "The word \"overloading\" is overloaded itself."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "OVERLOADING:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In everyday context, it usually means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "1. To put too large a load on or in (something)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In a regular programming context, it means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "2. Having multiple implementations of a function with the same name."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "How this work in practice depends on the language. For example, some languages, like JavaScript, don't support overloading. So you can not do it. And in others, like C++, you can create multiple functions with the same name, and the compiler will choose which definition to use based on the types of the arguments.\n",
+ "\n",
+ "In Haskell, \"overloading\" means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "3. Having multiple implementations of a function or value with the same name."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Of course, Haskell had to step up the game. In Haskell, overloading is not restricted to functions. Values can be overloaded too. For example:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "- The literals `1`, `2`, etc. are overloaded because they could be interpreted as any numeric type (`Int`, `Integer`, `Float`, etc.)\n",
+ "\n",
+ "- The value `minBound` is overloaded because, for example, when used as a `Char`, it will have value `'\\NUL'` while as an `Int`, it's `-2147483648`. \n",
+ "\n",
+ "- The equality operator (`==`) works with many types, each with its own implementation.\n",
+ "\n",
+ "- The function `max` also works with many types, each with its own implementation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The first two are overloaded values, and the last are overloaded functions. So, we've been using overloaded functions and values all along. The question is: How do we get those in the first place? Well, the mechanism that allows overloading in Haskell is Type Classes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Steps to create Type Classes and Instances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In the \"introduction to type classes\" lesson, we saw the utility of type classes. It basically boils down to having functions that can be used by many different types while having the safety that they only take the ones they can work with. So, if you create a function that takes two numbers and adds them together, that function works with all numeric types while also having the compiler stop you when trying to give it a non-numeric type.\n",
+ "\n",
+ "Type classes are a pretty unique feature–not many programming languages have them. But the good thing is that they're surprisingly easy to use!\n",
+ "\n",
+ "When creating our own type classes, we only need two things."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "1. Create a Type Class stating some behaviors.\n",
+ "\n",
+ "\n",
+ "2. Make a Type an instance of that Type Class with the implementation of those behaviors for that specific type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Practice makes perfect, so let's learn by doing. We'll start by redefining the `Eq` Type Class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Eq` type class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Eq` type class comes with Haskell, so you don't have to define it. But let's say that we live in a world where the `Eq` type class doesn't exist, and every type has its own function to check for equality. Because of that, you have to learn a bunch of different functions that all do the same: Checking for equality.\n",
+ "\n",
+ "But, as Lennon said, imagine. While living in that horrible world, imagine all the types living in peace and using the same function. It's easy if you try. You may say I'm a dreamer, but let's do it anyway!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can define the `Eq` type class like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Eq a where\n",
+ " (==) :: a -> a -> Bool\n",
+ " (/=) :: a -> a -> Bool\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In the first line, we start with the `class` keyword to indicate we're creating a type class. Followed by how the type class will be called (`Eq`). Then, we write a type variable (`a`) that represents any type that will be made an instance of this type class in the future. So, it's like a placeholder. And finally, we use the `where` keyword to start the block where we define the behaviors of our newly created type class.\n",
+ "\n",
+ "And now comes the cool part. We have to define the behaviors. To do that, we write the name and type of the functions or values we need. In this case, we define the behaviors to be the `==` function–to check if two values are equal and the `/=` function–to check if two values are different.\n",
+ "\n",
+ "We also indicate that both take two values of the type `a` we specified as the parameter of the type class and return a `Bool`. `True` if the condition passes, and `False` if it doesn't."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And done! We have our `Eq` type class ready to go! This means we have the name and types of the two functions that the `Eq` type class provides. We don't have the definitions here because each type will have its own definitions. And those definitions are provided when defining an instance for the type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Defining an instance for the `Eq` type class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "First, we need a type, so let's define one for the payment methods a customer can use in our app:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data PaymentMethod = Cash | Card | CC -- CC stands for Cryptocurrency\n",
+ "\n",
+ "type User = (String, PaymentMethod)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And if we want, for example, to check if two users have the same payment method, we could write a function like this one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":2:28: error:\n • No instance for (Eq PaymentMethod) arising from a use of ‘==’\n • In the expression: pm1 == pm2\n In an equation for ‘samePM’: samePM (_, pm1) (_, pm2) = pm1 == pm2"
+ ]
+ }
+ ],
+ "source": [
+ "samePM :: User -> User -> Bool\n",
+ "samePM (_, pm1) (_, pm2) = pm1 == pm2 -- Won't work!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "But, the compiler won't let you use this code! And it tells us why:\n",
+ "\n",
+ "```\n",
+ "No instance for (Eq PaymentMethod) arising from a use of ‘==’\n",
+ "In the expression: pm1 == pm2\n",
+ "```\n",
+ "\n",
+ "We're using `==` in the expression `pm1 == pm1`. But, because `==` is a behavior of the `Eq` type class, and our new `PaymentMethod` type is not an instance of the `Eq` type class! So it doesn't have the implementations of `==` and `/=` to use. To fix this, we'll make it an instance!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- class Eq a where\n",
+ "-- ...\n",
+ "\n",
+ "instance Eq PaymentMethod where\n",
+ " -- Implementations for Eq behaviors specific to PaymentMethod\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To create an instance, we use the `instance` keyword followed by the name of the type class we want to make an instance for, the type that will be an instance of that type class, and the `where` keyword. Then, inside that block, we implement the functions defined in that type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, because now we're creating an instance for a type, we replace the type variable (`a`) we had in the type class definition with our specific type (`PaymentMethod`).\n",
+ "\n",
+ "And because we're creating an instance for the Eq type class, we need to implement the `==` and `/=` functions. So we'll do just that:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- class Eq a where\n",
+ "-- (==) :: a -> a -> Bool\n",
+ "-- (/=) :: a -> a -> Bool\n",
+ "\n",
+ "-- data PaymentMethod = Cash | Card | CC\n",
+ "\n",
+ "instance Eq PaymentMethod where\n",
+ " -- Implementation of ==\n",
+ " Cash == Cash = True\n",
+ " Card == Card = True -- Same as: (==) Card Card = True\n",
+ " CC == CC = True\n",
+ " _ == _ = False\n",
+ " \n",
+ " -- Implementation of /=\n",
+ " Cash /= Cash = False\n",
+ " Card /= Card = False\n",
+ " CC /= CC = False\n",
+ " _ /= _ = True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's it! That's how you define a type class and make a type an instance of it! Now, `PaymentMethod` can freely use the `Eq` behaviors (`==` and `/=`):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Card == Cash\n",
+ "CC /= Card"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And the previous function will work now:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "samePM :: User -> User -> Bool\n",
+ "samePM (_, pm1) (_, pm2) = pm1 == pm2 -- It's alive! \n",
+ "\n",
+ "samePM (\"Rick\", CC) (\"Marta\", CC)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Improving our `Eq` type class with Mutual Recursion"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Our work is technically done. We have our type class and our instance. But there's a property of the functions we just defined that we're not taking advantage of.\n",
+ "\n",
+ "If two values are equal, that means they are not different, and if they are different, that means they are not equal. So, we know that for each pair of values, `==` and `/=` will always give us the opposite `Bool` value.\n",
+ "\n",
+ "We're on our way to becoming epic Haskell developers, and epic Haskell developers can do better than that. So let's use this knowledge to improve our type class and instance! Starting by redefining the `Eq` type class like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Eq a where\n",
+ " (==), (/=) :: a -> a -> Bool\n",
+ " x /= y = not (x == y)\n",
+ " x == y = not (x /= y)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "**Which is how `Eq` is actually defined in Haskell!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's analyze this code. Because both functions have the same type, we can specify them in a single line. And, yes, we're also writing function definitions inside the type class. We can do that as long as they are type-agnostic because they have to work with all types.\n",
+ "\n",
+ "Looking at the definitions in more detail, we see we're using the `not` function. The `not` function takes a boolean and returns its opposite.\n",
+ "\n",
+ "So, in the third line, we're saying that the result of applying `/=` to `x` and `y` is the oposite (`not`) of the result of applying `==` to the same `x` and `y`. And in the fourth line, we're saying that the result of applying `==` to `x` and `y` is the oposite (`not`) of the result of applying `/=` to the same `x` and `y`.\n",
+ "\n",
+ "This is called mutual recursion because both functions are defined in terms of each other. By defining `==` and `/=` as the opposite of each other, Haskell can infer the behavior of one from the other. \n",
+ "\n",
+ "And, of course, like any other recursion, it needs a base case to know when to stop the recursion! And that's what we provide when implementing an instance! For example, let's redefine the PaymentMethod instance for this new type class:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Eq PaymentMethod where\n",
+ " Cash == Cash = True\n",
+ " Card == Card = True\n",
+ " CC == CC = True\n",
+ " _ == _ = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's it! Because now the compiler can infer the value of one function with the other, we don't need to implement both `==` and `/=`. We can implement the more convenient one and call it a day!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is called **minimal complete definition**. Because it's the minimum you have to implement to get a fully functional instance. You can take advantage of this by checking the minimal complete definition of any type class using `:i ` and implementing only those behaviors. For example, if you run `:i Eq` in GHCi, you'll get:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "type Eq :: * -> Constraint -- Eq takes a concrete type and returns a Constraint\n",
+ "class Eq a where\n",
+ " (==) :: a -> a -> Bool\n",
+ " (/=) :: a -> a -> Bool\n",
+ " {-# MINIMAL (==) | (/=) #-}\n",
+ "\n",
+ "-- ... and a bunch of instances.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this line:\n",
+ "\n",
+ "```haskell\n",
+ "{-# MINIMAL (==) | (/=) #-}\n",
+ "```\n",
+ "\n",
+ "It says that to have the *minimal complete definition* of the type class, you have to implement either `==` OR `/=`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In the real world, almost all types are instances of the `Eq` type class. But remember, we're in a parallel universe where you're a visionary creating the `Eq` type class to make the world a better place. So, if we stop here, the `==` and `/=` functions wouldn't be overloaded! Because they would have only the definition for `PaymentMethod`.\n",
+ "\n",
+ "But there's a reason you decided to create this `Eq` type class. And the reason is that you thought the behaviors it provides are useful for many types. For example, the Blockchain type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Create data type\n",
+ "data Blockchain = Cardano | Ethereum | Bitcoin\n",
+ "\n",
+ "-- Create instance of Eq\n",
+ "instance Eq Blockchain where\n",
+ " Cardano == Cardano = True\n",
+ " Ethereum == Ethereum = True\n",
+ " Bitcoin == Bitcoin = True\n",
+ " _ == _ = False\n",
+ "\n",
+ "\n",
+ "-- Test\n",
+ "Cardano /= Cardano"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, the `==` and `/=` are truly overloaded because they have more than one definition depending on the type of values they are applied to.\n",
+ "\n",
+ "We did it!! And we're on a roll, so let's keep going!\n",
+ "\n",
+ "So far, we've created two instances of the `Eq` type class. Both for non-parameterized types. Let's learn how we can define an instance for a parameterized type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Defining an instance for a parameterized type"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To create an instance for a parameterized type, first, we need the parameterized type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Box a = Empty | Has a"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now we can create our instance. But we can't do it like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Eq Box where\n",
+ "-- ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Why? Well, if we take a look at the type class using the `:i` command:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "type Eq :: * -> Constraint -- Eq takes a concrete type and returns a Constraint\n",
+ "class Eq a where\n",
+ " (==) :: a -> a -> Bool\n",
+ " (/=) :: a -> a -> Bool\n",
+ " {-# MINIMAL (==) | (/=) #-}\n",
+ "\n",
+ "-- ... and a bunch of instances.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We get remided that the type variable `a` is a concrete type. We can see this in two places:\n",
+ "- If we check the types of the functions, we see that the type variable `a` is alone between arrows, so it represents a concrete type by itself.\n",
+ "- And, because of that, the kind of `Eq` (`type Eq :: * -> Constraint`) clearly states that it takes a concrete type and produces a `Constraint`.\n",
+ "\n",
+ "Type classes always have a kind that returns a `Constraint` because type classes don't produce a type. They produce a constraint for polymorphic values. So, If we see a kind that ends in `Constraint`, we know it's a type class, and it goes to the left of the `=>` arrow to constraint polymorphic types. \n",
+ "\n",
+ "On top of that, we don't need to check the functions to know how the type class uses the type variable `a`. The kind already tells us if it needs a concrete type or a specific type constructor.\n",
+ "\n",
+ "So, because of `Eq :: * -> Constraint`, we know that the `a` in `Eq a` is a concrete type. But if we check the kind of `Box`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box :: * -> *"
+ ],
+ "text/plain": [
+ "Box :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Box"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We see that it's not a concrete type but a type constructor that takes one type as a parameter and returns a concrete type.\n",
+ "\n",
+ "So, what do we do? We could apply `Box` to another type to get a concrete type, like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box Int :: *"
+ ],
+ "text/plain": [
+ "Box Int :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Box Int"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That technically gives us a concrete type, so we could create the instances like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Eq (Box Int) where\n",
+ "-- ...\n",
+ "\n",
+ "instance Eq (Box String) where\n",
+ "-- ...\n",
+ "\n",
+ "instance Eq (Box PaymentMethod) where\n",
+ "-- ...\n",
+ "\n",
+ "--- etc\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And it would work perfectly. But, Hmm, this is a lot of work. And we already went through this when defining functions and solved it with type variables. This time is no different!:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Eq (Box a) where\n",
+ "-- ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "By defining this instance, all the types created using the `Box` type constructor (like `Box String` or `Box Int`) will be an instance of `Eq`.\n",
+ "\n",
+ "Now, wait a second. How do we define the instance if we don't know the type of the value inside the box? Well, if we decide that:\n",
+ "\n",
+ "- Two boxes containing equal elements are equal.\n",
+ "- Two empty boxes are equal.\n",
+ "- And everything else is different.\n",
+ "\n",
+ "We can define the behaviors like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":2:20: error:\n • No instance for (Eq a) arising from a use of ‘==’\n Possible fix: add (Eq a) to the context of the instance declaration\n • In the expression: x == y\n In an equation for ‘==’: Has x == Has y = x == y\n In the instance declaration for ‘Eq (Box a)’"
+ ]
+ }
+ ],
+ "source": [
+ "instance Eq (Box a) where\n",
+ " Has x == Has y = x == y\n",
+ " Empty == Empty = True\n",
+ " _ == _ = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In the first formula, we define `==` for the `Box a` type by applying `==` to the `a` type it contains. Because `Has x` is of type `Box a`, `x` is of type `a`. Same for the rest of values. So, if both boxes contain the same element, the boxes themselves are the same. Else, they are different. So, were making the instance of `Box a` depend on the instance of `a`. \n",
+ "\n",
+ "In the second formula, we specify that if both boxes are empty, they are equal.\n",
+ "\n",
+ "For every other case, the boxes are different."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This makes sense, but there's a HUGE oversight on our part! Did you spot it? It's ok if you didn't. That's what the compiler is here for! If we run the cell, we'll get a compiler error:\n",
+ "\n",
+ "```\n",
+ "No instance for (Eq a) arising from a use of ‘==’\n",
+ "```\n",
+ "\n",
+ "Ok, so the compiler is telling us that we're applying the `==` function to a type that doesn't have an instance of `Eq`.\n",
+ "\n",
+ "Where are we doing that?\n",
+ "\n",
+ "```\n",
+ "In the expression: x == y\n",
+ "In an equation for ‘==’: Has x == Has y = x == y\n",
+ "In the instance declaration for ‘Eq (Box a)’\n",
+ "```\n",
+ "The compiler is correct! We're using `==` between two values (`x` and `y`) of type `a` without making sure that the `a` type itself is an instance of `Eq`!\n",
+ "\n",
+ "So, what should we do? Well, the compiler also told us how to fix this:\n",
+ "\n",
+ "```\n",
+ "Possible fix: add (Eq a) to the context of the instance declaration\n",
+ "```\n",
+ "\n",
+ "Same as with functions, we can add the constraint that the type `a` in the instance of `Eq (Box a)` has to also be an instance of the `Eq` type class. Like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance (Eq a) => Eq (Box a) where\n",
+ " Has x == Has y = x == y\n",
+ " Empty == Empty = True\n",
+ " _ == _ = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This way, the type `Box a` will be an instance of `Eq` for all the types `a` that are also an instance of `Eq`.\n",
+ "\n",
+ "Aaaaaaand we're done! We can use this new instance like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:1: error:\n • No instance for (Eq Choice) arising from a use of ‘==’\n • In the expression: Has Yes == Has No\n In an equation for ‘it’: it = Has Yes == Has No"
+ ]
+ }
+ ],
+ "source": [
+ "Has Cardano /= Has Ethereum -- True\n",
+ "\n",
+ "Has Card == Empty -- False\n",
+ "\n",
+ "Has Bitcoin /= Has Bitcoin -- False\n",
+ "\n",
+ "\n",
+ "data Choice = Yes | No -- We didn't create an Eq instance for Choice\n",
+ "\n",
+ "Has Yes == Has No -- Angry compiler: There's no instance for (Eq Choice), you fool!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, even when wrapping the type inside another type, the compiler will still protect us of our human mistakes.\n",
+ "\n",
+ "Ok. Now that we did everything step-by-step with the `Eq` type class, let's do everything again, but quicker and with a new type class that it's not part of standard Haskell."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `WeAccept` Type Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Imagine we're writing an app that accepts payments for a company, and this company doesn't accept all payment methods, blockchains, and countries. So, you have to create functions to check that:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Function to check if we accept that payment method\n",
+ "weAcceptPayment :: PaymentMethod -> Bool\n",
+ "weAcceptPayment p = case p of\n",
+ " Cash -> False\n",
+ " Card -> True\n",
+ " CC -> True\n",
+ "\n",
+ "-- Function to check if we accept that blockchain\n",
+ "weAcceptBlockchain :: Blockchain -> Bool\n",
+ "weAcceptBlockchain b = case b of\n",
+ " Bitcoin -> True\n",
+ " Ethereum -> False\n",
+ " Cardano -> True\n",
+ "\n",
+ "-- Country type\n",
+ "newtype Country = Country { countryName :: String }\n",
+ "\n",
+ "-- Function to check if we accept that country\n",
+ "weAcceptCountry :: Country -> Bool\n",
+ "weAcceptCountry c = case countryName c of\n",
+ " \"Mordor\" -> False\n",
+ " _ -> True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Seeing this code, we realize that this behavior of checking if the company accepts something could be used in many other aspects. Like providers, technologies, etc. There are a lot of things a company could decide to accept or not.\n",
+ "\n",
+ "To avoid having a bunch of different functions that do the same all over your code, we decide to create a type class that represents this behavior.\n",
+ "\n",
+ "And that type class looks like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "WeAccept :: * -> Constraint"
+ ],
+ "text/plain": [
+ "WeAccept :: * -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Creating WeAccept type class\n",
+ "class WeAccept a where\n",
+ " weAccept :: a -> Bool\n",
+ "\n",
+ "-- Checking kind of WeAccept\n",
+ ":k WeAccept"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now that we have our type class, we can create the instances for `PaymentMethod`, `Blockchain`, `Country`, and even `Box` like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance WeAccept PaymentMethod where\n",
+ " weAccept x = case x of\n",
+ " Cash -> False\n",
+ " Card -> True\n",
+ " CC -> True\n",
+ "\n",
+ "instance WeAccept Blockchain where\n",
+ " weAccept x = case x of\n",
+ " Bitcoin -> True\n",
+ " Ethereum -> False\n",
+ " Cardano -> True\n",
+ "\n",
+ "instance WeAccept Country where\n",
+ " weAccept x = case countryName x of\n",
+ " \"Mordor\" -> False\n",
+ " _ -> True\n",
+ "\n",
+ "instance (WeAccept a) => WeAccept (Box a) where\n",
+ " weAccept (Has x) = weAccept x\n",
+ " weAccept Empty = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And done! This gives us the ability to apply the overloaded `weAccept` function to three different types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "weAccept Cardano\n",
+ "weAccept Cash\n",
+ "weAccept (Country \"Mordor\")\n",
+ "weAccept (Has Bitcoin)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can also create functions that can be applied to all the types that are instances of `WeAccept`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"Do something fancy\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Do something fancy\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Don't do it!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Do something fancy\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Creating fancyFunction\n",
+ "fancyFunction :: (WeAccept a) => a -> String\n",
+ "fancyFunction x =\n",
+ " if weAccept x\n",
+ " then \"Do something fancy\"\n",
+ " else \"Don't do it!\"\n",
+ " \n",
+ "-- Using fancyFunction\n",
+ "fancyFunction Cardano\n",
+ "fancyFunction Card\n",
+ "fancyFunction (Country \"Mordor\")\n",
+ "fancyFunction (Has Bitcoin)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Another type class under our belt! It's getting easier by the minute! \n",
+ "\n",
+ "We'll do one more example before continuning to the next section. This one is a bit more difficult, but if you understand it, you'll be able to understand any type class! No matter how complicated it gets!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Container` Type Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is the scenario: We're working on a logistics software that has two different types of packages. A regular box that may or may not contain something, and a present, that may or may not contain something, but that it always has a name tag of who's the present for. So, we have these two types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box :: * -> *"
+ ],
+ "text/plain": [
+ "Box :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Present :: * -> * -> *"
+ ],
+ "text/plain": [
+ "Present :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Box a = Empty | Has a deriving (Show)\n",
+ "data Present t a = EmptyPresent t | PresentFor t a deriving (Show)\n",
+ "\n",
+ ":k Box\n",
+ ":k Present"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because we decided that the tag of the present (`t`) can be a number, a name, or anything else that could identify a customer, we'll also parameterize its type.\n",
+ "\n",
+ "Now, a few parts of the process require functions common to both types. We need:\n",
+ "- One to check if a box or present is empty.\n",
+ "- One to check if a specific value is contained inside the box or present.\n",
+ "- And one to replace the contents of the box or present.\n",
+ "\n",
+ "Instead of writing the functions by themselves and then transforming them to a type class and instances as we did in the two previous examples, let's go straight to the type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Container c where\n",
+ " isEmpty :: -- ...\n",
+ " contains :: -- ...\n",
+ " replace :: -- ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The type class will be called `Container` because it provides behaviors related to containers. The type variable is called `c` because it's a container.\n",
+ "\n",
+ "Now, let's write down the type signatures. We'll start with the `replace` function. Cause why not?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Container c where\n",
+ " isEmpty :: -- ...\n",
+ " contains :: -- ...\n",
+ " replace :: c a -> b -> c b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`replace` takes two inputs:\n",
+ "- A container `c` that has some value of type—let's say `a`—inside.\n",
+ "- And another value that can be of the same or different type than the one inside the container. Let's call it `b`. \n",
+ "\n",
+ "The function replaces the value of type `a` inside the container with the one of type `b`. So, in the end, we get a value of type `c b` because the value it contains is now of type `b`.\n",
+ "\n",
+ "Now, let's do the `contains` function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Container c where\n",
+ " isEmpty :: -- ...\n",
+ " contains :: (Eq a) => c a -> a -> Bool\n",
+ " replace :: c a -> b -> c b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`contains` takes two inputs:\n",
+ "- A container `c` that has some value of type `a` inside.\n",
+ "- And another value that will be compared to the one inside the container. So it needs to be of the same type `a`, and an instance of `Eq` because we'll need to use `==` to check if it's the same value.\n",
+ "\n",
+ "The function takes the value, checks if it's the same as the one inside the container, and returns `True` if it is and `False` if it isn't. So, we return a boolean.\n",
+ "\n",
+ "And finally, let's do the `isEmpty` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Container c where\n",
+ " isEmpty :: c a -> Bool\n",
+ " contains :: (Eq a) => c a -> a -> Bool\n",
+ " replace :: c a -> b -> c b"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`isEmpty` takes one input:\n",
+ "- A container `c` that has some value of type `a` inside.\n",
+ "\n",
+ "The function takes the container and returns `True` if it contains a value and `False` if it doesn't. So it returns a value of type `Bool`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Our type class is ready to go! \n",
+ "\n",
+ "And because each `->` (arrow) separates a value, and all values need to have a concrete type, we know that both `a` and `b` are concrete types by themselves. Because they are alone between arrows.\n",
+ "\n",
+ "Using the same reasoning, we know that `c a` and `c b` have to be concrete types. And because `a` and `b` are concrete types, this means that `c` is a type constructor that takes a concrete type and returns a concrete type.\n",
+ "\n",
+ "We can see this if we check the kind of our type class:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Container :: (* -> *) -> Constraint"
+ ],
+ "text/plain": [
+ "Container :: (* -> *) -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Container"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now that we have our type class, let's create the instances for the `Box` type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Box :: * -> *"
+ ],
+ "text/plain": [
+ "Box :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Container :: (* -> *) -> Constraint"
+ ],
+ "text/plain": [
+ "Container :: (* -> *) -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- class Container c where\n",
+ "-- isEmpty :: c a -> Bool\n",
+ "-- contains :: (Eq a) => c a -> a -> Bool\n",
+ "-- replace :: c a -> b -> c b\n",
+ "\n",
+ "instance Container Box where\n",
+ "\n",
+ " isEmpty Empty = True\n",
+ " isEmpty _ = False\n",
+ " \n",
+ " contains (Has x) y = x == y\n",
+ " contains Empty _ = False\n",
+ " \n",
+ " replace _ x = Has x\n",
+ " \n",
+ "\n",
+ ":k Box\n",
+ ":k Container"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notice that we create an instance for `Box`, not `Box a`. For the `Eq` type class, we applied `Box` to the type variable `a` to obtain the concrete type `Box a` because the `Eq` type class needed a concrete type as a parameter. But `Container` takes a constructor of kind `* -> *`, which is the same kind as `Box`. So we have to pass `Box` without applying it to anything.\n",
+ "\n",
+ "The actual implementation of the functions is pretty straightforward. Because `Box` has two constructors, we have two formulas per function.\n",
+ "\n",
+ "Now let's create the instance for the `Present` type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Present :: * -> * -> *"
+ ],
+ "text/plain": [
+ "Present :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Container :: (* -> *) -> Constraint"
+ ],
+ "text/plain": [
+ "Container :: (* -> *) -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Present String :: * -> *"
+ ],
+ "text/plain": [
+ "Present String :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- class Container c where\n",
+ "-- isEmpty :: c a -> Bool\n",
+ "-- contains :: (Eq a) => c a -> a -> Bool\n",
+ "-- replace :: c a -> b -> c b\n",
+ "\n",
+ "\n",
+ "instance Container (Present t) where\n",
+ " \n",
+ " isEmpty (EmptyPresent _) = True\n",
+ " isEmpty _ = False\n",
+ " \n",
+ " contains (PresentFor _ x) y = x == y\n",
+ " contains (EmptyPresent _) _ = False\n",
+ " \n",
+ " replace (PresentFor tag _) x = PresentFor tag x\n",
+ " replace (EmptyPresent tag) x = PresentFor tag x\n",
+ "\n",
+ "\n",
+ ":k Present\n",
+ ":k Container\n",
+ ":k Present String"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, the instance is for the `Present t` type constructor. This is because `Present` by itself has kind `* -> * -> *`, but because `Container` takes a type constructor of kind `* -> *`, we have to apply `Present` to a type—like `Present String`—to obtain the kind we need. And because we want to be able to use any type as a tag, we use the type variable `t`.\n",
+ "\n",
+ "So, this part is important. The `t` in `Present t` is the tag. And the whole `Present t` type constructor is `c`. We can treat the `Present t` type constructor as `c` because it's a type that never changes. We don't change the tag's type in any of the functions. But we do modify the type of the contents in the `replace` function. When we use `replace`, the type of the contents can change from `a` to `b`, so we can't treat them as a constant type like `t`. That's why they are parameters to the `c` type constructor, so we can change the type in the `replace` function if we need to.\n",
+ "\n",
+ "Same as before, the actual implementation of the functions are straight forward."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And to rip the rewards of our work, here are a few examples using our new type class behaviors:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "PresentFor \"Tommy\" \"Arduino\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"WROOONG!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"WROOONG!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Has False `contains` False -- True\n",
+ "\n",
+ "isEmpty (Has 'a') -- False\n",
+ "\n",
+ "PresentFor \"Tommy\" 5 `contains` 5 -- True\n",
+ "\n",
+ "PresentFor \"Tommy\" 5 `replace` \"Arduino\" -- PresentFor \"Tommy\" \"Arduino\"\n",
+ "\n",
+ "\n",
+ "guessWhat'sInside :: (Container a, Eq b) => a b -> b -> String\n",
+ "guessWhat'sInside x y =\n",
+ " if x `contains` y \n",
+ " then \"You're right!!\"\n",
+ " else \"WROOONG!\"\n",
+ "\n",
+ "guessWhat'sInside (PresentFor \"Mary\" \"A Raspberry Pi!\") \"A Ponny!\" -- **Mary's Dissapointment increasses**\n",
+ "guessWhat'sInside (Has 1) 15"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Understanding this type class and instances was the trickiest part of the lesson. It might take a while to fully grasp what we just saw. But don't worry, if something doesn't click, it will with some practice. That's why it's important to do the homework.\n",
+ "\n",
+ "Now, let's learn about subclassing. After everything we went through, this is a piece of cake."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Exploring the `Ord` type class (Subclassing)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We never talked about subclassing before, but you already know how it works.\n",
+ "\n",
+ "Let's see it in practice while defining an instance for the `Ord` type class.\n",
+ "\n",
+ "If we run the info command on the `Ord` type class (`:i Ord`), we would get something like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "type Ord :: * -> Constraint -- Takes a concreate type\n",
+ "class Eq a => Ord a where -- That \"Eq a =>\" is new!! 🤔\n",
+ " compare :: a -> a -> Ordering \n",
+ " (<) :: a -> a -> Bool -- A bunch of functions\n",
+ " (<=) :: a -> a -> Bool\n",
+ " (>) :: a -> a -> Bool\n",
+ " (>=) :: a -> a -> Bool\n",
+ " max :: a -> a -> a\n",
+ " min :: a -> a -> a\n",
+ " {-# MINIMAL compare | (<=) #-} -- We can only implement \"compare\" or \"<=\".\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Everything checks out. Except for that `Eq a =>`. We've seen this in both functions and instances. But never on type class definitions.\n",
+ "\n",
+ "This (`Eq a =>`) means what you'd imagine:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "**To make a type `a` an instance of `Ord`, first we have to make it an instance of `Eq`! Meaning that `Eq` is a prerequisite for `Ord`. In other words, `Eq` is a superclass of `Ord` or `Ord` is a subclass of `Eq`.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Superclasses allow simpler signatures to be inferred. By saying that a type is an instance of `Ord`, not only do we know that it has the behaviors of `Ord`, but also the behaviors of `Eq`. Also, this allows us to use behaviors of the `Eq` type class to define the instances of the `Ord` type class. Which is actually something that happens in this case. The `Ord` type class uses functions provided by the `Eq` type class.\n",
+ "\n",
+ "We can't see it because the info command doesn't show the whole type class definition. Same as when we run the info command for the `Eq` type class, it doesn't show the mutually recursive definitions of `==` and `/=` that we just wrote.\n",
+ "\n",
+ "Still, even though we can't see them, we know there are a bunch of function definitions defined in terms of each other. That's why we can implement the entire instance by only defining `compare` or `<=`.\n",
+ "\n",
+ "The info command doesn't show all that code because we, the developers, don't care about it. We only want to know:\n",
+ "- Which behaviors come with the type class. To see if it's what we need.\n",
+ "- The kind of the type class and minimum behaviors we need to implement. To only implement those. \n",
+ "- If it depends on another type class. To implement that one before this one.\n",
+ "- And, finally, which types are already an instance of this type class. To see which types can already use those behaviors.\n",
+ "\n",
+ "And that's what the info command shows us."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, to make a type an instance of `Ord`, first, we have to make it an instance of `Eq`. Luckily, we already created a few instances for `Eq` before, so we're already halfway through if we want to create `Ord` instances for any of those types.\n",
+ "\n",
+ "For example, if we want to create an instance of `Box a` for the `Ord` type class, we have to implement one of the functions needed for the minimal complete definition! In this case, we chose the `compare` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "LT"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- type Ord :: * -> Constraint \n",
+ "-- class Eq a => Ord a where \n",
+ "-- compare :: a -> a -> Ordering \n",
+ "\n",
+ "instance (Ord a) => Ord (Box a) where\n",
+ " Has x `compare` Has y = x `compare` y\n",
+ " Empty `compare` Has _ = LT\n",
+ " Has _ `compare` Empty = GT\n",
+ " Empty `compare` Empty = EQ\n",
+ " \n",
+ "\n",
+ "Has 9 >= Has 5\n",
+ "Empty `compare` Has 0\n",
+ "Empty < Empty"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is what is happening here:\n",
+ "\n",
+ "- If both boxes have some value inside, we compare the values. And because we're applying the `compare` function to `x` and `y` of type `a`, we need to add the constraint that the `a` type has to be an instance of `Ord`.\n",
+ "- If one of the boxes is `Empty` and the other isn't, it doesn't matter what's inside the one that has something. It will always be greater than the `Empty` one. Because I said so.\n",
+ "- If both are `Empty`, of course, they are equal."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And Boom! that's it!\n",
+ "\n",
+ "We created:\n",
+ "- The `Eq` type class with 3 different instances.\n",
+ "- The `WeAccept` type class with 4 instances.\n",
+ "- Then, the `Container` type class with 3 instances.\n",
+ "- And finally, we made a type an instance of the `Ord` type class.\n",
+ "\n",
+ "**Congratulations! 🎉 You know everything needed to work with type classes!!**\n",
+ "\n",
+ "As the final section of this lesson, we'll learn how and when to automatically derive instances. Saving us some precious time and reducing the amount of code we have to maintain."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Deriving"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Derived instances are an automatic way of making a type an instance a type class. This is possible because many common type classes are usually implemented the same way. And some clever guys with PhDs figured out how to generate this code based on the type's definition.\n",
+ "\n",
+ "This is limited to `Eq`, `Ord`, `Enum`, `Show`, and others defined in either the Prelude or a standard library—libraries that come with Haskell and that we'll explore in future lessons. For now, think that all the type classes we used until now, and that we didn't create ourselves, can be derived."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To use this feature, add the `deriving` keyword at the end of your type declaration with the names of all the type classes you want to derive. For example, if we do this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Choice = No | Idk | Yes deriving (Eq, Ord, Show, Bounded, Enum)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Yes\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "No"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Idk"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Yes /= No -- Are these values different? (Behavior from Eq)\n",
+ "\n",
+ "Yes > No -- Is Yes bigger than No? (Behavior from Ord)\n",
+ "\n",
+ "show Yes -- Transform Yes to String (Behavior from Show)\n",
+ "\n",
+ "(minBound) :: Choice -- Smallest value of type Choice (Behavior from Bounded)\n",
+ "\n",
+ "succ No -- Successor of No (Behavior from Enum)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's it!! Your `Choice` type has the behaviors provided by all those type classes. \n",
+ "\n",
+ "So, if we could do that from the start, why in the world would you care for manually deriving instances?\n",
+ "\n",
+ "Well... One reason is that not all type classes can be derived. And another is that deriving can sometimes go wrong."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Deriving can go wrong"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Each type class has its own set of rules for deriving instances. For example, when deriving the `Ord` type, value constructors defined earlier are smaller. So, in this case:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "GT"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data PaymentMethod = Cash | Card | CC deriving (Eq, Ord)\n",
+ "\n",
+ "Cash > Card\n",
+ "Card < CC\n",
+ "CC `compare` Cash"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`Cash` is smaller than `Card`, which is smaller than `CC`.\n",
+ "\n",
+ "And in this case:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "LT"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Box a = Empty | Has a deriving (Eq, Ord)\n",
+ "\n",
+ "Has 5 `compare` Has 6\n",
+ "Has \"Hi\" >= Has \"Hello!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If a value constructor has a parameter (`Has a`), and two values are made from the same constructor (`Has 5` and `Has 6`), the parameters are compared (like we did when we defined the instances ourselves)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Those are the rules the compiler follows to automatically create the `Ord` instance for your type. Other type classes have other rules. We won't go over the rules of each type class, but I'll provide a [link](https://www.haskell.org/onlinereport/haskell2010/haskellch11.html) with a short explanation in the interactive lesson. In case you want to learn more."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, let's say we want to use a type to manage lengths for Civil engineering software.\n",
+ "\n",
+ "We work with both meters and kilometers, but because we don't want to accidentally mix those and get a potentially catastrophic error, we define a data type with two constructors. One for meters and one for kilometers. Both contain a value of type `Double`. We'll also derive the `Eq` type class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Length = M Double | Km Double deriving (Eq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "But, as soon as we start using this data type, we find a little problem. We know that 1000 Meters equals 1 Kilometer, but when we test this in our code, we get that it's not!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "M 1000 == Km 1 -- False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's because when we derived `Eq`, Haskell generated this code:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Eq Length where\n",
+ " (==) (M x) (M y) = x == y \n",
+ " (==) (Km x) (Km y) = x == y \n",
+ " (==) _ _ = False\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This works great if we're comparing meters to meters and kilometers to kilometers. But we have the wrong implementation to compare between constructors because Haskell doesn't know that the values of different constructors relate in any way!! Haskell just assumed that if the constructors are different, the values are too!\n",
+ "\n",
+ "So, in this case, we have to write the instance ourselves to take into account the relationship between the constructors. Like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Length = M Double | Km Double\n",
+ "\n",
+ "instance Eq Length where\n",
+ " (==) (M x) (M y) = x == y\n",
+ " (==) (Km x) (Km y) = x == y\n",
+ " (==) (M x) (Km y) = x == 1000 * y\n",
+ " (==) (Km x) (M y) = x * 1000 == y\n",
+ "\n",
+ "\n",
+ "M 3000 == Km 3 -- True\n",
+ "Km 7 /= M 14 -- True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's why it's a good idea to be conscious of how each type class is derived. To know when you can derive them and when you have to write the instance by hand.\n",
+ "\n",
+ "And to finish the lesson, here are a few tips for real-world coding:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Tips for real-world coding"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- Everything I explained here today applies to all type classes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- We don't define type classes that often. Usually, the ones that come with Haskell are all we need."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- We do implement instances quite a lot. And it's usually (but not always) a good idea to derive them. If you're in doubt, try automatic deriving and check your assumptions. You can always come back and manually define the instances."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- You can peek at a type class definition using `:i` on GHCi to see the minimum behaviors to implement when creating your instance. Implement those, and you're done."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/11-Basic-IO.ipynb b/lessons/11-Basic-IO.ipynb
new file mode 100644
index 00000000..8a347018
--- /dev/null
+++ b/lessons/11-Basic-IO.ipynb
@@ -0,0 +1,2864 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Basic I/O"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Pure functions\n",
+ "* Introduction to IO actions\n",
+ "* IO actions under the hood\n",
+ "* IO actions in practice\n",
+ " - The `()` type\n",
+ "* Interacting with the user\n",
+ " - `getChar`, `getLine`, and `putStrLn`\n",
+ "* Actions are first-class values\n",
+ "* Composing IO actions (`>>` and `>>=` operators)\n",
+ "* The do block\n",
+ " - Using `let`, nesting do-blocks, escaping `IO` and `return`\n",
+ "* The `main` action\n",
+ "* Recap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Pure functions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So far, we've been working with pure functions. These functions have no side effects and take all their arguments as input and produce a value as an output that depends only on those arguments."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "What we mean by input and output is crucial here. A function's input can be only the values we provide as arguments, and its output is the value it returns.\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `lame` function takes a single numeric parameter and returns the value multiplied by three. The output of this function depends exclusively on the value we provide as input. So, every time we apply this `lame` function to four, we'll get twelve."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Even if we don't explicitly show the input of `max6`, we know that because `filter` is partially applied, it takes a list of `Integers`. (I simplified the signature a bit.) Same as before, the output depends exclusively on the value we provide as input. So, every time we apply this `max6` function to the same list, we'll get the same result.\n",
+ "\n",
+ "And, of course, functions are curried. So if a function looks like it takes multiple arguments, it actually takes a single parameter and returns a function that takes another single parameter, and so on, until all parameters are applied, and we get the final value. If we use the same parameters, we get the same result. And in every intermediate step, we also get the same intermediate result."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "All good so far. But what if we don't have the input yet?\n",
+ "\n",
+ "What if we want to make an interactive program? A website? Or a game? When we write our program, we have no idea what the user will do with it. We can't know in advance if a player in our game will move to the left or right. Or if a user on our website will click on a specific button or not. \n",
+ "\n",
+ "Those things happen while running the program, so there's no way for the programmer to pass them as inputs of a function.\n",
+ "\n",
+ "I mean, we could, but imagine if we chose them beforehand. A game that always does and finishes the same way. Without any way for the player to interact with it. That sounds more like a movie. Still not bad. Until you realize that you can't even show the image on the screen because that would entail sending and receiving information from your computer's screen while running the program. So, if you run this \"game,\" you're basically using your computer as a very expensive heater.\n",
+ "\n",
+ "The only way to provide our program with the information and capabilities it needs is to give it a method to interact with the real world.\n",
+ "\n",
+ "And for that, Haskell uses IO actions."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Introduction to IO Actions "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Before starting with IO actions, I'll address the elephant in the room. I just told you that everything we coded so far was pure, and we couldn't interact with it. But we've been interacting with our functions and passing arguments since lesson one! That's because we've been cheating by using GHCi, which performs IO actions in the background without explicitly telling us. So, at the end of the day, if we want our program to interact with the real world, we still need IO actions."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "***IO action* (or just *action*/*computation*)** can interact with and change the world outside our program. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The name IO action gives room for misinterpretation. When we talk about IO actions, we're not talking about the input and output of the function. We talk about the input and output between our program and the real world. IO actions can interact with and change the world outside our program. They might or might not interact with the world, but they CAN. That's key. They are **allowed** to interact with the world.\n",
+ "\n",
+ "These IO actions are what we call a side effect."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**A side effect is any observable effect other than the effect of returning a value.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's why all the functions we've written so far have been pure. The only thing they did was to return a value. It might sound a bit abstract, so let's see a few examples so we can build up our intuition:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "- Obtain the result of what a person typed on the keyboard."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notice that IO actions are on top of the diagram. We represented functions with boxes that took their inputs from the left and returned the outputs on the right. But now that we're dealing with actions, we indicate side effects at the top of the diagram with arrows going both in and out because side effects can both send and receive information."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "- Show some text, an image, or something on the screen."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "- Call an API or a database (it doesn't really matter what you do)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, so we are clear about the idea of IO actions. But how does Haskell handle those IO actions?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## IO actions under the hood"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, I'm going to show you the definition of the type that alows us to safely interact with the real world using IO actions. A heads up! Don't try to understand the code. We'll only use it to create a mental model of what's happening under the hood.\n",
+ "\n",
+ "Without further ado, here's the `IO` type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `IO` type constructor has a single `IO` value constructor.\n",
+ "\n",
+ "The interesting part is the function passed as a parameter of the value constructor. It takes the state of the real world, does something to it, and returns a tuple containing the new state of the real world and a value of type `a` that was generated while all that was happening."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Aren't the designers of Haskell a bunch of smarty-pants? If you give a function the state of the whole real world, that function can do anything! Talk to databases, access files on your computer, allow penguins to fly, anything!\n",
+ "\n",
+ "We'll use it to print stuff on the screen, though.\n",
+ "\n",
+ "Of course, this is not what is really happening under this seemingly magical type. But we can think of it this way. The IO type is actually an abstract type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**An abstract data type is a type whose representation is hidden 🫣 but provides associated operations.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, the inner workings of IO are hidden. And instead, we get a bunch of functions and actions that use this type. Which means:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**We don't use the IO constructor directly. We use functions and actions that operate with IO values.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So there you go, that's as far as we're going to go about this. If you want to know more, there are tons of tutorials out there, or you could even explore the source code itself! But I'd recommend waiting until you're more fluent in Haskell to tackle that challenge."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, let's switch to the practical side. In practice, we don't care about the details of how the interaction with the real world is handled. We don't even care about how the `IO` type is defined! The only thing we care about is that, if we use it properly, the compiler will handle the details, and we won't get any surprises when running our code. So let's learn how to properly use the `IO` type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## IO actions in practice"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `IO a` type tells us that `something` is an IO action that first interacts with the real world and then returns a value of type `a`.\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "action1 :: IO Bool -- Performs IO and returns a Bool\n",
+ "\n",
+ "action2 :: IO Int -- Performs IO and returns an Int\n",
+ "\n",
+ "action3 :: IO (Double -> Char) -- Performs IO and returns a function\n",
+ "\n",
+ "action4 :: IO () -- Performs IO and returns ()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "There are three key things to note here:\n",
+ "1. One is that, after performing the action, we get a VALUE of the specified type we can use in our code.\n",
+ "2. The other is that the action returns a value AFTER performing the IO action. We CAN NOT get that value without interacting with the real world.\n",
+ "3. And finally, we see a new type in the last action: The unit type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "### The unit type"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is the unit type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data () = ()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We see that it has only one value constructor that takes no parameters (also called a nullary constructor). So, it's a type that has only one possible value (`()`). Also, notice that the type and the value have the same name. \n",
+ "\n",
+ "But wait! Why do we learn about this just now? Well, because, until now, we've been working with pure functions. If the only thing a pure function does is to return `(),` why do you even bother to use that function? Just use the value directly! And if a function takes the unit value as a parameter, why do you even bother requiring it if it's always the same value? Just remove that parameter and use the unit inside the function directly! \n",
+ "\n",
+ "So, when you think about it, we could remove the unit type from any pure function and lose nothing. BUT! Now that we're dealing with actions and side effects, there are plenty of cases when we don't care about what the action returns as a value because we care only about the side effect it performs. Take printing something on the screen or deleting a file. We don't need something in return. We care only about the side effect.\n",
+ "\n",
+ "That's why, now, it makes sense to have this type. To represent a value that we don't care about.\n",
+ "\n",
+ "If it doesn't quite click, don't worry, we'll see a couple of real-life examples during this lesson. Let's start wit how to retrieve the input from a user."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Interacting with the real world (`getChar`, `getLine`, and `putStrLn`)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The most basic IO action I can think of is `getChar`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This function performs the IO action of asking the user to write a single character on the standard input. As soon as the user writes that character, it takes it and finishes the IO action. The value of type `Char` it returns is the character the user wrote. We can run the function here to see how that looks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetChar: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "-- getChar :: IO Char\n",
+ "getChar"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, when we run this cell, a text area appears so we can input a character. There's still some magic going on because we're using these Jupyter cells that run GHCi under the hood as training wheels. But we'll get rid of them soon enough. \n",
+ "\n",
+ "Now, there are cases when a single character is enough, like when you need to confirm or deny an action in a CLI. But what about a whole phrase? For that, we can use another function called `getLine`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This one performs the IO action of asking the user to write a line on the standard input. And the value of type `String` it returns is the text the user wrote until it pressed Enter."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "-- getLine :: IO String\n",
+ "getLine"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `getChar` and `getLine` functions are great and all. But, if we run a program with just these, it will prompt us for a character or string without any explanation. Like when your dog suddenly stands in front of you and silently but intensely stares at you with puppy eyes. You know it wants something, but what?\n",
+ "\n",
+ "Not only do we need a way to get but also to send messages to the outside world. And for that, we use `putStrLn`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`putStrLn` is a function that takes a string as input and returns an IO action. And what does the action returned by `putStrLn` do?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "It prints the `String` we previously passed as a parameter to the standard output.\n",
+ "\n",
+ "As an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hello World from inside the program!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "putStrLn \"Hello World from inside the program!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this case, `putStrLn` is applied to the `String` and returns an action of type `IO ()`. Then, because we're using a Jupyter notebook, it automatically performs the action, and we get the `String` in our standard output below the cell."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "But check this out:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x = putStrLn \"Hello World from inside the program!\" "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If we run the last cell, we don't get the `String` in our standard output. That's because we didn't ask for the action to be performed. We just named it `x`, and that's it. We never actually need the action to be performed, so Haskell didn't perform it.\n",
+ "\n",
+ "And that's another property of actions. They are what in programming are called first-class values."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Actions are first-class values"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "It means that you can treat actions the same as any other value. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- putStrLn is an example of a function that returns an action\n",
+ "\n",
+ "\n",
+ "-- Bind to names\n",
+ "x = putStrLn \"Hello World from inside the program!\"\n",
+ "\n",
+ "\n",
+ "-- Put them inside lists or other data structures\n",
+ "listOfActions :: [IO ()]\n",
+ "listOfActions = [putStrLn \"a\", putStrLn \"b\"]\n",
+ "\n",
+ "\n",
+ "-- Pass them as function parameters\n",
+ "fakeLength :: [IO ()] -> Int\n",
+ "fakeLength list = 1 + length list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And, if we run the `fakeLenghth` function passing the `listOfActions` as parameters..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fakeLength listOfActions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We don't get any of the messages in the standard output because none of the actions inside the list are performed! Why should they be performed? We just asked about the length of the list, not the values it contains. Remember that Haskell is lazier than a cat sunbathing. It won't do anything unless it has to.\n",
+ "\n",
+ "So, until they are performed, IO actions are just plans to do something, programs to be run, or actions to be performed. And because the side effects aren't performed while evaluating all those expressions, we can keep reasoning about our code like we're used to. And only care for the actual side effects when we ask Haskell to perform them.\n",
+ "\n",
+ "So, how can we ask Haskell to perform a few different actions? By composing them with special operators."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Composing IO actions (`>>` and `>>=` operators)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We'll create a few simple bots to learn how to compose IO actions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Rude bot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The first one is a rude bot. As soon as you interact with it, it yells at you.\n",
+ "\n",
+ "It starts with a simple message:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hey!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "rudeBot :: IO ()\n",
+ "rudeBot = putStrLn \"Hey!\"\n",
+ "\n",
+ "rudeBot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The bot has to catch its breath before yelling at you again, so we'll add the next phrase in a second action. To do that, we'll introduce the \"then\" operator:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(>>) :: IO a -> IO b -> IO b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is a specialized signature specifically for IO actions. And as you can see, the operator takes two actions as inputs: `IO a` and `IO b`. It first executes `IO a`, ignores the result (whatever this `a` is), and returns the `IO b` action.\n",
+ "\n",
+ "If this were a pure operator, the implementation would look like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "pureThen :: a -> b -> b\n",
+ "x `pureThen` y = y\n",
+ "\n",
+ "\n",
+ "3 `pureThen` 5\n",
+ "\n",
+ "3 `pureThen` 5 `pureThen` 6 `pureThen` 'a'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We'd be just throwing away the first value and returning the second.\n",
+ "\n",
+ "But! Because we're dealing with IO actions, this operator has some secret sauce that allows it to perform the first IO action before returning the second one. More specifically:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(>>) :: IO a -> IO b -> IO b\n",
+ "\n",
+ "```\n",
+ "\n",
+ "\n",
+ "**The `>>` operator sequentially composes two actions, discarding any value produced by the first.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "a\n",
+ "b\n",
+ "c"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "abc :: IO ()\n",
+ "abc = putStrLn \"a\" >> putStrLn \"b\" >> putStrLn \"c\"\n",
+ "\n",
+ "abc"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`abc` is an action composed by multiple actions.\n",
+ "\n",
+ "When we ask Haskell to perform the `abc` action, it first performs the `putStrLn \"a\"` action and discards the result, then performs the `putStrLn \"b\"` action and discards the result, and finally, performs the `putStrLn \"c\"` action and returns whatever the last action returns. So, because `putStrLn \"c\"` returns the unit after the side effects, the `abc` action also returns the unit after the side effects."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The direction of the arrows indicates the direction of the sequence. We perform actions from left to right.\n",
+ "\n",
+ "Now, let's use this operator to finish the rude bot:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hey!\n",
+ "Get out of my lawn!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "rudeBot :: IO ()\n",
+ "rudeBot = putStrLn \"Hey!\" >> putStrLn \"Get out of my lawn!\"\n",
+ "\n",
+ "rudeBot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Exactly what we wanted!\n",
+ "\n",
+ "But maybe we could make it even ruder."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### An even ruder bot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "How could we make this bot more annoying? Hmm... I know! Let's make it seem interested in us and then yell at us! So it also wastes our time.\n",
+ "\n",
+ "So, we'll make the bot ask our name, completely ignore it, and then yell at us. All that with this simple code:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "evenRuderBot :: IO ()\n",
+ "evenRuderBot =\n",
+ " putStrLn \"What's your name?\" -- IO ()\n",
+ " >> getLine -- IO String\n",
+ " >> putStrLn \"like I care! Get lost!\" -- IO ()\n",
+ " \n",
+ "evenRuderBot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "It's getting a bit too long, so we'll split it into several lines.\n",
+ "\n",
+ "Besides that, not much has changed. The only real change is that we added a `getLine` function between the `putStrLn` ones. But if we run it now, we see that\n",
+ "\n",
+ "1. First, it performs the side effect of asking for our name in the standard output.\n",
+ "2. Then, it performs the side effect of waiting for us to type it and returning that as a value. But because we're using the `>>` operator, we ignore it.\n",
+ "3. And finally, it sends the last message."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "It's definitely ruder than before, but maybe we took it too far. Let's learn how to use the value returned by `getLine` by taking our rude bot to therapy."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Rude bot after therapy"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "After therapy, our bot feels way better and wants to be our friend. And for that, it has to use our name. We can't use the `>>` operator cause that ignores it. So, we need another operator that does the same but passes the result of the first action to the second one. And that's the \"bind\" operator."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(>>=) :: IO a -> (a -> IO b) -> IO b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is also a specialized signature for IO actions. It looks complicated, but it's not that different from the `>>` operator.\n",
+ "\n",
+ "This operator takes an `IO a` action and a function that takes a value of the same type `a` as the one produced by the first action and returns another action, `IO b`. \n",
+ "\n",
+ "What this operator does, is: It performs the IO action to get the value `a`, then passes that value to the function to get the `IO b` action, and returns that as a result. The operator does not perform the `IO b` action, just the `IO a` action.\n",
+ "\n",
+ "So:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**The `>>=` operator sequentially composes two actions, passing any value produced by the first as an argument to the second.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hey!!!!!!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Remainders:\n",
+ "-- (>>=) :: IO a -> (a -> IO b) -> IO b\n",
+ "-- getLine :: IO String\n",
+ "\n",
+ "\n",
+ "yellIt :: String -> IO ()\n",
+ "yellIt str = putStrLn (str ++ \"!!!!!!\")\n",
+ "\n",
+ "yellItBack :: IO ()\n",
+ "yellItBack = getLine >>= yellIt\n",
+ "\n",
+ "yellIt \"Hey\"\n",
+ "--yellItBack"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, we have the `yellIt` function that takes a String and returns the action of printing that string with a bunch of exclamation marks. \n",
+ "\n",
+ "And in the `yellItBack` function, we perform the action of `getLine` that returns a String. But instead of throwing the string away, this time, we use the `>>=` operator to pass it as the parameter of `yellIt`. `yellIt` returns that action, and because it's the last one, it gets evaluated too. Sending the outside world the same message but with more emphasis."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, let's use this operator on the—now nicer—bot:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "response :: String -> IO ()\n",
+ "response name = putStrLn $ \"Nice to meet you, \" ++ name ++ \"!\"\n",
+ "\n",
+ "rudeBotAfterTherapy :: IO ()\n",
+ "rudeBotAfterTherapy =\n",
+ " putStrLn \"What's your name?\"\n",
+ " >> getLine\n",
+ " >>= response"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We create the `rudeBotAfterTherapy` function that first asks for your name. Then, it waits for you to provide it. And after you do, it passes that value to the `response` function that uses it to print a message.\n",
+ "\n",
+ "It works, but the `response` function is a simple function that we only use once. And we already know what to do in these cases. We use a lambda function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "rudeBotAfterTherapy :: IO ()\n",
+ "rudeBotAfterTherapy =\n",
+ " putStrLn \"What's your name?\"\n",
+ " >> getLine\n",
+ " >>= (\\name -> putStrLn $ \"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " \n",
+ "\n",
+ "rudeBotAfterTherapy"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "There you go! A simple and compact function with just a little bit of noise."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Now it's a chatty bot!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Our bot is now completely rehabilitated, and it turns out it's a chatty bot! Let's add a few chatty features! \n",
+ "\n",
+ "For example, let's add another message that tells us the number of letters our name has:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "lettersInName :: String -> String\n",
+ "lettersInName name =\n",
+ " \"Your name has \"\n",
+ " ++ show (length name)\n",
+ " ++ \" letters, in case you where wandering...\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `lettersInName` function is a pure function that takes a name and returns a silly little comment on it. To add it to our chatty bot, we need to do it like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "chattyBot :: IO ()\n",
+ "chattyBot =\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " >> getLine\n",
+ " >>= ( \\name ->\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " >> putStrLn (lettersInName name)\n",
+ " )\n",
+ "\n",
+ "\n",
+ "chattyBot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We need the value `name` (provided as the result of the second action), so we need to compose our action inside that lambda function."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And now it's more of the same. We can keep adding more and more actions in the same way. Here's something a bit more complicated:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "finalChattyBot :: IO ()\n",
+ "finalChattyBot =\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " >> getLine\n",
+ " >>= ( \\name ->\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " >> putStrLn (lettersInName name)\n",
+ " >> putStrLn (\"So, \" ++ name ++ \", what do you do for fun?\")\n",
+ " >> getLine\n",
+ " >>= ( \\hobby ->\n",
+ " putStrLn (\"Are you kidding, \" ++ name ++ \"! I love \" ++ hobby ++ \"!\")\n",
+ " )\n",
+ " )\n",
+ " >> putStrLn \"OK, bye!\"\n",
+ "\n",
+ "\n",
+ "finalChattyBot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, if we keep increasing the interactions, we start to see a pattern. An ugly and hard-to-read pattern.\n",
+ "\n",
+ "Luckily, we have a sweeter alternative. Enter the \"Do Notation\":"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `do` notation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `do` notation is just syntactic sugar for expressions composed by `>>` an `>>=` operators.\n",
+ "\n",
+ "We're going to rewrite all the previous expressions with do notation so we can see the difference. Starting with the rude bot:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hey!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "rudeBot :: IO ()\n",
+ "rudeBot = putStrLn \"Hey!\"\n",
+ "\n",
+ "rudeBotDo :: IO ()\n",
+ "rudeBotDo = do\n",
+ " putStrLn \"Hey!\"\n",
+ "\n",
+ "\n",
+ "rudeBotDo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, we write the keyword `do` after the equal sign. And then, we start a block with the actions. \n",
+ "\n",
+ "In this case, this syntax is not useful. It's more straightforward to write it without the `do` syntax!\n",
+ "\n",
+ "Let's see the second version of the rude bot:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hey!\n",
+ "Get out of my lawn!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "rudeBot :: IO ()\n",
+ "rudeBot = putStrLn \"Hey!\" >> putStrLn \"Get out of my lawn!\"\n",
+ "\n",
+ "rudeBotDo :: IO ()\n",
+ "rudeBotDo = do \n",
+ " putStrLn \"Hey!\"\n",
+ " putStrLn \"Get out of my lawn!\"\n",
+ "\n",
+ "\n",
+ "rudeBotDo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now we start to see some slight improvement. It's not much, but we can see that each action is in a different line, making it easier to identify. \n",
+ "\n",
+ "Without the `do` notation, the actions go from left to right. With the `do` notation, they go from top to bottom. \n",
+ "\n",
+ "And what about the even ruder bot?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "evenRuderBot :: IO ()\n",
+ "evenRuderBot =\n",
+ " putStrLn \"What's your name?\" \n",
+ " >> getLine \n",
+ " >> putStrLn \"like I care! Get lost!\"\n",
+ " \n",
+ " \n",
+ "evenRuderBotDo :: IO ()\n",
+ "evenRuderBotDo = do\n",
+ " putStrLn \"What's your name?\" \n",
+ " getLine \n",
+ " putStrLn \"like I care! Get lost!\"\n",
+ " \n",
+ "evenRuderBotDo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Same as before, add the `do` keyword, remove the `>>` operators, and you're ready to go. Notice that now we're aligning everything at the same indentation level.\n",
+ "\n",
+ "Now is when the cool stuff starts. Here we have the rude bot after therapy:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "rudeBotAfterTherapy :: IO ()\n",
+ "rudeBotAfterTherapy =\n",
+ " putStrLn \"What's your name?\"\n",
+ " >> getLine\n",
+ " >>= (\\name -> putStrLn $ \"Nice to meet you, \" ++ name ++ \"!\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "How do we handle the `name`? \tIn the previous bot, we ignored it, but now we need a way to bind the result of `getLine` to a name. For that, we'll introduce the `<-` (left arrow or bind):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "rudeBotAfterTherapyDo :: IO ()\n",
+ "rudeBotAfterTherapyDo = do\n",
+ " putStrLn \"What's your name?\"\n",
+ " name <- getLine -- (getline :: IO String) so (name :: Sring)\n",
+ " putStrLn $ \"Nice to meet you, \" ++ name\n",
+ "\n",
+ "\n",
+ "rudeBotAfterTherapyDo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This left arrow binds the result of running the `putStrLn` action to `name`. And once you have the `name` variable, you can use it anywhere after that action.\n",
+ "\n",
+ "Now let's see how we can use do notation for the chatty bot:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "chattyBot :: IO ()\n",
+ "chattyBot =\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " >> getLine\n",
+ " >>= ( \\name ->\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " >> putStrLn (lettersInName name)\n",
+ " )\n",
+ "\n",
+ "\n",
+ "chattyBotDo :: IO ()\n",
+ "chattyBotDo = do\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " name <- getLine\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " putStrLn $ lettersInName name\n",
+ "\n",
+ "\n",
+ "chattyBotDo"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now the differences start to get big! You have to stop on `chattyBot` for a few seconds to grasp what it's doing, but `chattyBotDo` is really easy to follow! \n",
+ "\n",
+ "Finally, let's compare the most complicated one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "finalChattyBot :: IO ()\n",
+ "finalChattyBot =\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " >> getLine\n",
+ " >>= ( \\name ->\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " >> putStrLn (lettersInName name)\n",
+ " >> putStrLn (\"So, \" ++ name ++ \", what do you do for fun?\")\n",
+ " >> getLine\n",
+ " >>= ( \\hobby ->\n",
+ " putStrLn (\"Are you kidding, \" ++ name ++ \"! I love \" ++ hobby ++ \"!\")\n",
+ " )\n",
+ " )\n",
+ " >> putStrLn \"OK, bye!\"\n",
+ "\n",
+ "\n",
+ "finalChattyBotDo :: IO ()\n",
+ "finalChattyBotDo = do\n",
+ " putStrLn \"Hey! What's your name?\"\n",
+ " name <- getLine\n",
+ " putStrLn (\"Nice to meet you, \" ++ name ++ \"!\")\n",
+ " putStrLn (lettersInName name)\n",
+ " putStrLn (\"So, \" ++ name ++ \", what do you do for fun?\")\n",
+ " hobby <- getLine\n",
+ " putStrLn (\"Are you kidding, \" ++ name ++ \"! I love \" ++ hobby ++ \"!\")\n",
+ " putStrLn \"OK, bye!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Always keep all the statements aligned at the same indentation level to avoid confusing the compiler and having silly errors.\n",
+ "\n",
+ "As you can see, the `do` notation is nice to read and write! It allows for a more clean and concise code. But waaaaait a minute! This code looks oddly imperative! We're stating what to do step by step in sequence! Wasn't it that declarative code was so great? \n",
+ "\n",
+ "Until now, all the functions we wrote were pure, which means that, we'd always have the same result, no matter the order of evaluation. That's why we could write declarations and let the compiler handle the details.\n",
+ "\n",
+ "Now that we have side effects, the order in which things happen matters! We don't want to run the side effect of getting the name before the one that prints the \"What's your name?\" question. That's why we adopt a more imperative style with the do notation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's the do notation, in essence. But there are a few other things regarding the practical aspect of using the `do` notation. It gets a bit more complicated, but not much.\n",
+ "\n",
+ "Let's start with how to use `let` inside the `do` notation:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Using `let` inside the `do` notation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can use the `let` keyword inside a `do` block like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "unscramble :: IO ()\n",
+ "unscramble = do\n",
+ " putStrLn \"Write a bunch of numbers and letters scrambled together:\"\n",
+ " arg <- getLine\n",
+ " let numbers = filter (`elem` \"1234567890\") arg\n",
+ " letters = filter (`notElem` numbers) arg\n",
+ " putStrLn $ \"Numbers: \" ++ numbers\n",
+ " putStrLn $ \"Letters: \" ++ letters\n",
+ "\n",
+ "\n",
+ "unscramble"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "There are a few details to keep in mind:\n",
+ "- The constructs bounded with the `let` keyword are lazy. So, even though they occupy a line in the do block, they aren't evaluated unless needed somewhere else.\n",
+ "- We don't need the `in` keyword like we do outside the `do` notation. Everything after the let binding can access it. Sames as the variables bounded using the left arrow.\n",
+ "- If you have a couple of `let` bindings one after the other. The `let` keyword is needed only for the first one, and you have to maintain all on the same indentation level. It is not mandatory to use this feature (you can write `let` for every single one), but it's convenient."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This `let` binding syntax is pretty convenient. Same as the ability to nest `do` blocks."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Nesting `do` blocks"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We can nest do blocks as much as we want. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "plusADecade :: IO ()\n",
+ "plusADecade = do\n",
+ " putStrLn \"What is your age?:\"\n",
+ " ageString <- getLine\n",
+ " let validAge = all (`elem` \"1234567890\") ageString\n",
+ " if validAge\n",
+ " then do\n",
+ " let age = read ageString :: Int\n",
+ " putStrLn $ \"Int 10 years you will be \" ++ show (age + 10) ++ \" years old.\"\n",
+ " else do\n",
+ " putStrLn \"Your age should contain only digits from 0-9. (Press CTRL+C to close)\"\n",
+ " plusADecade\n",
+ "\n",
+ "\n",
+ "\n",
+ "plusADecade"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `plusADecade` function first asks for the age of the user. Then it checks that the String provided by the user contains only numbers. If it does, we use the `read` function to get the numeric representation, add 10 to it, and print a message about how old the user will be in 10 years.\n",
+ "\n",
+ "If it doesn't contain only numbers, we print a message letting the user know that only numbers are allowed and recursively call the `plusADecade` function to restart the program."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Important detail!: The last statement in a do block has to be an expression that returns an IO action of the correct type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If we look at how the `plusADecade` function ends, we have two possible cases depending if `validAge` is `True` or `False`.\n",
+ "\n",
+ "Inside the `then`, we see a do block that ends with a `putStrLn` applied to a string, so the expression is of type `IO ()` like we indicated at the `plusADecade` signature. All good.\n",
+ "\n",
+ "Inside the `else`, we see a do block that ends with a recursive call of the `plusADecade` function, so it's of type `IO ()`. All good.\n",
+ "\n",
+ "Of course, we couldn't have ended with something that returns a value of type `IO Int` or `IO Bool`. But there's more! We can not do something like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: IO String\n",
+ "twice = do\n",
+ " str <- getLine\n",
+ " let tw = str ++ str -- Error"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Why? Well, let's take a look at this code without all the sugary syntax:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: IO String\n",
+ "twice =\n",
+ " getLine >>= \\str -> \n",
+ " let tw = str ++ str in -- Error"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's right! If we take a peek under the hood, we see that the `let` binding expands to a complete `let` expression. And the problem is evident: It's an incomplete expression! Let `tw` in what?\n",
+ "\n",
+ "And that's when the brilliant idea that we all have when first learning Haskell comes to mind. What if I return the `String` like this?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: IO String\n",
+ "twice =\n",
+ " getLine >>= \\str -> \n",
+ " let tw = str ++ str in tw -- Error: Couldn't match type!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The let expression is complete. But now we get a type error because we have to return an `IO String`, and we're returning a pure `String`. Easy fix! We can change the type of `twice` to `String` since that's what we're returning!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: String\n",
+ "twice =\n",
+ " getLine >>= \\str -> \n",
+ " let tw = str ++ str in tw -- Error: Couldn't match type!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And, just for completeness, here's the equivalent code with do notation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: String\n",
+ "twice = do\n",
+ " str <- getLine\n",
+ " let tw = str ++ str -- tw :: String\n",
+ " tw -- Error: Couldn't match type!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If we try to compile any of the last two versions of `twice`, we'll get a type error. But we said that the type is String, and we're returning a String. What's the problem? \n",
+ "\n",
+ "The problem is that we used the `getLine` impure action inside twice, and we're trying to return a pure value! In Haskell, once you go impure, you can't go back. You can not ask for forgiveness, and there's no redemption. In other words, you can't escape IO."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Escaping `IO` and the `return` function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Just to make sure there's no confusion:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**`return` DOES NOT WORK LIKE IN OTHER PROGRAMMING LANGUAGES! IT'S A DIFFERENT THING!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If you don't know any other programming languages, don't worry about it. I wanted to open with that to avoid confusion or in case you were about to skip this section."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "OK, so let's go back to the problem at hand: Why doesn't this code compile?:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "twice :: String\n",
+ "twice = do\n",
+ " str <- getLine -- (str :: String) because (getLine :: IO String)\n",
+ " let tw = str ++ str -- tw :: String\n",
+ " tw -- Compiler error: Couldn't match type! "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because if we allow that to compile, for the one that uses the `twice` function, it would look like it's using a pure function! Pure and impure code would be able to mix without any way of differentiating them! And if that's the case, we'd never be sure if our code is pure, which means that we'd never know if our functions behave like we think they do. We would have to treat everything as if it was impure, and we'd lose all the advantages of purity! No more referential transparency, no more code that's easy to reason about, no more certainty that modifying a function doesn't break anything without you knowing about it, etc.\n",
+ "\n",
+ "That's why we can not return a pure value if we perform an impure action inside the function. There has to be a way for both the compiler and developer to identify if we used an impure action.\n",
+ "\n",
+ "The solution is simple: **Once we use any side effect, we will never be able to escape impurity.**\n",
+ "\n",
+ "How does this work in practice? Well, we can perform IO actions to get the resulting value and work with it (like we did with `getLine` to obtain `str`) as long as we do it inside another IO context! The compiler enforces this by checking that: **A function performing an IO action inside has to return an IO action.**\n",
+ "\n",
+ "So:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "twice :: IO ... -- IO of something because we use getLine inside\n",
+ "twice = do\n",
+ " str <- getLine -- getLine :: IO String\n",
+ " let tw = str ++ str\n",
+ " -- ... We have to do something else\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because we performed the `getLine` action inside `twice`, now `twice` has to return an `IO` action."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, so we want to return `tw`. But `tw` is of type `String`, and we need to return a value of type `IO String`. What can we do now? \n",
+ "\n",
+ "That's when the `return` function comes in handy:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "return :: a -> IO a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `return` function injects a value inside an `IO` action that does nothing. It takes a value of any type and returns an action that results in that value.\n",
+ "\n",
+ "Remember when we presented the idea of IO actions that we say they MAY interact with the real world? Well, in this case, it doesn't. The action returned by the `return` function is a dummy action that doesn't produce any side effects and returns the value.\n",
+ "\n",
+ "This is perfect for our use case. We need a value wrapped in an IO action, but we don't need to perform any more actions. \n",
+ "\n",
+ "So, using our new function, we obtain the final version of `twice`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "twice :: IO String\n",
+ "twice = do\n",
+ " str <- getLine\n",
+ " let tw = str ++ str\n",
+ " return tw\n",
+ " \n",
+ "twice"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Which, without sugar, looks like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ": hGetLine: end of file"
+ ]
+ }
+ ],
+ "source": [
+ "twice' :: IO String\n",
+ "twice' =\n",
+ " getLine >>= \\str ->\n",
+ " let tw = str ++ str in return tw\n",
+ "\n",
+ "twice'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Remember that `return` is just a function. It doesn't need to be used at the end, and you could use it multiple times like any other function.\n",
+ "\n",
+ "And that's how we work with side effects."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And you may be thinking: \"But, if, once we use an impure action, we have to carry that IO type forever, where does it end? And if we need to interact with the world at the beginning of our program, like when a user clicks the start button, doesn't that mean that the whole program would be impure?\"\n",
+ "\n",
+ "Those are valid questions. Let's answer them one at a time.\n",
+ "\n",
+ "Like many other programming languages, Haskell has what it's known as an \"entry point\"."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `main` action"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The entry point is the place in a program where its execution of it begins. The first thing that runs when you run the program."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Many programming languages have entry points. And in almost all cases, the entry point is named main. Sometimes it's a function, others a static method. Rust, Java, Dart, C, C++, and many more have it. And, of course, Haskell has it. In Haskell's case, its an IO action called `main`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This action is the only one that gets executed when we run any program written in Haskell. And it always has the type `IO ()`. Actually, you could return a value different than `()`, but it would get ignored because there's nothing above this action. So there's no point. \n",
+ "\n",
+ "Now, when you interact with the outside world and have to carry out the `IO` type, that's where it ends. In the main action.\n",
+ "\n",
+ "An important thing to know is that if you want a function or action to be evaluated and performed when running your program, it has to be—directly or indirectly—inside `main`. When composing actions and functions, in the end, we're gluing everything together in a single `IO ()` action that, when run, performs everything as we specified."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Why do we hear about this just now? Because we've been using GHCi that handles this behind the scenes. But, next lesson, we'll start working with real compiled programs. So we better start practicing now.\n",
+ "\n",
+ "On that note, let me present to you one of the shortest programs you could write in Haskell:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "main :: IO ()\n",
+ "main = putStrLn \"Hello World!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is it! A complete Haskell program! You could put this in a Haskell file and compile it using `ghc fileName.hs`, and you would get a program that prints `\"Hello World!\"` when executed!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, so if every time you perform an action, you have to carry the IO, and the first thing a Haskell program runs is an action that has all the code inside... At which moment do we write pure code?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Haskell programs in the real world usually have a small `main` function with a couple of actions and pure functions. And inside those pure functions is where the bulk of the code lives. That way, you minimize the interaction with side effects, and most of your code is pure and easy to deal with.\n",
+ "\n",
+ "Sort of like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " config <- getConfig -- Custom IO action\n",
+ " let result = pureFunction config -- Huge pure function\n",
+ " putStrLn $ \"Done! The result is: \" ++ show result\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The bulk of the code is pure and inside the `pureFunction` that takes a pure `config` value. And a small part of the program interacts with the real world to get the configuration and show the final result."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's pretty much everything I wanted to cover for this lesson. It's a lot to take in, but with everything we covered today and a little practice, you'll have no issues writing interactive programs! \n",
+ "\n",
+ "And because I know we covered a lot of new concepts and syntax, here's a recap to use as a refresher while working on the homework:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Pure VS Impure IO recap:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "| Pure | Impure |\n",
+ "| --- | --- |\n",
+ "| Always produces the same result when given the same parameters | May produce different results for the same parameters |\n",
+ "| Never has side effects | May have side effects |\n",
+ "| Never alters state | May alter the global state of the program, system, or world |\n",
+ "| Doesn't have `IO` | Has `IO` |\n",
+ "| Functions behave nicely | Be extra careful |"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Syntax recap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "| Symbol | Meaning |\n",
+ "| --- | ---|\n",
+ "| `>>` | Sequentially composes two actions, discarding any value produced by the first |\n",
+ "|`>>=` | Sequentially composes two actions, forwarding the value produced by the first to the second |\n",
+ "| `return` | Function to inject a value inside a context |\n",
+ "| `do` | Start a `do` block |\n",
+ "| `<-` | Inside the `do` block: Bind result of performing an action |\n",
+ "| `let` | Inside the `do` block: Lazy binding of any expression (it doesn't perform actions) |\n",
+ "| `main` | The entry point of a Haskell program. (All functions/actions end in `main`.) |"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And that's it for today! If you're curious about how to create your own programs, don't worry. Next lesson, we'll learn how to set up our own local development environment, how to use modules, and how to manage Haskell projects. We'll still provide everything in online Jupyter notebooks and online development environments, but we'll remove all training wheels so you can manage your projects by yourself if you want to.\n",
+ "\n",
+ "Make sure to complete your homework, and I'll see you on the next one! "
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "9.0.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/12-Installing-Haskell-and-first-program.ipynb b/lessons/12-Installing-Haskell-and-first-program.ipynb
new file mode 100644
index 00000000..cf004ed1
--- /dev/null
+++ b/lessons/12-Installing-Haskell-and-first-program.ipynb
@@ -0,0 +1,400 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Installing Haskell and creating our first program"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We're starting to get used to coding in Haskell, and we're ready to get serious. In the next series of lessons, we'll learn how to manage our development environment, create Haskell Projects, deal with errors, and solve problems in general. Basic skills every Haskell developer must have.\n",
+ "\n",
+ "This is the first lesson in the series. And it's a short one. Where we're going to set up our local development environment and compile our first program."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Installing Haskell\n",
+ " - Installing GHCup\n",
+ " - Installing GHC, Cabal, Stack, and HLS with GHCup\n",
+ " - Installing VSCode Extensions\n",
+ "* Creating our first program\n",
+ " - Writing the simplest Haskell program\n",
+ " - Compiling and running our program"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Installing Haskell Tooling (On all operating systems)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Feel free to ignore this section if you don't want to install anything locally and want to keep using the online dev environment."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We need a way to transform our Haskell source code into native code that our computer is able to run. And for that, we need a compiler. The most widely used Haskell compier is GHC. Let's learn how to install and use it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "GHCi (the interactive environment) already comes with GHC. There are a few different ways to install GHC. We could download it directly from its website. But there are better options. For example:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- The [Stack](https://docs.haskellstack.org/en/stable/ ) tool."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- The [GHCup](https://www.haskell.org/ghcup/#) comand line tool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We'll use GHCup because Stack does more than just installing the Haskell tooling and I want to go step by step. But feel free to use Stack if you prefer it.\n",
+ "\n",
+ "So, to install our tools, go to the [GHCup website](https://www.haskell.org/ghcup/#) and run the command it shows you on the terminal.\n",
+ "\n",
+ "You can click on \"Show all platforms\" if your OS is not shown.\n",
+ "\n",
+ "Once you run the command, it will ask you a few questions about what do you want to install. Make sure to have installed—at least—GHC, Haskell Language Server, and cabal-install."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And that's it! We have everything we need! Asuming, of course, that you have a text editor. And Mirosoft Word doesn't count.\n",
+ "\n",
+ "If you don't have one, install [VSCode](https://code.visualstudio.com/). It's the most widely used and very friendly.\n",
+ "\n",
+ "If VSCode ofers to install extensions, say yes. Else, search for \"Haskell\" in the extensions pan and isntall the two most downloaded ones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "OK! Enough with the setup! Let's compile our first program!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Compiling Haskell programs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this section, we will show how you can compile simple Haskell files, later we will show how you can compile a more complex project using Cabal."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the previous lesson, we saw one of the shortest Haskell programs you could write. This one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "main :: IO ()\n",
+ "main = putStrLn \"Hello World!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A simple program that prints \"Hello World\" on the standard output. Remember that all your programs have to have an action called main that functions as the entry point of your program.\n",
+ "\n",
+ "OK! Let's compile this bad boy!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Because we're on this Jupyter Notebook, we'll have to use some command line tools to make it happen. But you can just write a file with the `.hs` extension and compile it using `ghc` (without the `:!` prepended) like I show at the end."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "So. first, we'll save the main action in a file with this command:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": []
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":!echo \"main = putStrLn \\\"Hello World!\\\"\" >> hello.hs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "If we look for a haskell file, we see that it's there:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "hello.hs"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":!ls | grep 'hello'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And, if we check its contents, we find only the main action:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "main = putStrLn \"Hello World!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":!cat hello.hs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Ok, let's compile it! \n",
+ "\n",
+ "To compile a file, the only thing we need to do is to pass the file path as an argument to the ghc command line tool:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[1 of 1] Compiling Main ( hello.hs, hello.o )\n",
+ "Linking hello ..."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":!ghc hello.hs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, if we look for files named outSourcecode, we find three new files:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "hello\n",
+ "hello.hi\n",
+ "hello.hs\n",
+ "hello.o"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":!ls | grep 'hello'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- `hello.hs` is the file we created with the source code.\n",
+ "- `hello.o` is an object file and `hello.hi` is an interface file that will link the object file to the libraries that come with GHC to produce an executable. We don't really care about those right now.\n",
+ "- The one that we care about is `hello` (called `hello.exe` if we compile it on a Windows machine). This one is the acutal executable. A binary that we can run like like any other program.\n",
+ "\n",
+ "So, let's run it!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Hello World!"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":! ./hello"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And boom! We compiled and run our own Haskell program! Congratulations!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Of course, because GHCi came with GHC, we can also load the `hello.hs` file to GHCI (`ghci`) and play arround with the main function."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " GHCi the Haskell REPL allows you to load a Haskell file with the :l
command. There, it is not relevant whether the file has a main function or not. Once the file is loaded into GHCi you can call any of the functions or types defined in the file and test them if they work as you expected. If you load a main.hs
file into GHCi that imports some user-defined modules, they will also be included as in the compilation process.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We've been coding in Haskell for a while now. But everything has been quite simple and short. That's why, if you've been doing your homework, we've been always writting our whole code in a single file.\n",
+ "\n",
+ "But what if we're making a more complex application? Like a website, a game, or a blockchain? How many thousands of unreadable code lines would that single file have? The naive solution is to split it into a bunch of files. But that stil doesn't solve many of problems we would have. That's why we use modules."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## That's it for today!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/13-Modules.ipynb b/lessons/13-Modules.ipynb
new file mode 100644
index 00000000..bceb7446
--- /dev/null
+++ b/lessons/13-Modules.ipynb
@@ -0,0 +1,1592 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Modules"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Importing Modules\n",
+ " * Controlling environments\n",
+ " * Controlling namespaces\n",
+ "* Creating our own Modules\n",
+ "* The Prelude and Standard Libraries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ " \n",
+ " The video and written lessons differ because the video format allows for clear explanations through refactoring code, and the written one is more suitable for sequential explanations. \n",
+ " \n",
+ " Use this to your advantage. If something doesn't make sense in one medium, maybe it will in the other!\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Crudely speaking, a Haskell module is just a collection of related functions, types, and type classes that can be imported and used in your code. But they are more than just that."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**Modules allow us to structure, reuse, and maintain our code and environment.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But before learning how to create our own modules, let's see how we can use pre-defined ones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "## Importing Modules"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ "We're going to import many modules several times during this lesson. So, if you run the cells sequentially, you will encounter errors when you shouldn't. In those cases, restart the Kernel (In the Kernel menu above) to get rid of all the imports and run only the cell you're on, skipping all the previous ones.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's say your app needs to manipulate files and folders. We can use a module called `System.Directory` that has a bunch of functions, actions, and types related to file and directory manipulation.\n",
+ "\n",
+ "To import this module, we use the `import` keyword followed by the name of the module:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "import System.Directory\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This must be done before defining any functions, so imports are usually done at the top of the file. By adding this line of code, we gain access to all the functions, actions, types, and typeclasses of the `System.Directory` module. You can check everything that comes with this module here (link).\n",
+ "\n",
+ "One of the functions provided is `listDirectory`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "listDirectory :: FilePath -> IO [FilePath]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It takes a directory path of type `FilePath` (which is just a type synonym for String) and returns an IO action that, when performed, returns a list of all entries (files and directories, everything) inside the directory we passed as parameter.\n",
+ "\n",
+ "So, if we use it to check what's inside the current directory of this JupyterNotebook, we get:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"23-State-Monad.ipynb\",\"21-Reader-Monad.ipynb\",\"24-Monadic-functions.ipynb\",\"09-Creating-parameterized-and-recursive-types.ipynb\",\"04-Pattern-matching.ipynb\",\"jupyter-tutuorial.ipynb\",\"ourProgram.o\",\"rise.css\",\"simpleProgram.hs\",\"06-Recursion-and-folds.ipynb\",\"22-Writer-Monad.ipynb\",\"simpleProgram\",\"ourProgram.hi\",\"13-Bits-Bytes.ipynb\",\"10-Creating-Type-Classes.ipynb\",\"15-Learning-on-your-own-and-project.ipynb\",\"16-Semigroup-and-Monoid.ipynb\",\"03-Conditions-and-helper-constructions.ipynb\",\"14-Handling-Errors.ipynb\",\"19-Aeson.ipynb\",\"hello.hi\",\"02-Functions-Data-Types-and-Signatures.ipynb\",\"ourSourceCode.hi\",\"hello.o\",\"17-Functor.ipynb\",\"11-Basic-IO.ipynb\",\".ipynb_checkpoints\",\"20-Monad.ipynb\",\"12-Pragmas-Modules-and-Cabal.ipynb\",\"25-Monad-Transformers.ipynb\",\"18-Applicative.ipynb\",\"ourSourceCode.o\",\"01-Introduction-to-haskell.ipynb\",\"08-Creating-non-parameterized-types.ipynb\",\"05-Improving-and-combining-functions.ipynb\",\"07-Intro-to-Type-Classes.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory\n",
+ "\n",
+ "listDirectory \".\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, the current folder contains the files of all the lessons we covered so far."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, let's say we want to write a function to find files inside the current directory that contain a certain String as part of their name. Something like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "import System.Directory\n",
+ "\n",
+ "find' :: String -> IO [FilePath]\n",
+ "find' str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = -- filter entries\n",
+ " return found\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "First, we get a list of all the files and directories using listDirectory, and then we filter them. \n",
+ "\n",
+ "We could easily create the filtering function ourselves with some pattern matching and recursion. But realistically, this sounds like a common-enough function to be available as a library. And it is!!\n",
+ "\n",
+ "There's also a module called [`Data.List`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-List.html) that contains dozens of functions to work with lists.\n",
+ "\n",
+ "One of them is called `isInfixOf`. It takes two lists and returns `True` if the first list is contained, wholly and intact, anywhere within the second. Exactly what we need, so let's use it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"11-Basic-IO.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory\n",
+ "import Data.List\n",
+ "\n",
+ "find' :: String -> IO [FilePath]\n",
+ "find' str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "find' \"11\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Awesome! Because we have access to modules with pre-written code, we don't have to implement everything by ourselves!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok, so the function looks good, but it has a weird name. Why not just call it `find` without the `'`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now JupyterNotebook is, again, doing some magic to avoid errors. But if we tried to change the name of the function to `find` without the `'` and compile this code as a regular Haskell program (which you will be able to do after this lesson), we'd get this error:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "```\n",
+ "Ambiguous occurrence ‘find’\n",
+ " It could refer to\n",
+ " either ‘Data.List.find’,\n",
+ " imported from ‘Data.List’ at YourFileName.hs:3:1-16\n",
+ " (and originally defined in ‘Data.Foldable’)\n",
+ " or ‘YourFileName.find’, defined at YourFileName.hs:13:1\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The error is clear. We have two functions with the same name. One in our file and one that came when we imported `Data.List`. So the compiler doesn't know which one we're referring to.\n",
+ "\n",
+ "There are multiple solutions to this. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Controlling the environment"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The easiest solution, of course, is to change the name of our function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"11-Basic-IO.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory\n",
+ "import Data.List\n",
+ "\n",
+ "findEntry :: String -> IO [FilePath]\n",
+ "findEntry str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "findEntry \"11\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But this doesn't solve the underlying issue that our environment is polluted with dozens of functions and types of both `System.Directory` and `Data.List` that we're not planning to use. Which can cause all sorts of troubles."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A better solution is to import a specific function or type instead of the whole module. We can easily do it like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"11-Basic-IO.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory (listDirectory) -- import lsitDirectory from System.Directory\n",
+ "import Data.List (isInfixOf) -- import isInfixOf from Data.List\n",
+ "\n",
+ "find :: String -> IO [FilePath]\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "find \"11\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We add the functions inside a parenthesis on the right. And if we need to import more than one, we add it separated by a comma.\n",
+ "\n",
+ "For example, if we want to sort the entries before returning them, we can import the `sort` function from `Data.List` and use it like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"08-Creating-non-parameterized-types.ipynb\",\"09-Creating-parameterized-and-recursive-types.ipynb\",\"10-Creating-Type-Classes.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory (listDirectory)\n",
+ "import Data.List (isInfixOf, sort) -- import isInfixOf and sort from Data.List\n",
+ "\n",
+ "find :: String -> IO [FilePath]\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry -- filter and sort\n",
+ " return found\n",
+ "\n",
+ "find \"Creating\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And we can keep adding the functions we need when we need them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Finally, if we happen to need many functions of a module, importing them one by one might be cumbersome. Also, we might end up with a huge list of functions and types that contain almost the whole module.\n",
+ "\n",
+ "For those cases, you can use the `hidden` keyword. For example, let's say our `find` function is just one of many in our file. And we need to use a lot of functions provided by `Data.List`.\n",
+ "\n",
+ "We can change the import like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"08-Creating-non-parameterized-types.ipynb\",\"09-Creating-parameterized-and-recursive-types.ipynb\",\"10-Creating-Type-Classes.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory (listDirectory)\n",
+ "import Data.List hiding (find) -- import everything from Data.List, except `find`\n",
+ "\n",
+ "find :: String -> IO [FilePath]\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "find \"Creating\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Here, we're importing everything from `Data.List` except for the `find` function.\n",
+ "\n",
+ "And, same as before, you can hide more functions and types by adding them inside those parentheses separated by commas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's how you can control your environment while importing modules.\n",
+ "\n",
+ "But there's one more case we don't have a solution for. What if we import two modules that have functions with the same name?\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":4:22: error:\n Ambiguous occurrence ‘filter’\n It could refer to\n either ‘Data.Map.filter’, imported from ‘Data.Map’ (and originally defined in ‘Data.Map.Internal’)\n or ‘Prelude.filter’, imported from ‘Prelude’ (and originally defined in ‘GHC.List’)"
+ ]
+ }
+ ],
+ "source": [
+ "import System.Directory (listDirectory)\n",
+ "import Data.List hiding (find)\n",
+ "import Data.Map\n",
+ "\n",
+ "find :: String -> IO [FilePath]\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "find \"Creating\"\n",
+ "\n",
+ "-- 👇 More code that uses `filter` from Data.Map\n",
+ "-- ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's say we import a module called `Data.Map`. This module has types and functions that allow you to store associations between unique keys and values. For now, don't worry about it. We'll take a proper look at this module in future lessons.\n",
+ "\n",
+ "What's interesting is that both `Data.Map` and `Prelude` (a module we'll learn about shortly) provide a function called `filter`. The same as before, we have two different functions with the same name, and Haskell doesn't know which one we're referring to.\n",
+ "\n",
+ "In this scenario, we don't want to hide Map's `filter` function because we're using it somewhere else in our code. And we cannot change their name because we're importing both from other modules.\n",
+ "\n",
+ "Now is when namespaces come in handy!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Controlling namespaces"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Without all the jargon: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**A namespace is an environment created to hold a group of names.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We've been talking about \"our environment\" as a single thing. A \"space\" that contains our functions, types, and type classes all mixed together. But thanks to modules, we can have different environments (or namespaces).\n",
+ "\n",
+ "And is as easy as adding a single word to the import:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"23-State-Monad.ipynb\",\"21-Reader-Monad.ipynb\",\"24-Monadic-functions.ipynb\",\"09-Creating-parameterized-and-recursive-types.ipynb\",\"04-Pattern-matching.ipynb\",\"jupyter-tutuorial.ipynb\",\"ourProgram.o\",\"rise.css\",\"simpleProgram.hs\",\"06-Recursion-and-folds.ipynb\",\"22-Writer-Monad.ipynb\",\"simpleProgram\",\"ourProgram.hi\",\"13-Bits-Bytes.ipynb\",\"10-Creating-Type-Classes.ipynb\",\"15-Learning-on-your-own-and-project.ipynb\",\"16-Semigroup-and-Monoid.ipynb\",\"03-Conditions-and-helper-constructions.ipynb\",\"14-Handling-Errors.ipynb\",\"19-Aeson.ipynb\",\"hello.hi\",\"02-Functions-Data-Types-and-Signatures.ipynb\",\"ourSourceCode.hi\",\"hello.o\",\"17-Functor.ipynb\",\"11-Basic-IO.ipynb\",\".ipynb_checkpoints\",\"20-Monad.ipynb\",\"12-Pragmas-Modules-and-Cabal.ipynb\",\"25-Monad-Transformers.ipynb\",\"18-Applicative.ipynb\",\"ourSourceCode.o\",\"01-Introduction-to-haskell.ipynb\",\"08-Creating-non-parameterized-types.ipynb\",\"05-Improving-and-combining-functions.ipynb\",\"07-Intro-to-Type-Classes.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:1: error:\n • Variable not in scope: listDirectory :: String -> t\n • Perhaps you meant ‘IHaskellDirectory.listDirectory’ (imported from System.Directory)"
+ ]
+ }
+ ],
+ "source": [
+ "import qualified System.Directory (listDirectory) -- qualified import\n",
+ "\n",
+ "System.Directory.listDirectory \".\" -- This works\n",
+ "listDirectory \".\" -- This doesn't work now"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ "If you run the previous cell, listDirectory \".\"
will work because Jupyter's environment already has System.Directory
imported without qualifying it. If you want to reproduce this error, you'll have to restart the kernel and run the above cell first.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By adding the `qualified` keyword after `import`, instead of adding the `listDirectory` to our environment, we create a new one called `System.Directory` that contains it.\n",
+ "\n",
+ "That way, each time we want to use `listDirectory`, we have to look for it inside the `System.Directory` namespace.\n",
+ "\n",
+ "This solves our previous problem:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[\"08-Creating-non-parameterized-types.ipynb\",\"09-Creating-parameterized-and-recursive-types.ipynb\",\"10-Creating-Type-Classes.ipynb\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import System.Directory (listDirectory)\n",
+ "import Data.List hiding (find)\n",
+ "import qualified Data.Map\n",
+ "\n",
+ "find :: String -> IO [FilePath]\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " return found\n",
+ "\n",
+ "find \"Creating\"\n",
+ "\n",
+ "-- 👇 More code that uses `Data.Map.filter` from Data.Map\n",
+ "-- ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, we don't have errors no more. Now that we `qualified` the `Data.Map` import, we're still importing everything from that module, including the `filter` function. But all that is inside the `Data.Map` namespace, so it doesn't get mixed with the functions/types/type classes in our main environment."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And, to show how we would use code from `Data.Map`, let's take that list of filtered and ordered entries and transform them into a map:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "fromList [(1,\"08-Creating-non-parameterized-types.ipynb\"),(2,\"09-Creating-parameterized-and-recursive-types.ipynb\"),(3,\"10-Creating-Type-Classes.ipynb\")]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Data.List hiding (find)\n",
+ "import System.Directory (listDirectory)\n",
+ "import qualified Data.Map\n",
+ "\n",
+ "find :: String -> IO (Data.Map.Map Int String)\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " let foundMap = Data.Map.fromList $ zip ([1 ..] :: [Int]) found -- List to Map\n",
+ " return foundMap\n",
+ "\n",
+ "find \"Creating\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We only added a single line of code. As we said before, maps store associations between unique keys and values. We have the values but without keys!\n",
+ "\n",
+ "We'll use the `zip` function to assign a unique key to each value. As we saw on the recursion lesson's homework, the `zip` function takes two lists and returns a list of tuples with the corresponding pairs.\n",
+ "\n",
+ "We're zipping an infinite list of ordered numbers starting from one and the list of filtered and sorted entries. So, we should get a list of pairs with a number as the first element and an entry as the second.\n",
+ "\n",
+ "Conveniently enough, the `Data.Map` module provides a function called `fromList` that takes a list of pairs and returns a value of type `Map`. In this case, the value it returns is of type `Map Int String` because the keys are `Int` and the values are `String`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "With this final feature, we've gained complete control of our environments. Although, writing `Data.Map` everywhere gets old pretty quickly. And if we qualify imports with longer names or qualify several modules, our code starts to get cluttered and harder to read, like this sentence.\n",
+ "\n",
+ "Haskell allows us to rename the namespace to a more convenient one. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "fromList [(1,\"08-Creating-non-parameterized-types.ipynb\"),(2,\"09-Creating-parameterized-and-recursive-types.ipynb\"),(3,\"10-Creating-Type-Classes.ipynb\")]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Data.List hiding (find)\n",
+ "import System.Directory (listDirectory)\n",
+ "import qualified Data.Map as Map -- Renamed namespace\n",
+ "\n",
+ "find :: String -> IO (Map.Map Int String)\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " let foundMap = Map.fromList $ zip ([1 ..] :: [Int]) found -- List to Map\n",
+ " return foundMap\n",
+ "\n",
+ "find \"Creating\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ " Notice that module names are capitalized, if you are renaming them, this new name has to be capitalized as well!\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And as a final tip, we can combine all these different techniques. For example, if two modules do kind of the same thing and don't have any name clashes, we could give both namespaces the same name and treat them as if they came from a single module.\n",
+ "\n",
+ "This doesn't apply right now, but there's an import combination that does. Our `find` function looks pretty good. But one thing that bothers me is that `Map.Map`. I don't mind the `Map.fromList`. Actually, I prefer it! That lets me know that `fromList` comes from the `Data.Map` module. But `Map.Map` is kind of redundant. Of course that the `Map` type constructor comes from the `Data.Map` module!\n",
+ "\n",
+ "Let's combine a couple of imports to avoid this redundancy!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "fromList [(1,\"08-Creating-non-parameterized-types.ipynb\"),(2,\"09-Creating-parameterized-and-recursive-types.ipynb\"),(3,\"10-Creating-Type-Classes.ipynb\")]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Data.List hiding (find) \n",
+ "import System.Directory (listDirectory)\n",
+ "import qualified Data.Map as Map hiding (Map) -- import qualified + Rename namespace + hide Map\n",
+ "import Data.Map (Map) -- Import only Map\n",
+ "\n",
+ "find :: String -> IO (Map Int String)\n",
+ "find str = do\n",
+ " entry <- listDirectory \".\"\n",
+ " let found = sort $ filter (str `isInfixOf`) entry\n",
+ " let foundMap = Map.fromList $ zip ([1 ..] :: [Int]) found\n",
+ " return foundMap\n",
+ " \n",
+ "find \"Creating\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By hiding the `Map` type constructor from the qualified import and importing it separately, we essentially removed the `Map` type constructor from the `Map` namespace and added it to our main namespace.\n",
+ "\n",
+ "Everything else is the same, but now, the signature of `find` is easier to read."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "That's pretty much everything about importing modules and managing your environment. But remember, we said modules also allow us to better structure, reuse, and maintain our code? Well, let's see how!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Creating your own module"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Since modules are just plain Haskell files that can be imported into other Haskell files, it is easy to create a module on your own."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's say we want another version of the `sum` function that returns an error if we apply it to the empty list instead of the value 0 that `sum` returns."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To create a module that exposes such a module, we first create a Haskell file that we call `SumNonEmpty.hs`. At the top of this file, we write a module statement like\n",
+ "```haskell\n",
+ "module SumNonEmpty where\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "With this statement, we defined the name of our module as `SumNonEmpty`, which again should start with an upper case letter.\n",
+ "\n",
+ "**It is good practice to have the module's name as the name of the file**, though this is not mandatory."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And now, we can write the code that our module provides:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "module SumNonEmpty where\n",
+ "\n",
+ "data MyData a b = Error a | Result b deriving (Show)\n",
+ "\n",
+ "sumNonEmpty :: Num a => [a] -> MyData String a\n",
+ "sumNonEmpty [] = Error \"List is empty\"\n",
+ "sumNonEmpty xs = Result (sum xs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's pretty much it! We created our own module. \n",
+ "\n",
+ "Now we can import it in another file (in the same folder) like any other module:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "6"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Error \"List is empty\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Result 6"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import SumNonEmpty\n",
+ "\n",
+ "sum [] -- 0\n",
+ "sum [1..3] -- 6\n",
+ "\n",
+ "sumNonEmpty [] -- Error \"List is empty\" \n",
+ "sumNonEmpty [1..3] -- Result 6"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the previous example, the exported module is in the same folder as the file that imports it. But they could be in different places. In that case, the imports themselves express where the code is located."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For example, in this case:\n",
+ "\n",
+ "```haskell\n",
+ "import Data.Time.Calendar\n",
+ "import Data.Time.Clock.System\n",
+ "```\n",
+ "\n",
+ "We can infer that the imports translate to the next file structure:\n",
+ "\n",
+ "```\n",
+ "Data\n",
+ " | \n",
+ " |--- Time\n",
+ " |\n",
+ " |--- Calendar.hs \n",
+ " |--- Clock\n",
+ " | \n",
+ " |--- System.hs\n",
+ " \n",
+ "```\n",
+ "\n",
+ "Where the `Calendar` module is inside the `Data/Time/Calendar.hs` file, and the `System` module is inside the `Data/Time/Clock/System.hs` file."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In contrast to import restrictions, as we did until now, Haskell also gives you control over exports. That is, what the module exposes to the outside world.\n",
+ "\n",
+ "In the above example, our module exports all that is declared in its file. But there are plenty of cases when you don't want that. For example, when you create a helper function that is meant to be used only inside the module, like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "module SumNonEmpty1 where\n",
+ "\n",
+ "errorMessage1 = \"List is empty\"\n",
+ "\n",
+ "data MyData1 a b = Error1 a | Result1 b deriving (Show)\n",
+ "\n",
+ "sumNonEmpty1 :: Num a => [a] -> MyData1 String a\n",
+ "sumNonEmpty1 [] = Error1 errorMessage1\n",
+ "sumNonEmpty1 xs = Result1 (sum xs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In this case, anyone that imports `SumNonEmpty1` has access to `errorMessage`. But it doesn't make sense to use this error message outside the `sumNonEmpty` definition. So there's no reason for the consumer of the module to be able to access it!\n",
+ "\n",
+ "The solution is simple: Explicitly instantiate what the module exports. Like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "module SumNonEmpty2 (sumNonEmpty2, MyData2) where\n",
+ "\n",
+ "errorMessage2 = \"List is empty\"\n",
+ "\n",
+ "data MyData2 a b = Error2 a | Result2 b deriving (Show)\n",
+ "\n",
+ "sumNonEmpty2 :: Num a => [a] -> MyData2 String a\n",
+ "sumNonEmpty2 [] = Error2 errorMessage2\n",
+ "sumNonEmpty2 xs = Result2 (sum xs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " Note: It is common practice to put one export per line if they don't fit in one line.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By explicitly stating that the module exports `sumNonEmpty, MyData`, everything else inside the module becomes inaccessible to the end user (the one that imports it). As you can see here:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Result2 10.5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:1: error:\n • Variable not in scope: errorMessage2\n • Perhaps you meant ‘errorMessage1’ (imported from SumNonEmpty1)"
+ ]
+ }
+ ],
+ "source": [
+ "import SumNonEmpty2\n",
+ "\n",
+ "sumNonEmpty2 [3.5, 7]\n",
+ "\n",
+ "errorMessage2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But we also created a problem now. We exported the type `MyData2` but not its constructors. That means that we have no way of extracting the result by pattern-matching:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Result2 6375"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:13: error:\n Not in scope: data constructor ‘Error2’\n Perhaps you meant one of these: ‘Error’ (imported from SumNonEmpty), ‘Error1’ (imported from SumNonEmpty1), variable ‘error’ (imported from Prelude)"
+ ]
+ }
+ ],
+ "source": [
+ "import SumNonEmpty2\n",
+ "\n",
+ "resOfSum = sumNonEmpty2 [100..150]\n",
+ "\n",
+ "resOfSum\n",
+ "\n",
+ "twiceAsMuch Error2 _ = 0\n",
+ "twiceAsMuch Result x = x * 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is usually solved in two different ways. Either by exporting the constructors so we can use them when importing the module:\n",
+ "\n",
+ "```haskell\n",
+ "-- Alternative one:\n",
+ "module SumNonEmpty2 (sumNonEmpty2, MyData2 (Error2, Result2)) where\n",
+ "-- Alternative two:\n",
+ "module SumNonEmpty2 (sumNonEmpty2, MyData2 (..)) where -- (..) means \"all constructors\".\n",
+ "```\n",
+ "\n",
+ "Or by keeping the constructors unaccessible but exporting a function that can extract them:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "module SumNonEmpty3 (sumNonEmpty3, MyData3, getResult) where\n",
+ "\n",
+ "errorMessage3 = \"List is empty\"\n",
+ "\n",
+ "data MyData3 a b = Error3 a | Result3 b deriving (Show)\n",
+ "\n",
+ "sumNonEmpty3 :: Num a => [a] -> MyData3 String a\n",
+ "sumNonEmpty3 [] = Error3 errorMessage3\n",
+ "sumNonEmpty3 xs = Result3 (sum xs)\n",
+ "\n",
+ "getResult :: (Num a) => a -> MyData3 String a -> a\n",
+ "getResult def (Result3 x) = x\n",
+ "getResult def _ = def"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Result3 6375"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "6375"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "99"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import SumNonEmpty3\n",
+ "\n",
+ "resOfSum = sumNonEmpty3 [100..150]\n",
+ "\n",
+ "resOfSum\n",
+ "\n",
+ "getResult 99 resOfSum\n",
+ "getResult 99 (sumNonEmpty3 [])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now that we know how to work with modules, let's learn about the most famous of all."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The `Prelude` module"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "When working in GHCi or writing your own Haskell code, some functions are available by default, for instance, `head`, `sum`, and `length`.\n",
+ "\n",
+ "This is because those functions are part of a module called **Prelude** that is imported by default. \n",
+ "\n",
+ "The word prelude means an introduction to something more important. Which is the code and modules you will write.\n",
+ "\n",
+ "So, the `Prelude` module provides basic functions, data types, and type classes that the average developer might need for its code."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "You can find a list of all the functions, types, and type classes provided by **Prelude** [here](https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html). "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Besides the Prelude, there are other standard libraries that aren't imported by default but are available to be imported without the need for you to download them separately."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The Standard Libraries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "If you go to [this website](https://downloads.haskell.org/ghc/latest/docs/libraries/), you can see all the libraries that come baked into Haskell.\n",
+ "\n",
+ "It's daunting, I know. But you don't have to memorize all this. There are tools like [Hoogle](https://hoogle.haskell.org) that let you search through libraries whenever you need to. We'll explore how to use this and other convenient tools in a future lesson. For now, that's it for today!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## That's it for today!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This lesson's homework will be to read through everything provided by the Prelude.\n",
+ "\n",
+ "But! In there, there's also plenty of stuff we didn't cover, and that depends on concepts we'll learn further down this course. So it's not required to understand everything. The objective is to have an idea of what the Prelude module provides, use it as a refresher of all the functions, types, and type classes we've used so far, and get you used to reading the official documentation."
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/14-Cabal_and_language_extensions.ipynb b/lessons/14-Cabal_and_language_extensions.ipynb
new file mode 100644
index 00000000..af361912
--- /dev/null
+++ b/lessons/14-Cabal_and_language_extensions.ipynb
@@ -0,0 +1,1696 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Cabal and Language Extensions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline \n",
+ "\n",
+ "- Cabal\n",
+ " - Introduction to Cabal\n",
+ " - Creating a new Haskell project\n",
+ " - Going over the Cabal file using an external library\n",
+ " - Building and running our executable\n",
+ "- Language extensions and Pragmas\n",
+ " - NumericUnderscores\n",
+ " - TypeApplications"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " \n",
+ " The video and written lessons differ because the video format allows for explanations through exemplification, and the written one is more suitable for sequential explanations. \n",
+ " \n",
+ " Use this to your advantage! 😃 If something doesn't make sense in one medium, maybe it will in the other!\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Cabal"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "[Cabal](https://cabal.readthedocs.io/en/stable/intro.html) is a system for building and packaging Haskell libraries and programs. The name **cabal** stands for *Common Architecture for Building Applications and Libraries*.\n",
+ "\n",
+ "The term cabal is overloaded. It can refer to either: cabal-the-spec (.cabal files), cabal-the-library (code that understands .cabal files), or cabal-the-tool (the `cabal-install` package which provides the `cabal` executable);\n",
+ "\n",
+ "Because these three usually work in tandem, we're going to refer to all three as a single \"thing\" called Cabal."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "This lecture assumes you already have Cabal installed in your system; If you don't, please refer to the previous \"Installing Haskell Locally\" lesson. \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Cabal makes it easier for developers to:\n",
+ "- Use other developer's packages\n",
+ "- Create and share their own libraries\n",
+ "- Configure how to build and run tests and executables\n",
+ "- etc."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "By default, Cabal uses [Hackage](https://hackage.haskell.org/) as the place to look for libraries needed by the developer.\n",
+ "\n",
+ "Hackages is a central package archive that has thousands of Haskell libraries and programs ready for you to use them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Cabal uses a local index of Hackage to resolve dependencies, so you always want to keep it updated. To do that, you can run:\n",
+ "\n",
+ "```\n",
+ "cabal update\n",
+ "```\n",
+ "\n",
+ "(See [here](https://cabal.readthedocs.io/en/stable/cabal-commands.html#cabal-update) the documentation.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, we're ready to start our first Haskell project! 😄"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Creating a new Haskell project"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To create a new Haskell project that follows the cabal architecture, we use the `cabal init` command:\n",
+ "\n",
+ "```\n",
+ "cabal init\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "When we run this command, Cabal is going to start asking a bunch of questions about the software we want to build. **You can change everything later by modifying the `.cabal` file, so don't worry too much about it!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "After answering all those questions, Cabal will create files and folders inside your current directory. So make sure to create a directory and go inside before running `cabal init`!\n",
+ "\n",
+ "Depending on your chosen options, you might have a different folder structure. But it should look mostly like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "Project\n",
+ " |-- app\n",
+ " | |-- Main.hs\n",
+ " |-- CHANGELOG.md\n",
+ " |-- Project.cabal\n",
+ " |-- LICENSE\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- The `Project` folder is the folder containing your Haskell project\n",
+ "- The `app` folder contains the source code for your application (by default, a single `Main.hs` file.\n",
+ "- The `CHANGELOG.md` file to record the changes between versions.\n",
+ "- The `Project.cabal` file contains all the configuration to build and execute your executables, library, and tests (if any).\n",
+ "- The `LICENESE` file containing the license of the software"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Although this is likely how Cabal creates your project, it's common for Haskell projects to have the next folder structure:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "Project\n",
+ " |-- app\n",
+ " | |-- Main.hs\n",
+ " |-- src\n",
+ " |-- ...\n",
+ " |-- CHANGELOG.md\n",
+ " |-- Project.cabal\n",
+ " |-- LICENSE\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The difference is that most of the source code is inside the `src` folder, and the `app` folder only contains the `Main` action.\n",
+ "\n",
+ "It doesn't make a real difference in the final result, so do as you please.\n",
+ "\n",
+ "And now that we have our project ready to go, here there are a few key commands:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- `cabal build`: It builds your executable or library based on the contents of the source code and the `.cabal` file.\n",
+ "- `cabal exec Project`: It executes `Project`. The actual name of the executable is inside the `.cabal` file.\n",
+ "- `cabal run`: It runs `cabal build` and `cabal exec` in sequence. This is a convenient command to avoid the repetitive work of running the same two commands every time we change something.\n",
+ "- `cabal test`: Runs the tests specified in the `.cabal` file. (We won't work with tests in this lesson.)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As you can see, everything depends on the `.cabal` file. So let's learn about it!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Going over the Cabal file"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `.cabal` file contains rules in a similar format as YAML files. Let's see the most common ones, starting with the informational:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "cabal-version: 3.0\n",
+ "-- The cabal-version field refers to the version of the .cabal\n",
+ "-- specification and can be different from the cabal-install\n",
+ "-- (the tool) version and the Cabal (the library) version you\n",
+ "-- are using.\n",
+ "-- The way we write this (ForestGame.cabal) file changes over\n",
+ "-- time with new versions. By stating the version we use, Cabal\n",
+ "-- will be able to interpret it.\n",
+ "-- Starting from the specification version 2.2, the cabal-version\n",
+ "-- field must be the first thing in the cabal file.\n",
+ "\n",
+ "\n",
+ "-- The name of the package.\n",
+ "name: ForestGame\n",
+ "\n",
+ "-- The package version.\n",
+ "-- See the Haskell package versioning policy (PVP) for standards\n",
+ "-- guiding when and how versions should be incremented.\n",
+ "-- https://pvp.haskell.org\n",
+ "-- PVP summary: +-+------- breaking API changes\n",
+ "-- | | +----- non-breaking API additions\n",
+ "-- | | | +--- code changes with no API change\n",
+ "version: 0.1.0.0\n",
+ "\n",
+ "-- A short (one-line) description of the package.\n",
+ "synopsis: A cool game\n",
+ "\n",
+ "-- A longer description of the package.\n",
+ "description: This game is really, reeeally cool! Trust me.\n",
+ "\n",
+ "-- The license under which the package is released.\n",
+ "license: MIT\n",
+ "\n",
+ "-- The file containing the license text.\n",
+ "license-file: LICENSE\n",
+ "\n",
+ "-- The package author(s).\n",
+ "author: Robertino Martinez\n",
+ "\n",
+ "-- An email address to which users can send suggestions, \n",
+ "-- bug reports, and patches.\n",
+ "maintainer: robertino.martinez@iohk.io\n",
+ "\n",
+ "-- Category to find our package if we upload it to Hackage\n",
+ "category: Game\n",
+ "\n",
+ "-- The type of build used by this package. Leave it as it is\n",
+ "-- or remove it entirely. We won't use Custom builds.\n",
+ "build-type: Simple\n",
+ "\n",
+ "-- Extra doc files to be distributed with the package, such\n",
+ "-- as a CHANGELOG or a README.\n",
+ "extra-doc-files: CHANGELOG.md\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As you can see, we're adding information about ourselves and the package, but no instructions on how to process the source code yet.\n",
+ "\n",
+ "Now, let's see we want to build the executable of our game. For that, we can use the `executable` sections. Let's see one:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "executable ForestGame\n",
+ " main-is: Main.hs\n",
+ " other-modules: Forest.Level1\n",
+ " , User.Actions.Move\n",
+ " build-depends: base ^>=4.16.4.0\n",
+ " , random ^>=1.2.1.1\n",
+ " hs-source-dirs: app, src\n",
+ " default-language: Haskell2010\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We indicate what is part of the `executable` section using indentation. Let's go over one by one:\n",
+ "\n",
+ "- `executable ForestGame`: Indicates that we're specifying an executable called ForestGame.\n",
+ "- `main-is`: We specify where the main module resides.\n",
+ "- `other-modules`: We specify auxiliary modules used by our executable.\n",
+ "- `build-depends`: Declares the library dependencies (with version) required to build the current package component. In this case, we depend on the `base` and `random` libraries. (see the video of this lesson to know how we use them.)\n",
+ "- `hs-source-dirs`: Root directories for the module hierarchy. In this case, instead of using only `app`, we chose to use `app` and `src` to contain our source code.\n",
+ "- `default-language`: The version of Haskell we want to use."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "With only the informational and executable instructions, we have everything needed for Cabal to be able to build our executable.\n",
+ "\n",
+ "Although, we also have the option to create:\n",
+ "- [A library](https://cabal.readthedocs.io/en/stable/cabal-package.html#library),\n",
+ "- [Tests suites](https://cabal.readthedocs.io/en/stable/cabal-package.html#test-suites),\n",
+ "- And more executables if we wanted to. (For example, if we wanted to have \"development\" and \"production\" executables that differ in some way.)\n",
+ "\n",
+ "\n",
+ "You can configure A LOT with Cabal. Usually, the best course of action is to figure out what you want to do and search [Cabal's documentation](https://cabal.readthedocs.io/en/stable/index.html) to see how to do it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Building and running our executable"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now that we have our cabal file, we can build and execute our software without worrying about resolving dependencies, downloading packages, etc.\n",
+ "\n",
+ "We can just run:\n",
+ "\n",
+ "```\n",
+ "cabal build\n",
+ "```\n",
+ "\n",
+ "And Cabal will do everything for us! 😄🙌\n",
+ "\n",
+ "And once everything is built, we can run any executable with:\n",
+ "\n",
+ "```\n",
+ "cabal exec ForestGame\n",
+ "```\n",
+ "\n",
+ "Of course, while developing, you'd have to build+execute quite a lot. That's why Cabal has a special command for that:\n",
+ "\n",
+ "```\n",
+ "cabal run\n",
+ "```\n",
+ "\n",
+ "When running `cabal run`, Cabal will check if any source code changed, and if it did, it will rebuild only the files that changed and then run the executable! Extremely convenient! 😎"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "For a more step-by-step explanation with a real-world example, take a look at the corresponding video lesson! 😃 \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Language extensions and Pragmas"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Usually, in most mainstream programming languages, your language for a specific version always works the same. For example, Python 3.7 and 3.10 are different, but Python 3.7 will always have the same syntax and functionality. The only way you can change how Python works or its syntax is by changing the version of the language.\n",
+ "\n",
+ "Haskell is different. Haskell has language extensions.\n",
+ "\n",
+ "Language extensions are a way to modify how the Haskell compiler interprets your code. By activating language extensions, you essentially change the Haskell language to have a particular feature you want or need. This way, you can tailor the language to your taste or use case!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can add language extensions by specifying them in your `.cabal` file or by adding a **Language Pragma** to the top of the file you want the extension to be active.\n",
+ "\n",
+ "The syntax to add a language pragma to a Haskell file is by adding this statement at the top of the file:\n",
+ "\n",
+ "```\n",
+ "{-# LANGUAGE extension_name #-}\n",
+ "```\n",
+ "\n",
+ "Let's see a few examples!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### NumericUnderscores"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's say we're working with an app that uses big numbers, such as a game:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "15049231"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "userGems = 15894231\n",
+ "\n",
+ "purchase gems item = gems - item\n",
+ "\n",
+ "level1Vest = 52000\n",
+ "level2Vest = 147000\n",
+ "level3Vest = 845000\n",
+ "tank = 314159265358\n",
+ "\n",
+ "-- Usage example:\n",
+ "purchase userGems level3Vest"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It works. But because we're using large numbers, it's easy to mess up. It's hard to tell the number at a glance. One more zero looks kind of the same! 🤷♂️\n",
+ "\n",
+ "That's when the `NumericUnderscores` language extension comes in.\n",
+ "\n",
+ "This language extension adds support for expressing underscores in numeric literals. That is, underscores in numeric literals are ignored when `NumericUnderscores` is enabled. For instance, the numeric literal 1_000_000 will be parsed into 1000000 when `NumericUnderscores` is enabled. The underscores are only there to assist human readers.\n",
+ "\n",
+ "So, if we activate the extension, we can rewrite our code like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "15049231"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "{-# LANGUAGE NumericUnderscores #-}\n",
+ "\n",
+ "userGems = 15_894_231\n",
+ "\n",
+ "purchase gems item = gems - item\n",
+ "\n",
+ "level1Vest = 52_000\n",
+ "level2Vest = 147_000\n",
+ "level3Vest = 845_000\n",
+ "tank = 314_159_265_358\n",
+ "\n",
+ "-- Usage example:\n",
+ "purchase userGems level3Vest"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As you can see, the final result didn't change. But we effectively changed Haskell's syntax so that numeric values can look different (can have underscores)!\n",
+ "\n",
+ "Now, let's use a language extension that does a more significant change: `TypeApplications`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### TypeApplications"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`TypeApplications` allows you to instantiate one or more of a polymorphic function’s type arguments to a specific type.\n",
+ "\n",
+ "For example, the function `read` is of type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "read :: forall a. Read a => String -> a"
+ ],
+ "text/plain": [
+ "read :: forall a. Read a => String -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t read"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " The `forall a.` part indicates that all `a` are the same type. And `Read a =>` indicates that the same type is an instance of the `Read` type class.\n",
+ " \n",
+ "So, if we do:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ "Prelude.read: no parse"
+ ]
+ }
+ ],
+ "source": [
+ "read \"4\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this JupyterNotebook, we get that `Prelude.read: no parse` error. But in a real scenario, we'd get a long error that starts with:\n",
+ "\n",
+ "```\n",
+ "Ambiguous type variable ‘a0’ arising from a use of ‘parse’\n",
+ " prevents the constraint ‘(Read a0)’ from being solved.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We get an error because the literal `4` could be any numeric value, and we're not indicating which one. So the compiler doesn't know which instance of `Read` to use. Should it use the `Int` instance? The `Integer` instance? `Float`? `Double`?\n",
+ "\n",
+ "One way to solve this specific scenario is to use the `::` operator like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(read \"4\") :: Int"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We're explicitly expressing that the `read \"4\"` expression should evaluate to an `Int`, so now the compiler knows it should use the `Read Int` instance.\n",
+ "\n",
+ "Now, instead of doing this, we can use the `TypeApplications` language extension and use the `@` keyword to apply the function to a type like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "{-# LANGUAGE TypeApplications #-}\n",
+ "\n",
+ "read @Int \"4\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What does means comes back to the `read` type signature. If we compare the type signature of `read` and `read @Int`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "read :: forall a. Read a => String -> a"
+ ],
+ "text/plain": [
+ "read :: forall a. Read a => String -> a"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "read @Int :: String -> Int"
+ ],
+ "text/plain": [
+ "read @Int :: String -> Int"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "{-# LANGUAGE TypeApplications #-}\n",
+ "\n",
+ ":t read\n",
+ ":t read @Int"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We see that we're specializing the type variable `a` to `Int`.\n",
+ "\n",
+ "This works in more complex scenarios. In the following example, we define the `Number` type that holds either a whole number, a decimal number, or a string of an unidentified number:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "WHOLE :: forall a b. a -> Number a b"
+ ],
+ "text/plain": [
+ "WHOLE :: forall a b. a -> Number a b"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "WHOLE @Int :: forall b. Int -> Number Int b"
+ ],
+ "text/plain": [
+ "WHOLE @Int :: forall b. Int -> Number Int b"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "WHOLE @Int @Float :: Int -> Number Int Float"
+ ],
+ "text/plain": [
+ "WHOLE @Int @Float :: Int -> Number Int Float"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "{-# LANGUAGE TypeApplications #-}\n",
+ "\n",
+ "data Number a b = WHOLE a | DECIMAL b | NAN String deriving (Show)\n",
+ "\n",
+ ":t WHOLE\n",
+ ":t WHOLE @Int\n",
+ ":t WHOLE @Int @Float"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And if you want to specify the type of the second parameter (`b`) but leave the first type as a polymorphic one, you can use another language extension called `PartialTypeSignatures` that allows you to put wildcards in types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "DECIMAL @Int :: forall b. b -> Number Int b"
+ ],
+ "text/plain": [
+ "DECIMAL @Int :: forall b. b -> Number Int b"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "DECIMAL @_ @Float :: forall _. Float -> Number _ Float"
+ ],
+ "text/plain": [
+ "DECIMAL @_ @Float :: forall _. Float -> Number _ Float"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "{-# LANGUAGE TypeApplications #-}\n",
+ "{-# LANGUAGE PartialTypeSignatures #-}\n",
+ "\n",
+ "data Number a b = WHOLE a | DECIMAL b | NAN String deriving (Show)\n",
+ "\n",
+ ":t DECIMAL @Int\n",
+ ":t DECIMAL @_ @Float -- Now the \"a\" type is shown as \"_\"."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's see this in a way that makes a real difference in the final result.\n",
+ "\n",
+ "We create the `parse` function that takes in a `String` and parses it into a value of type `Number a b` depending if it represents a `WHOLE` number, a `DECIMAL` number, or if it couldn't identify the number (`NAN`):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "parse :: (Read a, Read b) => String -> Number a b\n",
+ "parse inp\n",
+ " | isNumber && isDecimal = DECIMAL $ read inp\n",
+ " | isNumber = WHOLE $ read inp\n",
+ " | otherwise = NAN inp\n",
+ " where\n",
+ " validChar = \".0123456789\"\n",
+ " isNumber = all (`elem` validChar) inp\n",
+ " isDecimal = '.' `elem` inp"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, if we try to use this function without indicating the type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ "Prelude.read: no parse"
+ ]
+ }
+ ],
+ "source": [
+ "parse \"98\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We get the same error we got before because GHC can't infer the type of `98`. \n",
+ "\n",
+ "If we indicate the types, everything works fine. But not only that! **By specializing the variables `a` and `b` types when applying the `parse` function, we get to choose what kind of precision we want when parsing a whole or a decimal number!**:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "WHOLE (-9223372036854775808)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "WHOLE 9223372036854775808"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "DECIMAL 1.2345679"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "DECIMAL 1.23456789"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "NAN \"para bailar la bamba!!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "longWhole = \"9223372036854775808\"\n",
+ "longDecimal = \"1.23456789\"\n",
+ "\n",
+ "-- 'a' 'b' \n",
+ "parse @Int longWhole\n",
+ "parse @Integer longWhole\n",
+ "parse @_ @Float longDecimal\n",
+ "parse @_ @Double longDecimal\n",
+ "parse @Int @Double \"para bailar la bamba!!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### The `@` symbol"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `@` symbol is also used to pattern-match an entire structure (like a list, record type, etc.) while pattern-matching for its parts."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the example below, the `counts` function takes in a list and returns a list of tuples with the unique value and how many times the value is in the list.\n",
+ "\n",
+ "When we pattern match to extract the first value `(x:_)`, we also add `list@` that creates a variable named `list`, which is equal to the entire list `x:xs`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[('s',3),('u',2),('c',2),('e',1),('f',1),('l',2),('y',1)]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "counts :: Eq a => [a] -> [(a, Int)]\n",
+ "counts [] = []\n",
+ "counts list@(x:_) = (x, length (filter (== x) list)) : counts (filter (/= x) list)\n",
+ "\n",
+ "counts \"successfully\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The main difference between when we use the `@` sign to apply a type or to pattern match is the space to is left: \n",
+ "- Pattern matching: it has no space to its left `list@(x:xs)`\n",
+ "- Apply type: it has a space `read @Int`\n",
+ "\n",
+ "That's how the compiler knows which is which."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "There are a ton of language extensions ([see this list for reference](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/table.html)), and we'll use new ones when needed. But for now, make sure to do your homework!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "The homework is based on the example in the video lesson. If you feel lost, take a look at the part where I explain the example's code.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## That's it for today! Make sure to do the homework! 😁"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/15-Handling-Errors.ipynb b/lessons/15-Handling-Errors.ipynb
new file mode 100644
index 00000000..19c7d925
--- /dev/null
+++ b/lessons/15-Handling-Errors.ipynb
@@ -0,0 +1,1922 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Handling🧑🚒🧯💨 🔥errors🔥 in Haskell"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Haskell is a compiled language, so we can subdivide all possible errors into:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- ✅ Compile time errors 🙌"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Compile time errors are awesome! We LOVE compile-time errors. Because that means our program had something wrong in it, and our compiler found it and reported it to us **BEFORE** it reached the end user. At the end of the day, the most important thing is that our users enjoy our software and don't encounter:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- ❌ Run time errors 🫣"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Runtime errors are the worst! Because these errors happen while running the program, they can and do, happen to end users. Users then get angry and stop paying, give us a 1-star review, or something like that."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Compared to most other programming languages, Haskell is exceptionally good at avoiding runtime errors. To the point that many say the phrase, \"in Haskell, if it compiles, it works.\"\n",
+ "\n",
+ "This is thanks, in large part, to its purity and powerful type system that can catch those errors at compile time. Essentially, moving many errors from runtime to compile time.\n",
+ "\n",
+ "Although, this doesn't mean that we can forget about runtime errors. As we said in previous lessons, even if we were able to write perfect code (which we cannot), we're still running it in the real world. Where computers run out of memory, files that should exist don't, internet connections fail, etc. And on top of that, users do unimaginable things with our software."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, in this lesson, we will learn both how to handle runtime errors and how to use the type system to move some of those errors to compile time so we catch them with the compiler's assistance.\n",
+ "\n",
+ "More specifically:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Outline\n",
+ "\n",
+ "- There're always `Exception`s to the rule\n",
+ "- Speed-running `Exception`s with a dumb self-driving 🤖 car 🚗\n",
+ " - I'm the `Exception` cause I have `class` 😎\n",
+ " - `throw` all the `Exception`s you want. I'll `catch` them all!\n",
+ "- `Maybe` give me a value? 🙏\n",
+ " - Benefits of optional values\n",
+ "- Ok, you `Either` give me a value or a reason why you didn't!\n",
+ "- From `Exception`s to optional values\n",
+ "- Tradeoffs\n",
+ " - So, what should I use?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's get started!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## There're always `Exception`s to the rule"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's write a simple program to calculate the velocity of an object based on a number written in a file and the input of a person:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "calcVel :: IO ()\n",
+ "calcVel = do\n",
+ " d <- readFile \"aNumber.txt\"\n",
+ " putStrLn \"Loaded the distance traveled by the object.\"\n",
+ " putStrLn \"Provide the time it took:\"\n",
+ " t <- getLine\n",
+ " let v = (read d :: Int) `div` read t\n",
+ " putStrLn $ \"The object's velocity is about: \" ++ show v\n",
+ " putStrLn \"Thank you for using this program!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this program, we read a file (the distance traveled by an object) and ask the user to provide a number (the time it took) using IO actions. Then, we use the `read` function to parse them into `Int` values and apply the `div` function to get the velocity of the object. In the end, we print back the velocity and a thank-you message.\n",
+ "\n",
+ "This is a pretty simple program. It compiles, and if we have a file in the `aNumber.txt` with a number in it, and the user provides a valid number, it works!!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Sadly, the real world is messy, and unexpected things can–and do–happen. When something unexpected happens, that's when we get an `Exception`. For example, here are a few (but not all) of the possible `Exception`s that the program could throw at us:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "If there's no `aNumber.txt` file:\n",
+ "```\n",
+ "*** Exception: aNumber.txt: openFile: does not exist (No such file or directory)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "If we don't provide valid numbers to `read`:\n",
+ "```\n",
+ "*** Exception: Prelude.read: no parse\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "If we provide `0`(zero) as time:\n",
+ "```\n",
+ "*** Exception: divide by zero\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Currently, all these `Exception`s cause our program to crash. But, of course, a robust program should not collapse if something unexpected happens. Luckily, we have a couple of ways to handle these `Exception`s."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Speed-running `Exception`s with a dumb self-driving 🤖 car 🚗"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Handling exceptions in Haskell is not a small subject. It has its own mechanism, and you can go crazy creating your own hierarchical error structures, using alternative libraries, etc. But you have more important things to learn before that. So, in this lesson, we're going to speedrun the subject in a way you'll have an overall idea of how it works, and you'll be able to handle common runtime errors."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok. Let's say we want to build an AI for a self-driving car. AI is all the rage right now, so we're going to build a prototype to get some VC funding and improve it later.\n",
+ "\n",
+ "To simplify the problem, we'll start with a car that can only go straight and react to traffic lights:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "dumbAICar :: IO ()\n",
+ "dumbAICar = do\n",
+ " putStrLn \"What color is the traffic light?\"\n",
+ " color <- getLine\n",
+ " putStrLn $ \"\\nThen I'll \" ++ nextMove color\n",
+ " dumbAICar\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In these complex cases, it's usual to have several different systems, sensors, and predictive models. But! Because no one invested in our AI yet, we don't have money to spend on a sensor. So we'll be the sensor. We'll sit inside the car and type the color of the traffic light.\n",
+ "\n",
+ "Once we have a `String` containing the color, we provide it to the `nextMove` function that eventually should send a signal to the gas and brakes. For now, it prints the next move on the screen.\n",
+ "\n",
+ "Now, let's create the `nextMove` function. We're taking a `String` that represents the color of traffic lights, so we could do something like:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data TrafficLight = Red | Yellow | Green deriving (Show, Read)\n",
+ "\n",
+ "nextMove :: String -> String\n",
+ "nextMove color = case read color of\n",
+ " Red -> \"Stop!\"\n",
+ " Yellow -> \"Waaaait...\"\n",
+ " Green -> \"Go!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is technically right. But we have two problems:\n",
+ "\n",
+ "1. If we write something different than any of those three constructors, our program halts, and subsequently, the car crashes with us inside.\n",
+ "2. And the other one is that the error provided in the case it fails doesn't provide much actionable information:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```\n",
+ "*** Exception: Prelude.read: no parse\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's solve the second issue first by providing a more expressive exception."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### I'm the `Exception` cause I have `class` 😎"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "To create our own `Exception`, we just need create a good old data type, and make it an instance of the `Exception` type class.\n",
+ "\n",
+ "So, lets create a data type that represents the possible failures:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data TrafficLightException = TrafficLightIsOff | WrongColor String\n",
+ " deriving (Show)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "One value for when the traffic light is off, and another when the sensor provides values that don't make sense. Now, this is still what we call \"normal\" values. To make them exceptional, we have to import the `Control.Exception` module:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "\n",
+ "Note: We have to import Control.Exception
, which is the standard library module that contains code related to exceptions. \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And make the type an instance of `Exception` like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Exception TrafficLightException\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notice we don't define the behaviors with the `where` keyword. That's because we don't have to! All the `Exception` type class functions have default implementations, so we only need to indicate that the `TrafficLightException` can use those.\n",
+ "\n",
+ "This conveniently allows us to use exceptional values without entering into how the `Exception` type class is defined and the needy-greedy of the mechanism. Things that would take too long to properly go over and would touch subjects that are a bit outside of the beginner's scope.\n",
+ "\n",
+ "So, yeah! That's all we need to do! With that single line of code, we transformed our \"normal\" type into an \"exceptional\" type. That means we can throw and catch values of type `TrafficLightException` like any other built-in exception.\n",
+ "\n",
+ "But what does that mean exactly?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### `throw` all the `Exception`s you want. I'll `catch` them all!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Throwing exceptions means halting the current program's execution and sending a signal with some information about what went wrong. That signal is the `Exception`.\n",
+ "\n",
+ "We have a couple of options to throw exceptions. We're going to go with:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "throwIO :: Exception e => e -> IO a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As the name and type suggest, `throwIO` gets a type that's an instance of the `Exception` type class and throws an exception within the `IO` context. We return a value within the impure `IO` context because exceptions can have side effects.\n",
+ "\n",
+ "Technically, you can throw exceptions from pure code using a function called `throw` (without the IO). But it's highly recommended to use `throwIO` to maintain our pure code exception-free."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, let's change the `nextMove` function to manually parse the `String` instead of using the `read` function and throw as many exceptions as we want:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "nextMove :: String -> IO String\n",
+ "nextMove color = case color of\n",
+ " \"Red\" -> return \"Stop!\"\n",
+ " \"Yellow\" -> return \"Wait!\"\n",
+ " \"Green\" -> return \"Go!\"\n",
+ " \"Black\" -> throwIO TrafficLightIsOff\n",
+ " _ -> throwIO . WrongColor $ color ++ \" is not a valid color!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And here's when we encounter one of the downsides to throwing exceptions. We don't want to throw from pure code (because, due to laziness, it is hard to predict), so we throw from the IO context. This means our previously pure `nextMove` function is now impure, and we lose all the advantages that come with purity. We'll talk more about this at the end of the lecture.\n",
+ "\n",
+ "So! Instead of using `read`, we parse the `String` by hand and provide extra cases to handle our newly created exceptions. \n",
+ "\n",
+ "Specifically, if we apply `nextMove` to one of the two final patterns, instead of returning a value, we throw an exception:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ ">> nextMove \"Black\"\n",
+ "*** Exception: TrafficLightIsOff\n",
+ "\n",
+ "\n",
+ ">> nextMove \"Rainbow\"\n",
+ "*** Exception: WrongColor \"Rainbow is not a valid color!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This solves the issue of not providing enough information because we can add custom messages that help when debugging the program. Keep in mind that this function can be buried inside a massive program. But because the exception is so specific, we instantly know what's wrong and where we should go to fix it.\n",
+ "\n",
+ "But, if we leave it like this, we just created a fancier way for our program to halt. 🤦♂️ To make our program more resilient, we're going to catch, handle, and recover from this exception! 💪"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Same as with `throwIO`, there are several ways to catch and handle an `Exception`. One of them is using the `catch` function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "catch :: Exception e -- Type e is an instance of Exception\n",
+ " => IO a -- The computation to run\n",
+ " -> (e -> IO a) -- Handler to invoke if an exception is raised\n",
+ " -> IO a -- We always return a value of type IO a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This function executes the `IO` action that we provide as the first parameter. If everything goes well, it returns the result, and that's it. BUT! If some exception arises, it catches it and gives it to the handling function we provide as a second argument. In that handling function, we can write all sorts of code to address this exception and resume our program.\n",
+ "\n",
+ "For example, we can use `catch` to handle our `TrafficLightException` like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "dumbAICar :: IO ()\n",
+ "dumbAICar = do\n",
+ " putStrLn \"What color is the traffic light?\"\n",
+ " color <- getLine\n",
+ " response <- nextMove color `catch` handler\n",
+ " putStrLn $ \"I'll \" ++ response\n",
+ " dumbAICar\n",
+ " \n",
+ " where\n",
+ " handler :: TrafficLightException -> IO String\n",
+ " handler e = do\n",
+ " -- do whatever you want...\n",
+ " putStrLn $ \"WARNING: \" ++ show e\n",
+ " case e of\n",
+ " TrafficLightIsOff -> return \"Proceed with caution.\"\n",
+ " WrongColor _ -> return \"Stop the car and shut down!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, it doesn't matter if the String isn't a valid color. The `nextMove` function will raise an exception. And, because the exception is of type `TrafficLightException`, the `dumbAICar` action will be able to catch it and give it to our `hanlder` to recover from it.\n",
+ "\n",
+ "In this case, if the traffic light is off, the car will proceed with caution. And if we happen to encounter any other color, that means something is wrong with the sensor. But instead of halting the program and leaving a moving car without a driver, the program continues execution and stops the car."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "\n",
+ ">> dumbAICar\n",
+ "What color is the traffic light?\n",
+ "> Red\n",
+ "I'll Stop!\n",
+ "What color is the traffic light?\n",
+ "> Green\n",
+ "I'll Go!\n",
+ "What color is the traffic light?\n",
+ "> Black\n",
+ "WARNING: TrafficLightIsOff\n",
+ "I'll Proceed with caution.\n",
+ "What color is the traffic light?\n",
+ "> Rainbow\n",
+ "WARNING: WrongColor \"Rainbow is not a valid color!\"\n",
+ "I'll Stop the car and shut down!\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, at least we won't crash if something's wrong with the sensor.\n",
+ "\n",
+ "If the exception is of any other type, it will keep propagating up the stack looking for a `catch` that can handle that specific type. If it doesn't find any, the program won't be able to recover.\n",
+ "\n",
+ "And that's the end of our speedrun. There are a ton of things we didn't cover, but everything revolves around this notion of throwing and catching exceptions. If you can foresee an exception and know what the program should do in that case, you can implement a handler to recover from it.\n",
+ "\n",
+ "And how do we know the usual exceptions we could encounter? Look no further than in the same `Control.Exception` module from where we imported the `Exception` type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "For example, if you're dealing with numeric operations, you can take a look at the `ArithException` data type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data ArithException\n",
+ " = Overflow\n",
+ " | Underflow\n",
+ " | LossOfPrecision\n",
+ " | DivideByZero\n",
+ " | Denormal\n",
+ " | RatioZeroDenominator \n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And if you're dealing with Arrays, you might encounter an exception of type `ArrayException`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data ArrayException\n",
+ " = IndexOutOfBounds String -- Indexed an array outside its bounds\n",
+ " | UndefinedElement String -- Evaluated an uninitialized element\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Etc, etc. \n",
+ "\n",
+ "In all those cases, all you have to do is catch the exception of a specific type and provide a handler function to recover from it. There may be no sensible way to continue. In those cases, we want to shut down gracefully. By shutting down gracefully, I mean cleaning up any open connections, killing orphan processes, writing some logs, etc. It depends on what you're doing."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now... The exceptions mechanism is great, but there's also a different way to handle runtime errors, and that is more straightforward and idiomatic. An that's using optional values."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## `Maybe` give me a value? 🙏"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In Haskell and other functional programming languages, we have this notion of \"opotional values.\" Those are values representing the possibility that a function may or **may not** return a meaningful value."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In Haskell, optional values are represented by the `Maybe` type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data Maybe a = Nothing | Just a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, it's a simple type. It has one nullary constructor (a constructor that doesn't have parameters) and one constructor with a polymorphic value. The major implementation difference with the other way to handle `Exceptions`, is that `Maybe` is a \"normal\" type. It's not special in any way other than what we (the developers) interpret by it.\n",
+ "\n",
+ "So, the critical thing to remember is how to interpret it when we encounter it. The `Maybe` type represents a value that might or might not be there. So, when you see something like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "someFunction :: Int -> String -> Maybe Bool\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We read it as:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "`someFunction` takes an `Int` and a `String`, and `Maybe` returns a `Bool`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "That's it.\n",
+ "\n",
+ "Now, let's see it in practice. One common example is to show how we can safely divide by zero.\n",
+ "\n",
+ "In your regular day-to-day math, dividing by zero doesn't make any mathematical sense. When programming, it's the same. So, if we divide by zero using the `div` function like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "55 `div` 0\n",
+ "\n",
+ "*** Exception: divide by zero\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Our program throws an Exception.\n",
+ "\n",
+ "Let's solve that without using the exception mechanism."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "One of the first things one could think to resolve the issue is to create a function that handles this specific case separately, sort of like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "safeDiv :: Integral a => a -> a -> a\n",
+ "safeDiv x 0 = ????? -- 0? -1? 9999999999999? \n",
+ "safeDiv x y = x `div` y\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `saveDivide` function takes two values that are instances of the `Integral` type class (the one that provides the `div` behavior) and returns a value of the same type. In the first case, we pattern-match to handle the case when the denominator is zero. And in the second, all other cases. The second case is easy... we know that `y` is not zero, so we can use `div` with full confidence. But how should we handle the first case? There's no number we could return that correctly represents this situation!\n",
+ "\n",
+ "\n",
+ "Here's when the `Maybe` type shines! Using `Maybe`, we can modify our function to have the option of not returning a value at all!!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "safeDiv :: Integral a => a -> a -> Maybe a\n",
+ "safeDiv _ 0 = Nothing\n",
+ "safeDiv x y = Just (x `div` y)\n",
+ "\n",
+ "\n",
+ "15 `safeDiv` 3\n",
+ "15 `safeDiv` 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, if there's no sensible value to be returned, we can plug the `Nothing` value. Notice that by avoiding the possibility of dividing by zero from the get-go, we bypass the exception altogether! No need to handle exceptions if they never happen!\n",
+ "\n",
+ "The price that we have to pay is that the value is now wrapped around a constructor. So we have to unwrap it before using it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, let's use the `safeDiv` function in a program. To do that, we'll keep with the speed theme and create a program that takes the distance traveled by an object and the time it took as inputs, and it returns the velocity:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " putStrLn \"provide the distance traveled by the object:\"\n",
+ " d <- getLine\n",
+ " putStrLn \"provide the time it took:\"\n",
+ " t <- getLine\n",
+ " case read d `safeDiv` read t of\n",
+ " Just v -> putStrLn $ \"The velocity is: \" ++ show v\n",
+ " Nothing -> do\n",
+ " putStrLn \"\\nThe time can't be zero!\"\n",
+ " main\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "By using this technique, we completely avoided a runtime error. It doesn't matter which numbers the user chooses. Our program won't crash! 🙌 But wait... What if the user doesn't write valid numbers as inputs? What if the user writes letters or symbols instead of numbers? 😵\n",
+ "\n",
+ "In that case, the `read` function won't be able to parse the `String` to a number, and... you guessed it... our program halts. 🫣\n",
+ "\n",
+ "Worry not! We have a solution! But before that, I'm going to extract some functions so the code fits in the slides:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "vel :: Int -> Int -> Maybe Int\n",
+ "vel dist time = dist `safeDiv` time\n",
+ "\n",
+ "getData :: IO (String, String)\n",
+ "getData = do\n",
+ " putStrLn \"provide the distance traveled by the object:\"\n",
+ " d <- getLine\n",
+ " putStrLn \"provide the time it took:\"\n",
+ " t <- getLine\n",
+ " return (d, t)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Extracting the `velocity` function isn't of much help now, but it will be in the future. And extracting the `getData` action hides code we don't care about. So our `main` function looks like this now:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " (d, t) <- getData\n",
+ " case vel (read d) (read t) of\n",
+ " Just v -> putStrLn $ \"The velocity: \" ++ show v\n",
+ " Nothing -> do\n",
+ " putStrLn \"\\nThe time can't be zero!\"\n",
+ " main\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Much cleaner! Ok, so we would like a `read` function that can avoid returning a meaningful value if it's unable to parse the value. Luckily for us, a function like that already comes with our base libraries, and it's inside the `Text.Read` module:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 57"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Text.Read (readMaybe)\n",
+ "\n",
+ "-- readMaybe :: Read a => String -> Maybe a\n",
+ "\n",
+ "readMaybe \"57\" :: Maybe Integer\n",
+ "readMaybe \"B00!\" :: Maybe Integer"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "By using `readMaybe` instead of `read`, we avoid **all runtime errors due to a bad user input**:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "import Text.Read (readMaybe)\n",
+ " \n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " (d, t) <- getData\n",
+ " case vel (readMaybe d) (readMaybe t) of\n",
+ " Just v -> putStrLn $ \"The velocity is: \" ++ show v\n",
+ " Nothing -> do\n",
+ " putStrLn \"\\nPlease, provide valid numbers (time /= zero)!\"\n",
+ " main\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We do have a small issue now. `velocity` cannot take `Int`s directly anymore because `readMaybe` returns a `Maybe`. So we have to modify `velocity` like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "vel :: Maybe Int -> Maybe Int -> Maybe Int\n",
+ "vel (Just dist) (Just time) = dist `safeDiv` time\n",
+ "vel _ _ = Nothing\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We pattern match for the cases when both are parsed correctly and pass the `Int`s to the `safeDiv` function. We ignore all other cases and return `Nothing`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "There are many pre-defined functions that use the `Maybe` type, both in the standard libraries and in libraries made by other developers. When you encounter them, you can use them all in the same way. Pattern-match, handle both cases, and you're good to go!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Benefits of optional values"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now that we understand how to use the `Maybe` data type, let's talk about the benefits it provides:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- You can handle the absence of value"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "`Maybe` provides a type-safe way to indicate the absence of a value, preventing many forms of runtime errors."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- Your code is more robust"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Using `Maybe` forces you to explicitly handle both the case when you have a value and when you don't. Ensuring you address all possibilities and write more robust code."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- Allows you to express uncertainty"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Optional values allow the developer to express uncertainty in a way that the consumer of the function is aware of and can handle without the need to know how the function is implemented."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- You can compose optional values"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As we did in the previous examples. We provided the output of `readMaybe` as inputs to `velocity` that used `safeDiv` inside. If something went wrong at any time, the `Nothing` value would propagate throughout the function tree."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, the `Maybe` data type is not the only data type in town we can use to handle possibly problematic values. One downside of using the `Maybe` data type is that when we have several layers of optional values, we don't know what went wrong! In the previous code, for example. If we got a `Nothing` at the end, what failed? The parsing of the first value? The parsing of the second value? Or the division by zero? We have no way to find out!\n",
+ "\n",
+ "Enter the `Either` data type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Ok, you `Either` give me a value or a reason why you didn't!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Either` data type is also pretty simple:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data Either a b = Left a | Right b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `Either` type represents values with two possibilities: a value of type `Either a b` is either `Left a` or `Right b`. You cannot have both. This is not necessarily related to error handling or optional values. Either can be used in many scenarios to represent a binary choice.\n",
+ "\n",
+ "However, in the context of error handling, you can think of `Either` as something that builds on top of the `Maybe` data type to provide a way to log what went wrong. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The way we use `Either` to handle errors is not forced in code but by convention. The community overall uses the `Left` constructor to hold an error value and the `Right` constructor to hold a correct value. Based on the mnemonic that \"right\" also means \"correct.\"\n",
+ "\n",
+ "So, now that we know about `Either`, let's modify `saveDiv` to let us know if we try to divide by zero:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Right 5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Left \"You can't divide by zero, you fool!!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "safeDivE :: Integral a => a -> a -> Either String a\n",
+ "safeDivE _ 0 = Left \"You can't divide by zero, you fool!!\"\n",
+ "safeDivE x y = Right (x `div` y)\n",
+ "\n",
+ "\n",
+ "15 `safeDivE` 3\n",
+ "15 `safeDivE` 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, we chose `String` as the type parameter for `Left` so we can leave a message to the user or developer. Although using a simple `String` to leave a message is the intuitive thing to try first, using values that we could later handle programmatically is even better. Something like:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data PermissionLevel = Guest | User | Admin\n",
+ "\n",
+ "data UIException = WrongInput String\n",
+ " | WrongPermission PermissionLevel\n",
+ " | UserDidNotLogIn\n",
+ "\n",
+ "someFunction :: Integral a => a -> a -> Either UIException a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And further down the line, we can use this extra information to do things programmatically. For example, if `Either` returns `Left UserDidNotLogIn`, we can redirect the user to the login page.\n",
+ "\n",
+ "We will stick to `String`s for today's examples. To keep it simple.\n",
+ "\n",
+ "There are also many functions already available that return `Either` values. As you might imagine, one of those is the `Either` equivalent of the `readMaybe` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Right 57"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Left \"Prelude.read: no parse\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Text.Read (readEither)\n",
+ "\n",
+ "--readEither :: Read a => String -> Either String a\n",
+ "\n",
+ "readEither \"57\" :: Either String Int\n",
+ "readEither \"B00!\" :: Either String Int"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Notice how it returns what we otherwise would get as an Exception, but this time we're getting it as a plain `String`, and our program keeps running.\n",
+ "\n",
+ "With all that, let's transform the rest of the code. The main function is almost the same, we just change `readMaybe` for `readEither`. And because we can return a custom messge with the error, we print that:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "import Text.Read (readEither)\n",
+ "\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " (d, t) <- getData\n",
+ " case vel (readEither d) (readEither t) of\n",
+ " Right v -> putStrLn $ \"The object's velocity is: \" ++ show v\n",
+ " Left s -> do\n",
+ " putStrLn s\n",
+ " main\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `velocity` function is the one that gets interesting. Now, we can return a message, so we can personalize it based on what went wrong:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "vel :: Either String Int -> Either String Int -> Either String Int\n",
+ "vel (Right d) (Right t) = d `safeDivE` t\n",
+ "vel (Left d) (Left t) = Left $ \"Both wrong!! d:\"++ d ++\" t:\"++ t\n",
+ "vel (Left d) _ = Left $ \"Wrong distance input!: \" ++ d\n",
+ "vel _ (Left t) = Left $ \"Wrong time input!: \" ++ t\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, the `velocity` function now explicitly handles more cases because we care about which input failed to parse. And that's pretty much it!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## From `Exception`s to optional values"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Finally, there are ways to go from one to the other. We already saw how to go from normal values to exceptions... We throw an exception.\n",
+ "\n",
+ "But what if we have an exception and we want an optional value? The easiest way to do that is using the `try` function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "try :: Exception e => IO a -> IO (Either e a)\n",
+ "try ioa = (ioa >>= return . Right) `catch` (return . Left)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The `try` function uses `catch` under the hood. `try` takes an IO action and tries to run it. If everything goes well, it returns the final result using the `Right` constructor. But, if something goes wrong, `catch` will capture all exceptions of type `e` and return them using the `Left` constructor. Same as before, if the raised exception is of a type different than `e`, it will keep propagating."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "So, let's say we want to read a file:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " contents <- putStrLn \"Where's the file?\" >> getLine >>= readFile\n",
+ " putStrLn $ \"result: \" ++ contents\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this case, we cannot prevent the possibility of an `Exception`. We're interacting with the outside world, and unexpected things might happen.\n",
+ "\n",
+ "But! Using the `try` function, we can handle the `Exception` under the hood and only interact with optional values ourselves. Like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "main :: IO ()\n",
+ "main = do\n",
+ " ec <- try $ putStrLn \"Where's the file?\" >> getLine >>= readFile\n",
+ " case ec of\n",
+ " Left e -> do\n",
+ " putStrLn $ \"WARNING: \" ++ show (e :: IOException)\n",
+ " main\n",
+ " Right contents -> putStrLn $ \"result: \" ++ contents\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "As you can see, the exception, in this case, is of type `IOException`. These are exceptions related to IO operations, like reading and writing files. And from now on, you can do whatever you want with your values following the usual execution of your program.\n",
+ "\n",
+ "OK. We learned how to handle exceptions using the exception mechanism, how to use optional values to keep our error handling within the regular execution of our program, and finally, how to move from one to the other. To end this lecture, let's compare the two of them to see what the tradeoffs are."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Tradeoffs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In theory, the key difference between using exceptions and optional values is that when we use optional values, we're handling errors in the program itself. We're not stopping the execution and silently propagating the error throughout the stack. The error handling happens in a specific place and is part of the normal execution of the program.\n",
+ "\n",
+ "In practice, there are more things to take into account."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "|Optional values | Exceptions |\n",
+ "| --- | --- |\n",
+ "They are evident by the types, so you know when you're dealing with them | They are hidden from you, so you're unaware of how a function can fail by only looking at its type|\n",
+ "| They are pure values, easy to predict | You can only catch them inside impure code, which makes your program harder to predict|\n",
+ "| They are easy to use | They can be complicated to use |\n",
+ "| Managing multiple levels of optional values may* add complexity to the code | The code complexity of the \"happy path\" stays the same, but there's added complexity elsewhere. |\n",
+ "| They don't compose well with functions that take the unwrapped value. Which generates a loss in modularity | They don't affect composability |\n",
+ "| Accidental strictness: It's not unavoidable, but it becomes easy to make our functions strict | Doesn't affect laziness and compiler optimizations |\n",
+ "| They can't handle asynchronous `Exceptions` cause those happen outside the code | We can `catch` asynchronous `Exceptions` |"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### So, what should I use?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "For now:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- Try to use optional values (Maybe, Either, etc.) as much as possible to implement pure algorithms."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- If a failure is expected, it's better to explicitly indicate it with an optional type. For example, the `lookup :: (Eq a) => a -> [(a,b)] -> Maybe b` function returns a `Maybe` because it's expected that the key we asked for might not be there. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- If the failure is a rare \"This should never happen\" case, using an `Exception` is usually the best choice."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- In many cases, the choice between exceptional and non-exceptional can be blurry. In those cases, you'll have to use your best judgment."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And finally, if you feel like this is not enough, don't worry. Further along the line, you'll encounter knowledge that will unlock new ways of handling errors and exceptions. Giving you even more flexibility!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today! 😄"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/16-Gaining-your-independence-and-project.ipynb b/lessons/16-Gaining-your-independence-and-project.ipynb
new file mode 100644
index 00000000..bc2741dc
--- /dev/null
+++ b/lessons/16-Gaining-your-independence-and-project.ipynb
@@ -0,0 +1,1267 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Gaining your independence 💪"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Small tips and tricks\n",
+ " * REPL\n",
+ " * Hackage\n",
+ " * Hoogle\n",
+ " * `undefined`\n",
+ " * Type Holes\n",
+ "* Section's Final Project"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## REPL"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Remember that you always have the repl available to you. And If you enter the REPL using `cabal repl`, you can also import and explore modules you downloaded using Hackage. If you want to see how it works, see the example in the video version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Hackage\n",
+ "\n",
+ "\n",
+ "## https://hackage.haskell.org/"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Hackage is the Haskell community's central package archive. At the time of writing this lesson, there are over 16 thousand Haskell packages available in Hackage.\n",
+ "\n",
+ "We already saw how you can use it with Cabal to add libraries to your projects. But in the video lesson, we'll explore how to find and choose libraries and explore the documentation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Hoogle\n",
+ "\n",
+ "\n",
+ "## https://hoogle.haskell.org/"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Hoogle allows you to search a commonly used subset of Haskell libraries by either function name or approximate type signature. \n",
+ "\n",
+ "This is useful in several scenarios, for example:\n",
+ "\n",
+ "1. If you want to print a string to the console but forget the name of the function, searching for \"String -> IO ()\" will provide all functions with a signature that matches your intention. \n",
+ "2. If you want to use a function but forget from which module it is, you can search for the function, and it will tell you where it is.\n",
+ "3. If you want to work with some concept, like natural numbers or web sockets, you can try searching those terms to see if any library, type, module, function, or type class roughly matches the name."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## `undefined`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If we look for `undefined` in Hoogle, we'll see that, in theory, it's just a fancy error. It's a value that, as soon as you evaluate it, halts your program. Of course, as we saw in the Handling Errors lesson, we don't like runtime errors! So, why I'm sharing it as a tip?\n",
+ "\n",
+ "In practice, `undefined` is a great tool to keep the type checker assisting you while working on a half-baked code. Let's see how."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's say we want to create a function that reads a CSV file, transforms the content into JSON format, and writes a new file with the new contents. \n",
+ "\n",
+ "First, we create newtype wrappers for both CSV and JSON values. To avoid mixing CSV and JSON values by accident and allow the compiler to provide more specific hints. Even if the underlying value in both cases is just a String.\n",
+ "\n",
+ "Then, because we don't want our program to crash if, for some reason, the reading and writing of files or the conversion fails, we'll catch the errors and return an Either:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype CSV = CSV String \n",
+ "newtype JSON = JSON String\n",
+ "\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok, now we can start implementing the function. Because we're just starting and we don't have a clear idea of how we want to implement this, we'll go step by step. I'll behave naively to showcase the usefulness of `undefined`. \n",
+ "\n",
+ "Let's start by printing a message to the console and reading the file:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " rawCSV <- readFile -- ❌ typecheck: Error here!\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "If we write this, we get this error:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```\n",
+ "The last statement in a 'do' block must be an expression\n",
+ " rawCSV <- readFile\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Which is not the error we would expect. Especially because there's an even more fundamental error that the type checker is not telling us about because it's stuck in this one.\n",
+ "\n",
+ "We could be making huge mistakes like this one:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " askldj56jklsdjf564lkjsdlf -- Cat walked on the keyboard 🐈\n",
+ " rawCSV <- readFile -- ❌ typecheck (oblivious to the previous line): Error here!\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And we wouldn't know because the type checker is stuck with the same error as before.\n",
+ "\n",
+ "This is one of the cases when `undefined` is handy. If we add an `undefined` as the last statement of the `do` block, we're essentially telling the type checker: \"trust me, bro, from here on, everything is fine. Don't sweat it.\"\n",
+ "\n",
+ "So, the type checker, assuming everything is fine there, will continue to analyze the rest of our code and give us useful information.\n",
+ "\n",
+ "In this case, if we do this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile = do\n",
+ " putStrLn \"Reading CSV file\" -- ❌ typecheck: Wrong type!\n",
+ " askldj56jklsdjf564lkjsdlf -- ❌ typecheck: What's wrong with you?\n",
+ " rawCSV <- readFile -- ❌ typecheck: Where's the readFile's argument?\n",
+ " undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We get a bunch of helpful errors that help us realize that there's a line with gibberish, that we didn't specify the argument name of the `csvFileToJsonFile` function, and that we didn't provide the argument to the `readFile` function. So, we fix them:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile fp = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " rawCSV <- readFile fp\n",
+ " undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, we have no type errors, and we can be reasonably certain that everything up until `undefined` is ok."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Another use of `undefined` is to check the behaviors during development. For example, we still have to parse the contents to CSV, convert them into JSON, and then write the new file. And on top of that, we have to do error handling. \n",
+ "\n",
+ "Instead of writing the whole thing and checking that everything works at the end, we can check the values in intermediate steps by running the code with the `undefine` there. For example, we can check the content of the files by printing them:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile fp = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " rawCSV <- readFile fp\n",
+ " print rawCSV\n",
+ " undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, we open a REPL and run `csvFileToJsonFile \"somefile.csv\"` and we get the contents of the file printed at the console, and after that, we get an exception:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```\n",
+ "*** Exception: Prelude.undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Because `undefined` is just a fancy error, we'll get a runtime error if we run the program. Of course, by the time you're done with the code, there has to be no `undefined` left. `undefined` is just a tool for your convenience during a development session. You could get fired if you ship an `undefined` value to production.\n",
+ "\n",
+ "But! We don't care at this point because we're mid-development, and we just want to check if everything we coded so far is fine."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Finally, one last good use case for `undefined` is to use it as an implementation TODO. For example, let's say we keep going with our function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile fp = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " rawCSV <- readFile fp\n",
+ " let csv = parseCSV rawCSV -- ❌ typecheck: Who is parseCSV? A friend of yours?\n",
+ " undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "At this point, we'd have an error because there's no `parseCSV` function. And there's no `parseCSV` function because we haven't implemented it yet.\n",
+ "\n",
+ "One option would be to implement `parseCSV` right away. That would be fine. But what if, halfway through implementing it, you realize you need to implement another function? And another one. This specific case wouldn't be that complicated. But you can see how, in more complex cases, by the time you finish implementing all the internal functions, you lose track of what you had in mind for the original one.\n",
+ "\n",
+ "So, if you have a rough idea of the overall structure of the original function, you can defer implementing the internal functions until you finish implementing the original function by creating the internal functions signatures and setting the actual implementation of it as undefined. Like this: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype CSV = CSV String deriving (Show)\n",
+ "newtype JSON = JSON String deriving (Show)\n",
+ "\n",
+ "csvFileToJsonFile :: FilePath -> IO (Either String JSON)\n",
+ "csvFileToJsonFile fp = do\n",
+ " putStrLn \"Reading CSV file\"\n",
+ " rawCSV <- readFile fp\n",
+ " let csv = parseCSV rawCSV\n",
+ " (JSON json) = csvToJson csv\n",
+ " writeFile \"newFile.json\" json\n",
+ " undefined\n",
+ "\n",
+ "parseCSV :: String -> CSV\n",
+ "parseCSV = undefined\n",
+ "\n",
+ "csvToJson :: CSV -> JSON\n",
+ "csvToJson = undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And now, each `undefined` is like a TODO item. The first indicates that you still have to add error handling. And the other two that you still have to implement those functions. You essentially split the work into three, and you can start tackling your TODOs one by one."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, let's move to the final tip of the lesson: Type holes!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Type holes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Typed holes are a feature of GHC specifically designed to help you figure out what code to write when you're unsure.\n",
+ "\n",
+ "It works like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Let's say you're implementing a function that parses a list of `String`s into valid emails:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = undefined\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This function verifies that the user input is a valid email by checking it contains the `@` sign. And then, it has to normalize them by converting all characters into lowercase.\n",
+ "\n",
+ "Ok. So, let's start easy by just straight-up converting Strings into Emails without doing anything.\n",
+ "\n",
+ "If we change the undefined to an underscore, we get a pretty interesting error:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = _\n",
+ "```\n",
+ "-------------------------------------------------------------\n",
+ "```\n",
+ "• Found hole: _ :: [String] -> [Email]\n",
+ "• In an equation for ‘parseEmails’: parseEmails = _\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:99:1)\n",
+ " Valid hole fits include\n",
+ " parseEmails\n",
+ " mempty\n",
+ " Valid refinement hole fits include\n",
+ " map _\n",
+ " concatMap _\n",
+ " (<$>) _\n",
+ " fmap _\n",
+ " ($) _\n",
+ " const _\n",
+ " pure _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This is what type holes bring to the table in Haskell. By putting an underscore in our unfinished code, we asked the type checker for hints about what could be done there. The type checker brings all the information it can:\n",
+ "\n",
+ "- It tells us what's the type of the hole.\n",
+ "- Where the hole is.\n",
+ "- What are relevant bindings (in this case, the only relevant binding is the same function we're defining, but if we have a `where` clause or `let` bindings, those would show up as well).\n",
+ "- Then, it shows us which values in our scope perfectly fit the hole.\n",
+ "- Finally, it tells us which functions and constructors don't fit perfectly but could take us one step closer to the final answer."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "In this case, we know we have to `map` over the list of `String`s, so we take the first \"refinement hole\" suggestion and write `map` with an underscore (a new type hole) in front:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map _\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: String -> Email\n",
+ "• In the first argument of ‘map’, namely ‘_’\n",
+ " In the expression: map _\n",
+ " In an equation for ‘parseEmails’: parseEmails = map _\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:100:1)\n",
+ " Valid hole fits include Email\n",
+ " Valid refinement hole fits include\n",
+ " ($) _\n",
+ " const _\n",
+ " pure _\n",
+ " return _\n",
+ " ($!) _\n",
+ " (Map.!) _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, we get a new set of the same information but for our new hole. And, if we look at the \"Valid hole fits\", we see that the `Email` constructor is there!\n",
+ "\n",
+ "If we wrap a string with the `Email` value constructor, we get a value of type `Email`, which is exactly what we set out to do!\n",
+ "\n",
+ "So, we take the type hole suggestion and write the `Email` constructor after `map`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map Email\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "And voilá! Our function compiles.\n",
+ "\n",
+ "But we're far from finished here. We said we wanted to filter emails that didn't contain the `@` sign, so let's do that. \n",
+ "\n",
+ "Of course, we want to filter the emails before constructing them, so we'll use function composition to add the `filter` function before the `map` function: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map Email . filter _\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: String -> Bool\n",
+ "• In the first argument of ‘filter’, namely ‘_’\n",
+ " In the second argument of ‘(.)’, namely ‘filter _’\n",
+ " In the expression: map Email . filter _\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:94:1)\n",
+ " Valid hole fits include\n",
+ " null\n",
+ " read\n",
+ " Valid refinement hole fits include\n",
+ " (==) _\n",
+ " (/=) _\n",
+ " (>) _\n",
+ " (<=) _\n",
+ " (>=) _\n",
+ " (<) _\n",
+ " ($) _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ " (Some refinement hole fits suppressed; use -fmax-refinement-hole-fits=N or -fno-max-refinement-hole-fits)\n",
+ " ```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok. So, we need a predicate. But, this time, the typed hole has a little message at the bottom. This is because it has more suggestions than the maximum allowed by default. One thing we could do to get more hints is to disable this maximum allowed by writing a pragma with the flag indicated right there like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map Email . filter _\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: String -> Bool\n",
+ "• In the first argument of ‘filter’, namely ‘_’\n",
+ " In the second argument of ‘(.)’, namely ‘filter _’\n",
+ " In the expression: map Email . filter _\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:94:1)\n",
+ " Valid hole fits include\n",
+ " null\n",
+ " read\n",
+ " Valid refinement hole fits include\n",
+ " (==) _\n",
+ " (/=) _\n",
+ " (>) _\n",
+ " (<=) _\n",
+ " (>=) _\n",
+ " (<) _\n",
+ " ($) _\n",
+ " notElem _\n",
+ " elem _\n",
+ " any _\n",
+ " all _\n",
+ " const _\n",
+ " pure _\n",
+ " return _\n",
+ " ($!) _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ " ```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Now, we have more options in the \"refinement hole fits\" sections. And, if we look at them, we're reminded that we could use `elem`. We know that `elem` is a predicate that returns true if the element is inside the list, which is what we needed. We substitute `_` with `elem _` and keep going:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map Email . filter (elem _)\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: Char\n",
+ "• In the first argument of ‘elem’, namely ‘_’\n",
+ " In the first argument of ‘filter’, namely ‘(elem _)’\n",
+ " In the second argument of ‘(.)’, namely ‘filter (elem _)’\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:95:1)\n",
+ " Valid hole fits include\n",
+ " maxBound\n",
+ " minBound\n",
+ " Valid refinement hole fits include\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ " pred _\n",
+ " succ _\n",
+ " toEnum _\n",
+ " read _\n",
+ " ```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "This case is pretty obvious. We need a character to check if it is part of the `String`, and we know which character that is:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map Email . filter (elem '@')\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We're done with the filtering! Now, let's normalize the emails. Because we have to normalize the strings before wrapping them with the `Email` constructor, we do the same as before and compose a type hole:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map (Email . _) . filter (elem '@')\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: String -> String\n",
+ "• In the second argument of ‘(.)’, namely ‘_’\n",
+ " In the first argument of ‘map’, namely ‘(Email . _)’\n",
+ " In the first argument of ‘(.)’, namely ‘map (Email . _)’\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:98:1)\n",
+ " Valid hole fits include\n",
+ " show\n",
+ " reverse\n",
+ " cycle\n",
+ " init\n",
+ " tail\n",
+ " id\n",
+ " mempty\n",
+ " fail\n",
+ " read\n",
+ " Valid refinement hole fits include\n",
+ " (:) _\n",
+ " (++) _\n",
+ " max _\n",
+ " min _\n",
+ " map _\n",
+ " concatMap _\n",
+ " (<$>) _\n",
+ " fmap _\n",
+ " take _\n",
+ " drop _\n",
+ " ($) _\n",
+ " takeWhile _\n",
+ " dropWhile _\n",
+ " const _\n",
+ " filter _\n",
+ " (<>) _\n",
+ " mappend _\n",
+ " pure _\n",
+ " sequenceA _\n",
+ " foldMap _\n",
+ " return _\n",
+ " (<*>) _\n",
+ " (=<<) _\n",
+ " (<*) _\n",
+ " (<$) _\n",
+ " sequence _\n",
+ " ($!) _\n",
+ " asTypeOf _\n",
+ " scanl1 _\n",
+ " scanr1 _\n",
+ " showChar _\n",
+ " showString _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ " mconcat _\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We get a huuuuuge list of options, but the one that clearly looks like the best option is to refine our hole with a `map`. We have a list of characters. So, maybe we can go through every character and return the lowercase version, one by one. So, we accept that suggestion and replace the `_` with `map _`: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map (Email . map _) . filter (elem '@')\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: Char -> Char\n",
+ "• In the first argument of ‘map’, namely ‘_’\n",
+ " In the second argument of ‘(.)’, namely ‘map _’\n",
+ " In the first argument of ‘map’, namely ‘(Email . map _)’\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:100:1)\n",
+ " Valid hole fits include\n",
+ " id\n",
+ " pred\n",
+ " succ\n",
+ " Valid refinement hole fits include\n",
+ " max _\n",
+ " min _\n",
+ " ($) _\n",
+ " const _\n",
+ " pure _\n",
+ " return _\n",
+ " ($!) _\n",
+ " asTypeOf _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Ok. We know that we need a function that goes from character to character. But none of the provided ones seem to either fit perfectly or help us move in the right direction. But we have one more ace up our sleeve: Imports! \n",
+ "\n",
+ "We get those suggestions because those are the ones available in our environment. So, if we want more suggestions, we can add more to our environment. In this case, we want to work with characters, so a good initial idea would be to import a module full of functions to work with characters. The `Data.Char` module is the prime candidate. Let's do that and see which new options we get:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "import Data.Char\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map (Email . map _) . filter (elem '@')\n",
+ "```\n",
+ "---\n",
+ "```\n",
+ "• Found hole: _ :: Char -> Char\n",
+ "• In the first argument of ‘map’, namely ‘_’\n",
+ " In the second argument of ‘(.)’, namely ‘map _’\n",
+ " In the first argument of ‘map’, namely ‘(Email . map _)’\n",
+ "• Relevant bindings include\n",
+ " parseEmails :: [String] -> [Email]\n",
+ " (bound at /Users/roberm/scratchpad/typedHoles.hs:100:1)\n",
+ " Valid hole fits include\n",
+ " id\n",
+ " pred\n",
+ " succ\n",
+ " toLower\n",
+ " toUpper\n",
+ " toTitle\n",
+ " Valid refinement hole fits include\n",
+ " max _\n",
+ " min _\n",
+ " ($) _\n",
+ " const _\n",
+ " pure _\n",
+ " return _\n",
+ " ($!) _\n",
+ " asTypeOf _\n",
+ " head _\n",
+ " last _\n",
+ " id _\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Behold! Between the new suggestions, there's a function that perfectly fits our hole with the name of `toLower`. It looks too enticing to ignore, so let's replace it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "{-# OPTIONS_GHC -fno-max-refinement-hole-fits #-}\n",
+ "\n",
+ "import Data.Char\n",
+ "\n",
+ "newtype Email = Email String deriving Show\n",
+ "\n",
+ "parseEmails :: [String] -> [Email]\n",
+ "parseEmails = map (Email . map toLower) . filter (elem '@')\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "It compiles! And it looks like we finished implementing all the functionality we wanted. And if we test the function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "parseEmails [\"luffy@onepiece.com\",\"ZorO@OnePiece.cOm\", \"son.goku\"]\n",
+ "\n",
+ "-- [Email \"luffy@onepiece.com\",Email \"zoro@onepiece.com\"]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "We see that it works as expected. \n",
+ "\n",
+ "As you can see, type holes can be really useful. Especially when you're working with many polymorphic values or nested structures. Just as a final remark, you can have more than one hole at a time and name them by adding the name right after the underscore (without a space in between). Make it easier to distinguish them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Section's Final Project (what we did so far)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In this lesson, you're going to prove to yourself that you can code in Haskell.\n",
+ "\n",
+ "If you've been doing the homework by now, you have a couple of programs under your belt:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* In lesson 9's homework, you built a closed and open maze (we called it Forest) solver. \n",
+ "* In lesson 11's homework, you had the opportunity to build a program that prints a tree-like graph representing a folder structure.\n",
+ "* In lesson 14, we built a CLI game in which the user tries to escape from a forest before it runs out of stamina. And in the homework of the same lesson, you added a battle system to fight golems while trying to escape.\n",
+ "* In lesson 15's homework, you went through and understood the code of a CLI program that you can use to manage your to-do list. And on top of that, you added error handling to fix the bugs I purposely hid in the code."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "At every step of the way, we introduced new concepts, and I provided you with thorough guidance about what to take into account and how to approach those challenges. Now, it's time for you to build something by yourself.\n",
+ "\n",
+ "Here are the requirements:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Section's Final Project (project requirements)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "* Build a Tic-Tac-Toe game.\n",
+ "* It has to be a CLI executable with the business logic implemented as a library.\n",
+ "* There has to be a single-player (against the machine) and a multiplayer (two-player) mode.\n",
+ "* The machine has to play randomly.\n",
+ "* The board has to be printed nicely on the console.\n",
+ "* Use any library you want. However, the provided solution will only use the `random` library and Haskell features explained up until now. So, if you're tempted to use more advanced features, you're likely overcomplicating it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "The only way to learn how to code is by coding. So, make sure to do the project, and I'll see you on the next one."
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/17-Semigroup-and-Monoid.ipynb b/lessons/17-Semigroup-and-Monoid.ipynb
new file mode 100644
index 00000000..0864ce57
--- /dev/null
+++ b/lessons/17-Semigroup-and-Monoid.ipynb
@@ -0,0 +1,2122 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Abstractions, Semigroup, and Monoid"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is the first lesson of the \"Abstracting Patterns\" section of the course. In this lesson, we'll cover:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "- What does it mean to abstract a pattern?\n",
+ "- Why abstracting patterns (in general)?\n",
+ "- Teaser: Why abstracting `Semigroup` and `Monoid`?\n",
+ "- The `Semigroup` type class\n",
+ "- The `Monoid` type class\n",
+ "- What can we do with `Semigroup` and `Monoid`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## What does it mean to abstract a pattern?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We humans are very good at detecting patterns. For example, in the 6th lesson of this course, we wrote these functions:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "sum' :: [Int] -> Int\n",
+ "sum' [] = 0\n",
+ "sum' (x:xs) = x + sum' xs\n",
+ "\n",
+ "product' :: [Int] -> Int\n",
+ "product' [] = 1\n",
+ "product' (x:xs) = x * product' xs\n",
+ "\n",
+ "and' :: [Bool] -> Bool\n",
+ "and' [] = True\n",
+ "and' (x:xs) = x && and' xs\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We figured out that there was a repeating pattern in all of those functions, so we created a single one that contains that pattern and takes whatever is different as arguments:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- +/*/&& -> 0/1/True -> [Int]/[Bool] -> Int/Bool\n",
+ "foldr :: (a -> b -> b) -> b -> [a] -> b\n",
+ "foldr _ v [] = v\n",
+ "foldr f v (x:xs) = f x (foldr f v xs)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And then, because we had a function that represented the abstract idea of:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\"Applying a function to combine the first value of a list and the result of recursively applying the same function to the rest of the list\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Or more sucinctly:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\"Reducing a list using a binary operator\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We replaced the implementation of the original functions with the abstraction like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "sum' :: [Int] -> Int\n",
+ "sum' = foldr (+) 0 -- We partially apply foldr\n",
+ "\n",
+ "product' :: [Int] -> Int\n",
+ "product' = foldr (*) 1\n",
+ "\n",
+ "and' :: [Bool] -> Bool\n",
+ "and' = foldr (&&) True\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "That seires of steps:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "1. Write some code.\n",
+ "2. Identifying a pattern.\n",
+ "3. Create a structure* to contain that pattern (if useful).\n",
+ "4. Use that structure instead of explicitly writting the pattern.\n",
+ "\n",
+ "*By \"structure,\" we mean types, functions, and type classes. Other programming languages may use different ones (like OOP classes)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok, so this is what we mean when we talk about abstracting a pattern.\n",
+ "\n",
+ "As a small caveat, it's important to note that we shouldn't extract all patterns. As I said before, we humans are very good at detecting patterns, but not all of them are worthy of abstraction. But don't worry about that for now. You'll learn to tell the difference with practice."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Why abstracting patterns (in general)?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "One somewhat reasonable question would be to ask: \"Why should I abstract patterns?\" And there are several reasons, the most important ones are:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- To easily reuse code"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As we showed in the previous `foldr` example, now you have to implement the recursive pattern once, and you can use it anywhere."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- To hide the unimportant details"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Also, in the previous example, we hid the details of how exactly we implement the recursion inside `foldr`. Thanks to that, the code becomes a short one-liner that only shows what we care about: Which binary function we apply and what is the starting value."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- To have clear and concise code that you (and others) can quickly understand"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By using `foldr` instead of explicitly writing the pattern, the reader instantly understands what we're doing. We're folding a list. That's it. 1 second is enough to know what this line does and keep moving. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These reasons apply for every correctly derived abstraction. But what about `Semigroup` and `Monoid` specifically?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Teaser: Why abstracting `Semigroup` and `Monoid`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To make sure I get your full attention throughout the rest of this lesson, I'll share a real-world problem that becomes significantly easier by abstracting `Semigroup` and `Monoid`, and that is:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "### Scalability"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "More specifically:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "- **Scaling computations**\n",
+ "- **Scaling result complexity without increasing code complexity**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If you've worked with any programming language in production, you'll likely know this is a tough and complex problem. So, we developers need all the help we can get."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "At the end of the lesson, after learning about `Semigroup` and `Monoid`, we'll see how these abstractions allow us to more easily scale."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Semigroup` type class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the first example, we abstracted away a pattern into a function, and now we'll abstract away into a type class. This is not something new. If you think about it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "``` haskell\n",
+ "type Num :: * -> Constraint\n",
+ "class Num a where\n",
+ " (+) :: a -> a -> a\n",
+ " (-) :: a -> a -> a\n",
+ " (*) :: a -> a -> a\n",
+ " negate :: a -> a\n",
+ " abs :: a -> a\n",
+ " signum :: a -> a\n",
+ " fromInteger :: Integer -> a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `Num` type class is an abstraction of the properties and behaviors a number should have. All numbers should be able to be added, subtracted, multiplied, and so on. Some types we think of as numbers behave fundamentally differently in some ways. For example, we can get fractional numbers with `Float`, but we can not with `Integer`. So, the `Num` type class abstracts away only the behaviors every number-like type should have."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We are going to do the same someone else did for numbers, but for a different concept. Take a look at this code:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(\"Abstract \" ++ \"me\") ++ \"!\" -- \"Abstract me!\"\n",
+ "\"Abstract \" ++ (\"me\" ++ \"!\") -- \"Abstract me!\"\n",
+ "\n",
+ "(2 * 3) * 4 -- 24\n",
+ "2 * (3 * 4) -- 24\n",
+ "\n",
+ "(True && False) && True -- False\n",
+ "True && (False && True) -- False\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "What do we have in common in all three cases? Well, in all three cases, there's a binary function (a function that takes two parameters) that somehow combines two values of one type to produce a new value of the same type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(++) :: [a] -> [a] -> [a]\n",
+ "\n",
+ "(*) :: Num a => a -> a -> a\n",
+ " \n",
+ "(&&) :: Bool -> Bool -> Bool\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And, on top of that, the binary operation is associative! Meaning the order in which we apply the binary function doesn't matter. And that's it! That's the whole concept:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ " A `Semigroup` is a type that has an associative binary operation."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It seems like an arbitrary choice. Like, why this abstraction instead of another? Well, it'll be clear why at the end of the lesson. However, as with all the abstractions we'll cover in this course, it boils down to this: We realized they are more useful than others.\n",
+ "\n",
+ "Ok. So, now that we have the concept we want to represent and know that we need it to be available for multiple types, we'll create a type class that represents it. \n",
+ "\n",
+ "Aaand... this is it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Semigroup a where\n",
+ " (<>) :: a -> a -> a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I know, I know, a bit anti-climactic. All that hype for a two-line type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We chose to use an operator that looks like a diamond instead of a regular prefix function because the most common use case (as we saw in the examples we used to extract the pattern) is to apply the binary function as an infix function."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, wait a minute... what about the assosociativity? Where does that appear in the code? \n",
+ "\n",
+ "Well, the sad truth is that not even Haskell's type system, the most powerful of all general-purpose mainstream languages, can restrict this property. And, because we can not use code to transmit these requirements, we use laws written in plain text and kindly ask developers to follow them. Of course, developers follow them because these laws bring a lot of value.\n",
+ "\n",
+ "In this case, every time you create an instance of `Semigroup`, you have to make sure to satisfy the associativity law:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**Associativity law:**\n",
+ "```haskell\n",
+ "x <> (y <> z) = (x <> y) <> z\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok, we have our abstraction ready. Let's implement an instance to see how that would look, starting with the list instance. \n",
+ "\n",
+ "We have to choose a binary operation. This is really easy for lists. If you explore the `Prelude` and the `Data.List` modules, or even easier, if you look up the type using Hoogle, you'll find out that there's only one operator that takes two lists to generate another list, and on top of that, it's associative! And that's the `++` operator that appends two lists:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(\"Abstract \" ++ \"me\") ++ \"!\" -- \"Abstract me!\"\n",
+ "\"Abstract \" ++ (\"me\" ++ \"!\") -- \"Abstract me!\"\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Awesome! We have our operator. And now, we can implement our instance. Just this time, I'll show all possible ways to do it. But, I trust that, by now, you could figure this out by yourself:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Semigroup [a] where\n",
+ " (<>) = (++)\n",
+ "\n",
+ "-- same as doing:\n",
+ "\n",
+ "instance Semigroup [a] where\n",
+ " (<>) [] ys = ys\n",
+ " (<>) (x:xs) ys = x : xs <> ys\n",
+ " \n",
+ "-- same as doing:\n",
+ "\n",
+ "instance Semigroup [a] where\n",
+ " [] <> ys = ys\n",
+ " (x:xs) <> ys = x : xs <> ys\n",
+ " \n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "All three implementations are the same thing written differently, so you could choose whichever you want. In this case, because the operator is already defined, the best would be to just use it, as shown in the first implementation. And, if you're curious, that's how it's actually defined in the `base` library."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To do a rough check that the associativity law holds, we could do a few tests by hand:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(\"is \" <> \"this \") <> \"True?\" == \"is \" <> (\"this \" <> \"True?\")\n",
+ "\n",
+ "(([1] <> [2]) <> []) <> [3,4] == [1] <> ([2] <> ([] <> [3,4]))\n",
+ "\n",
+ "([True] <> ([True] <> [False])) == [True] <> [True] <> [False]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, this is not proof that the law holds. It's just a suggestion that it seems to work, which is more than enough for us. However, thanks to Haskell's purity, we could prove this law by induction or property testing. That's out of the scope of this course, but I'll link an explanation in the video description. Just in case you're curious."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok, done! We have our first instance of `Semigroup`. It seems we're done with Semigroup, but there's one more thing to take into account: What if there's no clear answer as to which operation we should use? For example, if we're using numbers, a quick search would give us four binary operations:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(+) :: Num a => a -> a -> a\n",
+ "(*) :: Num a => a -> a -> a\n",
+ "(-) :: Num a => a -> a -> a\n",
+ "substract :: Num a => a -> a -> a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But wait, if we quickly check of associativity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "9"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "9"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "24"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "24"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "-5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "-1"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(2 + 3) + 4 -- 9\n",
+ "2 + (3 + 4) -- 9 ✅\n",
+ "\n",
+ "(2 * 3) * 4 -- 24\n",
+ "2 * (3 * 4) -- 24 ✅\n",
+ "\n",
+ "(2 - 3) - 4 -- -5\n",
+ "2 - (3 - 4) -- 3 ❌\n",
+ "\n",
+ "(2 `subtract` 3) `subtract` 4 -- 3\n",
+ "2 `subtract` (3 `subtract` 4) -- -1 ❌"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We see that the `(-)` (minus) and `subtract` functions aren't associative. This makes sense because subtraction isn't associative in maths, either.\n",
+ "\n",
+ "So, we're left with just two functions:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(+) :: Num a => a -> a -> a\n",
+ "\n",
+ "(*) :: Num a => a -> a -> a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Which one should we use? Both functions satisfy the `Semigroup` requirements of being an associative binary function. But we can not choose more than one... or can we? "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It turns out that some types, in this case, all numeric types, have more than one valid `Semigroup` instance. To resolve this, we create one `newtype` per valid and useful operation that wraps the original type. That way, we can implement as many instances as we need because they are different types!\n",
+ "\n",
+ "In the current case, because both the sum and product operations are valuable, we'll wrap a type in the `Sum` and `Product` `newtypes`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "newtype Sum a = Sum { getSum :: a }\n",
+ " deriving (Show, Eq)\n",
+ "\n",
+ "newtype Product a = Product { getProduct :: a }\n",
+ " deriving (Show, Eq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, we just wrapped the `a` type with a `Sum` and `Product` constructors to get the new `Sum` and `Product` types. On top of that, we used record syntax to easily extract the wrapped value without the need for pattern-matching in case we want to.\n",
+ "\n",
+ "And now comes the magic. We'll implement their `Semigroup` instances using their corresponding binary operation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Num a => Semigroup (Sum a) where\n",
+ " (Sum a) <> (Sum b) = Sum (a + b)\n",
+ "\n",
+ "instance Num a => Semigroup (Product a) where\n",
+ " (Product a) <> (Product b) = Product (a * b)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, the only thing that changes is that we have to make sure the type inside `Sum` and `Product` are also instances of `Num` in order to have the `+` and `*` operators available to us.\n",
+ "\n",
+ "Other than that, it's just pattern-matching to unwrap the numbers from the parameters, applying the binary operation to the numbers, and wrapping the result.\n",
+ "\n",
+ "Let's try them!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Sum {getSum = 5}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Product {getProduct = 45}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "30"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Sum 3 <> Sum 2\n",
+ "\n",
+ "Product 5 <> Product 9\n",
+ "\n",
+ "(Sum 4 <> Sum 5) <> Sum 1 == Sum 4 <> (Sum 5 <> Sum 1)\n",
+ "\n",
+ "getProduct $ Product 3 <> Product 5 <> Product 2\n",
+ "\n",
+ "-- Sum 9 <> Product 10 -- ❌ Won't compile! Different types!!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It works! This is not the only case. We also have two options between all the orderable types:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "max :: Ord a => a -> a -> a\n",
+ "\n",
+ "min :: Ord a => a -> a -> a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Both `max` and `min` functions are associative binary operations, and both make sense to use, so we do the same. We create `newtype` wrappers:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "newtype Max a = Max { getMax :: a }\n",
+ " deriving (Show, Eq)\n",
+ "\n",
+ "newtype Min a = Min { getMin :: a }\n",
+ " deriving (Show, Eq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And then, we create the `Semigroup` instances with the corresponding associative binary operations:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Ord a => Semigroup (Max a) where\n",
+ " (Max a) <> (Max b) = Max (a `max` b)\n",
+ "\n",
+ "instance Ord a => Semigroup (Min a) where\n",
+ " (Min a) <> (Min b) = Min (a `min` b)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Finally, we test it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Min {getMin = 3}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Max {getMax = 9}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Min 3 <> Min 6\n",
+ "\n",
+ "Max 9 <> Max 0\n",
+ "\n",
+ "(Min 4 <> Min 5) <> Min 1 == Min 4 <> (Min 5 <> Min 1)\n",
+ "\n",
+ "getMax $ Max 3 <> Max 5 <> Max 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The case for booleans is very similar; So similar that, in fact, you'll have to implement it as part of this lesson's homework.\n",
+ "\n",
+ "But before we move on, let's implement a `Semigroup` for a type we came up with ourselves. For example, the `Severity` type. A type that represents the severity of an emergency:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "data Severity = Low | Medium | High | Critical deriving (Show, Eq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We don't have any pre-existent associative binary operations, so we'll have to come up with one. What do you think would be a good associative binary operation for severity?: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<>) :: Severity -> Severity -> Severity\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Pause the video if you want to think about it for a bit. Better yet, try to implement it yourself!\n",
+ "\n",
+ "Ok. So, we want to combine severity levels. It makes sense that if we have two emergencies of the same severity, we should return one with the same severity. And if they are of different severities, we should return the highest one. So, we could define `Severity`'s `Semigroup` instance like this: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Semigroup Severity where\n",
+ " Critical <> _ = Critical\n",
+ " _ <> Critical = Critical\n",
+ " High <> _ = High\n",
+ " _ <> High = High\n",
+ " Medium <> _ = Medium\n",
+ " _ <> Medium = Medium\n",
+ " _ <> _ = Low"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I think this makes quite a lot of sense. Let's check if the binary operation is actually associative: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "High"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Medium"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "High <> Medium\n",
+ "\n",
+ "Low <> Medium <> Low\n",
+ "\n",
+ "(High <> Low) <> Critical == High <> (Low <> Critical)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's it! We created our fifth `Semigroup` instance. If you understand everything up until now, the next abstraction will be a piece of cake. So, let's talk about the `Monoid` type class:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Monoid` type class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `Monoid` type class builds on top of the `Semigroup` type class to add a small but significant extra behavior. Let's take a look at the same example we saw at the beginning, but with a slight tweak: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(\"Abstract \" ++ \"me\") ++ \"!\" -- \"Abstract me!\"\n",
+ "\"Abstract \" ++ \"\" ++ (\"me\" ++ \"!\") -- \"Abstract me!\"\n",
+ "\n",
+ "(2 * 3) * 4 -- 24\n",
+ "2 * 1 * (3 * 4) -- 24\n",
+ "\n",
+ "(True && False) && True -- False\n",
+ "True && True && (False && True) -- False\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Do you notice the changes I made in the code? And what about the changes in the result?\n",
+ "\n",
+ "As you can see, I added one more operation in the second line of each example, but it doesn't affect the end result because one of the values doesn't do anything. We call a value that doesn't modify the result: The \"Identity\" value. It's not the first time we encountered this concept. We first learned about identities when we learned about recursion and how vital identity values are in defining the base cases.\n",
+ "\n",
+ "And as you can see, the `1` is the identity for multiplication, the `True` is the identity for `&&`, and the empty string is the identity for concatenating `String`s, which, more generally speaking, means that the empty list is the identity of concatenating lists."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, if we speel it out, the pattern we're seeing right here is:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "A `Monoid` is a type that has an associative binary operation with an identity."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But we already have a type class representing an associative binary operation. So, instead of repeating ourselves, we can make `Monoid` a subclass of `Semigroup` and add only the identity. Something like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Semigroup a => Monoid a where\n",
+ " mempty :: a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Here, the `mempty` value represents the identity. It's called like that due to convention. You can read it as `m` (from `Monoid`) `empty`. \n",
+ "\n",
+ "And this would be conceptually it. But, if we take a look at the actual `Monoid` type class in Haskell, it might look like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Semigroup a => Monoid a where\n",
+ " mempty :: a -- Identity element\n",
+ " mappend :: a -> a -> a -- <>\n",
+ " mconcat :: [a] -> a -- foldr <> mempty\n",
+ " {-# MINIMAL mempty | mconcat #-}\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And why is that? Why the extra functions?\n",
+ "\n",
+ "Well, because in previous versions of Haskell, we didn't have the `Semigroup` type class. The `Monoid` type class was self-contained, and needed to define its own associative binary operator. The \"monoid append\" or `mappend` function was the associative binary operation we defined in `Semigroup`, and the \"monoid concat\" or `mconcat` function is a behavior that we get for free thanks to having the `mempty` and `mappend` functions. It's just `foldr` applied to the binary operator and `mempty`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I say that it \"might look like this\" because, by the time you're watching this video, we might not have `mappend` in `Monoid` anymore since it's redundant now that we have `Semigroup`. \n",
+ "\n",
+ "We didn't remove `mappend` from `Monoid` when `Semigroup` was introduced because that would've broken virtually every program written in Haskell. So, to avoid receiving angry emails from every Haskell developer, the maintainers phased out the changes to give everyone the time to catch up before removing it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Notice, however, that here it's happening the same as it happened with the associativity for `Semigroup`. The restriction that the `mempty` element has to be the identity of the operation is nowhere to be seen. We cannot enforce it with code, so we create laws that indicate to the developer that they have to adhere to some extra rules when implementing `Monoid` instances:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Right identity**\n",
+ "```haskell\n",
+ " x <> mempty = x -- e.g.: Sum 4 <> Sum 0 == Sum 4\n",
+ "```\n",
+ "**Left identity**\n",
+ "```haskell\n",
+ " mempty <> x = x -- e.g.: Sum 0 <> Sum 4 == Sum 4 \n",
+ "```\n",
+ "**Associativity**\n",
+ "```haskell\n",
+ " x <> (y <> z) = (x <> y) <> z -- (Semigroup law)\n",
+ "```\n",
+ "**Concatenation**\n",
+ "```haskell\n",
+ " mconcat = foldr (<>) mempty\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok! Let's implement a few `Monoid` Instances.\n",
+ "\n",
+ "This is actually pretty easy because we did the hard part when implementing the `Semigroup` type class. These are the `Monoid` instances of all the types we worked with today:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "--instance Monoid [a] where\n",
+ "-- mempty = []\n",
+ "\n",
+ "instance Num a => Monoid (Sum a) where\n",
+ " mempty = Sum 0\n",
+ "\n",
+ "instance Num a => Monoid (Product a) where\n",
+ " mempty = Product 1\n",
+ "\n",
+ "instance (Ord a, Bounded a) => Monoid (Max a) where\n",
+ " mempty = Max minBound\n",
+ "\n",
+ "instance (Ord a, Bounded a) => Monoid (Min a) where\n",
+ " mempty = Min maxBound\n",
+ "\n",
+ "instance Monoid Severity where\n",
+ " mempty = Low"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, all of the instances are pretty straightforward. You have to think of the value that doesn't change the result when applied to the `Semigroup`'s associative binary operation.\n",
+ "\n",
+ "- If you sum `0` to a value, you get the same initial value.\n",
+ "- If you multiply a value by `1`, you get the same initial value.\n",
+ "- If you compare if a value is greater than the smallest possible value, you get the same initial value.\n",
+ "- If you compare if a value is smaller than the largest possible value, you get the same initial value.\n",
+ "- If you combine any severity with the lowest one, you get the same initial severity.\n",
+ "\n",
+ "Here are a few examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Max {getMax = -9223372036854775808}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Max {getMax = 3}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Min {getMin = 9223372036854775807}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Min {getMin = 2}"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Medium"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Sum 2 <> mempty <> Sum 3 == Sum 2 <> Sum 3 -- True\n",
+ "\n",
+ "mconcat [Product 2, Product 3, mempty] == Product 2 <> Product 3 -- True\n",
+ "\n",
+ "(mempty) :: Max Int -- Max {getMax = -9223372036854775808}\n",
+ "\n",
+ "Max 2 <> mempty <> Max 3 :: Max Int -- Max {getMax = 3}\n",
+ "\n",
+ "(mempty) :: Min Int -- Min {getMin = 9223372036854775807}\n",
+ "\n",
+ "mempty <> Min 2 <> mempty :: Min Int -- Min {getMin = 2}\n",
+ "\n",
+ "mconcat [mempty, Medium, mempty, mempty] -- Medium\n",
+ "\n",
+ "Sum 9 <> Sum 11 == Sum 9 `mappend` Sum 11 -- True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's it! We created our first 5 instances of `Monoid`.\n",
+ "\n",
+ "Now that we know about `Semigroup` and `Monoid`, let's answer the big question. Why are they useful?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## What can I do with `Semigroup` and `Monoid`?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We already established that by abstracting patterns, you can more easily reduce code, hide implementation details that aren't part of the core logic, and have clear and concise code that you (and others) can quickly understand. But that's for all abstractions in general. What do I gain with `Semigroup` and `Monoid` specifically?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Distributed computation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "Imagine you have a dataset of stars with various data points: Their size, mass, brightness, etc. And we want to know which one is the oldest.\n",
+ "\n",
+ "We cannot measure the age of a star, so we have to calculate it with a formula that takes all the data of a star and returns its approximate age.\n",
+ "\n",
+ "If that computation takes 1 second, and we have a dataset of 1000 stars, it would take around 17 minutes to complete. Not a big deal.\n",
+ "\n",
+ "But... that's not a realistic number of stars. Gaia, one of the European Space Agency's telescopes, is currently taking precise measurements of close to 1 billion of the brightest stars in the sky. That's too big of a number to wrap our heads around, so let's say we get our hands on a dataset of 1 million stars. If you want to run your function on that dataset, it will take 114 years to complete. You'll likely be dead before that finishes.\n",
+ "\n",
+ "If only there was a way to reduce the wait time..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Well, if the result of an `IO` computation is a `Monoid`, the `IO` computation is a `Monoid` too!! This means you could wrap the computation's result with the `Max` monoid, split the work into 200 servers that run in parallel, and merge the results as soon as two consecutive servers finish their computation.\n",
+ "\n",
+ "The end result? Instead of waiting 114 years, you have to wait only 6 months. 0.5% of the time it would take using a single server. And, of course, you could keep reducing the wait by spinning more servers."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, this feat could be accomplished without a `Semigroup` or `Monoid` instance. But having them made it waaaaay easier. So much easier, in fact, that we didn't have to change the computation!! We just wrapped the result with the `Max` constructor and called it a day. We changed 1 line of code to make our computation parallelizable."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Scaling result complexity without increasing code complexity"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's say we have a social media app with a form that a user has to complete with their personal information to create their account:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The user asks to be able to configure their experience, so we add a settings page tha its just a another form:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "After some time, we add a form to change their profile image. We need to add this to the settings. But also inside the one to create an account. So we created a reusable component and put it inside both:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Companies also want to use our app, so we added a form for company settings that also has to be inside the one to open the account:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, people lose their passwords, so we'll create a reusable form to change our password that we'll put inside the regular user and company settings: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok, I think that's enough, you get the idea. This is not only about forms. It's the conventional architecture most programs follow:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Combine several components of type `A` to generate a \"network\" or \"structure\" of a different type `B`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "That means that every time we add something that generates a more complex end-user experience, the complexity of our code exponentially increases because we have to not only create the new component but also integrate it into the system. And with each addition, it gets harder and harder."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, what if the forms themselves where `Semigroup`s? In that case, we don't need to worry about integrating them since that's done by our associative binary operation:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "We combine several components of type `A` to generate a new one of the same type `A`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, if I want to add a new form now, it doesn't matter if we already have 1, 10, or 100 forms. The complexity is always the same. You still have to build the new form, but you get the integration for free."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Those are two fairly obvious ways that `Semigroup` and `Monoid` instances help. If you want more examples, you'll have to do the homework.\n",
+ "\n",
+ "And if you're thinking: \"Why did we need to separate `Semigroup` and `Monoid` again? Can't we just have `Monoid` and that's it?\"\n",
+ "\n",
+ "`Monoid` is more powerful than `Semigroup` because the identity element allows for an easy way to fold and concatenate the elements. So, based on what we know now, it would make sense to only have the `Monoid` type class. But here's the thing: Some types can be instances of `Semigroup` but not of `Monoid`, and it doesn't make sense we have to lose most of the power just because they don't have an identity element. \n",
+ "\n",
+ "For example, there's a type in Haskell that represents a list that can never be empty. And because of that, it doesn't have an identity element and can never be a `Monoid`! Curious about how that works? Well..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Do your homework to find out, and I'll see you in the next one!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/18-Functor.ipynb b/lessons/18-Functor.ipynb
new file mode 100644
index 00000000..cdac454c
--- /dev/null
+++ b/lessons/18-Functor.ipynb
@@ -0,0 +1,6785 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ ":opt no-lint"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Functor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Welcome to a new lesson of the Haskell curse. This one is all about the `Functor` type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "### Functors are 🌏 EVERYWHERE!! 🌎\n",
+ "\n",
+ "In this lesson:\n",
+ "- You'll understand the concept of `Functor`\n",
+ "- You'll learn everything you need to know to use them in practice."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We have Functors in mathematics, programming languages, linguistics, under your bed waiting for you to go to sleep... Functors are everywhere!! You might have worked with functors without even knowing.\n",
+ "\n",
+ "After this lesson, you will not only understand the concept of `Functor` in Haskell, but we'll also go the extra mile so you can learn everything you need to know to actually use it in practice.\n",
+ "\n",
+ "And how are we going to do that? This is how:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "* Abstracting the `map` function\n",
+ "* The `Functor` type class\n",
+ "* Defining `Functor` instances\n",
+ "* Seemingly unintuitive `Functor` instances\n",
+ " * The `Either a` functor 🤔\n",
+ " * The `(,) a` functor 🤨\n",
+ " * The `(->) r` functor 🤯\n",
+ "* Defining `<$>` and Lifting 🏋️ a function\n",
+ "* `Functor` nesting dolls 🪆\n",
+ "* Extra functions and `Functor` as defined in `base`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Abstracting the `map` function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This chapter will be super easy since you already know about `map`. Let's implement a function that returns the lower-case version of a String:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"hi, how's it going?\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Data.Char (toLower)\n",
+ "\n",
+ "lowerString :: [Char] -> [Char]\n",
+ "lowerString [] = []\n",
+ "lowerString (x:xs) = toLower x : lowerString xs\n",
+ "\n",
+ "lowerString \"Hi, How's it Going?\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, let's implement a function that adds one to a list of numbers:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[2,2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "addOne :: Num a => [a] -> [a]\n",
+ "addOne [] = []\n",
+ "addOne (x:xs) = (x + 1) : addOne xs\n",
+ "\n",
+ "addOne [1,1,2,3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And now, let's implement a function that transforms a list of boolean values to a list of characters representing bits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"101\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "boolToBit :: [Bool] -> [Char]\n",
+ "boolToBit [] = []\n",
+ "boolToBit (x:xs) = (if x then '1' else '0') : boolToBit xs\n",
+ "\n",
+ "boolToBit [True,False,True]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. So, I'm sure you see where I'm going with this. There's a repeating pattern, so we'll extract it into its own function.\n",
+ "\n",
+ "Let's start with the type. As input, we have a list of characters, a list of types that are instances of the `Num` type class, and a list of booleans. So, the most general type would be `[a]` (a list of `a`) for the input list. \n",
+ "\n",
+ "As output type, at first glance, we could use the same `[a]`, but as we see in the `boolToBit` function, there are cases when you return a list with values of different types. So, there's no need to add an extra restriction to have the same type for the list that goes in and the one that goes out. So, let's use a different variable, like `[b]`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "map :: [a] -> [b]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, let's extract the pattern:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "map [] = []\n",
+ "map (x:xs) = f x : map xs\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This looks mostly ok. But we have to get the function `f` from somewhere, so we add it as a parameter. And, because the function `f` takes a value of type `a` from the input list and generates a value of type `b` from the output list, it follows that the function's type is `a -> b`. So, the final expression of our abstraction looks like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"hi, how's it going?\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"101\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "map :: (a -> b) -> [a] -> [b]\n",
+ "map _ [] = []\n",
+ "map f (x:xs) = f x : map f xs\n",
+ "\n",
+ "\n",
+ "map toLower \"Hi, How's it Going?\"\n",
+ "\n",
+ "map (+1) [1,1,2,3]\n",
+ "\n",
+ "map (\\x -> if x then '1' else '0') [True,False,True]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Awesome! We abstracted away the concept of applying an arbitrary function to every value of a list, and we called this abstraction a \"map.\" Then, we used this function to avoid repeating ourselves and simplify our code. This is cool, but we can do better. Let's go one level higher with the `Functor` type class:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Abstracting the `Functor` Type Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In Haskell, we have lots of types. So, let's say we're working with optional values using the `Maybe` type. Same as with lists, we also need a way to conveniently modify values inside `Maybe` types. No biggy, we know the drill. We can define the `maybeMap` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just '1'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "maybeMap :: (a -> b) -> Maybe a -> Maybe b\n",
+ "maybeMap _ Nothing = Nothing\n",
+ "maybeMap f (Just x) = Just (f x)\n",
+ "\n",
+ "\n",
+ "maybeMap toLower (Just 'A')\n",
+ "\n",
+ "maybeMap (+1) (Just 3)\n",
+ "\n",
+ "maybeMap (\\x -> if x then '1' else '0') (Just True)\n",
+ "\n",
+ "\n",
+ "maybeMap toLower Nothing\n",
+ "\n",
+ "maybeMap (+1) Nothing\n",
+ "\n",
+ "maybeMap (\\x -> if x then '1' else '0') Nothing"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, the `maybeMap` function can't do anything when the `Maybe` value is `Nothing`. This makes sense: Something went wrong before the value arrived at this function, so we should propagate the error. But, when we have a value, we apply the function to the value and wrap it again in a `Just` constructor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Notice how the function we apply as the first parameter in both `map` and `maybeMap` is oblivious to the structure that contains the value it modifies. This is important because it means we could use the same functions in both cases and let `map` and `maybeMap` handle the details."
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "OK. Let's do a harder one. Let's say we want to work with binary trees:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 2) 1 (Node (Leaf 4) 3 (Leaf 5))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data Tree a = Leaf a | Node (Tree a) a (Tree a) deriving (Show, Eq)\n",
+ "\n",
+ "\n",
+ "exampleTree = Node (Leaf 2) 1 (Node (Leaf 4) 3 (Leaf 5))\n",
+ "exampleTree\n",
+ "\n",
+ "\n",
+ "-- 1\n",
+ "-- / \\\n",
+ "-- 2 3\n",
+ "-- / \\\n",
+ "-- 4 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, I can think of many scenarios that involve modifying the values of each node and leaf without changing the structure of the tree. Maybe the tree represents a family tree, and each number is the age of a family member. After one year passes, we have to update the ages of everyone by one. Or maybe it represents the hierarchical structure of positions in a company, and we have to update the salaries by some percentage to account for inflation.\n",
+ "\n",
+ "Either way, we need to apply a function to the values without losing the structure. Same as we did for lists and `Maybe` values. So, we create `treeMap`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 'b') 'a' (Node (Leaf 'd') 'c' (Leaf 'e'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 3) 2 (Node (Leaf 5) 4 (Leaf 6))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf '0') '1' (Node (Leaf '0') '1' (Leaf '1'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "treeMap :: (a -> b) -> Tree a -> Tree b\n",
+ "treeMap f (Leaf x) = Leaf (f x)\n",
+ "treeMap f (Node lt x rt) = Node (treeMap f lt) (f x) (treeMap f rt)\n",
+ "\n",
+ "\n",
+ "treeMap toLower (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))\n",
+ "\n",
+ "treeMap (+1) (Node (Leaf 2) 1 (Node (Leaf 4) 3 (Leaf 5)))\n",
+ "\n",
+ "treeMap (\\x -> if x then '1' else '0') (Node (Leaf False) True (Node (Leaf False) True (Leaf True)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If it's a `Leaf`, `treeMap` applies the function to the current value. And if it's a `Node`, `treeMap` also applies the function to the current value and recursively calls `treeMap` on both branches."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, a new pattern is emerging:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "map :: (a -> b) -> [a] -> [b]\n",
+ "map _ [] = []\n",
+ "map f (x:xs) = f x : map f xs\n",
+ "\n",
+ "\n",
+ "\n",
+ "maybeMap :: (a -> b) -> Maybe a -> Maybe b\n",
+ "maybeMap _ Nothing = Nothing\n",
+ "maybeMap f (Just x) = Just (f x)\n",
+ "\n",
+ "\n",
+ "\n",
+ "treeMap :: (a -> b) -> Tree a -> Tree b\n",
+ "treeMap f (Leaf x) = Leaf (f x)\n",
+ "treeMap f (Node lt x rt) = Node (treeMap f lt) (f x) (treeMap f rt)\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "- The `map` function modifies the elements of a list without changing its length or order.\n",
+ "- The `maybeMap` modifies the elements of an optional value without changing its nature. It returns `Nothing` if the original value is `Nothing` and `Just` if it's `Just`.\n",
+ "- The `treeMap` function modifies the elements of a tree without changing the amount and arrangement of nodes and leaves.\n",
+ "\n",
+ "In all cases we apply a function to a value or values inside a structure without modifying the structure itself."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This seems like an extremely valuable abstraction, so let's extract it.\n",
+ "\n",
+ "To extract `map`, we had to generalize the types of values inside the lists and the function we applied. Because that was what changed between examples.\n",
+ "\n",
+ "Now, the thing that changes is the structure that holds these values, which, here, is represented by the list, `Maybe`, and `Tree` types. So, we have to provide this abstraction as a type class that types could implement. We'll call this type class `Functor` because of math terminology, but that doesn't change what the abstraction means to us.\n",
+ "\n",
+ "So, what is a `Functor` in Haskell?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "A `Functor` is a type that can apply a function to the values of a structure without modifying the structure itself."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We need only one behavior, the function that does the mapping. If we look at the `map`, `maybeMap`, and `treeMap` functions, we see that the first parameter, the function, is always the same. This makes sense since we said before that we wanted the functions to be independent of the structure. Then, the second and third parameters are always structures of `a`s that become structures of `b`s. So, the type is almost there, we just need to generalize the structure itself. And that's how we obtain the type class definition:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Functor f where\n",
+ " fmap :: (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `fmap` (or \"functor map\") function takes a function that goes from `a` to `b` and a structure `f` containing elements of type `a`, and applies the function to the elements to return the same structure `f` but with elements of type `b`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If, for some reason, having polymorphic type constructors such as `f` doesn't feel intuitive, we'll talk more about it when creating instances. But I'd still recommend you revisit lesson 10 (\"Creating Type Classes and Instances\"), where we discuss this subject in depth and define several type classes with increasing levels of complexity.\n",
+ "\n",
+ "Ok. So, we have our `Functor` type class. But this is not over. We also have the requirement to avoid `fmap` changing the structure. What would that look like, though? Well, let's implement `fmap` for lists the wrong way:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,2,3,3,4,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[1,2,3]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[1,1,1,1,2,2,2,2,3,3,3,3]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- map :: (a -> b) -> [a] -> [b]\n",
+ "-- map _ [] = []\n",
+ "-- map f (x:xs) = f x : map f xs\n",
+ "\n",
+ "wrongFmap :: (a -> b) -> [a] -> [b]\n",
+ "wrongFmap _ [] = []\n",
+ "wrongFmap f (x:xs) = f x : f x : wrongFmap f xs\n",
+ "\n",
+ "map (+1) [1,2,3]\n",
+ "wrongFmap (+1) [1,2,3]\n",
+ "\n",
+ "(map (\\x -> x - 1)) . (map (+1)) $ [1,2,3]\n",
+ "(wrongFmap (\\x -> x - 1)) . (wrongFmap (+1)) $ [1,2,3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If the structure doesn't change, we should get the original value if we apply a function and then apply the inverse. In this example, we first add one and then subtract one. In both cases, the types are correct. And we do get the original list using `map`. But because the `wrongMap` implementation concatenates each value twice, we get a completely new list instead of the original one!\n",
+ "\n",
+ "An easier way to test this, however, would be with a function that doesn't do anything. That way, if we apply `fmap` to that function, we know we should get the same result as if we didn't do anything. Haskell has that function, and it's called `id` for identity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"HHeelllloo!!\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- id x = x\n",
+ "\n",
+ "id 3 == 3\n",
+ "\n",
+ "id [1,2,3] == [1,2,3] -- Apply id to the whole list\n",
+ "\n",
+ "map id \"Hello!\" == \"Hello!\" -- Apply id to every element of the list\n",
+ "\n",
+ "wrongFmap id \"Hello!\" == \"Hello!\"\n",
+ "\n",
+ "wrongFmap id \"Hello!\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Same as with `Semigroup` and `Monoid`, since we can't enforce this property through the type system, we'll define a law using this `id` function and ask developers to pretty-please follow it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Identity law**\n",
+ "```haskell\n",
+ "fmap id == id\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This basically means: \"If you apply the identity function to every element of a structure using `fmap`, it should be the same as applying the identity function to the whole structure.\"\n",
+ "\n",
+ "Because you have to return the same value and the `id` function is only concerned about the values inside your structure, you're forced to implement `fmap` in a way that maintains the structure.\n",
+ "\n",
+ "This is it for this law. One itsy bitsy thing I didn't tell you, though, is that we have a second law. But before you feel betrayed, let me say you don't need to worry about this law! And I'll tell you why. Here's the composition law:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "**Composition law**\n",
+ "```haskell\n",
+ "fmap (f . g) == fmap f . fmap g\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This law states that if you apply the composition of two functions to the elements of a structure using `fmap`, you have to get the same result as if you `fmap` one function and then `fmap` the other.\n",
+ "\n",
+ "This is obviously an important property since we use function composition everywhere. But the cool thing is that you don't have to check for this law when defining your instance! Thanks to Haskell's type system, if you follow the identity law, you also implicitly follow the composition law!! We could prove that using equational reasoning, but it falls out of the scope of the course, so I'll leave a link in the description for the curious ones."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. So, our final type class looks like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Functor f where\n",
+ " fmap :: (a -> b) -> f a -> f b\n",
+ "\n",
+ "\n",
+ "```\n",
+ "**Identity law**\n",
+ "```haskell\n",
+ "fmap id == id\n",
+ "```\n",
+ "**Composition law**\n",
+ "```haskell\n",
+ "fmap (f . g) == fmap f . fmap g\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's it! This is what `Functor` is about. \n",
+ "\n",
+ "Some explanations present functors as containers of values. Others, as values that have context. You can think of it however you want. The bottom line is that any type that is an instance of the Functor type class and follows the Functor's laws is a `Functor`.\n",
+ "\n",
+ "And since we already have our type class, let's define some instances!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Defining `Functor` instances "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "First, let's review the type class kind:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Eq :: * -> Constraint"
+ ],
+ "text/plain": [
+ "Eq :: * -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Ord :: * -> Constraint"
+ ],
+ "text/plain": [
+ "Ord :: * -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Functor :: (* -> *) -> Constraint"
+ ],
+ "text/plain": [
+ "Functor :: (* -> *) -> Constraint"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Eq\n",
+ ":k Ord\n",
+ ":k Functor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Unlike `Eq`, `Ord`, and most other type classes we worked with before, `Functor` takes a type constructor of a kind star to star instead of a concrete type. \n",
+ "\n",
+ "This means that you can create instances of `Functor` only for type constructors that take one concrete type. Let's see how that looks in practice for the `Maybe` type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "Maybe -- * -> *\n",
+ "Maybe a -- * \n",
+ "\n",
+ "instance Eq a => Eq (Maybe a) where -- ✅\n",
+ "\n",
+ "instance Ord a => Ord (Maybe a) where -- ✅\n",
+ "\n",
+ "instance Functor a => Functor (Maybe a) where -- ❌\n",
+ "\n",
+ "instance Functor Maybe where -- ✅\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, for `Eq` and `Ord`, we need to apply `Maybe` to the type variable `a` to get the correct kind. But it is not necessary for `Functor`.\n",
+ "\n",
+ "Same with lists:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "[] -- * -> *\n",
+ "[a] -- * \n",
+ "\n",
+ "instance Eq a => Eq [a] where -- ✅\n",
+ " ...\n",
+ "\n",
+ "instance Ord a => Ord [a] where -- ✅\n",
+ " ...\n",
+ "\n",
+ "instance Functor a => Functor [a] where -- ❌\n",
+ " ...\n",
+ " \n",
+ "instance Functor [] where -- ✅\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And, as we saw on lesson 10, if you have a constructor that requires more than one concrete type, you can partially apply it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data Present t a = Empty t | PresentFor t a\n",
+ "\n",
+ "Present -- * -> * -> *\n",
+ "Present t -- * -> *\n",
+ "Present t a -- *\n",
+ "\n",
+ "-----------------------------------------------------------------------\n",
+ "\n",
+ "instance (Eq t, Eq a) => Eq (Present t a) where -- ✅\n",
+ " ...\n",
+ "\n",
+ "instance (Ord t, Ord a) => Ord (Present t a) where -- ✅\n",
+ " ...\n",
+ "\n",
+ "instance (Functor t, Functor a) => Functor (Present t a) where -- ❌\n",
+ " ...\n",
+ "\n",
+ "instance Functor (Present t) where -- ✅\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If you're not 100% clear on this, revisit lesson 10. If you are, let's define some instances!:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- class Functor f where\n",
+ "-- fmap :: (a -> b) -> f a -> f b\n",
+ " \n",
+ " \n",
+ "instance Functor [] where\n",
+ " -- fmap :: (a -> b) -> [a] -> [b]\n",
+ " fmap _ [] = []\n",
+ " fmap f (x:xs) = f x : fmap f xs\n",
+ "\n",
+ "\n",
+ "instance Functor Maybe where\n",
+ " -- fmap :: (a -> b) -> Maybe a -> Maybe b\n",
+ " fmap _ Nothing = Nothing\n",
+ " fmap f (Just x) = Just (f x)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These are the `Functor` instances for lists and `Maybe` types. I added the specialized type as a comment to help you visualize what's going on.\n",
+ "\n",
+ "We don't run this cell because these instances are already present in the Prelude. But we can create one that it isn't:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Functor Tree where\n",
+ " -- fmap :: (a -> b) -> Tree a -> Tree b\n",
+ " fmap f (Leaf x) = Leaf (f x)\n",
+ " fmap f (Node lt x rt) = Node (fmap f lt) (f x) (fmap f rt)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, the implementations are the same functions we previously had implemented as separate versions of `map`. So, no surprise there.\n",
+ "\n",
+ "We, however, now want to check if they work as expected and follow the identity law, so let's do that:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"010\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just '1'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf '0') '1' (Node (Leaf '0') '1' (Leaf '1'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "boolToBit :: Bool -> Char\n",
+ "boolToBit x = if x then '1' else '0'\n",
+ "\n",
+ "exampleTree = Node (Leaf False) True (Node (Leaf False) True (Leaf True))\n",
+ "\n",
+ "fmap boolToBit [False,True,False]\n",
+ "\n",
+ "fmap boolToBit (Just True)\n",
+ "\n",
+ "fmap boolToBit exampleTree\n",
+ "\n",
+ "fmap id [1,2,3] == id [1,2,3]\n",
+ "\n",
+ "fmap id (Just 'c') == id (Just 'c')\n",
+ "\n",
+ "fmap id exampleTree == id exampleTree"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And those were our first three Functors in action. If you made it up until here, you learned what Functors are. But stay with me because there are a few practical aspects that you should really know about."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Seemingly unintuitive `Functor` instances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A few `Functor` instances seem unintuitive at first, and many new Haskell developers struggle to make sense of them. As part of my effort to give you the whole picture, let's explore them."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The `Either a` functor 🤔"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `Either` type constructor that takes two concrete types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Either :: * -> * -> *"
+ ],
+ "text/plain": [
+ "Either :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Either String :: * -> *"
+ ],
+ "text/plain": [
+ "Either String :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k Either\n",
+ ":k Either String"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, to create a `Functor` instance, we have to partially apply one type variable:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor (Either a) where\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We could interpret this as the error value of type `a` being (quote and quote) \"part of the structure\" of the functor. This means that values of type `a` should be kept untouched since `fmap` has to preserve the structure.\n",
+ "\n",
+ "So, the instance of `Functor` for `Either a` is:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor (Either a) where\n",
+ " fmap _ (Left x) = Left x\n",
+ " fmap f (Right y) = Right (f y)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Which is virtually the same instance as `Maybe`. We ignore the failure case and only apply the function in the success scenario.\n",
+ "\n",
+ "Here are some examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Left 1"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Right 2"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Left 'A'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fmap (+1) (Left 1)\n",
+ "\n",
+ "fmap (+1) (Right 1)\n",
+ "\n",
+ "fmap toLower (Left 'A')\n",
+ "\n",
+ "fmap id (Left 'A') == id (Left 'A')\n",
+ "\n",
+ "fmap id (Right 'A') == id (Right 'A')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Wasn't that hard, right? Let's keep going."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The `(,) a` functor 🤨"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These are tuples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "('c', True) :: (Char, Bool)"
+ ],
+ "text/plain": [
+ "('c', True) :: (Char, Bool)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(True, 'c') :: (Bool, Char)"
+ ],
+ "text/plain": [
+ "(True, 'c') :: (Bool, Char)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "('c', True, 1 :: Int) :: (Char, Bool, Int)"
+ ],
+ "text/plain": [
+ "('c', True, 1 :: Int) :: (Char, Bool, Int)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t ('c', True)\n",
+ "\n",
+ ":t (True, 'c')\n",
+ "\n",
+ ":t ('c', True, 1 :: Int)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We already discussed in a previous lesson that the type of the tuple, unlike lists, depends on the amount and order of its values. If we ask the kind of all those tuples, of course, we get concrete types:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(Char, Bool) :: *"
+ ],
+ "text/plain": [
+ "(Char, Bool) :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(Bool, Char) :: *"
+ ],
+ "text/plain": [
+ "(Bool, Char) :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(Char, Bool, Int) :: *"
+ ],
+ "text/plain": [
+ "(Char, Bool, Int) :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "[Int] :: *"
+ ],
+ "text/plain": [
+ "[Int] :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "[] :: * -> *"
+ ],
+ "text/plain": [
+ "[] :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k (Char, Bool)\n",
+ ":k (Bool, Char)\n",
+ ":k (Char, Bool, Int)\n",
+ "\n",
+ ":k [Int]\n",
+ ":k []"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But these types are just parentheses with commas that surround other types. So, could we remove the types to get a type constructor like we do with lists? Yes, we can! Because parentheses with commas are value constructors at the value level and type constructors at the type level."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(,) :: * -> * -> *"
+ ],
+ "text/plain": [
+ "(,) :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(,,) :: * -> * -> * -> *"
+ ],
+ "text/plain": [
+ "(,,) :: * -> * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(,) Int :: * -> *"
+ ],
+ "text/plain": [
+ "(,) Int :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(,,) String Bool :: * -> *"
+ ],
+ "text/plain": [
+ "(,,) String Bool :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k (,)\n",
+ ":k (,,)\n",
+ "\n",
+ "\n",
+ ":k (,) Int -- :k (a,) doesn't work. No sugar for tuples.\n",
+ ":k (,,) String Bool "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These are just type constructors that take other types to create a concrete type. Tuples don't have the nice syntactic sugar that lists have, so we have to write first the constructor and then the type variables instead of having the type variables inside.\n",
+ " \n",
+ "Based on this, we can create `Functor` instances for tuples as long as we partially apply type variables to get a kind star to star:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "\n",
+ "instance Functor ((,) a) where\n",
+ " ...\n",
+ "\n",
+ "instance Functor ((,,) a b) where\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, we have our types. How should we implement these instances? Well, we don't have much of a choice. For the pair, the first value of type `a` is part of the structure we can't touch, and for the tuple of three values, the first two values of type `a` and `b` are part of the structure. So, we can't touch either of those.\n",
+ "\n",
+ "At the end of the day, the most obvious and straightforward definitions would be these ones:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor ((,) a) where\n",
+ " fmap f (x,y) = (x, f y)\n",
+ "\n",
+ "\n",
+ "instance Functor ((,,) a b) where\n",
+ " fmap f (x,y,z) = (x, y, f z)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is sometimes unintuitive because you would assume that we should apply the function to all values, the same as lists. But the key here is to think in terms of value and structure.\n",
+ "\n",
+ "The values of the lists are all the elements it contains, and the structure is the order and number of elements.\n",
+ "\n",
+ "The case of tuples is different. For tuples, all the values except for the last one are part of the structure, as evidenced by the type we create the instance for. So, the only thing we can modify is the one value we don't provide a type variable for.\n",
+ "\n",
+ "Let's test our instances:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1,2)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "('a',2)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "(True,'a',4)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "('A','b')"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "(1,'b')"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "(2,True,'a')"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fmap (+1) (1,1)\n",
+ "\n",
+ "fmap (+1) ('a',1)\n",
+ "\n",
+ "fmap (*2) (True,'a',2)\n",
+ "\n",
+ "fmap toLower ('A','B')\n",
+ "\n",
+ "fmap toLower (1,'B')\n",
+ "\n",
+ "fmap toLower (2, True, 'A')\n",
+ "\n",
+ "fmap id ('a',1 ) == id ('a',1 )\n",
+ "\n",
+ "fmap id (2, True, 'A') == id (2, True, 'A')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The `(->) r` functor 🤯"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This might blow your mind, but I have to say it... functions are functors! Hear me out. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(Char, Bool) :: *"
+ ],
+ "text/plain": [
+ "(Char, Bool) :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(,) :: * -> * -> *"
+ ],
+ "text/plain": [
+ "(,) :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "biggerThan3 :: Int -> Bool"
+ ],
+ "text/plain": [
+ "biggerThan3 :: Int -> Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(Int -> Bool) :: *"
+ ],
+ "text/plain": [
+ "(Int -> Bool) :: *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(->) :: * -> * -> *"
+ ],
+ "text/plain": [
+ "(->) :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k (Char, Bool)\n",
+ ":k (,)\n",
+ "\n",
+ "biggerThan3 :: Int -> Bool\n",
+ "biggerThan3 = (>3)\n",
+ "\n",
+ ":t biggerThan3\n",
+ ":k (Int -> Bool)\n",
+ "\n",
+ ":k (->)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The arrow we use to define the type signatures of functions is a type constructor. This type constructor takes two concrete types and returns a concrete type. \n",
+ "\n",
+ "Same as when we give two types like `Char` and `Bool` to the pair type constructor to get the type of a pair containing a character and a boolean if we give two types like `Int` and `Bool` to the arrow type constructor, we get the type of a function that takes an `Int` and returns a `Bool`. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, guess what. We can do with the arrow constructor the same we did for pairs to create the `Functor` instance. We'll partially apply a type variable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "(,) :: * -> * -> *"
+ ],
+ "text/plain": [
+ "(,) :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(,) Int :: * -> *"
+ ],
+ "text/plain": [
+ "(,) Int :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(->) :: * -> * -> *"
+ ],
+ "text/plain": [
+ "(->) :: * -> * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(->) Int :: * -> *"
+ ],
+ "text/plain": [
+ "(->) Int :: * -> *"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":k (,) -- ❌\n",
+ ":k (,) Int -- Functastic! ✅\n",
+ "\n",
+ ":k (->) -- ❌\n",
+ ":k (->) Int -- Functastic! ✅"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We also don't have syntactic sugar in this case. So, our `Functor` instance would start like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor ((->) r) where\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Remember that the `r` is the first type we apply the constructor to. So, it's the input type of the function.\n",
+ "\n",
+ "Now, I'm aware this type is a bit overwhelming the first time you encounter it, so let's work our way through it. This is the type of `fmap`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "fmap :: (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In this particular instance of `fmap`, the Functor `f` is the arrow applied to a type variable:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "fmap :: (a -> b) -> (->) r a -> (->) r b -- Specialize f to (->) r\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Haskell has syntactic sugar to show the arrow type in between the types as an infix function. That's how we've always used it. So let's change that:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "fmap :: (a -> b) -> (r -> a) -> (r -> b) -- (->) r a = (r -> a) \n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's the type of `fmap` for this particular instance. \n",
+ "\n",
+ "Now, we have to figure out the actual implementation. Both arguments are functions, so let's name them `f` and `g`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor ((->) r) where\n",
+ " -- fmap :: (a -> b) -> (r -> a) -> (r -> b)\n",
+ " fmap f g = ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. So, as always, we need to apply the function `f` to `g` without changing the functor's structure.\n",
+ "\n",
+ "In this case, the structure is arrow `r` (`(->) r`), which means that before applying `fmap`, the function takes a value of type `r`, so, after `fmap`, the function has to still take a value of type `r`, as shown in the resulting value. So, the only thing that changes is the value returned by the function. Before `fmap` is a value of type `a`, and after is a value of type `b`.\n",
+ "\n",
+ "I'm pretty sure you could guess only by looking at the types. We need a function that takes `r` and returns `b`. And we have to obtain it using a function that takes `r` and returns `a` and a function that takes an `a` and returns a `b`. So, we compose them:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor ((->) r) where\n",
+ " -- fmap :: (a -> b) -> (r -> a) -> (r -> b)\n",
+ " fmap f g = f . g\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's it. That's the instance. We can write it more succinctly, though. If we follow this series of simple transformations:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "fmap :: (a -> b) -> (r -> a) -> (r -> b)\n",
+ "fmap f g = f . g\n",
+ "fmap f g = (.) f g -- Move `.` as prefix\n",
+ "fmap f = (.) f -- Rmv `g` from both sides\n",
+ "fmap = (.) -- Rmv `f` from both sides\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We get that the `Functor` instance for functions is actually just function composition:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor ((->) r) where\n",
+ " -- fmap :: (a -> b) -> (r -> a) -> (r -> b)\n",
+ " fmap = (.)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And now, let's try it out and test for the laws. Take into account that because we can not print functions to the console, I'll have to apply the function to some random value:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "value1 :: Int -> Int"
+ ],
+ "text/plain": [
+ "value1 :: Int -> Int"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "6"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(fmap (>4) value1) :: Int -> Bool"
+ ],
+ "text/plain": [
+ "(fmap (>4) value1) :: Int -> Bool"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "value1 :: Int -> Int\n",
+ "value1 = (*2)\n",
+ "\n",
+ ":t value1\n",
+ "value1 3\n",
+ "\n",
+ ":t (fmap (>4) value1)\n",
+ "(fmap (>4) value1) 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "value2 :: Bool -> Char"
+ ],
+ "text/plain": [
+ "value2 :: Bool -> Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'1'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(fmap succ value2) :: Bool -> Char"
+ ],
+ "text/plain": [
+ "(fmap succ value2) :: Bool -> Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'2'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "value2 :: Bool -> Char -- boolToBit function\n",
+ "value2 x = if x then '1' else '0'\n",
+ "\n",
+ ":t value2\n",
+ "value2 True\n",
+ "\n",
+ ":t (fmap succ value2)\n",
+ "(fmap succ value2) True"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(fmap id value1) 5 == (id value1) 5\n",
+ "\n",
+ "(fmap id value2) False == (id value2) False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It works!! \n",
+ "\n",
+ "It's ok if you don't fully understand. This is one of the more abstract `Functor` instances you'll encounter. And, at the end of the day, if you remember that `fmap` for functions is function composition, you'll be fine.\n",
+ "\n",
+ "Ok! That was the last instance we'll create today. But there are still a few other things about using `Functors` that we have to take a look at."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Defining `<$>` and *lifting* 🏋️ a function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`fmap` takes only two arguments, so we can easily use it as an infix function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 'b') 'a' (Node (Leaf 'd') 'c' (Leaf 'e'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 'b') 'a' (Node (Leaf 'd') 'c' (Leaf 'e'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"1010\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"1010\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,4,6]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,4,6]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fmap toLower (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))\n",
+ "toLower `fmap` (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))\n",
+ "\n",
+ "\n",
+ "fmap (+1) (Just 3)\n",
+ "(+1) `fmap` (Just 3)\n",
+ "\n",
+ "\n",
+ "fmap (\\x -> if x then '1' else '0') [True,False,True,False]\n",
+ "(\\x -> if x then '1' else '0') `fmap` [True,False,True,False]\n",
+ "\n",
+ "\n",
+ "fmap (*2) [1,2,3]\n",
+ "(*2) `fmap` [1,2,3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And, it turns out, it's usually more convenient to apply `fmap` as an infix function. Luckily, the `base` library has us covered, and we have this beauty:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<$>) :: Functor f => (a -> b) -> f a -> f b\n",
+ "(<$>) = fmap\n",
+ "\n",
+ "infixl 1 <&>\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Notice that we have to constrain the `f` type constructor as an instance of the `Functor` type class because we're using `fmap` in the definition.\n",
+ "\n",
+ "Using this infix synonym for `fmap`, we can write the previous code like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 'b') 'a' (Node (Leaf 'd') 'c' (Leaf 'e'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 'b') 'a' (Node (Leaf 'd') 'c' (Leaf 'e'))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"1010\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"1010\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,4,6]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,4,6]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "toLower `fmap` (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))\n",
+ "toLower <$> (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))\n",
+ "\n",
+ "\n",
+ "(+1) `fmap` (Just 3)\n",
+ "(+1) <$> (Just 3)\n",
+ "\n",
+ "\n",
+ "(\\x -> if x then '1' else '0') `fmap` [True,False,True,False]\n",
+ "(\\x -> if x then '1' else '0') <$> [True,False,True,False]\n",
+ "\n",
+ "\n",
+ "(*2) `fmap` [1,2,3]\n",
+ "(*2) <$> [1,2,3]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's how you use it.\n",
+ "\n",
+ "Now, there's a reason as to why this operator has a dollar sign in the middle. This is an allusion to function application. If we look at the types:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ " ($) :: (a -> b) -> a -> b\n",
+ "(<$>) :: Functor f => (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We see that it looks quite familiar. Not only type-wise but conceptually as well. We know from lesson 5 that functions are right-associative, so we can surround the two types to the right, and it would be the same as not having them:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ " ($) :: (a -> b) -> ( a -> b)\n",
+ "(<$>) :: Functor f => (a -> b) -> (f a -> f b)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Looking at this, we see that the function application operator takes a function `(a -> b)` and returns the same function `(a -> b)` with no change. Which we already knew. We use this operator only because it's right-associative. Allowing us to remove the parenthesis.\n",
+ "\n",
+ "But, if we look at the fmap operator (`<$>`), we see that it takes a function `(a -> b)` and returns the same function, but that now works for the functor version of `a` and `b`. This is what we call \"lifting\" a function. We say that the fmap operator (`<$>`) lifts the function `(a -> b)` to be able to work at the `f` level.\n",
+ "\n",
+ "Just in case it didn't click, let's see two examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "toLower :: Char -> Char"
+ ],
+ "text/plain": [
+ "toLower :: Char -> Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(toLower <$>) :: forall (f :: * -> *). Functor f => f Char -> f Char"
+ ],
+ "text/plain": [
+ "(toLower <$>) :: forall (f :: * -> *). Functor f => f Char -> f Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "boolToBit :: Bool -> Char"
+ ],
+ "text/plain": [
+ "boolToBit :: Bool -> Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'0'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(boolToBit <$>) :: forall (f :: * -> *). Functor f => f Bool -> f Char"
+ ],
+ "text/plain": [
+ "(boolToBit <$>) :: forall (f :: * -> *). Functor f => f Bool -> f Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"0\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t toLower -- Type of original function\n",
+ "toLower 'A'\n",
+ "\n",
+ ":t (toLower <$>) -- Type of lifted function\n",
+ "toLower <$> Just 'A'\n",
+ "\n",
+ "\n",
+ "boolToBit :: Bool -> Char\n",
+ "boolToBit x = if x then '1' else '0'\n",
+ "\n",
+ ":t boolToBit -- Type of original function\n",
+ "boolToBit False\n",
+ "\n",
+ ":t (boolToBit <$>) -- Type of lifted function\n",
+ "boolToBit <$> [False]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And now that we have this new frame of looking at `fmap` and its infix synonym, we can easily understand how to deal with nested functors."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## `Functor` nesting dolls 🪆"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Nested functors are pretty common in Haskell because we combine types a lot. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "value1 :: Either String Bool\n",
+ "value1 = Right False\n",
+ "\n",
+ "value2 :: [Either String Bool]\n",
+ "value2 = [Left \"error\", Right True, Right False]\n",
+ "\n",
+ "value3 :: Maybe [Either String Bool]\n",
+ "value3 = Just [Left \"error\", Right True, Right False]\n",
+ "\n",
+ "value4 :: Tree (Maybe [Either String Bool])\n",
+ "value4 = Leaf $ Just [Left \"error\", Right True, Right False]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "All these values are nested functors. The number at the end of the name indicates the levels of nesting. `value4`, for example, has 4 levels of nesting: The `Either a` functor is inside the list functor, which is inside the `Mabye` functor, which is inside the `Tree` functor.\n",
+ "\n",
+ "If you notice, all `values` have booleans at their core. So, if we want to modify them, we need to map a function that takes booleans as inputs. We already have `boolToBit`, so let's use that one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Right '0'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "boolToBit :: Bool -> Char\n",
+ "boolToBit x = if x then '1' else '0'\n",
+ "\n",
+ "fmap boolToBit value1 -- value1 :: Either String Bool"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So far, so good. Now, if we try to do the same with `value2`: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ ":1:16: error:\n • Couldn't match type ‘Either String Bool’ with ‘Bool’\n Expected type: [Bool]\n Actual type: [Either String Bool]\n • In the second argument of ‘fmap’, namely ‘value2’\n In the expression: fmap boolToBit value2\n In an equation for ‘it’: it = fmap boolToBit value2"
+ ]
+ }
+ ],
+ "source": [
+ "fmap boolToBit value2 -- value2 :: [Either String Bool]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, we'll get an error because we're trying to apply a function that takes a boolean to an `Either String Bool`. The solution is deceptively simple. If you have two functor levels, you have to lift your function twice. Once to reach the list level and another one to reach the `Either` level:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "boolToBit :: Bool -> Char"
+ ],
+ "text/plain": [
+ "boolToBit :: Bool -> Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "(fmap boolToBit) :: forall (f :: * -> *). Functor f => f Bool -> f Char"
+ ],
+ "text/plain": [
+ "(fmap boolToBit) :: forall (f :: * -> *). Functor f => f Bool -> f Char"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "((fmap . fmap) boolToBit) :: forall (f1 :: * -> *) (f2 :: * -> *). (Functor f1, Functor f2) => f1 (f2 Bool) -> f1 (f2 Char)"
+ ],
+ "text/plain": [
+ "((fmap . fmap) boolToBit) :: forall (f1 :: * -> *) (f2 :: * -> *). (Functor f1, Functor f2) => f1 (f2 Bool) -> f1 (f2 Char)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[Left \"error\",Right '1',Right '0']"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ":t boolToBit\n",
+ ":t (fmap boolToBit)\n",
+ ":t ((fmap . fmap) boolToBit)\n",
+ "\n",
+ "(fmap . fmap) boolToBit value2 -- value2 :: [Either String Bool]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And you can do it as many times as needed:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just [Left \"error\",Right '1',Right '0']"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Leaf (Just [Left \"error\",Right '1',Right '0'])"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(fmap . fmap . fmap) boolToBit value3 -- value3 :: Maybe [Either String Bool]\n",
+ "\n",
+ "(fmap . fmap . fmap . fmap) boolToBit value4 -- value4 :: Tree (Maybe [Either String Bool])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I hope that makes it easier to understand the concept of lifting. If you're still fuzzy about it, work through the types, and it'll make sense.\n",
+ "\n",
+ "Now, as a final chapter, we'll see what other functions we get for free when we create an instance of functor."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Extra functions and `Functor` as defined in `base`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now that we have the `Functor` type class, if we create functions that depend solely on `fmap`, we'll get functions that work for every `Functor`. For example, take a look at this unzip function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(\"abcd\",[2,4,6,8])"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "unzip' :: [(a, b)] -> ([a], [b])\n",
+ "unzip' l = (map fst l, map snd l)\n",
+ "\n",
+ "\n",
+ "unzip' [('a', 2), ('b', 4), ('c', 6), ('d', 8)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `unzip` function takes a list of tuples and returns a tuple containing one list with all the first elements and another with all the second elements. This function can be used (for example) to separate keys and values from key-value pairs.\n",
+ "\n",
+ "Now, if we look at the definition, we use three functions. The `fst` and `snd` functions work on pairs. And `map` also works only on lists. But we have `fmap` now, a more general version of `map`. So, we can change the type from lists to `Functors` and use `fmap` instead:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(\"abcd\",[2,4,6,8])"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "(Just 'a',Just 2)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "(Node (Leaf 'a') 'b' (Leaf 'c'),Node (Leaf 2) 4 (Leaf 6))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "unzipf :: Functor f => f (a, b) -> (f a, f b)\n",
+ "unzipf xs = (fst <$> xs, snd <$> xs)\n",
+ "\n",
+ "\n",
+ "unzipf [('a', 2), ('b', 4), ('c', 6), ('d', 8)]\n",
+ "\n",
+ "unzipf (Just ('a', 2))\n",
+ "\n",
+ "unzipf $ Node (Leaf ('a', 2)) ('b', 4) (Leaf ('c', 6))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now we have an `unzip` function that works for every `Functor`.\n",
+ "\n",
+ "That was one example, but we have a few more. Here are all the functions that come for free in the `base` library after you create an instance of `Functor`. Starting with the flipped `fmap` operator:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[2,3,4]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Right 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(<&>) :: Functor f => f a -> (a -> b) -> f b \n",
+ "as <&> f = f <$> as -- Flipped version of '<$>'.\n",
+ "\n",
+ "\n",
+ "((+1) <$> (Just 2)) == (Just 2) <&> (+1)\n",
+ "\n",
+ "\n",
+ "[1,2,3] <&> (+1)\n",
+ "\n",
+ "\n",
+ "Right 3 <&> (+1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We have a version of the map infix operator that takes the arguments flipped. First, the functor, and second, the function. This could be useful (for example) when you define a long inline function. If your function is several lines long, having the functor at the end makes it more difficult to spot it. So you can use this instead and provide the functor first.\n",
+ "\n",
+ "Another one that comes for free is the \"const fmap\" operator :"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<$) :: a -> f b -> f a\n",
+ "(<$) = fmap . const -- Replace all locations with the same value\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"aaaaaa\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf 3) 3 (Node (Leaf 3) 3 (Leaf 3))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "'a' <$ Just 2\n",
+ "\n",
+ "'a' <$ Nothing\n",
+ "\n",
+ "'a' <$ [1,2,3,4,5,6]\n",
+ "\n",
+ "3 <$ (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, we completely ignore the value already present in the functor and replace it with the value we provide as the first argument.\n",
+ "\n",
+ "We also have a flipped version of this one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 'a'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"aaaaaa\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"aaaaaa\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "($>) :: Functor f => f a -> b -> f b\n",
+ "($>) = flip (<$) -- Flipped version of '<$'.\n",
+ "\n",
+ "'a' <$ Just 2\n",
+ "\n",
+ "Just 2 $> 'a'\n",
+ "\n",
+ "\n",
+ "'a' <$ [1,2,3,4,5,6]\n",
+ "\n",
+ "[1,2,3,4,5,6] $> 'a'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, they are like arrows pointing to the value you'll get. This makes it easy to know the direction of the operator.\n",
+ "\n",
+ "And finally, we have the `void` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just ()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[(),(),(),(),(),()]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Node (Leaf ()) () (Node (Leaf ()) () (Leaf ()))"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "void :: Functor f => f a -> f ()\n",
+ "void x = () <$ x -- Discard or ignore the result of evaluation\n",
+ "\n",
+ "\n",
+ "void (Just 2)\n",
+ "\n",
+ "void [1,2,3,4,5,6]\n",
+ "\n",
+ "void (Node (Leaf 'B') 'A' (Node (Leaf 'D') 'C'(Leaf 'E')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This one becomes handy when dealing with side effects. If the functor has side effects, you can map through it to trigger them. And if you only care about those side effects but not the result, you can use `void` to ignore them. We'll talk more about this in the future. Don't worry too much about it for now.\n",
+ "\n",
+ "So, after all this work, these are all the behaviors we get when defining an instance of `Functor`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Functor f where\n",
+ " fmap :: (a -> b) -> f a -> f b\n",
+ " \n",
+ " (<$) :: a -> f b -> f a\n",
+ " (<$) = fmap . const -- Replace with constant value.\n",
+ " {-# MINIMAL fmap #-}\n",
+ " \n",
+ "\n",
+ "(<$>) :: Functor f => (a -> b) -> f a -> f b\n",
+ "(<$>) = fmap -- An infix synonym for 'fmap'.\n",
+ "\n",
+ "(<&>) :: Functor f => f a -> (a -> b) -> f b \n",
+ "as <&> f = f <$> as -- Flipped version of '<$>'.\n",
+ "\n",
+ "($>) :: Functor f => f a -> b -> f b\n",
+ "($>) = flip (<$) -- Flipped version of '<$'.\n",
+ "\n",
+ "unzip :: Functor f => f (a, b) -> (f a, f b)\n",
+ "unzip xs = (fst <$> xs, snd <$> xs) -- Generalization of List's unzip\n",
+ "\n",
+ "void :: Functor f => f a -> f ()\n",
+ "void x = () <$ x -- Discard or ignore the result of evaluation\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If we look at how `Functor` is defined in the `base` library, we'll see that one of the functions we just talked about is part of the type class, and the rest is not. We don't really care about that. They are all derived from `fmap`, so we still get all of them for free when defining the instance. The only real difference in your day-to-day is that you'll have to import `Data.Functor` to get the ones that are not part of the type class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# That's it for today! 😃"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And that's it for today! I know it's a lot to take in, so feel free to revisit the lesson as many times as needed. Crucially, to genuinely understand it, you'll have to code it yourself. So, make sure to do your homework, and I'll see you in the next one!!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/lessons/19-Applicative.ipynb b/lessons/19-Applicative.ipynb
new file mode 100644
index 00000000..f9018194
--- /dev/null
+++ b/lessons/19-Applicative.ipynb
@@ -0,0 +1,3133 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ ":opt no-lint"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Applicative Functor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Outline\n",
+ "\n",
+ "- Why `Applicative` functors?\n",
+ " - Our journey until now\n",
+ " - The limits of Functor\n",
+ "- Function application at the `Functor` level 🥇\n",
+ "- Being `pure` 😇\n",
+ "- The `Applicative` type class\n",
+ "- The `Applicative` laws\n",
+ "- 🎆 Programming with effects 🎆\n",
+ "- Extra functions and `Applicative` as defined in `base`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Why `Applicative` functors?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "### Our journey until now"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We need Applicative Functors (or Applicatives for short) due to a limitation of regular `Functors`. To provide some context, let's recap our abstraction journey for `map` and `Functor`.\n",
+ "\n",
+ "We started with a bunch of recursive functions:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "lowerString :: [Char] -> [Char]\n",
+ "lowerString [] = []\n",
+ "lowerString (x:xs) = toLower x : lowerString xs\n",
+ "\n",
+ "addOne :: Num a => [a] -> [a]\n",
+ "addOne [] = []\n",
+ "addOne (x:xs) = (x + 1) : addOne xs\n",
+ "\n",
+ "boolToBit :: [Bool] -> [Char]\n",
+ "boolToBit [] = []\n",
+ "boolToBit (x:xs) = (if x then '1' else '0') : boolToBit xs\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These functions were useful. However, they were limited to applying a specific function to a list of specific types. However, we noticed that they had common traits that we could extract, and we got the `map` function:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "map :: (a -> b) -> [a] -> [b]\n",
+ "map _ [] = []\n",
+ "map f (x:xs) = f x : map f xs\n",
+ "\n",
+ "\n",
+ "lowerString = map toLower\n",
+ "\n",
+ "addOne = map (+1)\n",
+ "\n",
+ "boolToBit = map (\\x -> if x then '1' else '0')\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `map` function is a more powerful version of these functions because it's more general. Now, we can apply any function to a list of values. And because it's more general, we can recreate the original functions by passing the concrete function we want to apply to the values.\n",
+ "\n",
+ "But then, we faced a problem. `map` only works for lists. But there are plenty of structures with values we want to manipulate. If we implement their own map-like functions:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "map :: (a -> b) -> [a] -> [b]\n",
+ "map _ [] = []\n",
+ "map f (x:xs) = f x : map f xs\n",
+ "\n",
+ "\n",
+ "\n",
+ "maybeMap :: (a -> b) -> Maybe a -> Maybe b\n",
+ "maybeMap _ Nothing = Nothing\n",
+ "maybeMap f (Just x) = Just (f x)\n",
+ "\n",
+ "\n",
+ "\n",
+ "treeMap :: (a -> b) -> Tree a -> Tree b\n",
+ "treeMap f (Leaf x) = Leaf (f x)\n",
+ "treeMap f (Node lt x rt) = Node (treeMap f lt) (f x) (treeMap f rt)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We realize that these functions were useful. However, they were limited to specific structures. However, they had common traits that we could extract. And that's how we got the `Functor` type class:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "A `Functor` is a type that can apply a function to the values of its structure without modifying the structure itself.\n",
+ "\n",
+ "```haskell\n",
+ "class Functor f where\n",
+ " fmap :: (a -> b) -> f a -> f b\n",
+ " \n",
+ " (<$) :: a -> f b -> f a\n",
+ " (<$) = fmap . const\n",
+ " {-# MINIMAL fmap #-}\n",
+ "```\n",
+ "**Identity law**\n",
+ "```haskell\n",
+ "fmap id == id\n",
+ "```\n",
+ "**Composition law**\n",
+ "```haskell\n",
+ "fmap (f . g) == fmap f . fmap g\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `Functor` type class is a more powerful version of those functions because it's more general. Now, we can apply any function to any structure that is an instance of `Functor`. And, of course, we can obtain what we had before (and more) by creating the instances: "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor [] where\n",
+ " -- fmap :: (a -> b) -> [a] -> [b]\n",
+ " fmap _ [] = []\n",
+ " fmap f (x:xs) = f x : fmap f xs\n",
+ "\n",
+ "\n",
+ "instance Functor Maybe where\n",
+ " -- fmap :: (a -> b) -> Maybe a -> Maybe b\n",
+ " fmap _ Nothing = Nothing\n",
+ " fmap f (Just x) = Just (f x)\n",
+ " \n",
+ " \n",
+ "instance Functor Tree where\n",
+ " -- fmap :: (a -> b) -> Tree a -> Tree b\n",
+ " fmap f (Leaf x) = Leaf (f x)\n",
+ " fmap f (Node lt x rt) = Node (fmap f lt) (f x) (fmap f rt)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, every time we extracted a more general expression, we got a more powerful abstraction. But there are cases when `Functor` is not enough."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The limits of `Functor`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's try applying a few functions to a `Functor`. Let's say the `Maybe` functor: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ ":t (+1) -- Int -> Int\n",
+ ":t (+1) <$> Just 3 -- Maybe Int\n",
+ "(+1) <$> Just 3\n",
+ "\n",
+ "-----------------------------------------------------------------\n",
+ "\n",
+ "-- Add (Just 3) and (Just 2)?\n",
+ ":t (+) -- Int -> Int -> Int\n",
+ ":t (+) <$> Just 3 -- Maybe (Int -> Int)\n",
+ "\n",
+ "-----------------------------------------------------------------\n",
+ "\n",
+ "almostThere = (<$> Just 3) <$> ((+) <$> Just 2) \n",
+ "\n",
+ ":t almostThere -- Maybe (Maybe Int)\n",
+ "almostThere"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see with the first example, we have no issue \"fmapping\" the function +1 to Just 3. But what if we want to add two Functors? If we check the result of applying the plus operator to Just 3, we get a function at the Functor level. We are not prepared for this!\n",
+ "\n",
+ "We can use fmap to take regular functions and apply them to Functors. But we don't have a way to apply functions that already are at the Functor level to other Functors. In the last example, we did some juggling to get almost what we needed. We managed to add the values, but we ended up with duplicated Functors."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The worst thing is that this is a common scenario. We may want to add two optional values that we retrieved from our database, or maybe we want to apply a function to combine 3 Functors into one. It's more common to work with multiple Functors than with a single one. So, we need to solve this. \n",
+ "\n",
+ "One solution would be to create other versions of `fmap` that can handle a function of two arguments. For example, like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "fmap2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c\n",
+ "fmap2 f (Just a) (Just b) = Just (f a b)\n",
+ "fmap2 _ _ _ = Nothing\n",
+ "\n",
+ "\n",
+ "\n",
+ "fmap2 (+) (Just 3) (Just 2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We take a binary function and two `Maybe` Functors. If both values are `Just`, we apply the binary function to the values and wrap it again, else, we return nothing because, if we are missing one or both values, we have no way of returning a valid result.\n",
+ "\n",
+ "That would solve the problem. So, in theory, we could create a type class called, for example:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Functor2 where\n",
+ " fmap2 :: (a -> b -> c) -> f a -> f b -> f c\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But that only works for binary functions. If we need to use a function that takes three, four, or five arguments, we would need to create:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "class Functor3 where\n",
+ " fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c -> f d\n",
+ " \n",
+ "\n",
+ "class Functor4 where\n",
+ " fmap4 :: (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e\n",
+ "\n",
+ "\n",
+ "class Functor5 where\n",
+ " fmap5 :: ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "You know where we're going with this. This method is unsustainable. It doesn't matter how many type classes you create; there's always a use case that requires more parameters. And, on top of that, we would have maybe dozens of type classes representing the same concept. \n",
+ "\n",
+ "But don't worry, thanks to currying, there's a better way!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Function application at the `Functor` level 🥇"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is the key idea of the lesson. If you understand this section, the rest of the lecture will flow naturally."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. So, we know that writing a `fmap` for every possible number of arguments is not feasible. But we don't have to. If we take a look at Haskell's function application:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ " :: (a -> b) -> a -> b\n",
+ "($) :: (a -> b) -> a -> b\n",
+ "\n",
+ "result = (+1) 3 -- 4\n",
+ "-- ^\n",
+ "-- |\n",
+ "-- Function application\n",
+ "-- |\n",
+ "-- ⌄\n",
+ "result = (+1) $ 3 -- 4\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It takes a function that goes from `a` to `b` and a value of type `a` and returns a value of type `b`. It seems as if we could only apply a function of one argument to a value, but we know that in practice, we can do this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "calculate :: Int -> Int -> Int -> Int\n",
+ "calculate a b c = a + b * c\n",
+ "\n",
+ "calculate 3 6 4\n",
+ "-- ^ ^ ^\n",
+ "-- | | |\n",
+ "-- Function application"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, how does this work? It's quite simple. Let's see it step by step:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "($) :: (a -> b) -> a -> b\n",
+ "calculate :: Int -> Int -> Int -> Int\n",
+ "------------------------------------------------------------------------------------------\n",
+ "\n",
+ "-- (a -> b) a b\n",
+ "-- /--------------------------\\ /---\\ /----------------\\\n",
+ " (Int -> (Int -> Int -> Int)) -> Int -> (Int -> Int -> Int) -- type of first $\n",
+ "-- \\________________________________________________________/\n",
+ "-- |\n",
+ "calculate $ 3 :: Int -> Int -> Int\n",
+ "\n",
+ "------------------------------------------------------------------------------------------\n",
+ "\n",
+ "-- (a -> b) a b\n",
+ "-- /-------------------\\ /---\\ /----------\\\n",
+ " (Int -> (Int -> Int)) -> Int -> (Int -> Int) -- type of second $\n",
+ "-- \\__________________________________________/\n",
+ "-- |\n",
+ "(calculate $ 3) $ 6 :: Int -> Int\n",
+ "\n",
+ "------------------------------------------------------------------------------------------\n",
+ "\n",
+ "-- (a -> b) a b\n",
+ "-- /----------\\ /---\\ /---\\\n",
+ " (Int -> Int) -> Int -> Int -- type of third $\n",
+ "-- \\_________________________/\n",
+ "-- |\n",
+ "((calculate $ 3) $ 6) $ 4 :: Int\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I use the dollar sign operator to more explicitly signify the operator that does the function application. But this is the same if we apply functions with the space as we did before.\n",
+ "\n",
+ "So, we have the `calculate` function and the function application operator (`$`). Let's see the first step.\n",
+ "\n",
+ "Function application takes a function that goes from `a` to `b` as the first argument, a value of type `a` as the second argument, and applies the function to the value to get a value of type `b`. If we apply function application to `calculate` as the first argument and `3` as the second argument, then the operator will have the type shown here. The key detail to notice is that both `a` and `b` can be of any type, we have no constraints. In our case, `b` is a function. So, after applying `calculate` to `3`, we get a function that goes `Int -> Int -> Int`.\n",
+ "\n",
+ "And now, we are at the same point as before, but with a function that takes one less value of type `Int`. So we can repeat the process. As we see in the second step, we apply function application to `calculate $ 3` as the first argument and `6` as the second argument to get yet again a function, but this time one of type `Int -> Int`.\n",
+ "\n",
+ "And if we repeat the process one last time, we apply the function application to `calculate $ 3 $ 6` as the first argument and `4` as the second argument to get our final value of type `Int`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, we know that function application can be used to apply functions that take any amount of arguments by just applying the operation again and again until the function is fully applied.\n",
+ "\n",
+ "That's our solution. We want to create an operator that works like function application but for functors. That way, it will work for functions of any number of arguments."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And we can do it quite intuitively. If regular function application has this signature:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "($) :: (a -> b) -> a -> b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "How would the type signature of the same function look if everything was a `Functor`? Well, you guessed it, like this:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<*>) :: Functor f => f (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We call this the \"ap\" or \"apply\" operator. We use an operator because, of course, it will be used mostly as an infix function. The type reflects that the behavior is the same as regular function application but lifted to work at the functor level.\n",
+ "\n",
+ "It's important not to confuse the signatures of this functor-level function application with `fmap`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<$>) :: Functor f => (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`fmap` takes a function that is not at the functor level and applies it to a functor. Once you apply the function the first time, the end result, be it the final value or a partially applied function, is now at the functor level. So we can not use `fmap` anymore."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. To make it more concrete, let's define it for the `Maybe` functor:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b\n",
+ "Just f <*> Just x = Just (f x) \n",
+ "_ <*> _ = Nothing \n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We have to pattern-match to make sure that we actually have both the function and the value, then apply the function to get a value of type `b`, and wrap it again with the Just constructor. If we don't have both, we can't return a valid result, so we return `Nothing`. \n",
+ "\n",
+ "This is really similar to the `fmap` definition. With the caveat that now, not just the value is optional, but also the function.\n",
+ "\n",
+ "Now that we have our definition, let's try it out!:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "Just (+1) <*> (Just 3)\n",
+ "\n",
+ "Just (+) <*> (Just 3) <*> (Just 2)\n",
+ "\n",
+ "calculate :: Int -> Int -> Int -> Int\n",
+ "calculate a b c = a + b * c\n",
+ "\n",
+ "Just calculate <*> (Just 3) <*> (Just 2) <*> (Just 4)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Awesome!! It seems that it works. But, I assured you that the `<*>` operator is function application at the functor level, and I compared its type with the function application type to build my case. But I know that's not enough to convince a scholar like you, so it's time I prove it to you.\n",
+ "\n",
+ "To prove that the `<*>` operator is function application at the functor level, I'm going to create a functor that doesn't do anything. If the structure doesn't do anything, then the `<*>` operator should return the same result as regular function application, right? So let's do that.\n",
+ "\n",
+ "How do you imagine a functor that doesn't do anything looks like? It's actually pretty straightforward. We need a type that wraps another type and doesn't provide any functionality. Like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "newtype Identity a = Identity { getIdentity :: a } deriving (Show, Eq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, if we have a type with only one constructor, the only thing it does is hold the value inside. It's virtually the same as the underlying type but with the extra annoyance of having to wrap and unwrap it.\n",
+ "\n",
+ "We call this type the `Identity` type for the same reasons we call the identity values and the identity function like that. They preserve the identity of the values they interact with. They don't do anything, basically.\n",
+ "\n",
+ "Remember the `newtype` wrappers of the `Semigroup` and `Monoid` lessons? Those weren't identities because the behavior we gave them when defining their `Semigroup` and `Monoid` instances did something. So now, we have to also implement the type class instances in a way that they have no effect."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Ok. So, let's implement the `Functor` instance for the `Identity` type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Functor Identity where\n",
+ " --fmap :: (a -> b) -> Identity a -> Identity b\n",
+ " fmap f (Identity a) = Identity (f a)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To do that, we'll pattern match to extract the internal value, apply the function, and wrap it again. It's the same as the `Just` case of the `Maybe` type, with the difference that because we only have one constructor, there's no option to fail, so we'll always be able to apply the function to the underlying value."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's try it out:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "fmap (+1) (Identity 3) -- Identity 4\n",
+ "\n",
+ "(*2) <$> Identity 3 -- Identity 6\n",
+ "\n",
+ "'A' <$ Identity 3 -- Identity 'A'\n",
+ "\n",
+ "fmap id (Identity False) == Identity False -- True"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And as you can see, it works as expected and respects the `Functor` laws.\n",
+ "\n",
+ "Now, we can create the `<*>` operator. \n",
+ "\n",
+ "The type was:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<*>) :: Functor f => f (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, we had a function at the functor level applied to a functor with values of type `a` to get the functor with values of type `b`. Because the `Identity` functor is basically just an annoying wrapper, we know that we have to unwrap, then apply the function to the value of type `a`, and wrap again. So let's do that:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "(<*>) :: Identity (a -> b) -> Identity a -> Identity b\n",
+ "(Identity f) <*> (Identity x) = Identity (f x)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Pretty straightforward, almost the same as `fmap`. Now comes the moment of truth. Let's see if the apply operator is actually function application at the functor level:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "(+1) 1\n",
+ "getIdentity $ Identity (+1) <*> Identity 1\n",
+ "\n",
+ "(+) 1 2\n",
+ "getIdentity $ Identity (+) <*> Identity 1 <*> Identity 2\n",
+ "\n",
+ "(\\a b c -> a + b * c) 1 2 3\n",
+ "getIdentity $ Identity (\\a b c -> a + b * c) <*> Identity 1 <*> Identity 2 <*> Identity 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, we have three examples of applying the same function to a value or multiple values. Once without a functor using the built-in function application, and once using the apply operator within a `Functor` that doesn't do anything. And we get the same result in both cases.\n",
+ "\n",
+ "We just showed that the apply operator is a more generalized form of function application. It's more general because we get the same old and boring function application if we specialize our new operator to the `Identity` functor. But we can also apply it to other functors.\n",
+ "\n",
+ "Is this enough, though? Let's talk about `pure`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Being `pure` 😇"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We've shown that the apply operator is a generalized form of function application. Does that mean that we could handle all possible cases when working with `Functors`? Let's see.\n",
+ "\n",
+ "When working with `Functors`, there are three possible base cases that you could combine to get more. You can have everything at the functor level:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "\n",
+ "
If you ran all cells until now, restart the Kernel before continuing. This is to prevent errors due to the class of multiple <*>
definitions.
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "Just (\\a b c -> a + b * c) <*> Just 1 <*> Just 2 <*> Just 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "No issue with that. It works using the apply operator we just defined. \n",
+ "\n",
+ "Another case is to have the need to apply a function that is not a functor to a bunch of functors:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- ❌ Not a Functor ❌\n",
+ "-- /-------------------\\\n",
+ "-- (\\a b c -> a + b * c) <*> Just 1 <*> Just 2 <*> Just 3 -- ❌ \n",
+ "-- __|\n",
+ "-- |\n",
+ "(\\a b c -> a + b * c) <$> Just 1 <*> Just 2 <*> Just 3 -- ✅"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "No issue either. That is what `fmap` is for. We use it to lift the function and keep going with apply operators.\n",
+ "\n",
+ "But, we could also encounter a third scenario. One when one or more values aren't functors:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- DB Local DB\n",
+ "Just (\\a b c -> a + b * c) <*> Just 1 <*> 2 <*> Just 3\n",
+ "-- ^\n",
+ "-- |\n",
+ "-- ❌ Not a Functor! ❌\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This happens in tons of scenarios. For example, if two of the three values come from the database and the other one is provided locally.\n",
+ "\n",
+ "We currently have no way to solve this. `fmap` lifts functions, not constant values. So, we need a way to lift arbitrary values. This function should take any value and return it embedded in a Functor. We could call this function `lift`, but for reasons we'll explain in the second to last section of this lesson, we'll call it `pure`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "pure :: Functor f => a -> f a\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Regardless of how it's called, what it does is to lift values to the functor level. \n",
+ "\n",
+ "We can define it trivially for the `Maybe` type like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "pureMaybe :: a -> Maybe a\n",
+ "pureMaybe = Just\n",
+ "\n",
+ "pureMaybe 'a'\n",
+ "pureMaybe 4"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And having this function, we can solve the missing case like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "Just (\\a b c -> a + b * c) <*> Just 1 <*> pureMaybe 2 <*> Just 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, `pureMaybe` is specialized to the `Maybe` functor. So, it's the same as wrapping the value in a `Just` constructor. We want a function that works for all `Functors`, so this will also be part of our new type class. \n",
+ "\n",
+ "And with this second behavior, we can apply any function to any amount of values at the functor level. To prove it to you, let's define fmap for all the functions from zero to five arguments using these two new operators:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ " fmap0 :: a -> f a\n",
+ " fmap0 = pure\n",
+ " \n",
+ " \n",
+ " fmap1 :: (a -> b) -> f a -> f b\n",
+ " fmap1 g fa = pure g <*> fa -- same as: g <$> fa\n",
+ "\n",
+ "\n",
+ " fmap2 :: (a -> b -> c) -> f a -> f b -> f c\n",
+ " fmap2 g fa fb = pure g <*> fa <*> fb\n",
+ "\n",
+ "\n",
+ " fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c -> f d\n",
+ " fmap3 g fa fb fc = pure g <*> fa <*> fb <*> fc\n",
+ "\n",
+ "\n",
+ " fmap4 :: (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e\n",
+ " fmap4 g fa fb fc fd = pure g <*> fa <*> fb <*> fc <*> fd\n",
+ "\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "I removed the `Functor` constraint to avoid cluttering the slide, but remember that all the type-level `f`s are functors.\n",
+ "\n",
+ "For `fmap0`, we have a function that takes zero arguments. That's a constant value. So, we need to embed a constant value into a functor. Easy, we use `pure`.\n",
+ "\n",
+ "For `fmap1`, we have a function that takes one argument. We lift the function to the Functor level and then use the apply operator to apply the function to the argument. \n",
+ "\n",
+ "For `fmap2`, we have a function that takes two arguments. We lift the function to the Functor level and then use the apply operator to apply the function to both arguments.\n",
+ "\n",
+ "And it's the same for `fmap3`, 4, and 5. Lift the function and use the apply operator as many times as needed until the function is fully applied.\n",
+ "\n",
+ "And, as I'm sure you might notice, `fmap1` is the same as our regular `fmap`. And `fmap2`, 3, 4, and 5 all start with the same `pure g <*> fa` pattern. So, we can simplify a bit by replacing the first application of the apply operator with the functor operator to avoid having to lift the initial function before applying it:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ " fmap0 :: a -> f a\n",
+ " fmap0 = pure\n",
+ " \n",
+ " \n",
+ " fmap1 :: (a -> b) -> f a -> f b\n",
+ " fmap1 g fa = g <$> fa -- same as: pure g <*> fa \n",
+ "\n",
+ "\n",
+ " fmap2 :: (a -> b -> c) -> f a -> f b -> f c\n",
+ " fmap2 g fa fb = g <$> fa <*> fb\n",
+ "\n",
+ "\n",
+ " fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c -> f d\n",
+ " fmap3 g fa fb fc = g <$> fa <*> fb <*> fc\n",
+ "\n",
+ "\n",
+ " fmap4 :: (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e\n",
+ " fmap4 g fa fb fc fd = g <$> fa <*> fb <*> fc <*> fd\n",
+ "\n",
+ " ...\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Awesome!! We have proven that we only need the apply operator and pure to handle all cases. Plus, we can use fmap to make it more concise by avoiding explicitly lifting the initial function. I guess it's time to formalize our findings!!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Applicative` type class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's define an initial approximation of the `Applicative` type class based on what we know now:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Initial approximation:**\n",
+ "\n",
+ "\"An `Applicative` is a `Functor` with a function to lift expressions (`pure`) and an operator that works as function application at the functor level (`<*>`)\"\n",
+ "\n",
+ "```haskell\n",
+ "class Functor f => Applicative f where\n",
+ " pure :: a -> f a\n",
+ " (<*>) :: f (a -> b) -> f a -> f b\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is not the final type class. But it already encapsulates the concept of `Applicative`, and it's enough to define a couple of instances so we can have something to work with while we figure out the laws. Because, of course, it has laws."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's start by defining the instance for `Identity`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "newtype Identity a = Identity { getValue :: a } deriving (Show, Eq)\n",
+ "\n",
+ "instance Functor Identity where\n",
+ " --fmap :: (a -> b) -> Identity a -> Identity b\n",
+ " fmap f (Identity a) = Identity (f a)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, because `Applicative` is a subclass of `Functor`, we first define `Functor`. It's pretty straightforward. We pattern match to extract the value wrapped with the `Identity` constructor, apply the function to the underlying value, and wrap it again. Now to the new part:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "instance Applicative Identity where\n",
+ " -- pure :: a -> Identity a\n",
+ " pure = Identity\n",
+ " \n",
+ " -- (<*>) :: Identity (a -> b) -> Identity a -> Identity b\n",
+ " -- Identity f <*> Identity a = Identity (f a)\n",
+ " Identity f <*> ia = fmap f ia -- same as definition above"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To create an instance for the applicative type class, we need to define both `pure` and the apply operator. We don't have much choice for `pure`. We need to lift a value to the `Identity` level, and we have only one constructor with the same type as pure. So we use that.\n",
+ "\n",
+ "For the apply operator, we have two choices. We can pattern-match to extract both the function and the value, apply the function to the value, and wrap it again, or because we have `fmap` at our disposal, we can pattern-match to extract only the function and fmappit to the value. If you look at the definition of the functor instance, it's the same, but we avoid repeating ourselves.\n",
+ "\n",
+ "Now that the `Identity` type is an applicative functor, we can use the operators:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "Identity (+1) <*> Identity 1\n",
+ "\n",
+ "Identity (+) <*> Identity 1 <*> Identity 2\n",
+ "\n",
+ "Identity (\\a b c -> a + b * c) <*> Identity 1 <*> pure 2 <*> pure 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "That one was boring, so let's do a new one. Let's create an instance for the `Maybe` type. Same as before, we start with the functor instance:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "data Maybe a = Nothing | Just a\n",
+ "\n",
+ "-------------------------------------------------------------------------\n",
+ "\n",
+ "instance Functor Maybe where\n",
+ " -- fmap :: (a -> b) -> Maybe a -> Maybe b\n",
+ " fmap _ Nothing = Nothing\n",
+ " fmap f (Just x) = Just (f x)\n",
+ "\n",
+ "-------------------------------------------------------------------------\n",
+ "\n",
+ "instance Applicative Maybe where\n",
+ " -- pure :: a -> Maybe a\n",
+ " -- pure = \\x -> Nothing ❌\n",
+ " pure = Just\n",
+ " \n",
+ " -- (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b\n",
+ " Just f <*> ma = fmap f ma\n",
+ " _ <*> _ = Nothing\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We don't run this cell because these instances are already provided by the prelude.\n",
+ "\n",
+ "We already implemented the `Functor` instance in the previous lesson, so we won't go there.\n",
+ "\n",
+ "For the `Applicative` instance, we have two options that satisfy the types for `pure`. We could create a function that ignores the input and returns `Nothing`, or we could use `Just` that fits perfectly fine by itself. Conceptually, it makes sense to use `Just` because if we use `Nothing`, we'll always get `Nothing` independent of the value we lift. But, if that doesn't convince you, `Just` is the only option if we want to respect the laws of the type class!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The `Applicative` laws"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Remember that one of the characteristics of functors was that `fmap` didn't change the structure itself. Only the values. Here, the apply operator has the same restriction.\n",
+ "\n",
+ "Sadly, same as with `Semigroup`, `Monoid`, and `Functor`, not everything is reflected in the type class definition. So, we have to complement it with a few laws. Here they are:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Identity:**\n",
+ "```haskell\n",
+ "pure id <*> v = v\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Applying a lifted `id` function to an applicative should return the same applicative. This makes sense, right? Same as when we deal with `fmap`, the apply operator should respect the structure of the `Functor` that is now an `Applicative` functor. Let's see an example with the `Maybe` type:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "(pure id <*> Just 3) == Just 3\n",
+ "\n",
+ "--------------------------------------------\n",
+ "\n",
+ "wrongPure = \\x -> Nothing\n",
+ "\n",
+ "(wrongPure id <*> Just 3) == Just 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, the apply operator should also respect the structure of the functor and only modify the values. If we don't do anything to the values, we should get our original applicative functor. That's why we have to use `Just` as `pure`. If we use a function like `wrongPure` that ignores the input and always returns `Nothing`, we have the correct type, but we change the structure when applying a lifted id function.\n",
+ "\n",
+ "Next, we have the Homomorphism law:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Homomorphism:**\n",
+ "```haskell\n",
+ "pure f <*> pure x = pure (f x)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Applying a lifted function to a lifted value should be the same as applying the function to the value and then lifting the result. This enforces that the apply operator has to behave as the regular function application.\n",
+ "\n",
+ "Let's see an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "leftSide :: Maybe String\n",
+ "leftSide = pure show <*> pure 3\n",
+ "\n",
+ "rightSide :: Maybe String\n",
+ "rightSide = pure (show 3)\n",
+ "\n",
+ "leftSide == rightSide\n",
+ "leftSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Amazing! It works as expected!! We still have two more to go through, though. Next, we have the composition law:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Composition:**\n",
+ "```haskell\n",
+ "(pure (.) <*> u <*> v) <*> w = u <*> (v <*> w) \n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This one shows that the operator is associative, although it might not be evident at a glance, so here's an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "leftSide = (pure (.) <*> Just show <*> Just (*2)) <*> Just 3\n",
+ "rightSide = Just show <*> (Just (*2) <*> Just 3)\n",
+ "\n",
+ "\n",
+ "leftSide == rightSide\n",
+ "leftSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "On the left side, we want to combine the `show` function with the `(*2)` function first and then apply it to `3`. And we know that the way of combining functions is by composition. So, we lift function composition to the functor level and apply it to `Just show` and `Just (*2)`. Then, we apply the result of that composition to `Just 3`.\n",
+ "\n",
+ "On the right side, we first want to combine the last two and then combine the result with the first. In this case, we don't need to lift function composition because we can apply `Just (*2)` to `Just 3` directly. So we do that to get `Just 6` and then apply `Just show` to get the string representation of the number six.\n",
+ "\n",
+ "As you can see, we get the same result on both sides.\n",
+ "\n",
+ "Finally, we have the interchange law:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Interchange:**\n",
+ "```haskell\n",
+ "f <*> pure x = pure (\\f -> f x) <*> f\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This one states that we should get the same result if we flip the arguments when applying a function. We know this is true for regular function applications. For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "leftSide = (+1) 3\n",
+ "rightSide = (\\f -> f 3) (+1) \n",
+ "\n",
+ "\n",
+ "leftSide == rightSide\n",
+ "leftSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "notes"
+ }
+ },
+ "source": [
+ "On the left side, we apply the function `(+1)` and to 3 to get 4.\n",
+ "\n",
+ "On the right side, we apply a function that takes another function as a parameter and applies it to the number three to the `(+1)` function. This results in `+1` being passed as a parameter and being applied to `3`.\n",
+ "\n",
+ "As you can see, we have to do the extra step of adding a lambda function, but if we flip the arguments of function application, we still get the same result. That property should also hold with our apply operator:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": false,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "leftSide = Just (+1) <*> pure 3\n",
+ "rightSide = pure (\\f -> f 3) <*> Just (+1) \n",
+ "\n",
+ "\n",
+ "leftSide == rightSide\n",
+ "leftSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This is the same as we just saw but lifted at the applicative functor level.\n",
+ "\n",
+ "Same as before, we have to do the extra step of adding a lambda function. But if we flip the arguments of the apply operator, we still get the same result. And that's it! \n",
+ "\n",
+ "To hone in on the type class implementation, let's do one more before moving on. Let's create the `Applicative` instance for the `Either` type:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "instance Functor (Either e) where\n",
+ " fmap f (Left e) = Left e\n",
+ " fmap f (Right a) = Right (f a)\n",
+ "\n",
+ "-----------------------------------------------------------\n",
+ "\n",
+ "instance Applicative (Either e) where\n",
+ " -- pure :: a -> Either a a\n",
+ " pure = Right\n",
+ " -- (<*>) :: Either e (a -> b) -> Either e a -> Either e b\n",
+ " Left e <*> _ = Left e\n",
+ " Right f <*> r = fmap f r\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We use `Right` to lift the value with `pure`. If we follow the types, `Right` is our only choice. Also, it makes sense. Lifting anything to the `Left` constructor would be useless because it would make any function that uses it return `Left` regardless of the other values it's applied to. On top of that, it also makes sense from the `Functor` perspective. `Left e` is part of the structure. We want to lift a value, so we have to use a constructor that can hold a value, and the only one available is the `Right` constructor that can hold values of type `a`.\n",
+ "\n",
+ "And for the apply operator, we have to take into account the cases that the function is both a `Left` and `Right`. If the function is a `Left`, it means we don't have a function. We have an error. So, we have no other option than propagating the error.\n",
+ "\n",
+ "If the constructor is a `Right`, we do have the function. And in this case, we could have both `Left` and `Right` as the second argument, but we don't have to deal with that because we already defined `fmap` that does it for us. So, we just `fmap` the funciton.\n",
+ "\n",
+ "And that's it! If we try to use it, we see that the results are reasonable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "Right (+1) <*> pure 1\n",
+ "\n",
+ "(+) <$> Right 1 <*> Right 2\n",
+ "\n",
+ "(\\a b c -> a + b * c) <$> pure 1 <*> Left 2 <*> pure 3\n",
+ "\n",
+ "(\\a b c -> a + b * c) <$> Right 1 <*> pure 2 <*> pure 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But reasonable is not enough. We have to check if our instance follows the Applicative laws. So, let's do that. Starting with the identity law:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Identity: pure id <*> v = v\n",
+ "\n",
+ "\n",
+ "(pure id <*> Left 3) == Left 3\n",
+ "\n",
+ "(pure id <*> Right 'a') == Right (id 'a')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Nice. Our implementation follows the identity law. Now let's check for the Homomorphism law:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Homomorphism: pure f <*> pure x = pure (f x)\n",
+ "\n",
+ "\n",
+ "leftSide :: Either e Int\n",
+ "leftSide = pure (+1) <*> pure 1\n",
+ "\n",
+ "rightSide :: Either e Int\n",
+ "rightSide = pure ((+1) 1)\n",
+ "\n",
+ "\n",
+ "leftSide == rightSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Awesome! 2/4 done. Let's do composition next:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)\n",
+ "\n",
+ "\n",
+ "leftSide :: Either String Int\n",
+ "leftSide = pure (.) <*> u <*> v <*> w\n",
+ " where\n",
+ " u = Right (+1)\n",
+ " -- v = Right (*2)\n",
+ " v = Left \"some error\"\n",
+ " w = Right 3\n",
+ "\n",
+ "rightSide :: Either String Int\n",
+ "rightSide = u <*> (v <*> w)\n",
+ " where\n",
+ " u = Right (+1)\n",
+ " -- v = Right (*2)\n",
+ " v = Left \"some error\"\n",
+ " w = Right 3\n",
+ "\n",
+ "\n",
+ "leftSide == rightSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see, this law holds as well. And, of course, we can change any of these three values to different ones, for example, `Left \"some error\"`, and it will still hold.\n",
+ "\n",
+ "And finally, we check for the interchange law:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- Interchange: u <*> pure y = pure ($ y) <*> u\n",
+ "\n",
+ "\n",
+ "leftSide :: Either String Int\n",
+ "leftSide = u <*> pure 5\n",
+ " where\n",
+ " u = Right (+1)\n",
+ " -- u = Left \"some error\"\n",
+ "\n",
+ "rightSide :: Either String Int\n",
+ "rightSide = pure ($ 5) <*> u\n",
+ " where\n",
+ " u = Right (+1)\n",
+ " -- u = Left \"some error\"\n",
+ "\n",
+ "\n",
+ "leftSide == rightSide"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Amazing! Our implementation seems to follow all the `Applicative` laws. We now have the Identity, Maybe, and Either Applicatives under our belt. So, I think it's time to move on to programming with effects."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## 🎆 Programming with effects 🎆"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Congratulations! You now know how to program with effects!! Oh, you didn't realize that? Well, you do! Here's why.\n",
+ "\n",
+ "When we talked about `Maybe`, we said it gave us a way to represent values that might fail. We wanted to return a value, but we weren't sure if we were going to be able to, so we used `Mabye`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "-- We used optional values to create safe versions of partial functions\n",
+ "safeDiv :: Int -> Int -> Maybe Int\n",
+ "safeDiv x 0 = Nothing\n",
+ "safeDiv x y = Just (x `div` y)\n",
+ "\n",
+ "8 `safeDiv` 2\n",
+ "8 `safeDiv` 0\n",
+ "\n",
+ "-------------------------------------------------------------------------------\n",
+ "\n",
+ "-- We used optional values to handle possible failed results\n",
+ ":t lookup\n",
+ "valueFromDB = lookup \"Eren\" [(\"Eren\", 10), (\"Mikasa\", 25), (\"Armin\", 7)] -- Fake Database call\n",
+ "valueFromDB"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the first example, we see how we use optional values to create a safe version of `div` that doesn't crash our program if we try to divide by zero.\n",
+ "\n",
+ "In the second example, we represent a value we retrieve from a database. The value could have not been there, so the `lookup` function produces a value of type `Maybe` to be able to return `Nothing` if it can't find the value in the list.\n",
+ "\n",
+ "Now, imagine we need to apply the `calculate` function to a bunch of `Maybe` values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "calculate :: Int -> Int -> Int -> Int\n",
+ "calculate a b c = a + b * c"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Before learning about `Functor` and `Applicative`, we could have defined a function that takes three `Maybe` values and applies `calculate` to them like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "calculateMaybe ma mb mc = case ma of\n",
+ " Nothing -> Nothing\n",
+ " Just a -> case mb of\n",
+ " Nothing -> Nothing\n",
+ " Just b -> case mc of\n",
+ " Nothing -> Nothing\n",
+ " Just c -> Just (calculate a b c)\n",
+ "\n",
+ "\n",
+ "calculateMaybe (8 `safeDiv` 2) (pure 2) valueFromDB"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We create a new `calculateMaybe` function to manually handle all possible failure cases. If all three values return a `Just`, we know we have all three underlying numbers, and we can apply the original `calculate` function to them. And, of course, we wrap the result in a `Just` constructor to get the correct type.\n",
+ "\n",
+ "This has several disadvantages. One is that we have to create the new `calculateMaybe` function. Another is that `calculateMaybe` only works for `Maybe` values. Finally, we have to write a massive block pattern matching with case expressions to handle the `Maybe` values three times. The same code is repeated thrice."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "But now that we have `Functor` and `Applicative`, we can do the same in one line:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "calculate <$> (8 `safeDiv` 2) <*> (pure 2) <*> valueFromDB"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We don't have to create a new function, we can replace the values we apply `calculate` to with any type that is an instance of applicative, and we don't have to handle the failure cases by hand because that is handled by the operators under the hood. This is what we achieved code-wise. But there's a different way of looking at this."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We can also think of `Maybe` as a type that provides the simulated effect of exception handling. In that case, the apply operator is a way to sequence effects and combine their results. As you can see in `calculateMaybe`, we sequence the exception handling one after the other and then apply `calculate` at the end to combine their results.\n",
+ "\n",
+ "When we apply functions using the applicative operators, also called \"applicative style\", we do the same as in `calculateMaybe` but in a hidden and more convenient way. That's why it's understandable to be confused when I tell you we can think of it as an effect when you can clearly see in the body of `calculateMaybe` that it's actually pure code.\n",
+ "\n",
+ "In Haskell, we have a bit of a loose concept of effects. Sometimes, the same code can be thought of as having an effect or not, depending on how you interpret it. So, to make it as crystal clear as possible, here are a few key clarifications that will save you a lot of time and headaches:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "Haskell has both **real effects** and **simulated effects**.\n",
+ "- **Real** effects are impure and unpredictable (e.g., `IO`)\n",
+ "- **Simulated** effects look impure when you use them, but are actually pure under the hood (e.g., `Maybe`)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Simulated effects look impure because the computations are hidden from us behind types and operators. So, from the point of view of the user of those types and operators, it looks like they are running side effects, but it's actually pure code that they can not see.\n",
+ "\n",
+ "Another worthy clarification is that:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "Independently of whether they are real or simulated, **all effects** have an **associated type** and **at least an `Applicative` instance** (usually a `Monad` instance as well)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, `Applicative` and `Monad` are the interfaces for effects. You can think of these type classes as the boundary between what it is and is not usually considered an effect. As a side note, don't worry about `Monad` yet. We'll cover it in a future lesson. Just know that `Monads` have a close relationship with `Applicative` and the concept of effects.\n",
+ "\n",
+ "Let's see a few examples:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**Examples of effects in Haskell:**\n",
+ "\n",
+ "- `Identity` provides **no effect**.\n",
+ "
\n",
+ "\n",
+ "- `Maybe` provides the **simulated effect** of a computation that might fail.\n",
+ "
\n",
+ "\n",
+ "- `Either` provides the **simulated effect** of a computation that might fail with an error message or some context as to why we had an error.\n",
+ "
\n",
+ "\n",
+ "- `[]` provides the **simulated effect** of a non-deterministic computation. A computation that can have zero, one, or multiple results (each element on the list is a result).\n",
+ "
\n",
+ "\n",
+ "- `IO` provides the **real effect** of interacting with the world (keyboard, screen, internet,...)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We'll discuss more effects in later lectures, but let's stick with these ones for now to avoid overwhelming ourselves.\n",
+ "\n",
+ "And thinking on these terms, now it makes sense why we called `pure` the function we use to lift values to the applicative functor level.\n",
+ "\n",
+ "It's because we're embedding a value without an effect, a pure value, into an effectful context. \n",
+ "\n",
+ "Once again, all we do with `pure` is to wrap the value with the correct expression, the same as we have been doing during this lesson. That hasn't changed. It's the way we think about what we do that changed."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We'll talk more about effects when we cover `Monads`. For now, with this new point of view, let's finish this lesson the same way we did with the `Functor` one. By exploring the extra functions we get for free with `Applicative` and presenting the type class and how it's defined in the `base` library."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Extra functions and `Applicative` as defined in `base`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The first functions we'll take a look at are the lift functions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[2,3]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 4"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[4,5,5,6]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Right 5"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Left \"some error\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Just 14"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "liftA :: Applicative f => (a -> b) -> f a -> f b\n",
+ "liftA f x = pure f <*> x -- Lift a unary function to actions.\n",
+ "\n",
+ "liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c\n",
+ "liftA2 f x y = f <$> x <*> y -- Lift a binary function to actions.\n",
+ "\n",
+ "liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d\n",
+ "liftA3 f x y z = liftA2 f x y <*> z -- Lift a ternary function to actions.\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "\n",
+ "liftA (+1) [1, 2]\n",
+ "liftA (+1) (Just 3)\n",
+ "\n",
+ "liftA2 (+) [1, 2] [3, 4]\n",
+ "liftA2 (+) (Right 2) (Right 3)\n",
+ "\n",
+ "liftA3 (\\a b c -> a + b * c) (Right 2) (Left \"some error\") (Right 4)\n",
+ "liftA3 (\\a b c -> a + b * c) (Just 2) (Just 3) (Just 4)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "These three functions are the same as we defined earlier as `fmap`, `fmap2`, and `fmap3`. They take functions that take one, two, and three arguments and lift them to work at the applicative functor level. Their names start with `lift` because we're lifting functions. Then, they continue with a capital `A` because we're lifting them to the applicative level and end with the number of arguments the lifted function takes. You'll encounter functions with this naming convention regularly since it's used all over the `base` library. So it's good to get used to it.\n",
+ "\n",
+ "It seems like a bad joke. I just told you we don't need to define functions like these ones. And we don't! As you can see in the definitions, we can write any of them using only applicative style.\n",
+ "\n",
+ "But there are reasons as to why we have these ones. Well, there are historical reasons. So, maybe if we wanted to design Haskell from scratch with what we know right now, we wouldn't have these functions. But there are also subtle non-historical reasons as to why we have them.\n",
+ "\n",
+ "On one hand, because `liftA` and `fmap` are the same function, you could implement first the `Applicative` instance of a type and then implement the `Functor` instance defining `fmap` as a synonym to `liftA`. It seems counterintuitive because `Functor` is a superclass of `Applicative`. But, the same as we have mutually recursive functions for the Eq type class, the compiler doesn't care as long as both instances are defined and we eventually reach a base case. So, if, for some reason, defining the pure and apply operators is easier than defining `fmap`, you have the option of doing this. That's also why we use `pure` and the apply operator instead of `fmap` to define `liftA`. If we did that, and then you define `fmap` using `liftA`, we'll get infinite recursion. If that didn't make sense, it's OK. It's an implementation detail, not crucial.\n",
+ "\n",
+ "Moving on to `liftA2`, it turns out that some functors support an implementation of `liftA2` that is more efficient than using `fmap` and the apply operator. In particular, if `fmap` is an expensive operation, you can redefine the standard definition of `liftA2` shown here with a more optimized one. That way, it would be better to use the optimized `liftA2` than to `fmap` over the structure and then use `<*>`. We don't care about optimizing our code for now, so, for us, it's just a shorter way of lifting a binary function.\n",
+ "\n",
+ "And finally, `liftA3` is there because counting to two feels incomplete.\n",
+ "\n",
+ "Jokes aside, in most scenarios, you should use `fmap` instead of `liftA`. And, if you have to lift a binary or ternary function, feel free to use `liftA2` and `liftA3` or applicative style. Whatever you like the best."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, let's move to more interesting ones.\n",
+ "\n",
+ "Analogous to the `Functor` case, we have these two operators:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 3"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Sequence actions, discarding the value of the first argument.\n",
+ "(*>) :: Applicative f => f a -> f b -> f b\n",
+ "a1 *> a2 = (id <$ a1) <*> a2 \n",
+ "\n",
+ "\n",
+ "Just 2 *> Just 3\n",
+ "Nothing *> Just 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The one that discards the value of the first argument, and:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Just 2"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Nothing"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "-- Sequence actions, discarding the value of the second argument.\n",
+ "(<*) :: Applicative f => f a -> f b -> f a\n",
+ "(<*) = liftA2 const \n",
+ "\n",
+ "\n",
+ "Just 2 <* Just 3\n",
+ "Nothing <* Just 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The one that discards the value of the second argument."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We said that the apply operator sequences effects/computations/actions and combines their result. Well, these ones do the same but discard the value produced by one of the inputs. Notice that, same as with their analogous of the `Functor` type class, the arrow points to the value you'll get.\n",
+ "\n",
+ "If you want to perform the effect or computation but don't care about the result, like printing to the console, you can use one of these."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Then, we have the reversed application operator:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"Retrieve value from DB\"\n",
+ "\"Save value in DB\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Retrieve value from DB\"\n",
+ "\"Save value in DB\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "\"Save value in DB\"\n",
+ "\"Retrieve value from DB\""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "(<**>) :: Applicative f => f a -> f (a -> b) -> f b\n",
+ "(<**>) = liftA2 (\\a f -> f a) -- A variant of '<*>' with the types of the arguments reversed.\n",
+ "\n",
+ "-----------------------------------------------------------------------------------------------\n",
+ "\n",
+ "-- Attempt 1: Correct values, wrong effect order\n",
+ "(<*>) (id <$ print \"Retrieve value from DB\") (print \"Save value in DB\") \n",
+ "\n",
+ "-- Attempt 2: Corect values, wrong effect order\n",
+ "(flip (<*>)) (print \"Save value in DB\") (id <$ print \"Retrieve value from DB\")\n",
+ "\n",
+ "-- Attempt 3: Correct values, correct effect order\n",
+ "(<**>) (print \"Save value in DB\") (id <$ print \"Retrieve value from DB\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This operator does the same as the apply operator, but it first takes the applicative value and then the applicative function.\n",
+ "\n",
+ "This operator is helpful in cases where the values you get are ok, but the effects are in the wrong order. In this example, we're using `IO` effects. The `IO` type is an instance of `Applicative`. \n",
+ "\n",
+ "In the first attempt, we evaluate the first Applicative and retrieve the value from our database. Then, we evaluate the second Applicative and save the value to the database. After running both effects, we apply the `id` function to the result of print, which is a unit.\n",
+ "\n",
+ "The problem is, the effects are in the wrong order! We first need to save the value so we can then retrieve it. But we can not just change the order of the arguments. The result of the first effect is a function (in this case, the `id` function), and the result of the second effect is the constant value unit. One is a function, and the other is a constant value. So, what do we do?\n",
+ "\n",
+ "One solution you could think of is to use the `flip` function we learned many lessons ago. It's designed to specifically flip the arguments of the function. That's what we see in the second attempt. And the code looks fine. But, if we evaluate the expression, we reduce this expression, and we end up having the same final one. The `flip` function only changes how we provide values to the original function but not how the values are evaluated. So, even if we write the values in a different order, the effects run in the same order. This is the problem this new operator solves. It not only flips the arguments but also resolves the effects in the order they are presented. So, we can finally first save the value in the database and then retrieve it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Another useful one is the `forever` function:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "firsr\n",
+ "second\n",
+ "third\n",
+ "fourth\n",
+ "...\n"
+ ]
+ }
+ ],
+ "source": [
+ "forever :: (Applicative f) => f a -> f b\n",
+ "forever a = a *> forever a -- Same as: a *> a *> a *> a... infinitely\n",
+ "\n",
+ "\n",
+ "forever getLine"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As you can see by the definition, we get an applicative, perform its effect, ignore the result, and recursively run `forever` with the same Applicative. This function will never stop by itself.\n",
+ "\n",
+ "It's the first time we have a forever looping function that is actually useful! Now that we have Applicatives, we can perform effects. So, now, infinite loops make sense because we're not interested in what the function returns but in what the effect does. A common use case, for example, is to have a server forever listening to client connections. As we saw before, the `IO` type is an instance of Applicative, so we can use the `*>` sequence operator. You want the server to keep listening as long as you have it running, so you use an infinite loop.\n",
+ "\n",
+ "Another cool thing we can do with `Applicative` is to have conditional execution of expressions:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "-- Execute Applicative if Bool is True:\n",
+ "when :: (Applicative f) => Bool -> f () -> f ()\n",
+ "when = 😳\n",
+ "\n",
+ "-- Execute Applicative if Bool is False:\n",
+ "unless :: (Applicative f) => Bool -> f () -> f ()\n",
+ "unless = 🫣\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Here, we have the `when` and `unless` functions. `when` executes the applicative expression only when Bool is true. And `unless` executes it unless it's true. I'm not showing you the implementation because you'll have to implement them yourself on the homework. But an example will do fine:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "when is True"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "unless is False"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Control.Monad (when, unless)\n",
+ "\n",
+ "\n",
+ "when True (putStrLn \"when is True\")\n",
+ "when False (putStrLn \"when is False\")\n",
+ "-----------------------------------------\n",
+ "unless True (putStrLn \"unless is True\")\n",
+ "unless False (putStrLn \"unless is False\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "You might have noticed that I'm importing the functions from `Control.Monad` instead of `Control.Applicative`. That's because these functions (and others we'll discuss later) were constrained by the Monad type class before the Applicative type class was fully integrated into the base library. Because maintainers wanted to minimize breaking changes, these (and other) functions were left in the Monad module but with the Applicative constraint instead of the Monad constraint. So, for all intents and purposes, these are Applicative functions that happen to be in a different module because of historical reasons.\n",
+ "\n",
+ "If you think we're talking a lot about Monads for a lesson about Applicatives, it's because before, this behavior of sequencing effects was found in the Monad type class. So, the Applicative type class stole a lot of the work that used to be done by the Monad type class, and there are still a few loose ends sprinkled around the `base` library.\n",
+ "\n",
+ "Coming back to the `when` and `unless` functions, to give you some extra help, take a look at this example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "14\n",
+ "12\n",
+ "10\n",
+ "8\n",
+ "6\n",
+ "4\n",
+ "2\n",
+ "0"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "printEvenNumbersUntilZero :: Int -> IO ()\n",
+ "printEvenNumbersUntilZero n = when (even n) (print n) *> \n",
+ " unless (n <= 0) (printEvenNumbersUntilZero (n-1))\n",
+ "\n",
+ "printEvenNumbersUntilZero 15"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "You can almost read the expression as if it were plain English. The `printEvenNumbersUntilZero` function takes a number, and when it's even, it prints it. Then, unless it's less or equal to zero, it recursively calls `printEvenNumbersUntilZero` with the number reduced by one.\n",
+ "\n",
+ "This is just one example of things you could do with these functions.\n",
+ "\n",
+ "I think those were enough clues for you. Let's keep going, two functions to go:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "replicateM :: (Applicative m) => Int -> m a -> m [a]\n",
+ "replicateM n action | n <= 0 = pure []\n",
+ " | otherwise = (:) <$> action <*> replicateM (n-1) action\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `replicateM` function takes an `Int` that we named `n` and an applicative that we named `action`. Then, it executes the action n times and returns an Applicative with a list containing all the results.\n",
+ "\n",
+ "Same as with the `when` and `unless` functions, `replicateM` suffers from historical baggage. It has an upper-case `M` at the end, signaling that it works with Moads, and the type variable constrained by `Applicative` is a lower-case `m`, signaling that it used to be a type variable constrained by Monads. If we would write the `base` library from scratch today, we would likely call this function `replicateA` and use `f` for the type variable. But do not let that distract you. For you, this is an Applicative function, the same as the others.\n",
+ "\n",
+ "In the definition, we have two cases. If the number is zero or negative, we shouldn't do anything, so we return an empty list lifted to the Applicative level. Or, in other words, we embed a pure empty list in the effect of the Applicative. Whichever might be.\n",
+ "\n",
+ "Otherwise, we have a number larger than zero, so we run the action and add the result to a list. We can do that easily thanks to the fmap and apply operators. And since we have to accumulate the results in a list, we concatenate them. So, we use fmap to apply the pure cons operator to our applicative, and then we apply the partially applied cons operator at the applicative level to a recursive call of `replicateM` with a reduced `n`. So, we will keep lifting cons operators and applying them to the result of evaluating the applicative until we reach zero and return the empty list. At that point, we have a list of all the results concatenated in a list, which is what we needed.\n",
+ "\n",
+ "Let's see an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "scrolled": true,
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "What are your 3 favorite Pokemon?\n",
+ "[\"Pikachu\",\"Mimikyu\",\"Scizor\"]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Control.Monad (replicateM)\n",
+ "\n",
+ "get3Answers :: String -> IO [String]\n",
+ "get3Answers q = putStrLn q *> replicateM 3 getLine\n",
+ "\n",
+ "get3Answers \"What are your 3 favorite Pokemon?\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We create the `get3Answers` function that takes a `String`, which is the question we want to ask, and returns a list of Strings containing the answers. We can use the `*>` sequence operator to first print the question, ignore the result, and then use `replicateM` to run `getLine` three times and get the result as a list of strings."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We also have a variation of `replicateM` called `repliacteM_`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "replicateM_ :: (Applicative m) => Int -> m a -> m ()\n",
+ "replicateM_ = 🤷\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The underscore at the end is another naming convention followed by many functions that deal with effects. If a function ends with an underscore, it means that it ignores the result and returns the `()`. So, if you only care about the effect and not the value, use a function that ends with an underscore. \n",
+ "\n",
+ "It's usual to have the same function with and without the underscore. Both do the same, but one returns a value, and the other returns a useless `()`.\n",
+ "\n",
+ "So, now that you know this, you know what `replicateM_` does. It does the same as `replicateM` but returns `()` instead of the list of resulting values. I'm not going to show the implementation because you have to do it in the homework, but here is an example of why would you want to use it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "What are your 3 favourite pokemon?"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import Control.Monad (replicateM_)\n",
+ "\n",
+ "saveToDB = id -- use your imagination\n",
+ "\n",
+ "getAndSave3Answers :: String -> IO ()\n",
+ "getAndSave3Answers q = putStrLn q *> replicateM_ 3 (saveToDB getLine)\n",
+ "\n",
+ "getAndSave3Answers \"What are your 3 favourite pokemon?\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Here, we create the `getAndSave3Answers` function that does the same as the `get3Answers` function, but now it saves the answers in a database.\n",
+ "\n",
+ "In this case, we print the question, ignore the result and then use `replicateM_` to execute the `saveToDB getLine` applicative three times. Because the applicative's effect already saves the values to the database, I have no reason to return them, so I use the underscore version of `replicateM`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "And there are many more functions like these ones that we could talk about. But we're going to leave it here for the sake of time, and because after all we discussed so far, you can search for them in the documentation by yourself and quickly understand what they do."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, after all this work, this is what we've got. It's a lot, so I had to split it into 3 different slides:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "**An `Applicative` is a `Functor` with operations to embed pure expressions (`pure`) and sequence computations/effects combining their results (`<*>`).**\n",
+ "\n",
+ "```haskell\n",
+ "class Functor f => Applicative f where\n",
+ " pure :: a -> f a -- Lift a value.\n",
+ "\n",
+ " (<*>) :: f (a -> b) -> f a -> f b\n",
+ " (<*>) = liftA2 id -- Sequential application.\n",
+ "\n",
+ " liftA2 :: (a -> b -> c) -> f a -> f b -> f c\n",
+ " liftA2 f x = (<*>) (fmap f x) -- Lift a binary function to actions.\n",
+ "\n",
+ " (*>) :: f a -> f b -> f b\n",
+ " a1 *> a2 = (id <$ a1) <*> a2 -- <*> discarding the val of the 1st arg.\n",
+ " \n",
+ " (<*) :: f a -> f b -> f a\n",
+ " (<*) = liftA2 const -- <*> discarding the val of the 2nd arg.\n",
+ " {-# MINIMAL pure, ((<*>) | liftA2) #-}\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If we look at how `Applicative` is defined in the `base` library, we'll see that several of the functions we just talked about are part of the type class, and three that we don't see here are not. As with `Functors`, we don't care about that distinction since they all come for free when you define the `Applicative` instance.\n",
+ "\n",
+ "As you can see, we have mutual recursion between the apply operator and `liftA2`. The rest of the functions (except for `pure`) are defined using one of those. So, at the end of the day, we have to provide `pure` with either the apply operator or `liftA2`, whichever you prefer to implement.\n",
+ "\n",
+ "On top of that, every type that has an instance of this `Applicative` type class has to respect these laws:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Applicative laws\n",
+ "
\n",
+ "\n",
+ "**Identity:**\n",
+ "```haskell\n",
+ "pure id <*> v = v\n",
+ "```\n",
+ "**Homomorphism:**\n",
+ "```haskell\n",
+ "pure f <*> pure x = pure (f x)\n",
+ "```\n",
+ "**Composition:**\n",
+ "```haskell\n",
+ "(pure (.) <*> u <*> v) <*> w = u <*> (v <*> w) \n",
+ "```\n",
+ "**Interchange:**\n",
+ "```haskell\n",
+ "f <*> pure x = pure (\\f -> f x) <*> f\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Finally, we also gain a ton of utility functions that are not part of the type class but are available in the `base` library:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "```haskell\n",
+ "(<**>) :: Applicative f => f a -> f (a -> b) -> f b\n",
+ "\n",
+ "liftA :: Applicative f => (a -> b) -> f a -> f b\n",
+ "\n",
+ "liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d\n",
+ "\n",
+ "forever :: Applicative f => f a -> f b\n",
+ "\n",
+ "when :: Applicative f => Bool -> f () -> f ()\n",
+ "\n",
+ "unless :: Applicative f => Bool -> f () -> f ()\n",
+ "\n",
+ "replicateM :: Applicative m => Int -> m a -> m [a]\n",
+ " \n",
+ "replicateM_ :: Applicative m => Int -> m a -> m ()\n",
+ "\n",
+ "filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]\n",
+ "\n",
+ "-- 🔥 AND MORE!!!!! 🤯\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "It's dramatic the number of functions we unlock by creating an instance of Applicative. So many that we couldn't cover all of them. The power that this abstraction provides is huge! Use it responsibly."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## And that's it for today! 😃"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We introduced only one more abstraction today, but we also talked about how we can interpret types that are instances of `Applicative` as effects. If that didn't quite click, it's OK. We'll talk more about effects in the monad lesson. For now, the best would be to get comfortable both defining and using the operators independently of how you think about it. And for that, make sure to do your homework, and I'll see you in the next one!!"
+ ]
+ }
+ ],
+ "metadata": {
+ "celltoolbar": "Slideshow",
+ "kernelspec": {
+ "display_name": "Haskell",
+ "language": "haskell",
+ "name": "haskell"
+ },
+ "language_info": {
+ "codemirror_mode": "ihaskell",
+ "file_extension": ".hs",
+ "mimetype": "text/x-haskell",
+ "name": "haskell",
+ "pygments_lexer": "Haskell",
+ "version": "8.10.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
\ No newline at end of file