Programació Funcional a JavaScript: La aritat i les tuples

X

Privadesa i galetes

Aquest lloc utilitza galetes. A l’continuar, acceptes el seu ús. Aconsegueix més informació; per exemple, sobre com controlar les galetes.

Entès

Anuncis

14787298814_2ed9b48aa0_k.jpg

Tornem a la sèrie de posts dedicats a la programació funcional en JavaScript. Ens havíem quedat explicant la importància de l’ús dels mètodes encadenats per aconseguir codi més expressiu.

Al posts d’avui parlarem dels desavantatges que ens implica l’ús de mètodes encadenats. Aprendrem una nova forma de modularitzar nostre codi i de treure molt més partit a la reutilització de codi per mitjà de l’concepte de canonades que es pot fer servir en programació funcional.

Explicarem què són les canonades i les característiques que necessitem que compleixin les funcions per poder aconseguir aquesta sensació de codi que flueix per mitjà d’una canonada connectada. Explicarem què és la aritat i com les tuples o la currificació poden ajudar-nos a gestionar la complexitat de aridades elevades.

Com sempre, em teniu a la vostra disposició en els comentaris per si pogués resoldre alguna de les vostres dubtes. Comencem:

El problema dels mètodes encadenats

Els mètodes encadenats ens ajuden molt a escriure millor codi. Ho vam poder veure en el capítol anterior. Això no canvia. No obstant això, trobem un gran problema als mètodes encadenats.

Si ens recordem per exemple de mètode ‘filter’ de ES5, el mètode retorna un nou objecte array que conté de nou els mètodes. D’aquí la màgia de poder concatenar. Desafortunadament, el patró és una arma de doble tall ja que els mètodes es troben tan acoblats a el tipus d’objecte que els confina que ens dóna molt poca flexibilitat de reutilització.

Amb mètodes concatenats estem obligats a fer servir només el conjunt d’operacions que proporciona el propi objecte. Seria ideal que poguéssim trencar aquesta cadena i que d’aquí a el muntatge poguéssim organitzar funcions al nostre gust. Els mètodes encadenats no són la solució, així que haurem de buscar-nos una altra alternativa.

Les canonades

I l’alternativa ve en forma de canonada. Si amb els mètodes encadenats perdíem expressivitat i guanyàvem massa acoblament, amb l’organització de funcions en una canonada podríem tenir tot el contrari.

La clau d’organitzar funcions en canonades és la de poder comptar amb un arsenal de funcions desacoblades que es puguin executar seqüencialment. Com en una canonada, les nostres funcions passarien estats d’una funció a una altra. Els paràmetres de sortida d’una funció suposaran els paràmetres d’entrada de la següent.

D’aquesta manera, jo puc crear peces de codi molt independents, que compleixen una funció concreta i que per composició de les seves entrades i les seves sortides puguin crear un programa més gran.

Si fem servir la notació usada en Haskell, podrem explicar millor això. Podem definir funcions d’aquesta manera:

<nombre_funcion> :: <entradas> -> <salidas>

A la fin es molt semblant a definir lambdes, només que en aquest cas, ens interessa més saber que tipus de dades entren i surten d’una funció. És l’especificació d’una caixa negra.

Vist això, vegem un exemple. Jo puc compondre per mitjà de canonades les següents funcions:

f :: A -> Bg :: B -> C

on f i g són el nom d’una funció i A, B i C són tipus diferents. En aquest cas com la sortida de f coincideix amb el tipus de B. Jo podria executar-les com si fos una canonada:

g(f(A)); // donde se devuelve C

Això que a primera vista sembla obvi , pot ajudar-nos molt. No serà fàcil aconseguir això en entorns més complexos ja que hi haurà funcions que retornin objectes que no puguin ser reutilitzats.

No obstant això, és una cosa que hem de tenir en compte a l’hora de dissenyar funcions. És important veure que necessitem per fer que les funcions siguin compatibles entre si. Per conscienciar-nos d’això en el procés de disseny, és important que tinguem en compte dos factors: la compatibilitat de tipus de les entrades i sortides i l’aritat de les funcions.

