2010-07-13 20 views
26

Estoy tratando de entender una cosa específica acerca de los módulos OCaml y su compilación:declaración de tipo de OCaml (ml/MLI)

estoy yo obligado a redeclare tipos ya declarados en un .mli dentro de las implementaciones específicas .ml?

Sólo para dar un ejemplo:

(* foo.mli *) 
type foobar = Bool of bool | Float of float | Int of int 

(* foo.ml *) 
type baz = foobar option 

Esto, según mi forma habitual de pensar en interfaces/implementaciones, debe estar bien, pero se dice

Error: Unbound type constructor foobar

al tratar de compilar con

ocamlc -c foo.mli 
ocamlc -c foo.ml 

Por supuesto, el error desaparece si declaro foobar dentro de foo.ml también, pero parece una forma compleja ya que tengo que sincronizar todo en cada cambio.

¿Hay alguna manera de evitar esta redundancia o me veo obligado a redeclarar los tipos cada vez?

Gracias de antemano

Respuesta

15

OCaml intenta obligarlo a separar la interfaz (.mli) de la implementación (.ml. La mayoría de las veces, esto es algo bueno; para los valores, publique el tipo en la interfaz y conserve el código en implementación. Se podría decir que OCaml está imponiendo una cierta cantidad de abstracción (las interfaces deben publicarse, no hay código en las interfaces).

Para los tipos, muy a menudo, la implementación es la misma que la interfaz: ambos establecen que el tipo tiene una representación particular (y tal vez que la declaración de tipo es generativa). Aquí no puede haber abstracción porque el implementador no tiene información sobre el tipo que no desea publicar. (La excepción es básicamente cuando declara un tipo abstracto.)

Una forma de verlo es que la interfaz ya contiene suficiente información para escribir la implementación. Dada la interfaz type foobar = Bool of bool | Float of float | Int of int, solo hay una implementación posible. ¡Así que no escribas una implementación!

Un modismo común es tener un módulo dedicado a las declaraciones de tipo y hacer que tenga solo un .mli. Dado que los tipos no dependen de los valores, este módulo suele aparecer muy temprano en la cadena de dependencia. La mayoría de las herramientas de compilación se adaptan bien a esto; por ejemplo ocamldep hará lo correcto. (Esta es una ventaja sobre tener solo .ml.)

La limitación de este enfoque es cuando también necesita algunas definiciones de módulos aquí y allá. (Un ejemplo típico es definir un tipo foo, luego un módulo OrderedFoo : Map.OrderedType con type t = foo, luego otra declaración de tipo que involucre 'a Map.Make(OrderedFoo).t). Estos no se pueden poner en archivos de interfaz. A veces es aceptable desglosar las definiciones en varios fragmentos, primero un grupo de tipos (types1.mli), luego un módulo (mod1.mli y mod1.ml), luego más tipos (types2.mli). Otras veces (por ejemplo, si las definiciones son recursivas) debe vivir con .ml sin un .mli o duplicación.

14

Sí, que se ven obligados a redeclare tipos. Las únicas formas de evitarlo que conozco son

  • No utilice un archivo .mli; simplemente exponer todo sin interfaz. Terrible idea.

  • Utilice una herramienta de programación alfabetizada u otro preprocesador para evitar la duplicación de las declaraciones de interfaz en One True Source. Para proyectos grandes, hacemos esto en mi grupo.

Para proyectos pequeños, simplemente duplicamos las declaraciones de tipo. Y refunfuñar al respecto.

+0

No creo que haya ninguna limitación al permitir que un archivo '.ml' infiera los tipos descritos en el' .mli' acoplado. Por lo que entiendo, la implementación real __forces redundancy__, pero también que __ las dos firmas deben ser iguales__ por lo que esto está doblando las mismas declaraciones. Es por eso que pensé que debería ser consciente de estas declaraciones sin tener que copiarlas y pegarlas. El algoritmo de inferencia de tipo no sufriría problemas según yo. – Jack

+3

No obliga a la redundancia ni requiere que las firmas sean idénticas, solo que la declaración en el archivo ml debe ser igual o más específica que la declaración mli. El objetivo del mli es definir qué es visible en la interfaz, por lo que puede elegir no exponer el tipo (en cuyo caso no está en el archivo mli) o puede elegir exponer que hay un tipo, pero no cómo se usa (en cuyo caso las declaraciones de tipo son diferentes). Por supuesto, en su situación, tendría sentido para el compilador asumir el tipo ya que está completamente definido en el mli. –

+3

@Niki no hay redundancia forzada en declaraciones * value * (que son opcionales en el .ml de todos modos), y en la práctica es donde entra "al menos tan específico como". Pero en el 99% de los casos aparece un * manifest type * en una interfaz es idéntica a la definición del tipo en la implementación. Esto es redundante e irritante, pero como diseñador de lenguaje he pensado mucho sobre este problema y no he presentado una propuesta que creo que sea tanto de principios como significativamente mejor que OCaml. –

12

Puede dejar ocamlc generar el archivo MLI para usted desde el archivo ml:

ocamlc -i some.ml > some.mli 
3

En general, sí, usted está obligado a duplicar los tipos.

Puede solucionar esto, sin embargo, con Camlp4 y la extensión de sintaxis pa_macro (paquete findlib: camlp4.macro). Define, entre otras cosas, e INCLUYE constructo. Puede usarlo para factorizar las definiciones de tipos comunes en un archivo separado e incluir ese archivo en los archivos .ml y .mli. No he visto esto hecho en un proyecto OCaml implementado, sin embargo, así que no sé si calificaría como la práctica recomendada, pero es posible.

La solución de programación alfabetizada, sin embargo, es más limpia IMO.

-3

No, en el archivo mli, simplemente diga "type foobar". Esto funcionará