¿Por qué no estamos investigando más hacia las garantías de tiempo de compilación?

12

Me encanta todo lo que es tiempo de compilación y me encanta la idea de que una vez que compilas un programa, se hacen muchas garantías sobre su ejecución. En términos generales, un sistema de tipo estático (Haskell, C ++, ...) parece ofrecer garantías de tiempo de compilación más sólidas que cualquier sistema de tipo dinámico.

Por lo que entiendo, Ada va aún más lejos en lo que respecta a la verificación del tiempo de compilación, y es capaz de detectar una mayor gama de errores antes de la ejecución. También se considera bastante seguro, dado que, en algún momento, se eligió para campos delicados (cuando los errores de programación pueden costar vidas humanas).

Ahora, me pregunto: si las garantías estáticas más fuertes conducen a un código más documentado y seguro, ¿por qué no estamos investigando más en esa dirección?

Un ejemplo de algo que parece faltar sería un lenguaje que en lugar de definir un inttipo genérico que tenga un rango determinado por el número de bits de la arquitectura subyacente, uno podría tener rangos (en el siguiente ejemplo se Int [a..b]describe un tipo entero entre ayb incluidos):

a : Int [1..24]
b : Int [1..12]
a + b : Int [2..36]
a - b : Int [-11..23]
b - a : Int [-23..11]

o (tomando esto de Ada):

a : Int [mod 24]
b : Int [mod 24]
a + b : Int [mod 24]

Este lenguaje seleccionaría el mejor tipo subyacente para el rango y realizaría una verificación de tiempo de compilación en las expresiones. De modo que, por ejemplo, dado:

a : Int [-3..24]
b : Int [3..10]

luego:

a / b

nunca se definirá

Este es solo un ejemplo, pero siento que hay mucho más que podemos hacer cumplir en el momento de la compilación. Entonces, ¿por qué parece tan poca investigación sobre esto? ¿Cuáles son los términos técnicos que describen esta idea (para que pueda encontrar más información sobre este tema)? Cuales son los limites?

Zapato
fuente
2
Pascal tiene tipos de subrangos enteros (es decir, 1960), pero desafortunadamente la mayoría de las implementaciones solo los verifican en tiempo de ejecución (int (-1..4) es una asignación compatible con int (100..200) en tiempo de compilación). Hay beneficios limitados de eso, y la programación basada en contratos extiende la idea en una mejor dirección (Eiffel, por ejemplo). Los lenguajes como C # intentan obtener algunos de esos beneficios con atributos, no los he usado, así que no estoy seguro de cuán útiles sean en la práctica.
1
@ Ӎσᶎ: los atributos en C # son solo clases de metadatos, por lo que cualquier validación de datos se produciría en tiempo de ejecución.
Robert Harvey
8
¿Cómo sabes que hay poca investigación sobre esto? Intenta buscar en Google dependent typeo refinement type.
Phil
3
Estoy de acuerdo en que la premisa parece ser defectuosa; Este es ciertamente un campo de investigación activo. El trabajo nunca se hace . Por lo tanto, no entiendo cómo se puede responder a esta pregunta.
Raphael
1
@Robert Harvey: El hecho de que ADA brinde más garantías no significa que el compilador detecte todos los errores, solo hará que los errores sean menos probables.
Giorgio

Respuestas:

11

No estoy en condiciones de decir cuánto se debe hacer más investigación sobre el tema, pero puedo decirle que se está haciendo investigación, por ejemplo, el programa Verisoft XT financiado por el gobierno alemán.

Los conceptos que creo que está buscando se llaman verificación formal y programación basada en contratos , donde este último es una forma fácil de programar para hacer lo primero. En la programación basada en contratos, primero debe escribir su código de manera normal y luego insertar los llamados contratos en el código. Un lenguaje fácil de usar que se basa en este paradigma es Spec # de Microsoft Research, y la extensión de contratos de código funcionalmente similar pero un poco menos bonita para C # que ambos pueden probar en línea (también tienen herramientas similares para otros idiomas, consulte rise4fun ) El "int con tipo de rango" que mencionó se reflejaría en dos contratos en una función:

