Недавно я последовал через A Taste of Curry, а потом решили поставить тривиальный арифметический пример синтаксического анализа, чтобы проверить, написав несколько более существенный парсер: примитивный, но правильно и функциональный анализатор HTML.Создание синтаксического анализатора с `inverse`, с ограничениями по грамматике
я закончил с рабочей node2string
функции для работы на Node
(с атрибутами и детей), которые я потом inverse
д, чтобы получить parse
функцию, как проиллюстрировано в статье.
Первая наивная реализация была ошибкой в том, что она разбирала что-либо, кроме, например, тривиальный фрагмент HTML <input/>
в виде одного Node
; все остальное недетерминистически давало недопустимые вещи, такие как
Node { name = "input", attrs = [Attr "type" "submit"] }
Node { name = "input type=\"submit\"", attrs = [] }
и так далее.
После некоторых первоначальных наивных попыток исправить что изнутри node2string
, я понял, что момент, который я считаю, все закаленные логика программистов видеть мгновенно, что parse = inverse node2string
был прав более правильным и проницательным о sitatution, чем я: выше 2 синтаксического анализа результаты <input type="submit"/>
действительно были 2 правильными и конструктивными значениями Node
, которые приведут к представлениям HTML.
Я понял, что мне пришлось ограничивать Node
, чтобы разрешить прохождение в алфавитном порядке - ну не совсем, но давайте будем простыми именами (и, конечно же, для Attr
). В менее фундаментальной настройке, чем в логической программе (например, в регулярном Haskell с гораздо большим количеством рукописных и «обучающих» в отличие от чисто декларативного программирования), я просто спрятал бы конструктор Node
позади, например, a mkNode
функция дозорного устройства, но у меня такое чувство, что в Curry это не сработает из-за того, как работает движок вывода или решатель ограничений (я могу ошибаться в этом, и на самом деле я надеюсь, что это так).
Таким образом, я оказался вместо этого следующим. Я думаю, что метапрограммирование Карри (или Template Haskell, если бы Карри поддержал его), можно было бы использовать для очистки ручного оружия, но косметическая обработка - это только один выход из ситуации.
data Name = Name [NameChar] -- newtype crashes the compiler
data NameChar = A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z
name2char :: NameChar -> Char
name2char c = case c of A -> 'a'; B -> 'b'; C -> 'c'; D -> 'd'; E -> 'e'; F -> 'f'; G -> 'g'; H -> 'h'; I -> 'i'; J -> 'j'; K -> 'k'; L -> 'l'; M -> 'm'; N -> 'n'; O -> 'o'; P -> 'p'; Q -> 'q'; R -> 'r'; S -> 's'; T -> 't'; U -> 'u'; V -> 'v'; W -> 'w'; X -> 'x'; Y -> 'y'; Z -> 'z'
name2string :: Name -> String
name2string (Name s) = map name2char s
-- for "string literal" support
nameFromString :: String -> Name
nameFromString = inverse name2string
data Node = Node { nodeName :: Name, attrs :: [Attr], children :: [Node] }
data Attr = Attr { attrName :: Name, value :: String }
attr2string :: Attr -> String
attr2string (Attr name value) = name2string name ++ "=\"" ++ escape value ++ "\""
where escape = concatMap (\c -> if c == '"' then "\\\"" else [c])
node2string :: Node -> String
node2string (Node name attrs children) | null children = "<" ++ name' ++ attrs' ++ "/>"
| otherwise = "<" ++ name' ++ attrs' ++ ">" ++ children' ++ "</" ++ name' ++ ">"
where name' = name2string name
attrs' = (concatMap ((" " ++) . attr2string) attrs)
children' = intercalate "" $ map (node2string) children
inverse :: (a -> b) -> (b -> a)
inverse f y | f x =:= y = x where x free
parse :: String -> Node
parse = inverse node2string
Это, по сути, работает отлично (на мой взгляд):
Parser> parse "<input type=\"submit\"/>"
(Node [I,N,P,U,T] [(Attr [T,Y,P,E] "submit")] [])
Parser> parse "<input type=\"submit\" name=\"btn1\"/>"
(Node [I,N,P,U,T] [(Attr [T,Y,P,E] "submit"),(Attr [N,A,M,E] "btn1")] [])
(Карри не имеет классы типа, так что я не знаю еще, как сделать [NameChar]
печать более красиво)
Однако мой вопрос:
есть способ использовать что-то вроде isAlpha
(или функцию более реалистичного к фактическому HTML спецификации, конечно), чтобы достичь Реза ult эквивалентно этому, без необходимости проходить подробный шаблон, который NameChar
и его «поддерживающие элементы»? Кажется, что нет никакого способа разместить «функциональное ограничение» в любом месте ADT.
На языке программирования, зависящем от типизированного логического программирования, я бы просто выразил ограничение на уровне типа, и пусть механизм вывода или решатель ограничений справится с этим, но здесь я, похоже, не понимаю.
Это замечательно. Я не понимаю, как я сам не пришел к этому осознанию, но это возможно только потому, что я сдался раньше, думая, что я потерплю неудачу :) –
так что вы все еще не ограничиваете _set юридических конструкций_ 'Node' , но вы строго ограничиваете _ «канал отображения» _ между «Node» и «String». –
здесь немного болтают, но еще один запрос на проницательность: есть ли какие-то преимущества для того, как он в настоящее время разрабатывается в коде? помимо более корректной корректности с теоретической точки зрения? –