Compatibilitat de tipus

Com venim dient, la compatibilitat de tipus fa referència al fet que els tipus de sortida de funció es relacionen amb els tipus dels paràmetres d’entrada d’una altra. No és un problema de què hàgim de preocupar-nos molt en JavaScript doncs a al final a l’trobar-nos amb un llenguatge dèbilment tipat, podem tenir molta més flexibilitat.

A la fin això és el que se sol denominar ‘duck-types’, no m’interessa en les canonades de JavaScript tant que els tipus siguin idèntics com que l’objecte que retorna una funció, es comporti com l’el tipus de el paràmetre d’entrada que espera la meva següent funció. És a dir, si camina com un ànec i claca com un ànec, és un ànec. Si un objecte es comporta un tipus determinat i té els atributs que jo espere en aquest tipus, per a mi és d’aquest tipus. És el bo i el dolent de JavaScript.

Si volguéssim fer les nostres funcions estrictament compatibles, una bona idea seria utilitzar TypeScript. Us deixo la xerrada de Micael Gallec de nou per aquí per si us interessa aquesta alternativa.

Per tant, quan dissenyem funcions, hem de tenir molt clars els tipus amb els que juguen les nostres funcions. Imaginem que volem fer un sistema que donat un nombre d’identitat, li llevem els espais i els guions per a treballar millor amb això. Dissenyarem dues funcions com aquestes:

trim :: String -> Stringnormalize :: String -> String

Hem creat dues funcions compatibles per a la seva composició ja que l’entrada d’una coincideix amb el tipus de l’altra. A més són commutatives perquè si jo executo trim i després normalize, obtinc el mateix resultat que si va executar primer ‘normalize’ i després ‘trim’. Vegem la implementació.

// trim :: String -> Stringconst trim = (str) => str.replace(/^\s*|$/g, '');// normalize :: String -> Stringconst normalize = (str) => str.replace(/\-/g, '');normalize(trim('444-444-444')); // 444444444trim(normalize('444-444-444')); // 444444444

D’acord, l’exemple és molt tonto, però és important que tinguem consciència d’això en el futur. Tindrem més problemes en JavaScript amb el nombre de paràmetres que pot permetre una funció.

La aritat i les tuples

Coneixem per aritat a el nombre de paràmetres d’entrada que accepta una funció. És comú indicar també com a longitud d’una funció.

La aritat d’una funció pot fer que una funció guanyi en complexitat. Que una funció accepta diversos paràmetres fa que tots els arguments s’hagin hagut de calculats primer, això els fa perdre versatilitat.

A més, pot donar-se el cas que la complexitat interna d’una funció sigui més gran (que hi hagi gran aritat no comporta sempre major complexitat, simplement pot ser un símptoma d’això).

Comptar amb aridades elevades a més, fa que tinguem funcions menys flexibles. Les funcions amb un paràmetre (o unaries) solen ser més flexibles perquè, en general, només tenen una única responsabilitat, realitzar algun tipus d’operació o càlcul sobre el paràmetre d’entrada i tornar el resultat.

Malauradament , en la vida real és complicat trobar-se amb només funcions unaries. No obstant això, podem fer que una funció retorna una tupla, d’aquesta manera la següent funció seria unària i ja comptaria amb tot l’estat que necessita.

Una tupla és un llistat finit i ordenat de valors que es representen de la següent forma (a, b, c). Les tuples són un paquet de valors immutables, que tenen una relació i que es poden usar per tornar diversos valors a altres funcions.

Podem simular aquest comportament amb un objecte JSON o amb objectes array, si això com veiem a continuació amb les tuples tenim més avantatges:

  • Són immutables.
  • Eviten la creació de nous tipus. Moltes vegades crear classes per a un únic ús específic pot ser bastant poc usable per al desenvolupador.
  • Eviten crear arrays heterogenis: treballar amb aquests arrays heterogenis suposa molt desenvolupament de comprovacions de tipus. Els arrays és millor només usar-los quan es treballa amb col·leccions d’objectes amb el mateix tipus.

