¿Cómo verificar / probar la ortogonalidad de un lenguaje de programación?

10

Conozco el concepto de ortogonalidad, pero desde el punto de vista del lenguaje de programación, ¿hay alguna forma de verificarlo / probarlo?

Por ejemplo, en C #, uno puede usar publico staticpara una firma de método. Puede usar uno o ambos y no interferirían entre sí, por lo que son ortogonales entre sí, ¿verdad?

Mi pregunta es, ¿cómo hago para el resto de características, particularmente características que no están relacionadas entre sí?

¿Todas las características tienen que coexistir / apilarse juntas?

¿Existe un lenguaje de programación que sea 100% ortogonal?

Joan Venge
fuente
Comience desde el principio (cercano): lenguaje ensamblador?
Matthew Flynn
1
Para probar algo realmente, necesitas una definición formal para ello. Y si su definición va a ser algo tan grande como la especificación de C #, probar cualquier cosa requerirá mucho trabajo.
svick

Respuestas:

4

No estoy seguro de que la ortogonalidad pueda servir como métrica útil o válida en el caso de lenguajes de alto orden de propósito general como C #, porque requiere la distinción de "operaciones" y "operandos", las pequeñas partes del lenguaje que no son fáciles de entender. distinguible en lenguajes de alto orden como C #.

Mi comprensión de la ortogonalidad se basa en el lenguaje Assembler, donde la ortogonalidad del conjunto de instrucciones de una determinada CPU o microcontrolador en particular indica si hay algunas restricciones en las operaciones realizadas por esta CPU o controlador dependiendo de los tipos de datos. En los primeros tiempos esto era importante porque no todas las CPU admitían operaciones en números fraccionarios o números de diferente longitud, etc.

A este respecto, preferiría comprobar la ortogonalidad del lenguaje intermedio común utilizando el lenguaje Stack Machine como destino para el compilador de C #, no el propio C #.

Si está realmente interesado en la ortogonalidad de C # y no me equivoco aquí (para cualquier propósito) sugeriría buscar algunos algoritmos de programación genética . Puede usarlos para generar diferentes programas a partir del conjunto de palabras clave dado (incluso los que no tienen sentido) y puede verificar automáticamente si son compilables. Esto lo ayudaría a ver automáticamente qué elementos del lenguaje se pueden combinar y derivar algunos aspectos de su métrica de ortogonalidad.

Alexander Galkin
fuente
6

El término "ortogonalidad" es un término simple para una noción matemática precisa: los términos del lenguaje forman un álgebra inicial (búsquelo en Wikipedia).

Básicamente significa "hay una correspondencia 1-1 entre sintaxis y significado". Esto significa: hay exactamente una forma de expresar las cosas y, si puede poner alguna expresión en un lugar en particular, entonces puede poner cualquier otra expresión allí también.

Otra forma de pensar en "ortogonal" es que la sintaxis obedece al principio de sustitución. Por ejemplo, si tiene una instrucción con un espacio para una expresión, cualquier expresión se puede colocar allí y el resultado sigue siendo un programa sintácticamente válido. Además, si reemplaza

Quiero enfatizar que "significado" no implica un resultado computacional. Claramente, 1 + 2 y 2 + 1 son iguales a 3. Sin embargo, los términos son distintos e implican un cálculo diferente incluso si tiene el mismo resultado. El significado es diferente, así como dos algoritmos de clasificación son diferentes.

Es posible que haya oído hablar del "árbol de sintaxis abstracta" (AST). La palabra "abstracto" aquí significa precisamente "ortogonal". ¡Técnicamente, la mayoría de los AST no son de hecho abstractos!

¿Quizás has oído hablar del lenguaje de programación "C"? La notación de tipo C no es abstracta. Considerar:

int f(int);

Así que aquí hay una declaración de función que devuelve el tipo int. El tipo de puntero a esta función viene dado por:

int (*)(int)

Tenga en cuenta que no puede escribir el tipo de la función. ¡La notación de tipo C es una mierda! No es abstracto No es ortogonal. Ahora, supongamos que queremos hacer una función que acepte el tipo anterior en lugar de int:

int (*) ( int (*)(int) )

Todo bien ... pero ... ¿y si queremos devolverlo?

int (*)(int) (*) (int)

Woops! Inválido. Vamos a agregar parens:

(int (*)(int)) (*) (int)

Woops! Eso tampoco funciona. Tenemos que hacer esto (¡es la única forma!):

typedef int (intintfunc*) (int);
intintfunc (*)(int)

