2013-03-17 2 views
2

Dans Haskell, son simple pour créer un type de données pour un arbre récursif, comme ce que nous avons avec des documents XML:Comment puis-je modéliser une structure de données arborescente avec des restrictions sur l'emplacement de chaque type de nœud?

data XML = 
    Text String  -- Text of text node 
    | Elem String [XML] -- Tagname; Child nodes 

et ses plis connexes:

-- Simple fold (Child trees don't see the surrounding context.) 
foldt :: (String -> a) -> (String -> [a] -> a) -> XML -> a 
foldt fT fE (Text text) = fT text 
foldt fT fE (Elem tagname ts) = fE tagname (map (foldt fT fE) ts) 

-- Threaded fold for streaming applications. 
-- See http://okmij.org/ftp/papers/XML-parsing.ps.gz 
foldts :: (a -> String -> a) -> (a -> String -> a) -> (a -> a -> String -> a) -> a -> XML -> a 
foldts fT fE_begin fE_end = go 
    where 
    go seed (Text text) = fT seed text 
    go seed (Elem tag children) = 
     let seed' = fE_begin seed tag in 
     let seed'' = foldl go seed' children in 
     fE_end seed seed'' tag 

Mon problème est maintenant que je Je ne sais pas comment ajouter des restrictions supplémentaires à mon type de données Tree afin de modéliser HTML. En HTML, chaque élément ne peut apparaître que dans le bon contexts et chaque élément correspond à un contexte différent pour ses enfants. Par exemple:

  • Les éléments vides tels que img ont un modèle de contexte vide et ne doivent pas avoir d'enfants.
  • éléments avec un modèle de contenu de texte, tels que title, peuvent seulement avoir des nœuds de texte que les enfants (pas de balises imbriquées autorisées)
  • div éléments ne peuvent pas apparaître dans un contexte de Phrasing et par conséquent ne sont pas autorisés à être des descendants de span éléments.

Mes questions sont les suivantes:

  1. qu'aurais-je faire pour modéliser ces restrictions haskell98? (Je demande cela parce que je suppose que le modèle Haskell98 devrait mieux traduire vers d'autres langages de programmation)

    J'imagine que nous pourrions avoir à créer beaucoup de différents types de données pour les différents contextes, mais je ne sais pas comment le faire dans un principe et de manière claire. Comment puis-je faire cela sans me perdre et à quoi ressembleront les fonctions de pli? A quoi ressemblerait le modèle si nous pouvions utiliser des fonctionnalités GHC modernes telles que les GADT?

    je un GADTs de Hunch pourraient aider à pousser les restrictions dans le typechecker, en gardant les fonctions de pliage simple mais je suis ne pas beaucoup d'expérience avec eux ...

Je ne ai pas besoin une solution fonctionnant à 100%, puisque cela dépasserait évidemment le cadre d'une discussion Stack Overflow. J'ai juste besoin de suffisamment pour être capable d'avoir une meilleure compréhension des GADTs et des choses comme et pouvoir faire le reste par moi-même.

+0

Je pense que le modèle de constructeur intelligent est applicable .. mais ce que vous voulez vraiment ici ressemble un peu à un typage dépendant. http://www.haskell.org/haskellwiki/Smart_constructors – jozefg

+0

@jozefg: L'approche du constructeur intelligent aide (et est ce que j'utilise en ce moment) mais j'ai trouvé que ça contraignait comment vous êtes autorisé à construire des choses et ça ne joue pas très bien avec un streaming, API similaire à SAX. D'un autre côté, si j'ai un modèle entièrement sécurisé, je peux concevoir une version de streaming robuste basée sur la fonction de pliage correspondante. – hugomg

+0

@jozefg: En ce qui concerne les types dépendants, je ne pense pas que nous ayons besoin d'aller aussi loin si nous supposons que les noms d'éléments autorisés proviennent d'un ensemble fini. (Bien sûr, les choses peuvent devenir moche mais c'est le but de l'exercice) – hugomg

Répondre

2

