Las extensiones de lenguaje desaparecidas del compilador MetaWare High C (2023)
(duriansoftware.com)- El High C Compiler para FM TOWNS de 1989 no solo era compatible con entornos DOS, sino que incluía varias funciones de lenguaje orientadas al usuario poco comunes en los compiladores C de la época
- Al combinarse con el DOS extender de Phar Lap, se convirtió en el compilador C de primera parte de FM TOWNS dentro de un flujo de desarrollo que aprovechaba el 80386 de 32 bits en el entorno MS-DOS de 16 bits
- Funciones como guiones bajos en literales numéricos, argumentos etiquetados, rangos en
case, funciones anidadas y generadores llegaron a los estándares C/C++ mucho más tarde o todavía no forman parte del estándar - Las funciones anidadas ofrecían un “full function value” en forma de closure no escapante, que pasaba juntos un puntero a función y un puntero de contexto, con mayor expresividad que un puntero a función de C común
- Los generadores se implementaban como azúcar sintáctico sobre funciones anidadas: convertían el cuerpo del bucle
fordel llamador en una función anidada y la pasaban como argumento deyield, con una estructura simple
El lugar de FM TOWNS y High C
- Un manual de compilador C de los años 80, encontrado entre una pila de libros relacionados con FM TOWNS, contenía extensiones de lenguaje más ricas de lo esperado
- Durante mucho tiempo, para usar C y sus lenguajes derivados en entornos reales hicieron falta extensiones de proveedor
- Hoy, en entornos centrados en GCC, Clang y MSVC, las extensiones tienden a concentrarse en el manejo específico por plataforma o en el control de detalles de bajo nivel
- En los años 80, con muchas empresas más pequeñas compitiendo por adopción, las extensiones también eran más variadas
- Phar Lap creó uno de los primeros DOS extender, que permitía aprovechar procesadores 80386 de 32 bits en el entorno MS-DOS de 16 bits
- MetaWare portó High C Compiler al SDK del DOS extender de Phar Lap por encargo de Phar Lap
- Fujitsu integró el DOS extender de Phar Lap en el sistema operativo de la plataforma FM TOWNS basada en 80386, y High C se convirtió en el compilador C de primera parte de esta plataforma
- FM TOWNS se lanzó en 1989, justo antes de que se ratificara C89, el primer estándar ANSI C
Pequeñas comodidades que se adelantaron al estándar
-
Separadores con guion bajo en literales numéricos
- Para hacer más legibles los literales numéricos largos, se podían insertar separadores con guion bajo dentro de los números
- C++ introdujo en C++14 separadores con apóstrofo como
1'000'000 - C recién introdujo una función similar en C23
-
Argumentos etiquetados
- Permitía poner nombres a los argumentos en funciones con muchos parámetros o que usaban muchos tipos como
bool, cuyo significado no es fácil de ver en el punto de llamada - Los argumentos etiquetados de High C funcionaban de forma parecida a una función popular de Python
- Las etiquetas de argumentos eran opcionales
- Si había etiquetas, se podía especificar argumentos en cualquier orden con la sintaxis
argumentName => value - Se podían mezclar argumentos sin etiqueta y con etiqueta, pero todos los parámetros de la función debían tener un argumento correspondiente
- C y C++ estándar todavía no tienen esta función
- Permitía poner nombres a los argumentos en funciones con muchos parámetros o que usaban muchos tipos como
-
Rangos en
case- Ofrecía una función para hacer match con un rango de valores de una sola vez, como
case low..highen Pascal - C y C++ estándar no adoptaron esta función
- Ofrecía una función para hacer match con un rango de valores de una sola vez, como
Funciones anidadas y full function value
- High C permite declarar funciones anidadas dentro de una función, como Pascal
- Su implementación se acerca a una forma más completa que las funciones anidadas del Pascal estándar o que la extensión de funciones anidadas de GCC
- High C no solo permite declarar funciones anidadas, sino también declarar tipos full function value
- A diferencia de los punteros a función tradicionales de C, guardan junto al puntero a función un puntero de contexto
- Permiten recuperar el contexto capturado por una función anidada
- Son closures no escapantes, cuya vida no continúa después de que retorna la función externa
- La extensión de funciones anidadas de GCC, para permitir referenciar una función anidada como un puntero a función común, escribía código ejecutable en la pila de llamadas para hacer un thunk del puntero de contexto
- Este enfoque generó un riesgo importante de seguridad y llevó a que muchas plataformas desactivaran por completo esa función
- Las referencias a funciones locales en High C pueden usarse como valores de primera clase, pero su vida no se extiende más allá del retorno de la función externa
- Una función anidada también puede hacer
gotohacia la función padre- Como los bloques de Smalltalk, permite una salida no local que abandona la función anidada
- Con esto se pueden crear funciones que se comportan como flujo de control
- Objective-C obtuvo en 2009 blocks que pueden usarse como closures escapantes, y C++ introdujo lambdas en 2011
- Ninguna de las dos funciones tenía capacidad de salida no local
- C estándar todavía no tiene una función oficial de funciones anidadas
Coroutines generadoras
- MetaWare destacaba tanto la función de generadores que le dedicó un capítulo entero
- En 1989, High C admitía coroutines generadoras al estilo Python en plain C
- Una función generadora se declara con la sintaxis
void foo(Arg arguments) -> (Yield yields)- Dentro de la función se puede llamar varias veces a la función mágica
yield(values...)para generar una secuencia de valores - El llamador recorre en orden los valores generados con la nueva sintaxis de bucle
for, con la formafor variable... <- foo(arguments...) do { ... }
- Dentro de la función se puede llamar varias veces a la función mágica
- Esta implementación podía combinarse de maneras complejas con funciones anidadas
- Una función anidada dentro de un generador podía capturar la acción
yielddel generador externo - Una función anidada podía llamarse recursivamente a sí misma para recorrer un árbol o una estructura de datos recursiva y hacer
yielden cada paso
- Una función anidada dentro de un generador podía capturar la acción
- Esta forma parece difícil de implementar en Python o en muchos lenguajes populares con coroutines generadoras
Modo de implementación de los generadores y diferencias con lenguajes estándar
- Los generadores de High C funcionan como azúcar sintáctico sobre funciones anidadas, sin un runtime avanzado
- Una declaración de generador con la forma
void foo(Arg arguments) -> (Yield yields)equivale a una declaración de función comúnvoid foo(void yield(Yield yields)!, Arg arguments)yieldes un parámetro implícito de tipo “full function value”- Una llamada
yield(values)dentro del cuerpo del generador es una llamada común a ese parámetro de función implícito
- El cuerpo del bucle
fordel lado del llamador se transforma en una función anidada- Esa función anidada se pasa como argumento
yielddel generador - La estructura es simple, pero efectiva
- Esa función anidada se pasa como argumento
- Como las funciones anidadas admiten salida no local,
break,continueygotoque salen del cuerpo del buclefortambién funcionan haciendogotoa la ubicación apropiada fuera del bucle - Es poco probable que C estándar intente integrar una función así
- C++20 ofrece una función de coroutines muy flexible y compleja, basada en transformación de coroutines en tiempo de compilación
- Parece posible usarla para implementar generadores
- Sin embargo, el resultado probablemente no se combinaría de forma tan intuitiva con funciones locales
1 comentarios
Opiniones de Hacker News
Por suerte tengo un ejemplar de la versión en inglés de High C/C++ Language Reference
http://jdebp.uk./FGA/metaware-iterator-driven-for.html
http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
breakoreturn. ¿Tendría que haber sido una transformación donde la funciónyielddevolviera un código de estado y este se verificara en el punto de llamada?case, argumentos con nombre, funciones anidadas, funciones anidadas estáticas y algo parecido a generadoresPor ejemplo, se pueden usar formas como
int a = 1_234_567;,case 5 .. case 6:,test(b:3, a:4);Las funciones anidadas estáticas no pueden acceder a las variables del frame de la función externa, por lo que dan errores como
Error: static function test.foo.plus cannot access variable i in frame of function test.fooLo parecido a generadores está en https://dlang.org/spec/statement.html#foreach_over_struct_an...
Por ejemplo, si haces un servicio de caché en memoria, conviene que el recolector de basura no rastree los elementos de caché en sí, porque a menudo no conoce los patrones reales de acceso y puede estorbar. Pero para la mayoría de los demás componentes de ese servicio encaja mejor tener recolector de basura
¿Por qué los argumentos con nombre son de la forma
test(a:4, b:3)y notest(.a=4, b.=3);?También me intriga cómo se podrían manejar tipos de primera clase en C
lcc-winagregó sobrecarga de operadores, argumentos de función por defecto y sobrecarga de funciones. En la documentación hay que ver “generic functions” [1]El compilador C de Plan 9 también introdujo varias extensiones del lenguaje, y algunas de ellas, como estructuras/uniones anónimas, luego entraron en el estándar de C. Actualmente GCC acepta la bandera
-fplan9-extensions[2], que permite activar funciones bastante útiles, como convertir automáticamente punteros a estructuras en campos anónimos en llamadas a funciones y asignaciones[1] https://lcc-win32.services.net/C-Tutorial.pdf
[2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
Es una lástima que no se hayan difundido ampliamente ni influido en los estándares del lenguaje. Sorprende que existieran funciones así hace tanto tiempo
Ya se había hablado de esto antes en Hacker News: https://news.ycombinator.com/item?id=38938402
¿Habrá alguna copia en PDF en algún lado?
yield[0]. El lenguaje Icon, de una época similar, también tenía una función de generadores parecida [1], y usabasuspendparayield. Hasta donde sé, Ada (1983) también tenía algo asíEstas funciones de lenguaje no eran completamente desconocidas
[0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
[1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
Describe guiones bajos dentro de números, rangos en
case, parámetros con nombre, funciones anidadas e incluso variables de función completashttps://bitsavers.org/pdf/metaware/…
Hay que ver el Appendix A, unas 50 páginas antes del final del archivo
En mis tiempos de aprender y usar código, la conocí a través de sitios un poco sospechosos
También se descubre la rica historia de los lenguajes de programación de sistemas. Y se ve hasta qué punto los diseños de C y Go se parecen en haber ignorado lo que ocurría en otros ecosistemas y las experiencias del pasado
El enlace al manual del compilador está en https://winworldpc.com/product/metaware-high-c-cpp/33x
El PDF del manual de C muestra copyright de 2007
https://f.duriansoftware.com/@joe/113195961485703110
¥nen vez de\n, parece que estos ejemplos de código fueron escritos en Shift-JIS. En Shift-JIS,¥ocupa la posición del\de ASCII[0] https://en.wikipedia.org/wiki/JIS_X_0201
Para este uso, EUC-JP es mejor, porque no tiene ese problema. En Pascal, si usas comentarios
(* *)y no comentarios{ }, con Shift-JIS no tienes este problemaEn su lugar, es muy probable que se usara JIS X 0201(https://en.m.wikipedia.org.org/wiki/JIS_X_0201), que fue la base de Shift-JIS
C:¥, noC:\Call (Param_A => 1, Param_B => "Foo");, guiones bajos en números de base arbitraria (X : Integer := 1_000;), subprogramas anidados y comprobaciones basadas en rangosCreo que a menudo olvidamos lo increíblemente primitivo que era C en comparación con varios otros lenguajes de la época
No conozco lo suficiente la notación japonesa ni las reglas de kerning, pero parece como si hubieran tomado una fuente de ancho variable que incluye tanto kanji como caracteres latinos y la hubieran metido a la fuerza en celdas de ancho fijo
En cualquier caso, me gusta que los ejemplos de código no estén en una fuente de 8 pt como en muchos de los libros que tengo
try_fold()(https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)Pero justamente por esa razón parece que estas extensiones fueron relativamente poco conocidas, y décadas después tuvieron que redescubrirse y reinventarse en C/C++ moderno