Creo que he aprendido algunos / muchos / la mayoría de los conceptos básicos que subyacen a la programación funcional en JavaScript. Sin embargo, tengo problemas para leer específicamente el código funcional, incluso el código que he escrito, y me pregunto si alguien puede darme consejos, consejos, mejores prácticas, terminología, etc. que puedan ayudar.
Toma el código a continuación. Escribí este código. Su objetivo es asignar un porcentaje de similitud entre dos objetos, entre say {a:1, b:2, c:3, d:3}
y {a:1, b:1, e:2, f:2, g:3, h:5}
. Produje el código en respuesta a esta pregunta sobre Stack Overflow . Como no estaba seguro exactamente de qué tipo de similitud porcentual preguntaba el afiche, proporcioné cuatro tipos diferentes:
- el porcentaje de las claves en el primer objeto que se puede encontrar en el segundo,
- el porcentaje de los valores en el primer objeto que se puede encontrar en el segundo, incluidos los duplicados,
- el porcentaje de los valores en el primer objeto que se puede encontrar en el segundo, sin duplicados permitidos, y
- el porcentaje de pares {clave: valor} en el primer objeto que se puede encontrar en el segundo objeto.
Comencé con un código razonablemente imperativo, pero rápidamente me di cuenta de que este era un problema muy adecuado para la programación funcional. En particular, me di cuenta de que si podía extraer una función o tres para cada una de las cuatro estrategias anteriores que definían el tipo de característica que buscaba comparar (por ejemplo, las claves o los valores, etc.), entonces podría ser capaz de reducir (perdonar el juego de palabras) el resto del código en unidades repetibles. Ya sabes, manteniéndolo SECO. Entonces cambié a programación funcional. Estoy bastante orgulloso del resultado, creo que es razonablemente elegante, y creo que entiendo lo que hice bastante bien.
Sin embargo, incluso después de haber escrito el código yo mismo y comprender cada parte de él durante la construcción, cuando ahora lo recuerdo, sigo estando un poco desconcertado tanto sobre cómo leer cualquier media línea particular como sobre cómo leerlo. "grok" lo que realmente está haciendo cualquier media línea de código en particular. Me encuentro haciendo flechas mentales para conectar diferentes partes que se degradan rápidamente en un lío de espagueti.
Entonces, ¿alguien puede decirme cómo "leer" algunos de los bits de código más intrincados de una manera concisa y que contribuya a mi comprensión de lo que estoy leyendo? Supongo que las partes que más me atraen son aquellas que tienen varias flechas gruesas en una fila y / o partes que tienen varios paréntesis en una fila. Nuevamente, en esencia, eventualmente puedo descubrir la lógica, pero (espero) que haya una mejor manera de hacerlo rápida y clara y directamente "tomando" una línea de programación funcional de JavaScript.
Siéntase libre de usar cualquier línea de código de abajo, o incluso otros ejemplos. Sin embargo, si quieres algunas sugerencias iniciales de mí, aquí hay algunas. Comience con una razonablemente simple. De cerca de la final del código, hay una que se pasa como parámetro a una función: obj => key => obj[key]
. ¿Cómo se lee y comprende eso? Un ejemplo más es una función completa de cerca del inicio: const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));
. La última map
parte me atrapa en particular.
Tenga en cuenta, en este punto en el tiempo que estoy no en busca de referencias a Haskell o notación simbólica abstracta o los fundamentos de currificación, etc. Lo que estoy buscando es frases en inglés que en silencio puede boca mientras mira a una línea de código. Si tiene referencias que aborden específicamente exactamente eso, genial, pero tampoco estoy buscando respuestas que digan que debería ir a leer algunos libros de texto básicos. Lo he hecho y obtengo (al menos una cantidad significativa de) la lógica. También tenga en cuenta que no necesito respuestas exhaustivas (aunque tales intentos serían bienvenidos): incluso las respuestas cortas que proporcionan una forma elegante de leer una sola línea particular de código problemático serían apreciadas.
Supongo que una parte de esta pregunta es: ¿ puedo incluso leer código funcional linealmente, ya sabes, de izquierda a derecha y de arriba a abajo? ¿O uno se ve obligado a crear una imagen mental de cableado tipo espagueti en la página de código que definitivamente no es lineal? Y si uno debe hacer eso, todavía tenemos que leer el código, entonces, ¿cómo tomamos texto lineal y conectamos los espaguetis?
Cualquier consejo sería apreciado.
const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };
// x or X is key or value or key/value pair
const getXs = (obj, getX) =>
Object.keys(obj).map(key => getX(obj)(key));
const getPctSameXs = (getX, filter = vals => vals) =>
(objA, objB) =>
filter(getXs(objB, getX))
.reduce(
(numSame, x) =>
getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
0
) / Object.keys(objA).length * 100;
const pctSameKeys = getPctSameXs(obj => key => key);
const pctSameValsDups = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));
console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys: ', pctSameKeys (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups (obj1, obj2));
console.log('% same values, no duplicates: ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps (obj1, obj2));
// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys: 50
// % same values, incl duplicates: 125
// % same values, no duplicates: 75
// % same properties (k/v pairs): 25
fuente
No he realizado mucho trabajo altamente funcional en Javascript (lo cual diría que es esto: la mayoría de las personas que hablan sobre Javascript funcional pueden estar usando mapas, filtros y reducciones, pero su código define sus propias funciones de nivel superior , que es algo más avanzado que eso), pero lo he hecho en Haskell, y creo que al menos parte de la experiencia se traduce. Te daré algunos consejos sobre las cosas que he aprendido:
Especificar los tipos de funciones es realmente importante. Haskell no requiere que especifique cuál es el tipo de función, pero incluir el tipo en la definición hace que sea mucho más fácil de leer. Si bien Javascript no admite la escritura explícita de la misma manera, no hay razón para no incluir la definición de tipo en un comentario, por ejemplo:
Con un poco de práctica trabajando con definiciones de tipo como esta, hacen que el significado de una función sea mucho más claro.
La denominación es importante, quizás incluso más que en la programación de procedimientos. Muchos programas funcionales están escritos en un estilo muy conciso que es pesado en la convención (por ejemplo, la convención de que 'xs' es una lista / matriz y que 'x' es un elemento en ella es muy generalizado), pero a menos que comprenda ese estilo fácilmente sugeriría nombres más detallados. Mirando los nombres específicos que ha usado, "getX" es algo opaco y, por lo tanto, "getXs" tampoco ayuda mucho. Llamaría a "getXs" algo así como "applyToProperties", y "getX" probablemente sería "propertyMapper". "getPctSameXs" sería entonces "percentPropertiesSameWith" ("con")
Otra cosa importante es escribir código idiomático . Noté que estás usando una sintaxis
a => b => some-expression-involving-a-and-b
para producir funciones curry. Esto es interesante y podría ser útil en algunas situaciones, pero no está haciendo nada aquí que se beneficie de las funciones currificadas y sería más idiomático Javascript usar funciones tradicionales de argumentos múltiples en su lugar. Hacerlo puede hacer que sea más fácil ver lo que está sucediendo de un vistazo. También lo está utilizandoconst name = lambda-expression
para definir funciones, donde sería más idiomático utilizarlofunction name (args) { ... }
. Sé que son semánticamente ligeramente diferentes, pero a menos que confíes en esas diferencias, te sugiero que uses la variante más común cuando sea posible.fuente
obj => key => ...
se puede simplificar(obj, key) => ...
porque más tardegetX(obj)(key)
también se puede simplificarget(obj, key)
. Por el contrario, otra función currificada(getX, filter = vals => vals) => (objA, objB) => ...
no puede simplificarse fácilmente, al menos en el contexto del resto del código tal como está escrito.