Cela a été fait par le projet OCsigen, un cadre Web mis en œuvre dans OCaml, qui vise à fournir forte garantie de frappe. Vous pouvez voir leur interface Html5 sur this documentation page. Voir par exemple le type de img smart constructor (c'est une bouchée!):

val img : 
    src:Xml.uri -> 
    alt:Html5_types.text -> 
    ([< `Accesskey 
    | `Class 
    | `Contenteditable 
    | `Contextmenu 
    | `Dir 
    | `Draggable 
    | `Height 
    | `Hidden 
    | `Id 
    | `Ismap 
    | `OnAbort 
    | `OnBlur 
    | `OnCanPlay 
    | `OnCanPlayThrough 
    | `OnChange 
    | `OnClick 
    | `OnContextMenu 
    | `OnDblClick 
    | `OnDrag 
    | `OnDragEnd 
    | `OnDragEnter 
    | `OnDragLeave 
    | `OnDragOver 
    | `OnDragStart 
    | `OnDrop 
    | `OnDurationChange 
    | `OnEmptied 
    | `OnEnded 
    | `OnError 
    | `OnFocus 
    | `OnFormChange 
    | `OnFormInput 
    | `OnInput 
    | `OnInvalid 
    | `OnKeyDown 
    | `OnKeyPress 
    | `OnKeyUp 
    | `OnLoad 
    | `OnLoadStart 
    | `OnLoadedData 
    | `OnLoadedMetaData 
    | `OnMouseDown 
    | `OnMouseMove 
    | `OnMouseOut 
    | `OnMouseOver 
    | `OnMouseUp 
    | `OnMouseWheel 
    | `OnPause 
    | `OnPlay 
    | `OnPlaying 
    | `OnProgress 
    | `OnRateChange 
    | `OnReadyStateChange 
    | `OnScroll 
    | `OnSeeked 
    | `OnSeeking 
    | `OnSelect 
    | `OnShow 
    | `OnStalled 
    | `OnSubmit 
    | `OnSuspend 
    | `OnTimeUpdate 
    | `OnVolumeChange 
    | `OnWaiting 
    | `Spellcheck 
    | `Style_Attr 
    | `Tabindex 
    | `Title 
    | `User_data 
    | `Width 
    | `XML_lang 
    | `XMLns ], 
    [> `Img ]) 
    nullary 

(Dans la syntaxe standard OCaml, un type t avec trois paramètres de type a, b et c est écrit (a,b,c) t plutôt que t a b c) .

Le fait que <img> puisse ne pas avoir d'enfant est codé par l'utilisation du type "nullary" ici. Le reste de l'information statique code quels types d'attributs peuvent être utilisés sur ce nœud.

L'étoffe `Foo | `Bar | `Baz bizarre est une soi-disant « polymorphes variante » (présentée en exemple. this article), une sorte de type somme de structure extensible en utilisant la ligne polymorphisme qui est plus ou moins unique de OCaml (alors qu'ils seraient utile à tout langage de programmation, car les enregistrements extensibles généralisent les enregistrements nominaux habituels de OCaml et Haskell). Ici, il est principalement utilisé comme une forme de listes de niveau de type. En dehors de cela, c'est une utilisation relativement classique des types fantômes, seulement conduire à des tailles extrêmes en raison du nombre de cas que vous avez dans la spécification HTML. Les types fantômes sont les précurseurs de GADT pour appliquer une abstraction supplémentaire de type dans le monde ML. Dans Haskell98, vous essayez probablement de coder le même type d'informations de niveau de type en utilisant des classes de type plutôt que des abstractions de type directement.

7

Ceci n'a pas besoin de GADT (du moins pas encore). Vous devez juste apprendre au compilateur plus d'informations sur votre type d'arbre.

data HTML 
    = HTML HTMLHeader HTMLBody 

data HTMLHeader 
    = Header String 

data HTMLBody 
    = Body [HTMLContent] 

data HTMLContent 
    = Text HTMLText 
    | Title HTMLText 
    | Img String 
    | Elem String [HTML] 

data HTMLText 
    = Literal String 
    | Bold String 
    | Italic String 
    | TextElems [HTMLText] 

Maintenant, vous obtenez quelques invariants:

-- Must have header and body. titles can't contain images. 

x = HTML 
     (Header "TEST") $ Body [ 
      Title (Literal "Title") 
      ,Text (Bold "Content") 
     ] 

Une façon ordonnée d'obtenir cet arbre serait de le prendre d'un particulier la grammaire HTML - par exemple le XML EBNF peut-être - http://www.w3.org/TR/2006/REC-xml11-20060816/#sec-well-formed. Avec les GADT, certaines choses peuvent être encodées plus efficacement, et vous pouvez écrire des fonctions sur vos types de données qui peuvent appliquer des invariants plus forts.Lorsque vous commencez à rendre des propriétés de plus en plus vérifiables statiquement, il peut devenir plus complexe d'encoder les invariants. C'est alors que les GADT, les familles de types et d'autres extensions peuvent commencer à aider.

+0

Ça a du sens, je suppose que vous devriez pouvoir créer un type de données distinct pour chaque type de balise définissant le contexte: data A = PhrasingContext c' et utiliser une union discriminée pour chaque contexte qui vous renvoie aux tags: 'data PrasingContext = PhrA A | PhrI Img ... '. Le seul problème est maintenant que les fonctions de pliage pour travailler avec celles-ci deviennent vraiment moche :) – hugomg

+0

Oui, la difficulté de replier ces types de données imbriqués non-uniformes conduit à des traversées génériques fantaisistes. Par exemple. GHC Generics, ou SYB http://www.haskell.org/haskellwiki/GHC.Generics –

Questions connexes