Uploaded avatar of iHiD

¡Llegó Marzo mecánico!

@iHiD
hace Más de 3 años
Video

Te damos la bienvenida a nuestro segundo mes temático: Mechanical March. Este mes nos enfocamos en los lenguajes de sistema, esos que se compilan a código máquina.

Esto es en parte una entrada y en parte una transcripción del video de Mechanical March. Te voy a dar una introducción rápida al mes y después veremos un poco los lenguajes de sistema, su evolución a lo largo de la historia, los pros y los contras de compilar a código máquina, y un vistazo rápido a cada uno de los lenguajes destacados. Me acompaña de nuevo Erik, que va a hablar la mayor parte en la segunda mitad. Pero yo voy a empezar con algo de la información práctica.

Entonces, primero, los lenguajes destacados de este mes. Son C, C++, D, Go, Nim, Rust, V y Zig. Para obtener la insignia de Mechanical March necesitas completar cinco ejercicios en uno de esos lenguajes. Nuestro track de Go tiene uno de los mejores temarios de Exercism, así que te recomiendo mucho que lo pruebes. En Exercism también somos grandes fanáticos de Nim, ya que es un lenguaje relativamente simple para empezar y muy fácil de escribir, así que sin duda te recomiendo que también lo pruebes.

También tenemos cinco ejercicios destacados para probar:

  • Lista enlazada o lista enlazada simple (según el lenguaje): asignar y liberar memoria, punteros
  • Apretón de manos secreto: operaciones a nivel de bits
  • Pangrama: bucles for, strings y caracteres
  • Criba: arrays, bucles for
  • Búsqueda binaria: arrays, bucles

Hay una insignia nueva que anuncié en el video de actualización que hice hace unos días, y es por completar los cinco ejercicios destacados en los lenguajes temáticos. Así que para obtener esa insignia tendrás que completar todos esos lenguajes en un lenguaje de sistema en algún momento del año.

También tenemos muchas cosas divertidas en camino: entrevistas con un par de personas del equipo principal de Go, con suerte también de Rust y de algunos de los otros lenguajes. Además vamos a tener muchas transmisiones en vivo durante todo el mes. ¡Y también llegará pronto algo de merch de Mechanical March!

Entonces, profundicemos un poco más en el lado técnico.

¿Para qué se usan estos lenguajes?

Bueno, los usamos en todo Exercism. Nuestra CLI está escrita en Go, nuestra herramienta interna de gestión de tracks llamada configlet está escrita en Nim, y la biblioteca clave que cuenta las líneas de código de tus soluciones está escrita en Rust. Erik, ¿por qué elegimos esos lenguajes para esas partes de las herramientas?

CLI:

  • Creo que usamos Go porque era con lo que Katrina estaba más familiarizada.
  • Go es excelente para estas herramientas de línea de comandos más bien pequeñas.
  • El código de Go es relativamente sencillo, lo que facilita las contribuciones
  • Los binarios de Go son fáciles de desplegar porque no requieren un entorno de ejecución
  • Go hace bien la compilación cruzada

Nim

  • Nim tiene casi las mismas ventajas que Go
  • No teníamos mucha gente que supiera Go y pudiera mantener la CLI, así que cambiamos a Nim

¿En qué otros tipos de lugares esperarías encontrar estos lenguajes en uso?

  • En cualquier lugar donde el rendimiento sea importante (drivers, juegos, sistemas operativos, sistemas de compilación/compiladores)
  • En cualquier lugar donde los recursos sean limitados (por ejemplo, software embebido)
  • Cualquier cosa que necesite ser altamente portátil, es decir: que se ejecute en muchas plataformas diferentes

¿Qué es el código máquina?

Como dije antes, estos lenguajes del Mechanical March se distinguen por compilarse a código máquina. ¿Puedes explicar un poco qué es el código máquina y qué es, en contraste, el bytecode?

  • El código máquina es código que puede ejecutarse directamente en la máquina.
  • El bytecode, en cambio, necesita otro código que interprete o compile el bytecode a código máquina. Por lo tanto, el bytecode requiere un paso intermedio antes de poder ejecutarse.