Ahora está bien, pero tener que usar un typedef aquí es malo. C apesta. No es abstracto No es ortogonal. Así es como se hace esto en ML, que es:

 int -> (int -> int)

Condenamos C en el nivel de sintaxis.

Ok, ahora vamos a flog C ++. Podemos arreglar la estupidez anterior con plantillas y obtener una notación similar a ML (más o menos):

fun<int, int>
fun< fun<int,int>, int>

pero el sistema de tipos real está fundamentalmente viciado por las referencias: si Tes un tipo, ¿entonces es T&un tipo? La respuesta es imprecisa: en el nivel de sintaxis, si tiene un tipo U = T &, entonces U & está permitido pero solo significa T &: una referencia a una referencia es la referencia original. Esto apesta! Rompe el requisito de unicidad semánticamente. Peor: T & & no está permitido sintácticamente: esto rompe el principio de sustitución. Por lo tanto, las referencias de C ++ rompen la ortogonalidad de dos maneras diferentes, dependiendo del tiempo de unión (análisis o análisis de tipo). Si quieres entender cómo hacer esto bien ... ¡no hay problema con los punteros!

Casi ningún idioma real es ortogonal. Incluso Scheme, que pretende una gran claridad de expresión, no lo es. Sin embargo, se puede considerar que muchos buenos idiomas tienen una "base de características razonablemente similar a la ortogonal" y esa es una buena recomendación para un lenguaje, aplicado tanto a la sintaxis como a la semántica subyacente.

Yttrill
fuente
¿Entonces crees que ML es más ortogonal que otros? ¿Qué hay de Lisp y Haskell?
Joan Venge
1
@joan: bueno, lisp no tiene ninguna función, por lo que satisface el requisito en vacío :)
Yttrill
@joan: no soy un programador de Haskell, por lo que es un poco difícil de decir, pero la presencia en Haskell de "funcionalidad de nivel extremadamente alto" es indicativa de una fuerte ortogonalidad: simplemente no puede tener una implementación coherente de Mónadas o Flechas a menos que el resto de la lengua tiene sustancial "ortogonalidad"
Yttrill
Lo que piensas de Pascal. Parece mucho mejor que C.
supercat
Sé que mi comentario tiene casi 4 años de retraso, pero acabo de encontrarlo. Esta respuesta es incorrecta en casi todo. Incluso todo "¡es la única forma!" parte simplemente está mal. Puede expresarlo fácilmente sin un ejemplo typedef int (*intintfunc())(int) { ... }: intintfunc es una función que no toma argumentos y devuelve un puntero a una función que toma 1 argumento int y devuelve un valor int.
Wiz
4

Probar la ortogonalidad es probar un negativo. Significa que no tienes construcciones que no sean ortogonales, lo que significa que es mucho más fácil demostrar que algo no es ortogonal de lo que es.

En la práctica, la mayoría de la gente habla sobre la ortogonalidad de los lenguajes de programación en términos de grados en lugar de ser completamente ortogonales o no. Cuando el conocimiento de hacer algo en un contexto se traduce en otro contexto y "hace lo que esperas", se dice que ese lenguaje es más ortogonal. LISP se considera altamente ortogonal porque todo es una lista, pero no creo que se pueda decir que es 100% ortogonal debido a algunas redundancias que lo hacen más fácil de usar. Se considera que C ++ no es muy ortogonal porque hay muchas pequeñas "trampas" en las que no funciona de la manera que piensas.

Karl Bielefeldt
fuente
3

Advertencia, no sé nada sobre este tema.

Un rápido vistazo a Wikipedia parece indicar que la ortogonalidad se dirige principalmente a patrones de diseño y diseño de sistemas. En términos de lenguajes de programación, la entrada indica que los conjuntos de instrucciones son ortogonales si hay una y solo una instrucción para cada acción posible, o mejor dicho, ninguna instrucción se superpone a otra.

Para C #, me imagino que es ortogonal, ya que la mayoría de los trucos de sintaxis ( foreachvienen a la mente) son simplemente front-end para versiones especialmente formadas de la construcción base (se foreachconvierten en forbucles). En general, el lenguaje solo es compatible con hacer cosas de una sola manera, a pesar de que el azúcar sintáctico proporciona formas adicionales de hacerlo. Y, por último, todo se compila en MSIL(o como se llame en estos días) y MSILes probable que sea ortogonal.

Si hace la advertencia de que el azúcar sintáctico es esencialmente un "envoltorio" para hacerlo de la "manera difícil", puede analizar las diversas características del lenguaje, omitir el azúcar y ver si hay construcciones que realmente se superponen. Si no, me imagino que podría declarar el idioma ortogonal.

