2010-07-13 13 views
26

Sto cercando di capire una cosa specifica sui moduli OCaml e la loro compilazione:ridondanza nella dichiarazione del tipo di OCaml (ml/MLI)

io sono costretto a ridichiarare tipi già dichiarati in .mli all'interno delle specifiche .ml implementazioni?

Tanto per fare un esempio:

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

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

Questo, secondo il mio normale modo di pensare interfacce/implementazioni, dovrebbe essere ok, ma si dice

Error: Unbound type constructor foobar

durante il tentativo di compilare con

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

Ovviamente l'errore scompare se dichiaro foobar anche all'interno di foo.ml ma sembra un modo complesso dato che devo tenere sincronizzate le cose ad ogni cambio.

C'è un modo per evitare questa ridondanza o sono costretto a ridenominare i tipi ogni volta?

Grazie in anticipo

risposta

15

OCaml prova a forzare l'utente a separare l'interfaccia (.ml dall'implementazione (.ml). La maggior parte delle volte questa è una buona cosa: per i valori, si pubblica il tipo nell'interfaccia e si mantiene il codice nel implementazione Si potrebbe dire che OCaml sta applicando una certa quantità di astrazione (le interfacce devono essere pubblicate, nessun codice nelle interfacce).

Per i tipi, molto spesso, l'implementazione è la stessa dell'interfaccia: entrambi indicano che il tipo ha una rappresentazione particolare (e forse che la dichiarazione del tipo è generativa). Qui, non può esserci astrazione, perché l'implementatore non ha alcuna informazione sul tipo che non vuole pubblicare. (L'eccezione è fondamentalmente quando si dichiara un tipo astratto.)

Un modo per osservare è che l'interfaccia contiene già abbastanza informazioni per scrivere l'implementazione. Data l'interfaccia type foobar = Bool of bool | Float of float | Int of int, c'è solo una possibile implementazione. Quindi non scrivere un'implementazione!

Un idioma comune è quello di avere un modulo dedicato alle dichiarazioni di tipo e farlo avere solo un .mli. Poiché i tipi non dipendono dai valori, questo modulo si presenta in genere molto presto nella catena delle dipendenze. La maggior parte degli strumenti di compilazione è all'altezza di questo; per esempio ocamldep farà la cosa giusta. (Questo è uno dei vantaggi rispetto al fatto di avere solo un .ml.)

La limitazione di questo approccio è quando sono necessarie anche alcune definizioni di modulo qua e là. (Un tipico esempio è la definizione di un tipo foo, quindi un modulo OrderedFoo : Map.OrderedType con type t = foo, quindi un'ulteriore dichiarazione di tipo che comprende 'a Map.Make(OrderedFoo).t.) Questi non possono essere inseriti nei file di interfaccia. A volte è accettabile abbattere le definizioni in diversi blocchi, prima un gruppo di tipi (types1.mli), quindi un modulo (mod1.mli e mod1.ml), quindi altri tipi (types2.mli). Altre volte (ad esempio se le definizioni sono ricorsive) devi vivere con uno .ml senza uno o una duplicazione.

14

Sì, si è costretti a ridichiarare tipi. Gli unici metodi che conosco sono

  • Non utilizzare un file .mli; basta esporre tutto senza interfaccia. Idea terribile

  • Utilizzare uno strumento di programmazione alfabetica o un altro preprocessore per evitare la duplicazione delle dichiarazioni dell'interfaccia nell'unità One True Source. Per grandi progetti, lo facciamo nel mio gruppo.

per i piccoli progetti, che abbiamo appena duplicato dichiarazioni di tipo. E borbotta a riguardo.

+0

Non penso ci debbano essere limitazioni nel consentire ad un file '.ml' di dedurre i tipi decritti in accoppiati' .mli'. Da quello che capisco l'effettiva implementazione __forces la ridondanza__ ma anche che __le due firme devono essere uguali__ quindi questo è effettivamente il raddoppio delle stesse dichiarazioni. Ecco perché ho pensato che dovrebbe essere a conoscenza di queste dichiarazioni senza doverle copiare e incollare. Secondo me, l'algoritmo di inferenza del tipo non avrebbe problemi. – Jack

+3

Non impone la ridondanza né richiede che le firme siano identiche, solo che la dichiarazione nel file ml deve essere uguale o più specifica della dichiarazione mli. Il punto del mli è definire cosa è visibile nell'interfaccia, in quanto tale è possibile scegliere di non esporre il tipo (nel qual caso non è nel file mli) o si può scegliere di esporre che c'è un tipo, ma non come è usato (nel qual caso le dichiarazioni di tipo sono diverse). Ovviamente nella tua situazione avrebbe senso che il compilatore assuma solo il tipo dato che è completamente definito nel mli. –

+3

@Niki non c'è ridondanza forzata nelle dichiarazioni * value * (che sono facoltative nel .ml), ed in pratica è qui che entra in gioco "almeno tanto quanto". Ma nel 99% dei casi un * manifest type * dichiarato in un'interfaccia è identica alla definizione del tipo nell'implementazione. Questo è ridondante e irritante, ma come progettista di linguaggi ho riflettuto a fondo su questo problema e non ho elaborato una proposta che ritengo sia di principio sia di gran lunga migliore di OCaml. –

12

Puoi lasciare ocamlc generare il file MLI per voi dal file ml:

ocamlc -i some.ml > some.mli 
3

In generale, sì, si sono tenuti a duplicare i tipi.

È possibile aggirare questo problema, tuttavia, con Camlp4 e l'estensione di sintassi pa_macro (pacchetto findlib: camlp4.macro). Definisce, tra le altre cose, il costrutto INCLUDE. È possibile utilizzarlo per scomporre le definizioni di tipo comune in un file separato e includerlo in entrambi i file .ml e . Non ho visto questo fatto in un progetto OCaml distribuito, tuttavia, quindi non so che si qualificherebbe come pratica raccomandata, ma è possibile.

La soluzione di programmazione informata, tuttavia, è IMO più pulito.

-3

No, nel file mli, basta dire "tipo foobar". Questo funzionerà.

Problemi correlati