¿Cuáles son los pros y los contras del código máquina frente al bytecode?

Pros:

  • Inicio más rápido (sin paso de compilación JIT)
  • Menor uso de memoria (no se carga un entorno de ejecución, no hay bytecode en memoria, ideal para sistemas embebidos)
  • La máquina destino no necesita tener instalado un entorno de ejecución (importante para mantener pequeños los contenedores de Docker)

Contras:

  • No es portátil. El bytecode es portátil, pero el código máquina compilado es específico de una plataforma
  • No permite optimizaciones avanzadas como la optimización guiada por perfil (determinar la mejor manera de compilar el código (o el bytecode) después de haberlo ejecutado un tiempo)

Nota: son posibles los enfoques híbridos, en los que el lenguaje se compila a bytecode y luego se usa otra herramienta para compilar ese bytecode a código máquina.

La evolución de la programación de sistemas

Ok. Veamos un poco la evolución de algunos de los lenguajes de este mes. Primero, empecemos por dónde comenzó todo con C y cómo evolucionó C++ a partir de ahí. Cuéntanos un poco sobre esos lenguajes

C es un lenguaje de muy bajo nivel. Se siente como si estuvieras apenas un poco por encima del código máquina. Eso lo hace muy potente y altamente optimizable, pero también algo propenso a bugs (por ejemplo, excepciones de puntero nulo y desbordamientos de búfer). La gestión de memoria es totalmente manual y, por lo tanto, responsabilidad de quien programa, lo que puede provocar bugs o fugas de memoria. C++ es como C pero con soporte para programación orientada a objetos. Sigue siendo bastante de bajo nivel y requiere que hagas la gestión de memoria de forma manual. ¡Tanto C como C++ te permiten escribir ensamblador en línea (ASM)!

¿Y qué hay de los lenguajes de sistema más nuevos? ¿Cómo han evolucionado?

Todos los lenguajes modernos de programación de sistemas admiten gestión automática de memoria, ya sea mediante conteo de referencias, un recolector de basura o algún otro mecanismo.

Los primeros lenguajes de programación de sistemas admiten punteros nulos (a lo que Tony Hoare llamó su «error de mil millones de dólares»). Son tristemente famosos por provocar errores en tiempo de ejecución y vulnerabilidades. Muchos lenguajes modernos eliminan el null, o al menos requieren un esfuerzo para usarlo.

Otro cambio es el paso de que los valores mutables sean lo predeterminado a que los valores inmutables lo sean por defecto. Por ejemplo, Rust y Vlang tienen valores inmutables por defecto, y hay que optar explícitamente por la mutabilidad.

Todos los lenguajes más nuevos tienen soporte para la interoperabilidad con C (o C++), ya que se ha escrito muchísimo código en esos lenguajes.

Otra cosa interesante es que algunos de los lenguajes más nuevos no se compilan directamente a código máquina, sino que usan otras herramientas para lograrlo. Por ejemplo, Rust y Zig usan LLVM, mientras que Nim permite usar una gran variedad de compiladores. Esto se conoce como transpilación.

¿Y qué hay de cosas como las macros y la metaprogramación?

Hay una división interesante con respecto a las macros y la metaprogramación. Las macros de C/C++ son potentes, pero tienen una reputación algo dudosa de ser difíciles de manejar. Rust, Nim y D ofrecen metaprogramación potente, pero de una manera mucho más agradable. En cambio, VLang y Zig mencionan explícitamente que la ausencia de macros es una característica de su lenguaje, y Go tiene un enfoque diferente con go generate.

Los lenguajes de sistema tienen la reputación de ser bastante de bajo nivel. ¿Sigue siendo justo eso?

Los lenguajes más nuevos trabajan en niveles de abstracción más altos que C/C++. Por ejemplo, Rust, D y Nim también permiten una forma muy funcional de escribir código. Nim y D incluso tienen el concepto de funciones «puras».

