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 中,每个元素节点只能出现在正确的上下文中,并且每个元素对应于其子元素的不同上下文。例如:

  • 像img这样的void 元素有一个空的上下文模型,并且不允许有任何子元素。
  • 具有 Text 内容模型的元素,例如title,只能将文本节点作为子节点(不允许嵌套标签)
  • div元素不能出现在 Phrasing 上下文中,因此不允许作为span元素的后代。

所以我的问题是:

  1. 在 Haskell98 中我需要做什么来模拟这些限制?(我问这个是因为我猜 Haskell98 模型应该更好地翻译成其他编程语言)

    我想我们可能必须为不同的上下文创建许多不同的数据类型,但我不知道如何以有原则和清晰的方式做到这一点。我怎样才能在不迷路的情况下做到这一点,折叠功能最终会是什么样子?

  2. 如果允许我们使用现代 GHC 功能(例如 GADT),模型会是什么样子?

    我有一种预感 GADT 可能有助于将限制推入类型检查器,保持折叠功能简单,但我对它们没有太多经验......

我不需要 100% 有效的解决方案,因为这显然超出了 Stack Overflow 讨论的范围。我只需要能够更好地掌握 GADT 之类的东西,并且能够自己完成剩下的工作。

4

2 回答 2

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、类型系列和其他扩展可以开始提供帮助。

于 2013-03-17T17:47:46.243 回答
2

这是由OCsigen项目完成的,这是一个在OCaml中实现的 Web 框架 ,旨在提供强类型保证。

您可以在此文档页面上查看他们的 Html5 界面。参见例如img 智能构造函数的类型 (这是一口!):

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具有三个类型参数 的类型ab并且c写成(a,b,c) t而不是t a b c)。

<img>可能没有孩子的事实是通过使用这里的“nullary”类型来编码的。其余的静态信息编码了可以在该节点上使用的属性类型。

奇怪的`Foo | `Bar | `Baz东西是所谓的“多态变体”(例如在本文中介绍),一种使用行多态性的可扩展结构和类型,或多或少是 OCaml 独有的(虽然它们对任何编程语言都有用,作为可扩展记录概括了 OCaml 和 Haskell 的通常名义记录)。这里主要用作类型级列表的一种形式。

除此之外,这是一种相对经典的幻象类型使用,由于 HTML 规范中有大量案例,只会导致极端尺寸。幻像类型是 GADT 的前身,用于在 ML 世界中强制执行额外的类型抽象。在 Haskell98 中,您可能会尝试使用类型类而不是直接类型抽象来编码相同类型的类型级信息。

于 2013-03-17T17:54:18.080 回答