Esta es una continuación del artículo
"¿Por qué Racket? ¿Por qué Lisp? Escribí aproximadamente un año después de descubrir
Racket . Como novato, no podía entender las alabanzas que se derramaban sobre Lisp por todos lados. No sabía que pensar. Cómo entender que Lisp finalmente causará una
"iluminación profunda" . Está bien, hermano, dices.
Tenía una pregunta simple:
¿de qué sirve? En un artículo anterior, traté de responderlo y resumí las razones por las cuales alguien querría aprender Lisp o, en particular, Racket.
He compilado una lista de las
nueve características lingüísticas más valiosas para mí como novato en Racket. Por ejemplo, la función número 5 es "la creación de nuevos lenguajes de programación". Este método también se llama
programación orientada al lenguaje o
JOP .
Desde entonces, IOP se ha convertido en mi parte favorita de Racket, y compartí mi admiración en el libro en línea
Beautiful Racket , que explica la técnica ROP y la herramienta Racket.
Un ejemplo en mi trabajo es el
polen . Escribí este lenguaje de programación para una tipografía conveniente de mis libros en línea. En Polen, el párrafo anterior se programa de la siguiente manera:
#lang pollen ◊link["https://beautifulracket.com/appendix/why-racket-why-lisp.html#so-really-whats-in-it-for-me-now"]{ }, Racket. , № 5 — « ». ◊em{- }, ◊em{}.
Otro ejemplo es
brag , un generador de analizador sintáctico (en el estilo de
lex/yacc
), que toma la
gramática BNF como código fuente. Un ejemplo simple para el lenguaje
bf :
#lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Ambos idiomas se implementan en Racket y se pueden ejecutar con el intérprete de Racket habitual o dentro del Racket IDE (llamado DrRacket).
Problemas principales
Y sin embargo ... A pesar de que el libro ha obligado a miles de personas a comenzar a explorar Racket, a veces me parece que pisé el mismo camino rápido que los fanáticos de Lisp que una vez critiqué.
Si NOP es tan genial, ¿por qué pasar unos días leyendo un libro? Derecho? Puedo explicar todo brevemente, sin más preámbulos. Deben responderse dos preguntas simples:
- ¿Qué problemas son los más adecuados para la programación de idiomas?
- ¿Por qué Racket es mejor para crear idiomas?
La segunda pregunta es simple. El primero no es. Me lo preguntaron muchas veces. A menudo citaba la
famosa frase del juez Potter Stewart: la entenderá cuando la vea. La respuesta es lo suficientemente buena para aquellos que están realmente interesados. Pero no para aquellos que están a un lado y quisieran escuchar argumentos sustantivos.
Entonces, lo intentaré. Tenga en cuenta que no soy profesor de informática y no puedo hablar sobre la teoría de los lenguajes de programación. Por el contrario, uso Racket y lenguajes específicos de dominio (DSL) para fines prácticos: mi trabajo diario depende de ellos. Por lo tanto, me enfocaré en aspectos prácticos.
Respuesta corta
- JOP es en realidad un método de diseño de interfaz. Es ideal para tareas que requieren una notación mínima mientras se mantiene la máxima precisión . La notación mínima significa la única notación permitida. Nada mas. La máxima precisión, es decir, el significado de esta notación, es exactamente lo que usted dice. Sin ambigüedades ni patrones. La PIO llega al punto como nada más.
(El impaciente puede pasar a categorías específicas de tareas que se beneficiarán de NOP).
- Racket es ideal para OOP debido a su sistema macro . Funcionan en estilo compilador, simplificando la conversión de código. El sistema macro Racket es mejor que cualquier otro.
En este punto, la mitad de los lectores del artículo querrán publicar comentarios anónimos criticando mis tesis. Pero tenga en cuenta: en cualquier caso, gané. JOP y Racket han aumentado increíblemente mi productividad de programación. Me complace compartir este conocimiento para que usted también pueda aprovechar estos beneficios. Pero también me alegrará si estas herramientas siguen siendo mi arma secreta. En este caso, permaneceré en el 0.01% de los programadores más productivos, obteniendo un resultado más impresionante y rentable que el 99.9% restante
Entonces la elección es tuya.
Respuesta larga
Si piensa en las preguntas más importantes, se reducen a una metapregunta: ¿por qué es difícil explicar los beneficios de las armas nucleares?
Quizás cuando hablamos de
idiomas , el término está cargado de expectativas sobre qué es un idioma y qué hace. Mientras estamos dentro de este paradigma, es difícil entender el valor de los lenguajes de programación.
Pero si reduce la escala y considera los idiomas como parte de una categoría más amplia de interfaces hombre-computadora, entonces es más fácil ver las ventajas específicas de OOP. Entonces hagámoslo.
Idiomas de uso general e idiomas orientados a la materia
En primer lugar, un poco de terminología. La programación orientada al lenguaje (también conocida como OOP) es la idea de resolver problemas de programación creando un nuevo lenguaje y luego escribiendo un programa en él. A menudo, estos "idiomas pequeños" se denominan lenguajes específicos de dominio (DSL).
Como su nombre lo indica, un lenguaje orientado a la materia se adapta a las tareas de un determinado campo. Por ejemplo, PostScript, SQL,
make
, expresiones regulares,
.htaccess
y HTML se consideran lenguajes orientados a temas. No están tratando de hacer todo. Más bien, están enfocados en hacer una cosa bien.
En el otro extremo del espectro
están los lenguajes de uso general . Aquí vemos C, Pascal, Perl, Java, Python, Ruby, Racket, etc. ¿Por qué no se consideran orientados a temas? Porque se posicionan para una amplia gama de tareas informáticas.
En la práctica, los lenguajes de propósito general a menudo se especializan en un campo particular. Por ejemplo, C es mejor que otros para la programación del sistema. Perl: para secuencias de comandos en la administración del sistema. Python se destaca como un lenguaje para principiantes. Raqueta para programación orientada al lenguaje. En cada caso, para esto fue diseñado originalmente el lenguaje.
Hay una línea muy fina entre DSL y los lenguajes de uso general. Por ejemplo, Ruby se creó como un lenguaje de propósito general, pero se hizo popular principalmente para aplicaciones web a través de su asociación con Ruby on Rails. JavaScript, por otro lado, fue originalmente un lenguaje orientado a temas para los scripts del navegador web. Pero él mutaba como un virus, y desde entonces ha crecido mucho más allá de la tarea original.
¿Qué es un idioma?
Si todo este amplio espectro se llama idiomas, ¿cuáles son las características definitorias del lenguaje?
Sé lo que estás pensando: “Aquí es donde te equivocas. HTML no es un lenguaje. Esto es solo marcado. No puede describir el algoritmo ". O: “Las expresiones regulares no son un lenguaje. No trabajan solos. Es solo sintaxis para otro idioma ".
Yo también pensaba eso. Pero cuanto más miraba, más borrosas parecían estas diferencias. Por lo tanto, mi primera declaración principal (de tres): el lenguaje de programación es inherentemente un medio de intercambio,
un sistema de notación que es comprensible para las personas y las computadoras .
El sistema de notación (notación) significa que el lenguaje tiene sintaxis. "Claro" significa que, con su sintaxis, el lenguaje transmite
significado (o
semántica , si usa una palabra más elegante). Esta definición cubre todos los lenguajes de programación de uso general. Y todo DSL. (Pero no todos los flujos de datos, que se discutirán con más detalle más adelante).
(Por cierto, aunque "programación" y "lenguaje" son palabras que se usan idiomáticamente juntas, estos lenguajes no solo son utilizados por las personas para programar computadoras. A veces las usan las computadoras para comunicarse con nosotros (por ejemplo, expresiones S), a veces para comunicarse entre sí (por ejemplo, XML, JSON, HTML.) Definitivamente, parece incorrecto excluir estas características. Pero en la práctica, sí, lo que generalmente hacemos con un lenguaje de programación es, de hecho, la programación).
Considere el código HTML: una forma de decirle a una computadora, en particular a un navegador web, cómo dibujar una página web. Este es un sistema de notación (corchetes angulares, etiquetas, atributos, etc.) que es comprensible para humanos y computadoras (el atributo
charset
indica la codificación de caracteres, la etiqueta
p
contiene un párrafo, etc.).
Aquí hay una pequeña página HTML:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>My web page</title> </head> <body> <p>Hello <strong>world</strong></p> </body> <html>
Suponga que no está de acuerdo con que HTML es un lenguaje de programación. Bueno Mostraremos nuestra página en Python. Este es un lenguaje de programación real, ¿verdad?
print "<!DOCTYPE html>" print "<html>" print "<head>" print "<meta charset=\"UTF-8\">" print "<title>My web page</title>" print "</head>" print "<body>" print "<p>Hello <strong>world</strong></p>" print "</body>" print "<html>"
Si Python es un lenguaje de programación, pero HTML no lo es, entonces esta muestra de Python es un programa, pero la muestra de HTML no lo es.
Obviamente, esta es una diferencia torturada. Aquí la pitonización no agrega nada más que complejidad y estereotipos. Lo picante es que el único contenido semántico interesante en un programa de Python, desde el punto de vista de administrar un navegador web, es que está incrustado en HTML (quizás las etiquetas HTML como
DOCTYPE
,
meta
y
strong
pueden considerarse funciones que toman argumentos). La lógica nos lleva a concluir que HTML, aunque es más simple y menos flexible, sigue siendo un lenguaje de programación.
Idiomas Embebidos
Se nos ocurrió un ejemplo con HTML y Python. Pero incrustar DSL en otro idioma es omnipresente. Los idiomas utilizados de esta manera se denominan
incrustados . Representan la forma más común de programación de lenguaje. Como programador, ha confiado en JOP durante muchos años, incluso si no sabía su nombre.
Por ejemplo, expresiones regulares (otros ejemplos:
printf
para formatear cadenas, CLDR para patrones de fecha / hora, SQL). No podemos pensar en la expresión regular como un lenguaje independiente. Pero cada programador sabe lo que es:
^fo+(bar)*$
Además, probablemente pueda ingresar esta expresión regular en su lenguaje de programación favorito y simplemente funcionará. Este comportamiento coherente solo es posible porque la notación de expresión regular es un lenguaje externo (
POSIX ).
Al igual que con HTML, podríamos escribir una expresión equivalente en la notación del lenguaje anfitrión. Por ejemplo, Racket admite
expresiones regulares de esquema (SRE): estas son expresiones regulares con notación de
expresión S. La plantilla anterior se escribirá así:
(seq bos "f" (+ "o") (* (submatch "bar")) eos)
Pero los programadores de Racket rara vez usan expresiones SRE. Son demasiado largos y difíciles de recordar.
Otro ejemplo omnipresente de DSL incorporado: expresiones matemáticas. Todo programador sabe lo que esto significa:
(1 + 2) * (3 / 4) - 5
Las expresiones matemáticas solas no crean programas interesantes. Necesitamos combinarlos con otras construcciones de lenguaje. Pero, como con las expresiones regulares, este es un registro ergonómico y práctico. Las expresiones matemáticas tienen su propia notación y significados, entendibles tanto para las personas como para las computadoras, por lo que califican como un lenguaje incorporado por separado.
¿Estás bromeando que HTML es programación?
No lo es Afirmo que HTML (tanto expresiones regulares como expresiones matemáticas) califica como lenguajes de programación rudimentarios. Esto significa que escribir HTML (o expresiones regulares o expresiones matemáticas) califica como programación rudimentaria.
Por favor no se asuste. Por supuesto, un "programador" en LinkedIn con conocimiento de solo HTML y aritmética no tiene sentido (aunque en una semana probablemente obtendrá $ 180,000 en trabajo). Pero este es un tema aparte, qué significa "programador" en el mercado laboral. No estamos hablando de eso.
Trampa de totalidad de Turing
Si esta definición de lenguajes de programación aún le molesta, tal vez piense que un lenguaje de programación real debería expresar todos los algoritmos posibles, es decir, debería ser
Turing completo .
Entiendo que intuitivamente tal pensamiento se sugiere a sí mismo. Cada lenguaje de programación de uso general es Turing completo.
Pero el problema es que esta es una barra baja. La integridad de Turing es una métrica técnica que no corresponde al uso del lenguaje en el mundo real. Por ejemplo, las expresiones regulares no están completas en Turing, pero son útiles cuando se expresan muchos cálculos con notaciones mínimas. HTML tampoco está completo en Turing, pero es una forma útil de controlar el navegador. Por el contrario, el lenguaje
bf es Turing completo, pero incluso las tareas más triviales requieren kilómetros de código infranqueable.
Restricciones de idioma
¿Hay algo bajo mi definición de lenguaje? No
- Los formatos de datos binarios no se consideran idiomas. Por ejemplo, un archivo
jpeg
. Aunque una computadora puede entenderlos, una persona no. O PDF: si lo piratea, hay algunas partes en el interior que pueden ser leídas por humanos. Pero esto se debe a cómo funciona el PDF. No tiene sentido escribir ideas usando construcciones PDF.
- Los archivos de texto no son idiomas. Supongamos que tenemos un archivo con la Ilíada de Homero. Los humanos podemos leerlo y entenderlo. Aunque la computadora puede procesar trivialmente el archivo, por ejemplo, imprimiendo su contenido, el texto dentro es incomprensible para la computadora.
- Las interfaces gráficas de usuario no son idiomas. Sí, estos son sistemas de notación (que se basan en texto e imagen). Pero son entendibles solo para las personas. Las computadoras dibujan una GUI pero no las entienden.
Los idiomas como interfaces
Arriba, describí el lenguaje de programación como un "medio de intercambio" entre las personas y las computadoras. Por lo tanto, los idiomas encajan en una categoría más amplia, que llamamos
interfaces .
Esto lleva a la segunda declaración básica (de tres): que la
programación del lenguaje es básicamente un método para diseñar una interfaz . Si te gusta pensar en interfaces, te gustará JOP. Si no, todavía te encantará JOP por hacer posibles interfaces que de otro modo serían inalcanzables.
Uno de mis ejemplos favoritos de un idioma como interfaz es
alardear , un lenguaje generador de analizador creado con Racket. Si alguna vez ha utilizado la cadena de herramientas lex / yacc, sabe que a menudo el objetivo es generar un analizador sintáctico a partir de una
gramática BNF . Por ejemplo, para
bf, se ve así:
bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Para hacer un analizador sintáctico en un lenguaje de propósito general, debe traducir esta gramática en un montón de código nativo. Este es un trabajo tedioso. Y sin sentido, ¿no hemos escrito ya la gramática? ¿Por qué hacerlo de nuevo?
Sin embargo,
brag
cumple nuestro deseo. Para hacer el analizador, simplemente agregamos la línea
#Lang brag
al archivo, que convierte mágicamente la gramática BNF al código fuente de
brag
:
#Lang brag bf- : (Bf-op | Bf-loop)* bf-op : ">" | "<" | "+" | "-" | "."| "," Bf-loop : "["(Bf-op | Bf-loop)* "]"
Hecho En la compilación, este archivo exporta la función de
parse
, que implementa esta gramática BNF.
Este es uno de mis ejemplos favoritos porque es indudablemente superior a otras opciones. Además, con un lenguaje de propósito general, dicha interfaz es prácticamente imposible.
Pero un programador de JOP está constantemente haciendo tales interfaces.
Donde el idioma es la mejor interfaz
Esto me lleva a mi tercera y última tesis básica de que los
idiomas tienen ventajas únicas sobre las interfaces . Por supuesto, las siguientes categorías no son exhaustivas ni exclusivas. Pero descubrí que la PIO tiene mucho que ofrecer en tales situaciones:
1. Cuando desee crear una interfaz para programadores menos calificados, o no programadores, o programadores perezosos (no subestime el tamaño de la última categoría).
Por ejemplo, Racket tiene una sofisticada
biblioteca de aplicaciones web . Pero un servidor web simple también se puede iniciar rápidamente usando el lenguaje de
web-server/insta
:
#lang web-server/insta (define (start request) (response/xexpr '(html (body "Hello LOP World"))))
Matthew Flatt en su artículo
"Creando idiomas en la raqueta" demuestra el lenguaje que genera aventuras de texto jugables. Al igual que
brag
, se parece más a una especificación que a un programa, pero funciona:
#lang txtadv ===VERBS=== north, n "go north" south, s "go south" get _, grab _, take _ "get" ===THINGS=== ---cactus--- get "Ouch!" ===PLACES=== ---desert--- "You're in a desert. There is nothing for miles around." [cactus, key] north meadow south desert
2. Cuando quieres simplificar la notación. Un ejemplo son las expresiones regulares. Otro ejemplo es mi lenguaje orientado a temas,
Polen, para escribir libros en línea. El polen es similar a Racket, solo que aquí comienza a trabajar en modo de texto y usa caracteres especiales para indicar los comandos de Racket que están incrustados en el contenido (Pollen se basa en el lenguaje de documentación de Racket llamado
Scribble , que toma la mayor parte de la carga). Entonces, el comienzo de este párrafo se programa de la siguiente manera:
. — . — - ◊link["https://pollenpub.com/"]{Pollen} -.
Al polen le importa pegar todas las etiquetas necesarias y convertirlas en HTML infalible. Todavía tengo todas las ventajas del marcado manual (control total sobre la página), pero no hay inconvenientes (por ejemplo, no puedo dejar accidentalmente una etiqueta sin cerrar).
Otro ejemplo de notación simplificada es
lindenmayer
, el lenguaje de generación y dibujo fractal del
sistema Lindenmayer , como este:
En una raqueta típica, un programa Lindenmayer podría verse así:
#lang racket/base (require lindenmayer/simple/compile) (define (finish val) (newline)) (define (A value) (display 'A)) (define (B value) (display 'B)) (lindenmayer-system (void) finish 3 (A) (A -> AB) (B -> A))
Pero puede usar la notación simplificada simplemente cambiando la notación
#lang
en la parte superior del archivo:
#lang lindenmayer/simple ## axiom ## A ## rules ## A -> AB B -> A ## variables ## n=3
El lenguaje asume que ya está familiarizado con el sistema L. Pero una notación simplificada hace que sea fácil escribir sus deseos en un programa que hace lo que quiere.
3. Cuando desee trabajar con una notación existente. Vimos arriba cómo
brag
usa la gramática BNF como código fuente.
#lang brag bf-program : (bf-op | bf-loop)* bf-op : ">" | "<" | "+" | "-" | "." | "," bf-loop : "[" (bf-op | bf-loop)* "]"
Otro ejemplo. Las personas que probaron Pollen dijeron: "Sí, eso es genial, pero prefiero Markdown". No hay problema:
pollen/markdown
es un dialecto de polen que ofrece semántica de polen pero acepta la notación de Markdown habitual:
. — . — - [Pollen]("https://pollenpub.com/") -.
¿Lo más agradable? Escribí este dialecto en solo una hora, combinando el
analizador Markdown con el código existente.
4. Si desea crear un objetivo intermedio para otros idiomas. JSON, YAML, expresiones S y XML son todos lenguajes orientados a temas que definen formatos de datos para lectura y escritura de máquina.
En Perfect Racket, un lenguaje de entrenamiento se llama
jsonic
. Le permite incrustar expresiones Racket en JSON, lo que hace que JSON sea programable. El código fuente se ve así:
#lang jsonic // a line comment [ @$ 'null $@, @$ (* 6 7) $@, @$ (= 2 (+ 1 1)) $@, @$ (list "array" "of" "strings") $@, @$ (hash 'key-1 'null 'key-2 (even? 3) 'key-3 (hash 'subkey 21)) $@ ]
Compila a JSON regular:
[ null, 42, true, ["array","of","strings"], {"key-1":null,"key-3":{"subkey":21},"key-2":false} ]
5. Cuando la parte principal del programa es la configuración. Por ejemplo, los archivos de puntos se pueden describir como DSL. Un ejemplo más complejo de Racket es Jesse Alama's Riposte, un lenguaje para probar la API HTTP basada en JSON:
#lang riposte $productId := 41966 $qty := 5 $campaignId := 1 $payload := { "product_id": $productId, "campaign_id": $campaignId, "qty": $qty } POST $payload cart/{uuid}/items responds with 200 $itemId := /items/0/cart_item_id GET cart responds with 200
Como lenguaje de script en miniatura, Riposte es mucho más inteligente que el archivo de puntos promedio. Oculta todo el código intermedio necesario para las transacciones HTTP y permite al usuario concentrarse en escribir pruebas. Todavía está limpiando la casa. Pero al menos puede concentrarse en el hogar que le interesa.
¿Por qué raqueta?
A menudo, los críticos de JOP preguntan: “¿Por qué hacer un lenguaje orientado a temas? ¿Es más fácil escribir una biblioteca nativa?
No, no es más fácil si tienes la herramienta adecuada. La raqueta es inusual: está diseñada desde cero específicamente para YOP.
Por lo tanto, implementar DSL en Racket es más rápido, más barato y más fácil que las alternativas. Por ejemplo, en la primera lección de mi libro, mostré cómo desarrollar un nuevo idioma en una hora, incluso si nunca ha usado Racket.Bajo el capó de cada DSL en Racket, el compilador de fuente a fuente realmente funciona , lo que convierte la notación DSL y la semántica en un programa Racket equivalente. Por esta razón, Racket DSL no podrá ejecutarse tan rápido como el código C. escrito manualmente, pero todas las herramientas y bibliotecas de Racket están disponibles para cada DSL. Pierde productividad, pero gana repetidamente en conveniencia. Y cuando crear un DSL es conveniente y fácil, se convierte en una opción realista para una gama mucho más amplia de problemas.Por lo tanto, para responder a las críticas, no, DSL no requiere necesariamente más trabajo que la biblioteca nativa. Además, como ya hemos visto, como interfaz, un lenguaje puede hacer lo que una biblioteca nativa no puede.¿Por qué macro?
Como todos los DSL se compilan en Racket, el programador debe escribir algunos transformadores de sintaxis que conviertan la notación DSL en el Racket nativo. Estos convertidores de sintaxis se conocen como macros . De hecho, pueden describirse como extensiones del compilador Racket.El sistema macro Racket es vasto, elegante y, sin duda, es la perla de su corona. Gran parte de mi libro trata sobre el placer de trabajar con macros Racket. Puedo nombrar dos funciones sobresalientes:- Racket , . . , , Racket , , , , , . ( . « » ).
- Las macros de raqueta son higiénicas , es decir, por defecto, el código creado por la macro conserva el contexto léxico a partir del cual se define la macro. En la práctica, esto elimina la gran cantidad de gestos innecesarios que generalmente se requieren para DSL (para obtener más detalles, consulte el capítulo "Higiene" ).
¿Es posible implementar DSL en, por ejemplo, Python? Por supuesto
De hecho, escribí mi primer DSL específicamente en Python, y todavía lo uso en mi trabajo de diseño de tipos . Bueno eso. Una vez fue suficiente. Desde entonces he estado usando Racket.Conclusión: victoria con YaOP
Por el momento, puede tener una de dos reacciones:- « , , » . , . , , . . . , . .
- «, , c Racket » . , - Riposte , — ( ):
[ ] - Racket. , , - … : « API , ?» : « Riposte». , , [DSL], , . «» Racket. DSL , .
Al final del artículo "¿Por qué Racket? ¿Por qué Lisp? Dije que el lenguaje Lisp "le brinda la oportunidad de liberar su potencial como programador y pensador y, por lo tanto, aumentar sus expectativas sobre lo que puede lograr".IOP ofrece una oportunidad similar: aumentar nuestras expectativas con respecto a lo que los lenguajes de programación pueden hacer por nosotros. Los idiomas no son cajas negras. Estas son las interfaces que podemos diseñar. Al mismo tiempo, abrimos nuevas posibilidades para lo que se puede hacer con la ayuda de programas.Si puede encontrar la mejor técnica de programación, úsela. Ahora que tengo OOP y Racket, nunca volveré.Lectura adicional