Introducción a los lenguajes del mes

Así que sería bueno ver cada uno de los lenguajes por turno. Todos los lenguajes tienen similitudes: todos son de tipado fuerte y estático. Pero veamos en qué se diferencian. ¿Empezamos con C?

C

  • Desarrollado por Dennis Ritchie
  • Uno de los lenguajes más antiguos y probablemente el más usado del mundo
  • Montones de software están escritos en C, como Unix y Linux
  • Muy influyente (piensa en: la existencia de lenguajes tipo C)
  • Gestión manual de memoria
  • Muy eficiente (cerca «del metal»)
  • Se ejecuta en cualquier parte
  • Perfecto para sistemas embebidos
  • Un lenguaje bastante pequeño

C++

  • Desarrollado por Bjarne Stroustrup
  • Sucesor de C, pero con orientación a objetos añadida (C con clases)
  • Ayudó a popularizar la programación orientada a objetos
  • Más características de alto nivel que C
  • Admite programación genérica mediante plantillas
  • Añade soporte de módulos mediante espacios de nombres
  • Muchos motores de juegos están escritos en C++, y grandes partes de Windows
  • Gestión manual de memoria
  • Sigue evolucionando con nuevas características que se añaden con regularidad (una especificación grande)

D

  • Desarrollado por Walter Bright, y más tarde se le unió Andrei Alexandrescu
  • Originalmente concebido como un C++ rediseñado (aprendiendo de sus «errores»), se inspira en muchos otros lenguajes
  • Multiparadigma, admite programación imperativa, orientada a objetos y funcional
  • Interoperabilidad fácil con C/C++
  • Sintaxis de llamada de función uniforme
  • Evaluación de funciones en tiempo de compilación (por ejemplo, generar una máquina de estados de regex en tiempo de compilación)
  • Admite programación funcional y funciones «puras»
  • Muchas características de seguridad
  • Seguridad de memoria mediante @safe
  • Contratos (condiciones previas y posteriores, invariantes)
  • Funciones puras
  • Enfoque en los tests unitarios, con los tests junto al código fuente que se prueba (requirió una excepción en el sitio web de Exercism :))
  • Unicode

Rust

  • Desarrollado por Graydon Hoare, empleado de Mozilla Research, luego adoptado oficialmente por Mozilla y ahora parte de la Rust Foundation
  • Multiparadigma, admite POO (pero con opiniones firmes, por ejemplo sin herencia), programación imperativa y funcional (tipos Option/Result, coincidencia de patrones)
  • Muchas herramientas nuevas escritas en Rust (por ejemplo SWC, pero también Gleam, y el segundo lenguaje soportado en el kernel de Linux; dependemos de él para el contador de líneas de código)
  • Enfoque en la fiabilidad y el rendimiento
  • Construido sobre LLVM
  • El lenguaje más querido en la encuesta de StackOverflow durante los últimos 7 años
  • Rápido, en parte gracias a un núcleo y una biblioteca estándar mínimos
  • Seguro, tanto en seguridad de memoria como en seguridad de hilos mediante ownership y lifetimes, inmutable por defecto
  • Sistema de tipos potente, que atrapa muchos bugs en tiempo de compilación (sobre todo relacionados con la memoria). El compilador produce errores muy útiles
  • Todo incluido: compilador, herramienta de compilación, formateador, gestor de paquetes, integraciones con el IDE
  • Documentación excelente (incluso hay un documento enorme sobre cómo funciona el compilador)
  • Portátil: se compila a un único binario estático y no requiere que se instale un entorno de ejecución
  • Interoperabilidad fácil con código C
  • Abstracciones de costo cero
  • Concurrencia sin miedo
  • Macros

