interface - Redundancia en la declaración de tipo OCaml(ml/mli)
module declaration (5)
Intento entender algo específico sobre los módulos ocaml y su compilación:
¿Estoy obligado a redeclarar los tipos ya declarados en .mli
dentro de las implementaciones .ml
específicas?
Solo para dar un ejemplo:
(* foo.mli *)
type foobar = Bool of bool | Float of float | Int of int
(* foo.ml *)
type baz = foobar option
Esto, de acuerdo con mi forma normal de pensar acerca de las interfaces / implementaciones, debería estar bien, pero dice
Error: constructor de tipo no vinculado foobar
al intentar 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 forma de evitar esta redundancia o me veo obligado a redeclarar los tipos todo el tiempo?
Gracias por adelantado
En general, sí, está obligado a duplicar los tipos.
Sin embargo, puede solucionar esto con Camlp4 y la extensión de sintaxis pa_macro (paquete camlp4.macro
: 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 IMO más limpia.
No, en el archivo mli, solo di "type foobar". Esto funcionará
OCaml intenta obligarlo a separar la interfaz ( .mli
) de la implementación ( .ml
. La mayoría de las veces, esto es bueno, para los valores, publica el tipo en la interfaz y mantiene el código en la 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 declaras un tipo abstracto).
Una forma de verlo es que la interfaz ya contiene suficiente información para escribir la implementación. Dado el type foobar = Bool of bool | Float of float | Int of int
interfaz type foobar = Bool of bool | Float of float | Int of int
type foobar = Bool of bool | Float of float | Int of int
type foobar = Bool of bool | Float of float | Int of int
, solo hay una implementación posible. ¡Así que no escribas una implementación!
Una expresión 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 generalmente aparece 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 un .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
, y luego otra declaración de tipo que incluya ''a Map.Make(OrderedFoo).t
.) Estos no se pueden poner en la interfaz archivos. 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 un .ml
sin un .mli
o una duplicación.
Puede dejar que ocamlc genere el archivo mli desde el archivo ml:
ocamlc -i some.ml > some.mli
Sí, te obligan a redeclarar tipos . Las únicas formas de evitarlo que yo sé son
No use un archivo .mli; simplemente exponer todo sin interfaz. Terrible idea.
Use 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.