2011-02-06 13 views
8

in OCaml che sto lottando con i tipi di sottoclasse e:Qual è il modo corretto di creare una sottoclasse di Ocaml con metodi aggiuntivi?

class super = 
    object (self) 
    method doIt = 
    ... 
end; 

class sub = 
    object (self) 
    inherit super 
    method doIt = 
    ... 
    self#somethingElse 
    ... 

    method somethingElse = 
    ... 
end; 

let myFunction (s:super) = 
    ... 

myFunction new sub 

A quanto pare in OCaml, classe sub è non un "sottotipo" di classe super, perché il metodo sub#doIt chiama un metodo in sub che non è presente in super. Tuttavia, questo sembra un caso d'uso piuttosto comune per la programmazione OO. Qual è il modo consigliato per realizzare questo?

+0

'sub' è un sottotipo di' super', poiché l'aggiunta di un metodo non impedisce di violare il contratto richiesto da 'super'. Come sottolinea Rémi, tuttavia, OCaml non esegue la coercizione automatica. –

risposta

7

Come accennato da Rémi, il problema con il codice è che il tipo di OCaml il sistema supporta solo un tipo per espressione: un'espressione di tipo sub non è di tipo super. Nel tuo esempio, myFunction prevede un argomento di tipo super e l'espressione new sub è di tipo sub, quindi il problema.

L'upcasting è essenziale per la programmazione orientata agli oggetti e OCaml lo supporta con due distinti costrutti.

Il primo è tipo coercizione. Se super è un supertipo di sub (nel senso che semanticamente, i valori di tipo sub si comportano come valori di super) e x : sub, quindi (x :> super) : super. L'operatore di tipo :> rende esplicita la conversione — l'equivalente di ciò che i linguaggi popolari orientati agli oggetti implicano implicitamente quando si utilizza il valore di tipo sub in cui è previsto super.

Il secondo è vincoli supertype: richiede che una determinata variabile di tipo sia un sottotipo di un determinato tipo.Questo è scritto come #super o (#super as 'a) se si desidera nominare la variabile di tipo all'interno. I vincoli Supertype in realtà non cambiano il tipo di espressione come fa la coercizione di tipo, ma controllano semplicemente che il tipo sia un sottotipo valido del tipo richiesto.

a diventare più consapevoli della differenza, si consideri il seguente esempio:

class with_size ~size = object 
    val size = size : int 
    method size = size 
end 

class person ~name ~size = object 
    inherit with_size ~size 
    val name = name : string 
    method name = name 
end 

let pick_smallest_coerce (a : with_size) (b : with_size) = 
    if a # size < b # size then a else b 

let pick_smallest_subtype (a : #with_size) (b : #with_size) = 
    if a # size < b # size then a else b 

Il tipo di pic_smallest_coerce è with_size -> with_size -> with_size: anche se hai superato due person casi, il valore di ritorno sarebbe di tipo with_size e si sarebbe non essere in grado di chiamare il suo metodo name.

Il tipo di pic_smallest_subtype è (#with_size as 'a) -> 'a -> 'a: se passate due person casi, il sistema di tipo determinerebbe che 'a = person e identificare correttamente il valore restituito come essendo di tipo person (che consente di utilizzare il metodo name).

In breve, i vincoli Supertype semplicemente assicurarsi che il codice verrà eseguito, senza perdere alcuna informazione di tipo affatto — la variabile mantiene il suo tipo di originale. Tipo coercizione perde effettivamente digitare le informazioni (che, in assenza di down-casting, è una cosa molto brutta), in modo da dovrebbe essere utilizzato solo come ultima risorsa in due situazioni:

1. Non è possibile avere una funzione polimorfica. I vincoli Supertype si basano su #super come variabile di tipo libero, quindi se non puoi permetterti di avere una variabile di tipo libero nel tuo codice, dovrai farne a meno.

2. È necessario in realtà negozio valori di diversi tipi effettivi nello stesso contenitore. Un elenco o di riferimento che può contenere sia person o box casi utilizzeranno with_size e coercizione:

let things = [ my_person :> with_size ; my_box :> with_size ] 

si noti che l'algoritmo di inferenza scoprirà vincoli Supertype di propria (non determinerà quale classe o di classe che si Tipo intendeva usare, ma si costruirà un tipo di classe letterale):

let pick_smallest_infer a b = 
    if a # size < b # size then a else b 

val pick_smallest_infer : (< size : 'a ; .. > as 'b) -> 'b -> 'b 

come tale, con rare eccezioni, l'annotazione dei vincoli Supertype reale è un esercizio utile solo quando documentare il codice.

8

sub è probabilmente un sottotipo di super. Ma in ocaml non esiste una conversione di tipo implicita. Quindi la tua funzione non accetta il sottotipo di super. Devi fare in modo esplicito una costrizione:

let myFunction (s:super) = 
    ... 

myFunction (new sub :> super) 

o, preferibilmente, accettare sottotipo di super:

let myFunction (s:#super) = 
    ... 

myFunction new sub 
0

Se si desidera myFunction di accettare qualsiasi sottotipo di super come argomento, allora si dovrebbe definire in questo modo :

let myFunction s = 
    let s = (s :> super) in 
    ... 

... o equivalentemente ...

let myFunction (s :> super) = 
    ... 

Per quanto riguarda il commento che sub sembra non essere un sottotipo di super nel tuo esempio perché il metodo doIt in sub è quello che viene inviato quando il valore oggetto alla sinistra dell'operatore # è di classe di tipo sub, beh, Penso che ti sbagli. Questo è esattamente ciò che dovresti aspettarti.

Per consentire metodi nella classe sub di richiamare il metodo doIt nella classe super, si dovrebbe definire loro in questo modo:

class super = object (self) 
    method doIt = 
    ... 
end; 

class sub = object (self) 
    inherit super as parent 

    method doIt = 
    ... 
    let _ = parent#doIt in 
    ... 
    self#somethingElse 
    ... 

    method somethingElse = 
    ... 
end; 
Problemi correlati