- Cognition es un proyecto de investigación de lenguajes que adopta una antisyntax completamente posfija para evitar el problema de lectura anticipada (read-ahead) de Lisp y los lenguajes concatenativos
- Sus mecanismos centrales, delimiter, ignore, singlet, falias, crank y metacrank, permiten que los programas cambien sus propias reglas de tokenización y sus ciclos de ejecución
- El bootstrap empieza en un estado donde todos los caracteres se leen como un solo token y por sí mismo migra a un entorno donde el espacio y el salto de línea se usan como delimitadores
crankymetacrankcontrolan cuándo se evalúan los tokens y cuándo se acumulan, lo que permite definir dentro de un sistema posfijo una sintaxis de prefijo como comentarios#, escape\\, quote[y macro(- Incluso un dialecto de Brainfuck se implementa no con un parser separado sino con palabras de Cognition y reglas de tokenización, mostrando una dirección donde la propia gramática se convierte en código y se automatiza
Qué objeta Cognition de las sintaxis existentes
- Lisp ofrece una metaprogramación poderosa con s-expressions y su sistema de macros, pero aun así sigue afectado por una sintaxis fija
- Un paréntesis izquierdo es la señal de que hay que seguir leyendo hasta encontrar el derecho, así que cambiar el rol de los paréntesis dentro del propio lenguaje es difícil o, en algunas implementaciones, imposible
- Si se quiere cambiar después la forma de separar tokens ya leídos, hace falta mucho procesamiento de strings
- El proceso de mirar la entrada actual y tener que leer más hacia adelante es syntax; en el momento en que se asume lectura anticipada por defecto, uno queda atado a ciertas formas sintácticas
- Para evitarlo, Cognition usa una antisyntax completamente posfija
- Se parece a los lenguajes concatenativos, pero considera que incluso los lenguajes concatenativos comunes tienen problemas de lectura anticipada por los corchetes o los caracteres de quote para strings
- El sistema de macros de Racket se distingue porque usa preprocesamiento, no cambios dinámicos de sintaxis durante la ejecución
El proyecto y la idea base
- Cognition es un proyecto de investigación activo desarrollado durante varios meses junto con Matthew Hinton
- El repositorio de implementación está en cognition-rust, y ahí también hay un paper sobre el lenguaje
- Para entenderlo ayuda tener base en parsing, tokenización y gramáticas
- La explicación sigue el recorrido desde el código de “baremetal cognition” hasta una evolución hacia una sintaxis parecida a Stem
Baremetal Cognition y tokenización
- A simple vista, baremetal Cognition se parece a Brainfuck, pero permite una metaprogramación más potente
- Su código de bootstrap, muy pequeño, usa incluso espacios y saltos de línea con significado, y en el estado inicial cada carácter individual se lee como un token
- Cognition es básicamente un diseño basado en pila, pero usa el término container en un sentido más general que stack
- En el entorno base, salvo un falias especial, ninguna palabra se ejecuta automáticamente
-
delimiter, ignore, singlet
- delimiter permite que el tokenizador sepa dónde termina un token y dónde empieza el siguiente
- La lista de delimitadores de un solo carácter puede modificarse y leerse desde el propio código de Cognition
- Los caracteres ignore se omiten en la etapa inicial de recolección de tokens de cada read-eval-print loop
- El valor por defecto es que todos los caracteres son delimitadores y no hay caracteres ignore
- Las listas delimiter, singlet e ignore pueden cambiar su comportamiento con flags de whitelist/blacklist
- La configuración por defecto es: sin delimitadores en blacklist, sin singlets en whitelist y sin caracteres ignore en whitelist
- singlet es una tercera categoría de tokenización: incluye el carácter en sí dentro del token y luego termina la recolección
-
falias
- falias es una lista de palabras que se ejecutan apenas se apilan en un container
- El falias base
fno se acumula en el container, sino que ejecuta la palabra que está arriba del container - En el ejemplo, cuando
fejecutad,dcambia la lista de delimitadores al valor string de una palabra - Después sigue un bootstrap que va convirtiendo paso a paso caracteres como
l,g,tyden no-delimitadores, y cambia espacio y salto de línea para que sean delimitadores e ignore
El entorno de ejecución que crea el bootstrap
- El bootstrap inicial cambia las reglas de delimiter e ignore hasta llegar a un entorno donde espacio y salto de línea son delimitadores de token y además se ignoran al inicio de la lectura del token
- Luego lee
1ycrank, yfejecutacrankpara entrar en un entorno de crank 1 - Lo clave del proceso es que la forma de tokenizar puede cambiarse durante la ejecución
- Los cambios a delimiter, singlet e ignore pueden automatizarse con programas
- Al ser un sistema posfijo y sin lectura anticipada, no hace falta parsear por adelantado uno o más tokens antes de evaluar una expresión
- falias permite ejecutar ciertas palabras incluso sin palabras de prefijo ni ejecución automática de palabras base
crank y metacrank
- crank es el mecanismo que decide con qué ciclo se ejecutan los tokens apilados
- La palabra
cranktoma un número como argumento y, a partir de ahí, ejecuta el tope de la stack en cada enésima palabra que entre al container- El
1 crankdel final del bootstrap crea un entorno donde se evalúan todos los tokens - En un estado
5 crank, las palabras pueden acumularse hasta que entra el quinto token
- El
- El código de ejemplo usa
unglue,swap,quote,preposeydefpara crear una palabra llamada2crankunglueobtiene el valor de una palabra, e incluso puede obtener el function pointer de un builtin comocrankpreposese parece acomposede Stem, pero adjunta por delante y coloca el resultado en VMACROdefdefine2crankpara que ponga2en la stack y llame al builtincrank
-
Los container y las macros en Cognition
- En Stem, una palabra puede ponerse directamente en la stack, pero en Cognition una palabra no evaluada entra en un container
- Gracias a este diseño, una palabra como
composepuede tratar de forma consistente containers de una sola palabra y otros containers con la misma API - La macro de Cognition no es igual al quote de Stem
- Cuando se evalúa una macro, ignora crank y se evalúan todos sus elementos internos
- Si se evalúa una macro enlazada a una palabra, se ejecuta la macro completa sin depender de crank y el cranker solo se incrementa una vez
- Las macros sirven tanto para código independiente de crank como para expansiones con fines de optimización
-
metacrank
n m metacrankconfigura una evaluación con períodomsobre el elemento que estánposiciones por debajo en la stackcrankequivale a0 m metacrank- Solo puede evaluarse un metacrank por token, y el metacrank más bajo tiene prioridad
- metacrank y crank se aplican no solo a los tokens del archivo, sino también al proceso recursivo de evaluar definiciones de palabras
- metacrank permite una manipulación directa de la gramática, como “quiero ejecutar este token después de leer n tokens”
- Después de programar una palabra de prefijo, se la puede
undefsi deja de hacer falta - También se pueden crear caracteres de prefijo que se detengan no por un carácter de cierre específico, sino después de cierta cantidad de tokens
- Se puede meter entrada del usuario en un programa matemático y pasar su salida a un sistema gramatical como metacrank
Evolución hacia un dialecto de Stem
- Después del bootstrap, Cognition construye gradualmente desde dentro del propio lenguaje una sintaxis cercana a Stem v2
- Primero elimina
fde la lista de falias y deja soloing- Como poner
fdirectamente en la stack lo ejecutaría, se creaffy luego se parte ese string por la mitad para obtener dosf - Después
fse define como una palabra vacía equivalente a false
- Como poner
-
Comentarios con
#- El carácter
#es el primer ejemplo de código que se comporta como un verdadero prefijo - Este carácter de comentario actúa como un prefijo que descarta el texto hasta antes del newline, creando una gramática donde el parser mira hacia adelante
- La implementación combina
geti,getd,gets,crankbase,halt,VMACRO cast,singlet,delimy otros geti,getdygetsobtienen respectivamente ignore, delimiter y singlet como stringshaltpone todos los metacranks en 0VMACRO castconvierte el container del tope de la stack en una macro- La definición de
#cambia primero las reglas de tokenización, luego invoca#sobre palabras que se tokenizarán en el futuro, descarta ese comentario y finalmente vuelve al crank y metacrank originales
- El carácter
-
escape, quote y macro
\\se define como un carácter de escape que permite poner en la stack incluso palabras que serían evaluadas- Después se agrega la definición de quote
[y luego esa misma quote se usa para redefinir[con una versión mejor que permite quote recursivo - Gracias a
defposfijo, es posible usar una definición previa para construir una nueva - Ese patrón es una forma de desarrollo frecuente en Cognition de bajo nivel
(se define como bracket de macro- Las macros se hacen para expandirse automáticamente, y se considera más eficiente enlazar a una palabra una macro ya expandida
- En términos funcionales, se evalúan de la misma manera
expandexpande recursivamente conungluelas definiciones de palabras dentro de un quote o una macro- Primero se define un
expandbásico y luego se redefine usando el propioexpandpara cubrir casos más generales
Dialecto de Brainfuck
- Sobre el dialecto de Stem ya desarrollado, Cognition define un dialecto de Brainfuck
- El ejemplo de ejecución es
../crank -s 2 bootstrap.cog helloworld.bf brainfuck.cog brainfuck.cogno es un parser de Brainfuck en el sentido convencional- Define palabras de Brainfuck
- Tokeniza Brainfuck
- Lo ejecuta en un entorno nativo de Cognition
- Este ejemplo muestra que con la gramática de Cognition se pueden definir con facilidad gramáticas alternativas
- En vez de leer símbolos y decidir acciones según cada símbolo, Cognition define como palabras los propios caracteres de prefijo que usan metacrank, metiendo la gramática dentro de las definiciones de palabras
La idea de dialect dialect
- Se puede imaginar una palabra como
mkprefix- Por ejemplo, una palabra que reciba las dos palabras de entrada
[y]y alguna operación, y defina automáticamente que[aplique esa operación hasta encontrar]
- Por ejemplo, una palabra que reciba las dos palabras de entrada
- Esta idea es posible porque tanto
metacrankcomodefson palabras normales - Como incluso
d,iysson palabras, se puede crear un dialecto más abstracto que automatice el proceso de implementar gramáticas - Todavía no está implementado en la librería estándar, pero sí hay ideas discutidas con Matthew Hinton sobre posibles bibliotecas estándar
- una metaword que genere y llame automáticamente palabras abstractas
- una búsqueda de word-generator que abstraiga automáticamente la wordlist actual
- una forma de indicar marcos de abstracción para resolver problemas
La posibilidad de tratar la gramática como código
- En Cognition, el procesamiento de strings equivale a posprocesamiento del tokenizador, así que las operaciones sobre strings tienen un significado fuerte
- Como posibles áreas de aplicación se mencionan la IA simbólica, la investigación de sintaxis y gramáticas, y los experimentos de prototipado con lenguaje y metalenguaje
- También aparecen ideas como programas que lean archivos de configuración, un shell basado en Cognition o incluso un sistema operativo basado en Cognition
- La idea central es que Cognition hace posible “syntax as code”
- la gramática puede programarse dinámicamente
- la propia generación de gramáticas puede automatizarse
- No se abordaron conceptos como Metastack y
cd, que quedan como posibles temas para un texto posterior
1 comentarios
Opiniones de Hacker News
Por ejemplo, en Racket se puede crear una implementación integrada de Datalog que use la sintaxis de Datalog y aun así interopere con otros módulos de Racket, sin cambiar el modelo de datos básico.
Es una forma de hacer metaprogramación sin quedar atrapado en las expresiones S, pero procesándola a un nivel más alto.
Este tipo de bootstrapping de sintaxis es genial y valioso como investigación, pero no sé si sea fundamentalmente mejor que el enfoque de Racket.
Las macros de Lisp, Scheme y Racket normalmente operan sobre un AST, pero Rhombus opera sobre "shrubbery", algo parecido a un AST que difiere algunas decisiones de parsing para más adelante, lo que da cierta flexibilidad para extender la sintaxis.
Referencias: https://docs.racket-lang.org/guide/hash-reader.html, https://docs.racket-lang.org/datalog/datalog.html, paper de Rhombus https://doi.org/10.1145/3580417
#langde Racket es más cómodo de usar que las readtables de CL.Solo con readtables ya son lo bastante potentes como para implementar un compilador de C: https://github.com/vsedach/Vacietis
Personalmente, me largué a reír en la parte donde aparece "metacrank".
En Emacs Lisp, Common Lisp e ISLISP, las macros simplemente reciben datos y devuelven datos; no existe un concepto como AST.
Cuando se llama a
(foo-macro ...),...puede ser cualquier dato arbitrario.Por ejemplo,
(defmacro rev (&rest items) (reverse items))solo toma la lista de argumentos fuente de la llamada a la macro y la invierte.Se puede usar como
(rev 1 2 3 4 +)o(rev (rev 10 n -) (+ a 20 b) (rev 30 a *) list), y lo que realmente se pasa son listas, números y símbolos.No es texto ni un AST, y funciona de la misma manera aunque se pasen a
evaldatos calculados.El reader de Lisp básicamente lee una capa de datos, las expresiones simbólicas, y EVAL, las macros y otras funciones reciben principalmente datos.
El compilador puede construir internamente una representación AST, pero eso queda a criterio de la implementación; el lenguaje Lisp normalmente se define sobre una sintaxis de datos, no sobre una sintaxis textual.
Un intérprete Lisp es un "List Processor" que procesa expresiones S en tiempo de ejecución, no texto, y
COMPILEtambién recibe expresiones S, no texto.Racket y Scheme tienen sistemas de macros separados.
Pasan más de 300 palabras antes de que se mencione Cognition, el proyecto real, y la discusión sobre Lisp está bien, pero me pregunto si es la parte más importante del proyecto.
Al leer un texto informativo, uno está evaluando constantemente: "¿vale la pena dedicarle mi tiempo?", así que el documento debería decir desde el principio de qué trata.
Algo como "Cognition es un nuevo lenguaje que explora sintaxis modificable por el usuario" habría bastado, pero incluso después de los primeros cuatro párrafos me costaba decidir si valía la pena seguir leyendo.
Si el tiempo es dinero, podría decirse que el tiempo que pasé leyendo este artículo fue desperdiciado.
En vez de esperar que todo el contenido de internet se adapte a los gustos personales, creo que conviene más adaptarse a los formatos existentes.
El texto no es un medio que deba consumirse solo de forma secuencial como el video, así que se puede leer en diagonal para encontrar partes interesantes y descartarlo si no las hay, o volver al inicio y leerlo si las hay.
La variedad de estilos de escritura es mejor porque obliga a filtrar conscientemente la información que uno consume; si solo se consume pasivamente, la mente se vuelve perezosa.
Eso sí, si hubiera sido un video, habría estado de acuerdo.
En un video hay que decidir antes de verlo si invertir tiempo, y aunque verlo a 2x o saltar 5 a 10 segundos ayuda un poco, no resuelve el problema.
Primero explica el problema y después presenta la solución.
Con solo leer unas frases ya se nota que es una solución quijotesca a un "problema" que al 99.999%, incluidos quienes como yo han oído hablar de Lisp pero nunca lo han usado fuera de archivos de configuración de Emacs, no le importa; aun así, simplemente seguí leyendo.
Sin esa sección, lo que viene después habría sido más difícil de entender.
Si uno no conoce ese trasfondo, puede perder el contexto de todo el texto, y también es difícil saber si se trata de un argumento de hombre de paja.
Por eso todo se siente como si existiera para una necesidad muy estrecha, mientras que el título suena mucho más general y como un concepto bastante interesante.
En las dos primeras frases ya queda claro cuál es el problema que intenta resolver, y eso me resulta mucho más útil para medir mi interés que la introducción propuesta.
Dicho eso, en lo personal, cuando miro hacia arriba en la escalera de la pureza de la programación, Forth es más o menos el límite de pureza filosófica que puedo soportar.
Vamos a seguir cubriendo más magia oscura en el futuro.
Lo que pasa es que casi todos los lenguajes, incluidos todos los Lisp, manejan mal la cita, y curiosamente
m4es la excepción.Lisp esquiva este problema con macros, permitiendo tratar sentencias del metalenguaje expresadas como sentencias del lenguaje objeto sin tener en cuenta las comillas
Este problema surge porque tanto en el lenguaje objeto como en el metalenguaje se trata el espacio como el fin de un átomo, sin distinguir entre ambos
El enfoque de Cognition, una antisintaxis totalmente posfija, se parece a los lenguajes de programación concatenativos, pero los lenguajes posfijos son el dual de los lenguajes prefijos y sufren el mismo problema
O bien hay que fijar de antemano la aridad de todos los símbolos y no usar funciones de orden superior, o se necesita un par de delimitadores que permita serializar el árbol
Depender de una pila implícita de aridad 0 es parecido a hacer una lobotomía para curar la depresión
Ni nosotros sabemos hasta qué punto lo que hicimos es nuevo, y si crees que se puede hacer en Lisp lo que hacemos nosotros, eres bienvenido a demostrar que estamos equivocados
m4La afirmación en sí es interesante, pero hace falta algo más concreto
Así como una lobotomía reduce las capacidades de procesamiento de orden superior, volver a la forma más primitiva de cálculo con cadenas de comandos puede verse como algo similar
https://www.hpmuseum.org/rpnvers.htm
Me gusta especialmente que el mecanismo sea tan pequeño
No soy experto en lenguajes, así que no sé si hay algo novedoso, pero mientras leía el artículo sentía la alegría de los autores al descubrir una nueva cordillera de posibilidades cada vez que superaban una colina
Si lo entendí bien, la idea es que con Cognition se puede construir de verdad una máquina pensante
Sin que el programa se detenga y se reinicie con nuevas instrucciones, puede escribir y ejecutar por sí mismo nuevas subrutinas a partir de nueva entrada
Es decir, el programa puede aprender y adaptarse creando nuevas abstracciones y conectándose a nuevas API
Para mí esto es más interesante que redes neuronales más grandes o nuevas técnicas de aprendizaje
Common Lisp tiene reader macros, así que puedes cambiar la sintaxis como quieras, e incluso hay compiladores de Fortran que leen sintaxis de Fortran mediante reader macros
Common Lisp tiene reader macros en tiempo de lectura, macros, y compiler macros en tiempo de compilación, y todos esos lenguajes de macros son Common Lisp
La metaprogramación no tiene mucho que ver con macros o sintaxis, sino con la semántica de tipos, interfaces, clases, métodos, etc., y con la capacidad de manipular su significado
Si CL por sí mismo no fuera lo bastante potente para eso, existe CLOS, es decir, el Common Lisp Metaobject Protocol
Con las reader macros de CL se puede usar otro tokenizador, pero hay que indicar el cambio de tokenizador mediante una expresión dentro de la read table
En Cognition, parece que al llamar a una función cambia el tokenizador del contexto del llamador
La razón por la que aprendimos que hacer algo así con una máquina de Turing o el cálculo lambda era importante es que mostraba que un lenguaje de alto nivel es equivalente a un lenguaje base, y por lo tanto lo razonado sobre el primero también puede aplicarse al segundo
El primer y único ejemplo que se me ocurre es el problema de la detención
A escala práctica, si se demuestra que el lenguaje base no tiene fugas de memoria, ¿se puede decir que el lenguaje derivado tampoco las tendrá?
Me pregunto qué ventajas tiene este tipo de bootstrapping
Si la respuesta es simplemente, como al escalar el Everest, "porque está ahí", también lo respeto
df, el espacio de la línea 3 y el salto de línea, pasé directamente a "gracias, pero no"Los tres espacios al final de la línea anterior indican sarcasmo, y donde no sea fácil distinguir los espacios finales, puede interpretarse literalmente
La parte de bootstrap mencionada es, en realidad, el momento en que se le indica al lector que trate los espacios y saltos de línea como delimitadores
Es decir, la queja sería que el espacio tiene significado en la sección donde se lo declara como delimitador
Por supuesto tienes derecho a pensarlo así, pero me pregunto si había una mejor forma de hacerlo
No se me ocurre cómo hacerlo sin que, al menos una vez, un espacio literal tenga significado de esa manera
¿De verdad crees que sin sintaxis se podría leer una oración como "oración esta sin tú sintaxis leer puedes"?
Cognition dice usar una antisintaxis totalmente posfija, pero lo posfijo también es sintaxis
Basta preguntarle a un hablante de alemán por los verbos que van al final de la oración
Incluso en el primer ejemplo, el orden de los operandos y los operadores importa, y eso es precisamente sintaxis
Esto parece un intento de crear un lenguaje absurdamente comprimido, y recuerda mucho a APL
Como pista para los autores: no eliminaron la sintaxis, solo la hicieron difícil de leer y entender para humanos, y la legibilidad y comprensibilidad son factores importantes en programación
Se siente como si las reglas bajo tus pies cambiaran constantemente, como si se introdujeran reglas y palabras y luego se redefinieran a voluntad
En conjunto tiene un aire muy Numberwang, y esa parece ser una de las razones por las que se percibe como sátira
Otra razón importante es que la etapa de bootstrapping está escrita de forma ridículamente exagerada, aunque eso parece intencional
Claramente hay algo profundo ahí, pero tendré que volver a leerlo después de tomar un café más fuerte
Soy uno de los autores de este artículo, y el problema es que hay muchísimo contenido que transmitir
Matthew y yo estuvimos intercambiando ideas varias horas al día durante 3 semanas sobre el diseño de este lenguaje, y también hay mucho contexto que hay que completar para personas que no me conocen realmente