2013-03-17 4 views
2

В Haskell, его просто создать тип данных для для рекурсивного дерева, как то, что мы имеем с XML документами:Как смоделировать структуру данных дерева с ограничениями на то, где могут появляться каждый вид узла?

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

и смежных складок:

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

Моя проблема сейчас в том, что я не знаю, как добавить некоторые дополнительные ограничения для моего типа данных дерева, чтобы моделировать HTML. В HTML каждый элемент узла может отображаться только в правильном contexts, и каждый элемент соответствует другому контексту для его дочерних элементов. Например:

  • Элементы Void, такие как img, имеют пустую контекстную модель и не имеют детей.
  • элементы с моделью содержимого текста, такие как title, могут иметь только текстовые узлы, как дети (не вложенные теги разрешены)
  • div элементы не могут появляться в контексте фразировки и, следовательно, не могут быть потомками span элементов.

Так что мои вопросы:

  1. Что я должен сделать, чтобы смоделировать эти ограничения в Haskell98? (Я спрашиваю об этом, потому что я думаю, что модель Haskell98 должна лучше переводить на другие языки программирования)

    Я думаю, нам, возможно, придется создавать множество разных типов данных для разных контекстов, но я не знаю, как это сделать в принципиальном и ясным образом. Как я могу сделать это, не теряясь и что бы функции фолда выглядели так?

  2. Как выглядит модель, если нам разрешено использовать современные функции GHC, такие как GADT?

    У меня есть догадка GADTs может помочь подтолкнуть ограничения Into проверки типов, сохраняя функцию кратные простой, но я не так много опыта работы с ними ...

мне не нужен 100% -ное решение, поскольку это, очевидно, выходит за рамки обсуждения переполнения стека. Мне просто нужно достаточно, чтобы иметь возможность лучше понять GADT и такие вещи, как и сделать все возможное самостоятельно.

+0

Я думаю, что шаблон умного конструктора применим .. но то, что вы действительно хотите здесь, звучит как зависимая типизация. http://www.haskell.org/haskellwiki/Smart_constructors – jozefg

+0

@jozefg: подход интеллектуального конструктора помогает (и это то, что я использую сейчас), но я обнаружил, что он ограничивает то, как вам разрешено создавать материал, и он не воспроизводится очень хорошо с потоковым, SAX-подобным API. С другой стороны, если у меня есть полностью безопасная модель, я могу разработать надежную поточную версию вещей, основанную на соответствующей функции сгиба. – hugomg

+0

@jozefg: Что касается зависимых типов, я не думаю, что нам нужно зайти так далеко, если предположить, что допустимые имена элементов из конечного набора. (Конечно, все может быть уродливым, но это точка упражнения) – hugomg

ответ

2

Это было сделано в рамках проекта OCsigen, веб фреймворк осуществляется в OCaml, которая стремится обеспечить надежную гарантию печати.

Вы можете посмотреть их интерфейс Html5 на this documentation page. См, например, типа img smart constructor (это полный рот!):

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 

(В стандартном синтаксисе в OCaml, типа t с тремя параметрами типа a, b и c написано (a,b,c) t, а не t a b c) ,

То, что <img> может не иметь ребенка, кодируется с использованием типа «нулевой» . Остальная часть статической информации кодирует, какие типы атрибутов могут быть использованы на этом узле.

Странная `Foo | `Bar | `Baz материал представляет собой так называемый «полиморфная вариант» (представлен в напр. this article), своего рода расширяемой структурного типа суммы с использованием Row полиморфизм, который более или менее уникальными для OCaml (в то время как они были бы полезен для любого языка программирования, поскольку расширяемые записи обобщают обычные номинальные записи OCaml и Haskell). Здесь в основном используются как форма списков типов.

Помимо этого, это относительно классическое использование типов фантомов, приводит только к экстремальным размерам из-за большого количества случаев, которые у вас есть в спецификации HTML. Типы фантомов являются предшественниками GADT для обеспечения дополнительной абстракции типа в мире ML. В Haskell98 вы, вероятно, попытаетесь использовать , чтобы кодировать такую ​​же информацию на уровне типа, используя типы классов , а не напрямую вводить абстракции.

7

Этого не нужно GADT (по крайней мере, пока). Вам просто нужно учить компилятору больше информации о вашем типе дерева.

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] 

Теперь вы получите некоторые инварианты:

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

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

Принципиальный способ вывести это дерево было бы взять его из конкретной грамматики HTML - например, XML EBNF возможно - http://www.w3.org/TR/2006/REC-xml11-20060816/#sec-well-formed.

С помощью GADT некоторые функции могут быть закодированы более эффективно, и вы можете написать функции над вашими типами данных, которые могут обеспечить более сильные инварианты.

Когда вы начинаете создавать все больше и больше свойств, статически проверяемых, может быть сложнее кодировать инварианты. Вот когда GADT, типы семейств и другие расширения могут начать помогать.

+0

Имеет смысл, я думаю, вы должны иметь возможность создавать отдельный тип данных для каждого типа тега, определяющего контекст: data A = PhrasingContext c' и использовать дискриминационный союз для каждого контекста, который отправляет вас обратно в теги: 'data PrasingContext = PhrA A | PhrI Img ... '. Единственная проблема теперь в том, что функции fold для работы с ними становятся действительно уродливыми :) – hugomg

+0

Да, сложность складывания этих неравномерных вложенных типов данных приводит к фантастическим общим обходам. Например. GHC Generics, или SYB http://www.haskell.org/haskellwiki/GHC.Generics –