2011-10-16 14 views
37
data Plane = Plane { point :: Point, normal :: Vector Double } 
data Sphere = Sphere { center :: Point, radius :: Double } 

class Shape s where 
    intersect :: s -> Ray -> Maybe Point 
    surfaceNormal :: s -> Point -> Vector Double 

Ho anche eseguito sia Plane che Sphere istanze di Shape.Elenco di tipi diversi?

Sto provando a memorizzare sfere e piani nella stessa lista, ma non funziona. Capisco che non dovrebbe funzionare perché Sphere e Plane sono due tipi diversi, ma sono entrambe le istanze di Shape, quindi non dovrebbe funzionare? Come potrei memorizzare forme e piani in una lista?

shapes :: (Shape t) => [t] 
shapes = [ Sphere { center = Point [0, 0, 0], radius = 2.0 }, 
     Plane { point = Point [1, 2, 1], normal = 3 |> [0.5, 0.6, 0.2] } 
     ] 
+1

http://www.haskell.org/haskellwiki/Heterogenous_collections –

+2

Ero consapevole di collezioni eterogenee, ma era qualcosa che volevo evitare. – Arlen

risposta

47

Questo problema rappresenta un punto di svolta tra il pensiero orientato agli oggetti e il pensiero funzionale. A volte persino gli Haskeller sofisticati sono ancora in questa transizione mentale, ei loro progetti spesso cadono nello schema existential typeclass, menzionato nella risposta di Thomas.

Una soluzione funzionale a questo problema implica reificare il typeclass in un tipo di dati (di solito una volta fatto questo, la necessità per il typeclass svanisce):

data Shape = Shape { 
    intersect :: Ray -> Maybe Point, 
    surfaceNormal :: Point -> Vector Double 
} 

Ora si può facilmente costruire una lista di Shape s , perché è un tipo monomorfico. Poiché Haskell non supporta il downcasting, nessuna informazione viene persa rimuovendo la distinzione di rappresentazione tra Plane se Sphere s. I tipi di dati specifici diventano funzioni che costruiscono Shape s:

plane :: Point -> Vector Double -> Shape 
sphere :: Point -> Double -> Shape 

Se non è possibile catturare tutto quello che c'è da sapere su una forma nel tipo di dati Shape, è possibile enumerare i casi con un tipo di dati algebrico, come suggerisce Thomas . Ma vorrei raccomandarlo contro se possibile; invece, prova a trovare le caratteristiche essenziali di una forma di cui hai bisogno piuttosto che elencare solo degli esempi.

+4

Questo non significa che non dovremmo pensare alle classi di tipi come ad ereditarietà O o alle interfacce e pensare al loro sottotipo. Sono solo un insieme di funzioni che un tipo può supportare – Ankur

+0

E se fosse necessario digitare tipicamente un oggetto o un oggetto? Allora suggeriresti lo stile ADT? – CMCDragonkai

+0

@CMCDragonkai, probabilmente creerei una 'Sfera dati 'che ha le proprietà necessarie di una sfera,' Piano dati 'che ha le proprietà necessarie di un piano, e quindi funzioni di iniezione come' toShape :: Sphere -> Shape' . Magari mettilo in un 'classe ToShape' (come ripensamento) – luqui

24

Siete alla ricerca di una lista eterogenea, che la maggior parte non lo fanno Haskellers piace particolarmente anche se loro stessi hanno chiesto la stessa domanda quando prima imparare Haskell.

Lei scrive:

shapes :: (Shape t) => [t] 

Questo dice la lista è di tipo t, che sono tutti uguali e capita di essere una forma (la stessa forma!). In altre parole - no, non dovrebbe funzionare come l'hai.

Due modi comuni per gestirlo (un modo Haskell 98, poi un modo più elaborato che Non consiglio secondo) sono:

utilizzare un nuovo tipo di sindacali staticamente i sottotipi di interesse:

data Foo = F deriving Show 
data Bar = B deriving Show 

data Contain = CFoo Foo | CBar Bar deriving Show 
stuffExplicit :: [Contain] 
stuffExplicit = [CFoo F, CBar B] 

main = print stuffExplicit 

Questo è bello visto che è diretto e non si perde alcuna informazione su ciò che è contenuto nella lista. È possibile determinare che il primo elemento è un Foo e il secondo elemento è un Bar. Lo svantaggio, come probabilmente già sapete, è che è necessario aggiungere esplicitamente ciascun tipo di componente creando un nuovo costruttore di tipo Contain. Se questo non è desiderabile, continua a leggere.

Tipi Uso esistenziali: Un'altra soluzione comporta la perdita di informazioni circa gli elementi - basta conservare, ad esempio, la conoscenza che gli elementi sono in una classe particolare. Di conseguenza, puoi utilizzare solo le operazioni da quella classe sugli elementi della lista. Ad esempio, il seguito sarà solo ricordare gli elementi sono della classe Show, quindi l'unica cosa che si può fare per gli elementi sono funzioni d'uso che sono polimorfici in Show:

data AnyShow = forall s. Show s => AS s 

showIt (AS s) = show s 

stuffAnyShow :: [AnyShow] 
stuffAnyShow = [AS F, AS B] 

main = print (map showIt stuffAnyShow) 

Questo richiede alcune estensioni al linguaggio Haskell , ovvero ExplicitForAll e ExistentialQuantification. Abbiamo dovuto definire esplicitamente showIt (utilizzando la corrispondenza dei modelli per decostruire il tipo AnyShow) perché non è possibile utilizzare i nomi dei campi per i tipi di dati che utilizzano la quantificazione esistenziale.

Non ci sono più soluzioni (si spera un'altra risposta utilizzerà Data.Dynamic - se nessuno lo fa e sei interessato poi leggere su di esso e si sentono liberi di inviare tutte le domande che la lettura genera).

+0

Il primo metodo è la prima cosa che ho provato e non ha funzionato bene. Non sono sicuro se mi piace il secondo metodo! Vorrei anche evitare di usare le estensioni. – Arlen

+0

Non ne sono entusiasta - sembra una sintassi più strana quando i concetti sono già (probabilmente) abbastanza estranei al lettore. Forse in un'altra risposta? –