Mis dos centavos.

digitlworld
fuente
Creo que si tanto for como foreach son características de un lenguaje, mientras que uno es un azúcar sintáctico del otro (donde los efectos de foreach se pueden lograr usando for), el lenguaje pierde su ortogonalidad allí.
vpit3833
¿No do...whilese puede usar para proporcionar el mismo efecto que for? Nunca he oído hablar de ninguno de ellos como considerado azúcar sintáctico.
Matthew Flynn
1
@MatthewFlynn: ¡Bah! Son AMBOS azúcar sintáctico, ¡podrías reemplazar tu iteración con una función recursiva! ;)
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: ¿No es eso solo azúcar sintáctico para GOTO's?
Ivan
2
@MatthewFlynn do whilegarantiza una ejecución de bucle único y verifica la condición después del hecho. forcomprueba primero la condición y no garantiza una sola ejecución.
digitlworld
2

Mi pregunta es, ¿cómo hago para el resto de características, particularmente características que no están relacionadas entre sí?

Continúas haciendo lo que estás haciendo, enumerando todas las combinaciones que funcionan o están prohibidas.

Eso es todo. Es bastante doloroso hacerlo.

¿Todas las características tienen que coexistir / apilarse juntas?

Si todas las características se pueden dividir en subconjuntos disjuntos que no interfieren entre sí, entonces seguro, todo sería sensato.

Todas las estructuras de datos funcionan con todos los tipos primitivos. Todos los operadores de expresión trabajan con todos los tipos. Esas son definiciones comunes de ortogonalidad. Pero es posible que desee más (o menos)

A veces, sin embargo, hay casos especiales debido a sistemas operativos o bibliotecas heredadas que no son ortogonales.

Además, algunos tipos no son realmente muy conformes en absoluto. Por ejemplo, Python le permite comparar dos objetos de diccionario para "ordenar". Pero casi no hay una definición sensata de "ordenar" entre los diccionarios. Python define uno, pero es bastante discutible. ¿Ese caso especial hace que los diccionarios fallen una prueba de ortogonalidad?

¿Qué tan ortogonal es "suficientemente ortogonal"? ¿Qué es lo que necesita ver para ser feliz con el grado de ortogonalidad en su idioma.

S.Lott
fuente
2

La lista de características no ortogonales es de hecho larga en la mayoría de los lenguajes de programación, p. Ej.

  • clases anónimas entran en conflicto con la reflexión de Java
  • conflicto de borrado genérico y tipo con reflexión java
  • Las matrices son algo diferentes a otros objetos, debido a su tipo especial, aunque sean objetos.
  • los métodos estáticos frente a instancias no son lo mismo, por ejemplo, no puede anular un método estático
  • clase anidada son un pensamiento posterior
  • impacto del tipeo dinámico vs. estático en la estrategia de envío de mensajes (ver, por ejemplo, este caso límite en C #)
  • etc.

Esos son algunos que me vienen a la mente, pero hay muchos otros, y también en otros idiomas.

Es difícil asegurarse de que no haya una interferencia sutil entre las características del lenguaje. Como CAR Hoare indica en su documento "Consejos sobre el diseño del lenguaje de programación":

Parte del diseño del lenguaje consiste en la innovación. Esta actividad conduce a nuevas características del lenguaje de forma aislada. La parte más difícil del diseño del lenguaje radica en la integración : seleccionar un conjunto limitado de características del lenguaje y pulirlas hasta que el resultado sea un marco simple y consistente que no tenga más asperezas.

Probablemente, un buen movimiento para aumentar la ortogonalidad es unificar conceptos (que va en la dirección de la respuesta @ karl-bielfeld). Si todo es, digamos una lista o un objeto, es probable que haya menos conflictos. O, en lugar de tener una clase anidada y un pensamiento posterior, conviértalo en una característica central.

La mayoría de los documentos sobre lenguajes de programación demuestran ciertas propiedades del lenguaje (p. Ej., Solidez de tipo) en un subconjunto (un "núcleo") del lenguaje que se formaliza. Aquí debemos hacer lo contrario, demostrar que todas las características se componen de forma segura. Además, eso significa que uno debe definir lo que significa "componer". ¿Significa "correr"? (En este caso, el enlace anterior sobre el caso de borde con escritura dinámica y estática es seguro). ¿Significa estar "seguro"? ¿Significa ser predecible desde el punto de vista del desarrollador?

Todo eso es muy interesante, pero también muy desafiante.

ewernli
fuente