El problema que tenim, és que encara que les tuples és un element en la majoria de llenguatges de programació funcional com Scala, en JavaScript no existeix res que doni suport a les tuples. La millor opció per a això és que nosaltres creiem una petita llibreria que de suport a les tuples. L’opció que Luis Atencio proposa en el seu llibre és aquesta:

const Tuple = function () { const typeInfo = Array.prototype.slice.call(arguments, 0); const _T = function () { const values = Array.prototype.slice.call(arguments, 0); if (values.some((val) => val === null || val === undefined) { throw new ReferenceError('Tuples may not have any null values'); } if (values.length !== typeInfo.length) { throw new TypeErro('Tuple arity does not match its prototype'); } values.map(function (val, index) { this = checkType(typeInfo)(val); }, this); Object.freeze(this); }; _T.prototype.values = function () { return Object.keys(this).map(function (k) { return this; } } return _T;}

Uf, si, molta chicha que tallar. Si ens adonem és una funció tuple que va tornar una classe. La funció serveix perquè puguem indicar la mida i els tipus dels elements de la tupla. Per tant podrem fer això, per exemple:

const Status = Tuple(Number, String);

‘Status’ és una classe. Si us adoneu tuple, està tornant _T. Per mitjà d’un clousure se’ns retorna una classe omplir amb el que necessitem. Màgic. Ara podem definir una nova tupla d’aquesta manera:

const status = new Status(401, 'No Authorize');

Quan instanciamos ‘status’ fa una comprovació que no s’estan passant valors nuls i que la grandària de la tupla creada correspon amb la mida de la tupla per omissió. També comprova que el tipus que s’ha passat és l’esperat.

Finalment té un mètode perquè puguem obtenir els valors en format array.És molt útil perquè si fem servir de nou la desestructuració, podem obtenir els valors de la tupa de l’aquesta manera:

const = status.values();

Bé … no són el més còmode de l’ món les tuples en JavaScript, però tenim formes de simular. I si no haguéssim de simular? I si JS ia les portés de sèrie? Doncs una altra vegada TypeScript ens ajuda en això. Jo en TS podria fer això:

let status: ; status = ; // OK status = ; // Error

Molt millor :). Torno a repetir, tingueu en compte a TypeScript, ha vingut per quedar-se.

Les tuples són un bon mecanisme per evitar l’aritat de funcions. No obstant això hi ha un altre mètode anomenat currificació, que no només ens ajudarà amb l’aritat, sinó que és un bon mecanisme per millorar la modularitat i la reusabilitat, però … crec que ho deixarem aquí per avui 🙂

Conclusió

Entendre les canonades i la seva formes d’usar-les, ens ajudarà a desenvolupar codi més llegible. Seguirem parlant de tècniques necessàries per abastar tots els conceptes que es troben presents en les canonades.

Per ara, crec que és un bon pas saber que dissenyar funcions de manera que tinguin una aritat reduïda i que conèixer els tipus dels paràmetres d’entrada i de sortida de les nostres funcions, ens ajudaran per a realitzar codi molt més mantenible, modularitzable i reutilitzable.

ens llegim 🙂

PD: Us deixo el mapa conceptual de aquesta entrada:

les-canonades-en-javascript

Anteriors posts de Programació Funcional en JavaScript:

Introducció

  1. La Programació Funcional en JavaScript
  2. Programació Funcional a JavaScript: Els Objectes
  3. programació funcional en JavaScript: Les funcions

El control de flux funcional

  1. programació funcional a JavaScript: Els mètodes funcionals
  2. programació funcional en JavaScript: La recursivitat

La modularitat funcional

  1. Programació Funcional a JavaScript: La aritat i les tuples
  2. Programació Funcional a JavaScript: La currificació

Imatge de portada | flickr

Deixa un comentari

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *