- La forma de programar de izquierda a derecha mantiene el programa en un estado válido desde el momento en que se escribe el código, lo que maximiza el soporte de herramientas como el autocompletado del editor
- Las list comprehensions de Python dificultan el autocompletado por el uso de variables no declaradas y la ausencia de inferencia de tipos
- Rust y JavaScript permiten construir programas de forma natural de izquierda a derecha, por lo que el uso de variables y la exploración de métodos resultan más intuitivos
- El estilo funcional en C y Python perjudica una experiencia de codificación eficiente por la baja capacidad de descubrimiento de nombres de funciones o estructuras
- En una lógica de alta complejidad, el código que se desarrolla de izquierda a derecha es más fácil de leer y ofrece mejor mantenimiento y escalabilidad
Programar de izquierda a derecha
El código debe ser válido desde el momento en que se escribe
Limitaciones de las list comprehensions de Python
- La sintaxis de list comprehensions de Python,
words_on_lines = [line.split() for line in text.splitlines()], genera el problema de que el editor no puede ofrecer correctamente autocompletado ni inferencia de tipos, porque necesita acceder a una variable no declarada (line) - Durante el proceso de escribir el código de forma parcial
- si se escribe
words_on_lines = [line.sp, el editor no puede saber el tipo deline, así que no puede sugerir métodos - también se vuelve difícil detectar errores potenciales, como un typo en el nombre de la variable (
lime, por ejemplo)
- si se escribe
- Para recibir sugerencias correctas, hay que escribir código incompleto, y ese proceso resulta poco intuitivo e incómodo
Composición de izquierda a derecha en Rust
- En el ejemplo de Rust,
let words_on_lines = text.lines().map(|line| line.split_whitespace());- como la variable (
line) se considera declarada en el momento en que aparece por primera vez junto con la función anónima, se vuelve posible obtener de inmediato autocompletado y sugerencias de métodos - de hecho, el método
split_whitespacetambién fue fácil de encontrar gracias a la sugerencia automática
- como la variable (
- Este enfoque mantiene el programa siempre en un estado válido al menos de forma parcial, por lo que el IDE o editor puede asistir la escritura de código en tiempo real
Divulgación progresiva (Progressive Disclosure) y usabilidad de APIs
- La divulgación progresiva (Progressive Disclosure) es un principio de diseño en el que el usuario solo experimenta la complejidad que necesita, y también puede aplicarse a la programación
- ejemplo: es similar a la UX de un procesador de texto donde las opciones relacionadas solo aparecen al insertar una imagen
- El lenguaje C carece de este tipo de apoyo
- como no se pueden explorar todas las funciones relacionadas con
FILE *filemediantefile., hay que memorizar patrones en los nombres de funciones (fread,fclose, etc.), y resulta difícil descubrir funcionalidades - en cambio, en un lenguaje ideal, sería posible descubrir progresivamente funciones relacionadas mediante sugerencias de métodos a través de
file.
- como no se pueden explorar todas las funciones relacionadas con
Diferencia en la capacidad de descubrimiento de funciones y métodos
- Comparación entre los ejemplos
map(len, text.split())de Python ytext.split(" ").map(word => word.length)de JavaScript- en Python, como no es predecible cuál será el nombre de la función (
len,length,size, etc.), hay que probar varias opciones para saber cuál funciona realmente - en JavaScript, basta con escribir
.ldespués deword.para que el editor sugiera métodos comolength, lo que da una alta capacidad de descubrimiento - incluso con funciones de orden superior como
map, el valor de retorno real y el tipo de dato se vuelven evidentes de inmediato
- en Python, como no es predecible cuál será el nombre de la función (
Cuanto más compleja es la lógica, mayores son las ventajas de escribir con estructura
- En el caso de una lógica compleja (código largo de Python con
filterylambdaanidados)- hay que revisar repetidamente el inicio y el final del código, y aparecen problemas de legibilidad y de comprensión con expresiones condicionales o emparejamiento de paréntesis
- En la versión equivalente en JavaScript, el código puede leerse y entenderse secuencialmente de arriba hacia abajo y de izquierda a derecha
Principio clave
El código debe ser válido en cada momento en que se escribe
- Incluso al escribir solo
text, el programa permanece en un estado válido - Aunque se escriba hasta
text.split(" "), y luego se continúe con.map(word => word.length), el estado intermedio completo siempre sigue siendo válido - Este patrón de escritura de código incrementa la posibilidad de soporte en tiempo real por parte del editor, y en un entorno REPL incluso permite verificar resultados al instante
Conclusión
- El diseño de APIs y lenguajes debe permitir escribir código de forma natural de izquierda a derecha y crear un programa válido en cada etapa intermedia
- Un buen diseño de APIs es la clave para mejorar esta experiencia de codificación
1 comentarios
Comentarios de Hacker News
SELECT * FROM tablepodría bastar con escribir soloFROM tabley omitir la cláusula SELECT; sé que por quejarme de esto sueno como un viejo regañón, pero es solo una añoranza personalTABLE <table>, que vale la pena revisarfunctools.map)returnfaltante, etc.){3} for {2} in {1}también es posible; estas herramientas ofrecen un punto medio entre una “sintaxis fácil de leer” y una “sintaxis fácil de teclear”; yo me inclino por priorizar la sintaxis legible, aunque sea apoyándose en tooling; no creo que haya que aferrarse necesariamente a la estructura “for-in”bake(divide(add(knead(mix(flour, water, sugar, butter)),eggs),12),450,12), con pipe se vuelve mucho más sencillo y legible:mix(flour, water, sugar, butter) %>% knead() %>% add(eggs) %>% divide(12) %>% bake(temp=450, minutes=12)from some_library import child_modulees muy intuitiva; en JS una estructura comoimport { asYetUnknownModule } from SomeLibraryse siente bastante menos intuitivalen(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs))), me parece mucho mejor aprovechar un array de NumPy, porque no hace falta crear listas nuevas en memoria y además permite operar sobre toda la línea de una vez; por ejemplo: esto refleja mucho mejor la idea de “izquierda a derecha”