Liste de différents types?

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 

J’ai aussi créé des exemples de Shape Plane et Sphere .

J’essaie de stocker les sphères et les avions dans la même liste, mais cela ne fonctionne pas. Je comprends que cela ne devrait pas fonctionner parce que Sphere et Plane sont deux types différents, mais ils sont tous deux des instances de Shape , alors ça ne devrait pas fonctionner? Comment pourrais-je stocker des formes et des avions dans une liste?

 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] } ] 

Ce problème représente un tournant entre la pensée orientée object et la pensée fonctionnelle. Parfois, même les Haskellers sophistiqués sont encore dans cette transition mentale et leurs conceptions tombent souvent dans le modèle de classe existentielle mentionné dans la réponse de Thomas.

Une solution fonctionnelle à ce problème consiste à réifier la classe de type en un type de données (généralement, une fois cette opération effectuée, la classe de types disparaît):

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

Maintenant, vous pouvez facilement construire une liste de Shape , car c’est un type monomorphe. Étant donné que Haskell ne prend pas en charge la décomposition, aucune information n’est perdue en supprimant la distinction représentative entre les Plane et les Sphere . Les types de données spécifiques deviennent des fonctions qui construisent des formes:

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

Si vous ne pouvez pas capturer tout ce que vous devez savoir sur une forme dans le type de données Shape , vous pouvez énumérer les cas avec un type de données algébrique, comme Thomas l’a suggéré. Mais je recommanderais contre cela si possible; au lieu de cela, essayez de trouver les caractéristiques essentielles d’une forme dont vous avez besoin plutôt que de simplement énumérer des exemples.

Vous recherchez une liste hétérogène que la plupart des Haskellers n’apprécient pas particulièrement alors qu’ils se sont posé la même question en apprenant Haskell.

Vous écrivez:

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

Cela signifie que la liste a le type t , qui sont tous identiques et se trouvent être une forme (la même forme!). En d’autres termes – non, ça ne devrait pas marcher comme vous l’avez.

Deux manières courantes de le gérer (une façon Haskell 98 en premier, puis une manière plus sophistiquée que je ne recommande pas en second lieu) sont les suivantes:

Utilisez un nouveau type pour relier statiquement les sous-types d’intérêt:

 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 

C’est bien vu que c’est simple et que vous ne perdez aucune information sur ce qui est contenu dans la liste. Vous pouvez déterminer que le premier élément est un Foo et que le second élément est une Bar . L’inconvénient, comme vous le savez probablement déjà, est que vous devez explicitement append chaque type de composant en créant un nouveau constructeur de type Contain . Si cela n’est pas souhaitable, continuez à lire.

Utilisez les types existentiels : une autre solution consiste à perdre des informations sur les éléments – vous conservez, par exemple, la connaissance que les éléments appartiennent à une classe particulière. Par conséquent, vous ne pouvez utiliser que les opérations de cette classe sur les éléments de la liste. Par exemple, les éléments ci-dessous ne retiendront que les éléments de la classe Show . La seule chose que vous pouvez faire pour les éléments est d’utiliser des fonctions polymorphes dans 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) 

Cela nécessite des extensions du langage Haskell, à savoir ExplicitForAll et ExistentialQuantification . Nous avons dû définir explicitement showIt (en utilisant la correspondance de modèle pour déconstruire le type AnyShow ) car vous ne pouvez pas utiliser de noms de champs pour des types de données qui utilisent une quantification existentielle.

Il y a plus de solutions (j’espère qu’une autre réponse utilisera Data.Dynamic – si personne ne le fait et que cela vous intéresse, lisez-le et n’hésitez pas à poster toutes les questions que la lecture génère).