¿Alguien puede explicarme la escritura dependiente? Tengo poca experiencia en Haskell, Cayenne, Epigram u otros lenguajes funcionales, por lo que cuanto más simples sean los términos que puedas usar, más lo agradeceré.
82
¿Alguien puede explicarme la escritura dependiente? Tengo poca experiencia en Haskell, Cayenne, Epigram u otros lenguajes funcionales, por lo que cuanto más simples sean los términos que puedas usar, más lo agradeceré.
Respuestas:
Considere esto: en todos los lenguajes de programación decentes puede escribir funciones, p. Ej.
Aquí,
f
toma un valorarg
y calcula un valorresult
. Es una función de valores a valores.Ahora, algunos lenguajes le permiten definir valores polimórficos (también conocidos como genéricos):
Aquí,
empty
toma un tipoT
y calcula un valor. Es una función de tipos a valores.Por lo general, también puede tener definiciones de tipo genérico:
Esta definición toma un tipo y devuelve un tipo. Puede verse como una función de tipos a tipos.
Hasta aquí lo que ofrecen los idiomas comunes. Un lenguaje se denomina de tipo dependiente si también ofrece la cuarta posibilidad, es decir, definir funciones desde valores hasta tipos. O en otras palabras, parametrizar una definición de tipo sobre un valor:
Algunos lenguajes convencionales tienen alguna forma falsa de esto que no debe confundirse. Por ejemplo, en C ++, las plantillas pueden tomar valores como parámetros, pero deben ser constantes en tiempo de compilación cuando se aplican. No es así en un lenguaje de tipificación verdaderamente dependiente. Por ejemplo, podría usar el tipo de arriba así:
Aquí, el tipo de resultado de la función depende del valor real del argumento
j
, por lo tanto, la terminología.fuente
BoundedInt
embargo, ¿no es el ejemplo un tipo de refinamiento? Eso es "bastante cercano" pero no es exactamente el tipo de "tipos dependientes" que, por ejemplo, Idris menciona primero en un tutorial sobre el tipo de dep.Los tipos dependientes permiten eliminar un conjunto mayor de errores lógicos en tiempo de compilación . Para ilustrar esto, considere la siguiente especificación sobre la función
f
:Sin tipos dependientes, podría hacer algo como esto:
Aquí el compilador no puede detectar si de
n
hecho es par, es decir, desde la perspectiva del compilador, la siguiente expresión está bien:Este programa se ejecutará y luego lanzará una excepción en tiempo de ejecución, es decir, su programa tiene un error lógico.
Ahora, los tipos dependientes te permiten ser mucho más expresivo y te permitirían escribir algo como esto:
Aquí
n
es de tipo dependiente{n: Integer | n mod 2 == 0}
. Podría ser útil leer esto en voz alta comoEn este caso, el compilador detectaría en el momento de la compilación un error lógico en el que se le ha pasado un número impar
f
y evitaría que el programa se ejecute en primer lugar:Aquí hay un ejemplo ilustrativo que utiliza tipos dependientes de la ruta de Scala de cómo podríamos intentar implementar una función que
f
satisfaga dicho requisito:case class Integer(v: Int) { object IsEven { require(v % 2 == 0) } object IsOdd { require(v % 2 != 0) } } def f(n: Integer)(implicit proof: n.IsEven.type) = { // do something with n safe in the knowledge it is even } val `42` = Integer(42) implicit val proof42IsEven = `42`.IsEven val `1` = Integer(1) implicit val proof1IsOdd = `1`.IsOdd f(`42`) // OK f(`1`) // compile-time error
La clave es notar cómo
n
aparece el valor en el tipo de valor, aproof
sabern.IsEven.type
:Decimos que el tipo
n.IsEven.type
depende del valor, den
ahí el término tipos dependientes .fuente
f(random())
lugar a un error de compilación?f
a alguna expresión requeriría que el compilador (con o sin su ayuda) proporcione que la expresión es siempre par, y no existe tal prueba pararandom()
(ya que de hecho puede ser impar), porf(random())
lo tanto , no podría compilar.Si conoce C ++, es fácil proporcionar un ejemplo motivador:
Digamos que tenemos algún tipo de contenedor y dos instancias del mismo
typedef std::map<int,int> IIMap; IIMap foo; IIMap bar;
y considere este fragmento de código (puede asumir que foo no está vacío):
Esto es basura obvia (y probablemente corrompe las estructuras de datos), pero verificará bien el tipo ya que "iterador en foo" e "iterador en barra" son del mismo tipo,
IIMap::iterator
aunque son totalmente incompatibles semánticamente.El problema es que un tipo de iterador no debería depender solo del tipo de contenedor, sino del objeto contenedor , es decir, debería ser un "tipo de miembro no estático":
foo.iterator i = foo.begin(); bar.erase(i); // ERROR: bar.iterator argument expected
Tal característica, la capacidad de expresar un tipo (foo.iterator) que depende de un término (foo), es exactamente lo que significa la escritura dependiente.
La razón por la que no ve a menudo esta función es porque abre una gran lata de gusanos: de repente termina en situaciones en las que, para verificar en tiempo de compilación si dos tipos son iguales, termina teniendo que probar dos expresiones son equivalentes (siempre producirán el mismo valor en tiempo de ejecución). Como resultado, si compara la lista de wikipedia de lenguajes de tipo dependiente con su lista de probadores de teoremas , puede notar una similitud sospechosa. ;-)
fuente
Citando los tipos de libros y lenguajes de programación (30.5):
fuente