Contract.Requires(-3 <= a && a <= 24);
Contract.Requires( 3 <= b && b <= 10);

Si desea llamar a esa función, debe usar parámetros que se aseguren de cumplir con estos criterios, u obtendrá un error de tiempo de compilación. Los anteriores son contratos muy simples, puede insertar casi cualquier suposición o requisito sobre variables o excepciones y su relación que pueda pensar y el compilador verificará si cada requisito está cubierto por una suposición o algo que pueda garantizarse, es decir, derivado de los supuestos Es por eso de donde proviene el nombre: la persona que llama establece requisitos , la persona que llama se asegura de que se cumplan, como en un contrato comercial.

Bajo el capó, los contratos de código generalmente se combinan con el conocimiento sobre el funcionamiento interno (semántica operativa) del lenguaje de programación en una lista de condiciones de verificación . Esta lista representa básicamente una gran proposición lógica con variables libres: las entradas de su programa. Si la proposición es verdadera para todas las asignaciones de variables posibles, entonces el programa se considera correcto. Para verificar si este es o no el caso, un Probador SMTn PP(x1,x2,...,xn)nPes usado Desde el lado de la CS, esas dos son las partes críticas del proceso: la generación de condiciones de verificación es complicada y SMT es un problema NP-completo o indecidible, dependiendo de las teorías consideradas. Incluso hay una competencia para los solucionadores SMT, por lo que ciertamente se investiga un poco al respecto. Además, existen enfoques alternativos para usar SMT para la verificación formal, como la enumeración del espacio de estado, la verificación de modelos simbólicos, la verificación de modelos acotados y muchos más que también se están investigando, aunque SMT es, afaik, actualmente el enfoque más "moderno".

En cuanto a los límites de la idea general:

  • Como se indicó anteriormente, demostrar que el programa es correcto es un problema computacionalmente difícil, por lo que es posible que la verificación del tiempo de compilación de un programa con contratos (u otra forma de especificación) tome mucho tiempo o incluso sea imposible. Aplicar la heurística que funciona bien la mayor parte del tiempo es lo mejor que se puede hacer al respecto.
  • Cuanto más especifique sobre su programa, mayor será la probabilidad de tener errores en la propia especificación . Esto puede conducir a falsos positivos (la comprobación del tiempo de compilación falla aunque todo esté libre de errores) o la falsa impresión de estar seguro, aunque su programa todavía tenga errores.
  • Escribir contratos o especificaciones es realmente un trabajo tedioso y la mayoría de los programadores son demasiado vagos para hacerlo. Intente escribir un programa C # con contratos de código en todas partes, después de un tiempo pensará "vamos, ¿es realmente necesario?". Es por eso que la verificación formal generalmente solo se usa para el diseño de hardware y los sistemas críticos de seguridad, como el software que controla aviones o automóviles.

Una última cosa que vale la pena mencionar que no se ajusta del todo a la explicación anterior es un campo llamado "Teoría de la complejidad implícita", por ejemplo, este artículo . Su objetivo es caracterizar los lenguajes de programación en los que cada programa que puede escribir se encuadra en una clase de complejidad específica, por ejemplo P. En dicho lenguaje, cada programa que escribe se "asegura" automáticamente para que tenga un tiempo de ejecución polinómico, que se puede "verificar" en tiempo de compilación simplemente compilando el programa. Sin embargo, no conozco ningún resultado prácticamente utilizable de esta investigación, pero también estoy lejos de ser un experto.

Stefan Lutz
fuente
¿No debería ser posible generar tipos dependientes o contratos a partir de una combinación de pruebas de ejemplo y código sin tipo, dada una cierta "estrategia" que puede elegir dependiendo de su proyecto?
aoeu256