最近,我跟着通過A Taste of Curry,事後決定把瑣碎的算術解析器例子來測試,通過編寫一個較爲可觀的解析器:一種原始的,但正確功能性的HTML解析器。的語法生成帶`inverse`解析器,有約束
我結束了一個工作node2string
的函數,在Node
操作(具有屬性和兒童),然後我inverse
d以獲得parse
函數,如在文章中舉例說明。
第一個天真的實現有錯誤,它解析任何東西,但例如將簡單的<input/>
HTML代碼片段合併爲一個Node
表示形式;一切都不確定地產生無效的東西,如
Node { name = "input", attrs = [Attr "type" "submit"] }
Node { name = "input type=\"submit\"", attrs = [] }
等等。
一些初始幼稚嘗試修復從node2string
內,我實現了點,我相信所有經驗豐富的邏輯程序員看到瞬時,即parse = inverse node2string
是正確的更正確的和有見地的關於sitatution比我後:上述2解析<input type="submit"/>
的結果實際上正好是導致HTML表示的Node
的2個有效值和可構造值。
我意識到我不得不限制Node
只允許按字母順序傳遞 - 不是真的,但讓我們保持簡單 - 名稱(當然相同Attr
)。在一個比邏輯程序更基礎的環境中(比如像純粹的聲明式編程那樣手寫更多的「指令性」的常規Haskell),我會簡單地將構造函數隱藏在例如後面。一個mkNode
sentinel函數,但我覺得這在Curry中不會很好,因爲推理機或約束求解器是如何工作的(我可能對此錯誤,實際上我希望是這樣)。
所以我最終取而代之以下。我認爲庫裏元編程(或模板哈斯克爾,如果庫裏支持它的話)可以用來清理手動編程器,但美容處理只是脫離情況的一種方式。
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]
打印更漂亮)
不過,我的問題是:
是有使用類似(或功能更忠實於實際的HTML規範,當然),實現了資源的方式ult等同於此,而不必經過NameChar
及其「支持成員」的詳細樣板文件?似乎沒有辦法將「功能限制」放在ADT的任何地方。
在一個依賴類型的函數式邏輯編程語言中,我只是在類型級別上表達約束條件,並讓推理機或約束求解器處理它,但在這裏我似乎處於虧損狀態。
這太棒了。我無法理解我自己怎麼沒有實現這個目標,但那可能只是因爲我放棄了早期,認爲我會失敗:) –
所以基本上你還沒有限制'Node'的合法構造_set ,但你基本上限制了'Node mapping'和'String'之間的_「映射管道」。 –
在這裏變得有點瑣碎,但只有一個洞察力的要求:是否有任何優勢,以目前在代碼中設計?除了從類型理論的角度來看更多的設計正確性? –