Nim

  • Desarrollado por Andreas Rumpf (originalmente llamado Nimrod)
  • Sintaxis inspirada en Python
  • Multiparadigma
  • Se usa en Exercism en configlet
  • Gran rendimiento: iteradores sin sobrecarga, preferencia por la asignación en la pila de tipos basados en valores
  • Sistema de tipos moderno y expresivo: inferencia de tipos, tuplas, genéricos, tipos suma, async/await
  • Recolección de basura, pero con soporte para gestión de memoria determinista (varias opciones de gestión de memoria)
  • Ejecución de código en tiempo de compilación
  • Sintaxis de llamada uniforme
  • Macros: fácil de extender el lenguaje
  • Sistema de efectos: codificar los efectos secundarios en el sistema de tipos

Go

  • Desarrollado por Robert Griesemer, Rob Pike y Ken Thompson en Google
  • Mayormente imperativo/procedural, con un enfoque similar a la orientación a objetos (pero sin herencia)
  • Se usa en grandes proyectos como Docker y Kubernetes. También es excelente para backends y CLIs (por ejemplo esbuild)
  • Busca ser lo bastante simple como para tenerlo en la cabeza (poca sintaxis)
  • Seguridad de memoria mediante recolector de basura
  • Rápido: compilación rápida, tests rápidos y ejecución rápida. ¡Soporte integrado para escribir benchmarks!
  • Con opiniones firmes: mucho esfuerzo por influir en el estilo del código Go: poca sintaxis, formateo del código mediante go fmt, herramientas para verificar el uso idiomático del código, errores en cosas que en otros lenguajes son advertencias (por ejemplo variables sin usar), documentación que enumera los modismos de Go que conviene usar
  • Sistema de tipos ligero que hace a Go muy flexible (admite inferencia de tipos)
  • Portátil: se compila a un único binario estático y no requiere que se instale un entorno de ejecución. Compilación cruzada sencilla. Concurrencia mediante goroutines (ligeras) y comunicación mediante canales
  • Tipado estructural mediante interfaces (parecido al duck typing, pero verificado de forma estática)
  • Manejo de errores: el lenguaje anima a comprobar y gestionar los errores

VLang

  • Desarrollado por Alexander Medvednikov y Delyan Angelov
  • Inspirado en Go:
  • La misma estrategia de «solo una forma de hacer las cosas»
  • La misma estrategia de «sintaxis mínima»
  • Corrutinas
  • Diferente de Go:
  • Sin nil/null, pero usa un tipo result
  • Inmutable por defecto
  • Tipos suma (funcionales)
  • Interpolación de strings
  • Entorno de ejecución y binarios más pequeños
  • Coincidencia de patrones
  • Trabajan para que el GC sea opcional (autofree)
  • Interoperabilidad con C sin costo
  • Genera documentación a partir del código
  • Compilador rápido que usa poca memoria

Zig

  • Desarrollado por Andrew Kelley
  • Bastante poca sintaxis (un archivo de gramática PEG de 500 líneas)
  • Ejecución de código y reflexión en tiempo de compilación
  • Busca ser «obvio», sin flujo de control, asignaciones ni macros/metaprogramación ocultos
  • Asignación manual de memoria
  • Admite diferentes asignadores
  • Las funciones de la biblioteca estándar que asignan memoria tienen un parámetro de asignador
  • El framework de tests puede detectar fugas de memoria
  • Seguridad:
  • Los errores son valores y deben gestionarse
  • Sin null, usa un tipo opcional
  • Los tests se pueden escribir en el mismo archivo que el código fuente (como en D)
  • Usa LLVM como backend
  • Puede compilar código C/C++
  • Interoperabilidad fácil con C
  • Compilación cruzada sencilla

Conclusión

Vamos a dejarlo aquí, ya que imagino que a estas alturas todos tienen la cabeza bastante llena.

Espero que haya sido una introducción útil y divertida a los lenguajes destacados de este mes. Ojalá te diviertas muchísimo explorando estos lenguajes. A Erik y a mí nos encantaría saber cuáles eliges y qué te parecen, así que por favor publica algo en los comentarios o en el foro.

¡Gracias por ver!

Translation missing: es-419.number.nth.ordinalized Mar 2023 · ¿Te resultó útil?