|
| 1 | +--- |
| 2 | +title: "Notación Hindley-Milner" |
| 3 | +description: "Entendiendo la notación de tipo Hindley-Milner en programación funcional para expresar firmas de funciones y relaciones de tipo" |
| 4 | +pubDate: "2019-04-11" |
| 5 | +tags: |
| 6 | + - declarative |
| 7 | + - Pure functions |
| 8 | + - Inmutable |
| 9 | + - currying |
| 10 | + - Hindley-Milner |
| 11 | +categories: |
| 12 | + - functional |
| 13 | +draft: false |
| 14 | +heroImage: "/blog-placeholder-13.jpg" |
| 15 | +--- |
| 16 | + |
| 17 | +Una forma de crear una notación para expresar qué tipos de parámetro toma una función y qué devuelve. |
| 18 | + |
| 19 | +## Lo básico |
| 20 | + |
| 21 | +Una función que toma un valor primario ("tipo antiguo" como cadena, número, booleano, matriz, función ...) y devuelve otro valor primario: |
| 22 | + |
| 23 | +`instruction :: String -> String` |
| 24 | + |
| 25 | +```javascript |
| 26 | +const instruction = function (verb) { |
| 27 | + return verb + " me"; |
| 28 | +}; |
| 29 | +``` |
| 30 | + |
| 31 | +la función instruction toma una cadena y devuelve una cadena |
| 32 | + |
| 33 | +También podría hacer algo así: |
| 34 | + |
| 35 | +`length :: String → Number` |
| 36 | + |
| 37 | +```javascript |
| 38 | +const length = function (s) { |
| 39 | + return s.length; |
| 40 | +}; |
| 41 | +``` |
| 42 | + |
| 43 | +En el caso de una matriz de números: |
| 44 | + |
| 45 | +`length :: [Number] → Number` |
| 46 | + |
| 47 | +```javascript |
| 48 | + const length = function(arr){ |
| 49 | + retrun arr.length |
| 50 | + } |
| 51 | +``` |
| 52 | + |
| 53 | +## Trabajando con funciones |
| 54 | + |
| 55 | +En el caso de una función, envolvemos nuestra función entre paréntesis y dentro de los paréntesis tenemos nuestro tipo de entrada y nuestro tipo de salida: |
| 56 | + |
| 57 | +`addOneToAll :: ((Number → Number),[Number]) → [Number]` |
| 58 | + |
| 59 | +```javascript |
| 60 | +const addOne = function (x) { |
| 61 | + return x + 1; |
| 62 | +}; |
| 63 | +const addOneToAll = (addOne, arr) => arr.map(addOne); |
| 64 | +``` |
| 65 | + |
| 66 | +En este caso tenemos una función llamada addOneToAll que espera como primer parámetro una función (en nuestro caso addOne) y esta función aceptará un número y devolverá un número. |
| 67 | +Y como segundo parámetro una matriz de números y devolverá otra matriz de números. |
| 68 | + |
| 69 | +### Funciones de currying |
| 70 | + |
| 71 | +Ahora, ¿qué pasa con una función que devuelve una función que devuelve otra función .... |
| 72 | + |
| 73 | +Siguiendo lo anterior, tendríamos algo como esto: |
| 74 | +`replace :: String -> (String -> (String -> String))` |
| 75 | + |
| 76 | +```javascript |
| 77 | +var replace = curry(function (find, replacement, str) { |
| 78 | + var regex = new RegExp(find, "g"); |
| 79 | + return str.replace(regex, replacement); |
| 80 | +}); |
| 81 | +``` |
| 82 | + |
| 83 | +En este caso también hacemos que la función sea curry para tomar parámetros uno por uno |
| 84 | + |
| 85 | +Y en la programación funcional podemos asumir que todo es curry, así que tendemos a dejar caer los corchetes y algo como esto: |
| 86 | + |
| 87 | +` replace :: String -> String -> String -> String` |
| 88 | + |
| 89 | +## Trabajando con funciones que toman múltiples parámetros como entrada (Variables arbitrarias de Hindley-Milner) |
| 90 | + |
| 91 | +Mostramos el ejemplo con la función de longitud donde podríamos tener: |
| 92 | +`length :: [Number] → Number` |
| 93 | +o |
| 94 | +`length :: string → Number` |
| 95 | + |
| 96 | +En este caso podríamos escribir ambos con una variable arbitraria como: |
| 97 | +`length :: [a] → Number` |
| 98 | + |
| 99 | +Otro ejemplo común es la identidad: |
| 100 | +`identity :: a -> a` |
| 101 | + |
| 102 | +Y un ejemplo más complejo: |
| 103 | +`map :: (a -> b) -> [a] -> [b]` |
| 104 | + |
| 105 | +```javascript |
| 106 | +const map = curry(function (callback, array) { |
| 107 | + return array.map(callback); |
| 108 | +}); |
| 109 | +``` |
| 110 | + |
| 111 | +La función de mapa toma una función que toma una variable de tipo `a` y devuelve una variable de tipo `b`. |
| 112 | +Luego toma una **matriz de valores**, todos de tipo `a`, y devuelve una **matriz de valores**, todos de tipo `b`. |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## Trabajando con Ramda |
| 117 | + |
| 118 | +## Tipos parametrizados |
| 119 | + |
| 120 | +Podemos imaginar fácilmente un tipo que representa una colección de elementos similares, |
| 121 | +llamémoslo una Caja. Pero ninguna instancia es una Caja arbitraria; cada uno solo puede contener un tipo de elemento. |
| 122 | + |
| 123 | +`makeBox :: Number -> Number -> Number -> [a] -> Box a` |
| 124 | + |
| 125 | +```javascript |
| 126 | + const makeBox = curry((height, width, depth, items) => /* ... */); |
| 127 | +``` |
| 128 | + |
| 129 | +## Alias de tipo |
| 130 | + |
| 131 | +Si tuviéramos un tipo parametrizado Usuario String, donde la String se supone que representa un nombre, y quisieramos ser más específicos acerca del tipo de String que se representa al generar una URL, podríamos crear un alias de tipo así: |
| 132 | + |
| 133 | +`toUrl :: User Name u => Url -> u -> Url` |
| 134 | + |
| 135 | +`Name = String` |
| 136 | + |
| 137 | +` Url = String` |
| 138 | + |
| 139 | +```javascript |
| 140 | +const toUrl = curry( |
| 141 | + (base, user) => base + user.name.toLowerCase().replace(/\W/g, "-") |
| 142 | +); |
| 143 | +toUrl("http://example.com/users/", { name: "Fred Flintstone", age: 24 }); |
| 144 | +//=> 'http://example.com/users/fred-flintstone' |
| 145 | +``` |
| 146 | + |
| 147 | +## Restricciones de tipo [Ord] |
| 148 | + |
| 149 | +A veces queremos restringir los tipos genéricos que podemos usar en alguna firma de alguna manera u otra. |
| 150 | + |
| 151 | +Podríamos querer una función máxima que pueda operar sobre Números, en Cadenas, en Fechas, pero no en Objetos arbitrarios. |
| 152 | + |
| 153 | +Queremos describir tipos ordenados, aquellos para los cuales **a < b siempre devolverá un resultado significativo** |
| 154 | + |
| 155 | +`maximum :: Ord a => [a] -> a` |
| 156 | + |
| 157 | +```javascript |
| 158 | +const maximum = (vals) => |
| 159 | + reduce((curr, next) => (next > curr ? next : curr), head(vals), tail(vals)); |
| 160 | +maximum([3, 1, 4, 1]); //=> 4 |
| 161 | +maximum(["foo", "bar", "baz", "qux", "quux"]); //=> 'qux' |
| 162 | +maximum([ |
| 163 | + new Date("1867-07-01"), |
| 164 | + new Date("1810-09-16"), |
| 165 | + new Date("1776-07-04"), |
| 166 | +]); //=> new Date("1867-07-01") |
| 167 | +``` |
| 168 | + |
| 169 | +`Ord a ⇒ [a] → a` dice que la máxima toma una colección de elementos de algún tipo, pero ese tipo debe adherirse a Ord. |
| 170 | + |
| 171 | +En JS, no hay forma de garantizar que el usuario no nos pasará [1, 2, 'a', false, undefined, null]. |
| 172 | +Así que toda nuestra anotación de tipo es **descriptiva y aspiracional** en lugar de ser impuesta por el compilador, como sería en, digamos, Haskell. |
| 173 | + |
| 174 | +## Firmas múltiples |
| 175 | + |
| 176 | +A veces, en lugar de tratar de encontrar la versión más genérica de una firma, es más sencillo enumerar varias firmas relacionadas por separado. |
| 177 | +Podríamos hacer eso como abajo: |
| 178 | + |
| 179 | +`getIndex :: a -> [a] -> Number` |
| 180 | +`:: String -> String -> Number` |
| 181 | + |
| 182 | +```javascript |
| 183 | +const getIndex = curry((needle, haystack) => haystack.indexOf(needle)); |
| 184 | +getIndex("ba", "foobar"); //=> 3 |
| 185 | +getIndex(42, [7, 14, 21, 28, 35, 42, 49]); //=> 5 |
| 186 | +``` |
| 187 | + |
| 188 | +## Funciones variadicas (específicas para Ramda) |
| 189 | + |
| 190 | +En Haskell, todas las funciones tienen una aridad fija. Pero Javsacript tiene que lidiar con funciones variadicas. |
| 191 | +`flip :: (a -> b -> ... -> z) -> (b -> a -> ... -> z)` |
| 192 | + |
| 193 | +```javascript |
| 194 | +const flip = (fn) => |
| 195 | + function (b, a) { |
| 196 | + return fn.apply(this, [a, b].concat([].slice.call(arguments, 2))); |
| 197 | + }; |
| 198 | +flip((x, y, z) => x + y + z)("a", "b", "c"); //=> 'bac' |
| 199 | +``` |
| 200 | + |
| 201 | +## Objetos simples |
| 202 | + |
| 203 | +Cuando un objeto se utiliza como un diccionario de valores de tipo similar (a diferencia de su otro papel como un Registro), entonces los tipos de las claves y los valores pueden volverse relevantes. |
| 204 | +Entonces podríamos representarlos así: |
| 205 | +`keys :: {k: v} -> [k]` |
| 206 | +`values :: {k: v} -> [v]` |
| 207 | + |
| 208 | +```javascript |
| 209 | +keys({ a: 86, b: 75, c: 309 }); //=> ['a', 'b', 'c'] |
| 210 | +values({ a: 86, b: 75, c: 309 }); //=> [86, 75, 309] |
| 211 | +``` |
| 212 | + |
| 213 | +## Ejemplo complejo |
| 214 | + |
| 215 | +`Lens s a -> (a -> a) -> s -> s` |
| 216 | +`Lens s a = Functor f => (a -> f a) -> s -> f s` |
| 217 | + |
| 218 | +Comenzamos con el alias de tipo, Lens s a = Functor f ⇒ (a → f a) → s → f s. |
| 219 | +Esto nos dice que el tipo Lens **está parametrizado por dos variables genéricas, s, y a**. |
| 220 | +Sabemos que hay una restricción en el tipo de la variable f utilizada en una Lens: **debe ser un Functor**. |
| 221 | +Con eso en mente, vemos que una Lens es una función acurruada de dos parámetros, el primero siendo una función de |
| 222 | +un valor del tipo genérico a a uno del tipo parametrizado f a, y el segundo siendo un valor del tipo genérico s. |
| 223 | + |
| 224 | +**El resultado** es un valor del tipo parametrizado `f・s` |
| 225 | + |
| 226 | +<div class="bibliography"> |
| 227 | +Bibliografía:<br><br> |
| 228 | + |
| 229 | +- [gentle introduction to functional javascript style](https://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-style#hindley-milnertypesignatures) |
| 230 | +- [function type signatures in Javascript](https://hackernoon.com/function-type-signatures-in-javascript-5c698c1e9801) |
| 231 | +- [Type signatures in Ramda](https://github.com/ramda/ramda/wiki/Type-Signatures) |
| 232 | +</div> |
0 commit comments