0

最近,我跟着通過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的任何地方。

在一個依賴類型的函數式邏輯編程語言中,我只是在類型級別上表達約束條件,並讓推理機或約束求解器處理它,但在這裏我似乎處於虧損狀態。

回答

1

只需使用Char即可獲得相同的結果。正如您已經指出的那樣,您可以使用​​將name2char定義爲部分標識。我更改了以下代碼行。

type NameChar = Char 

name2char :: NameChar -> Char 
name2char c | isAlpha c = c 

這兩個示例性表達式然後評估如下。

test> parse "<input type=\"submit\" name=\"btn1\"/>" 
(Node (Name "input") [(Attr (Name "type") "submit"),(Attr (Name "name") "btn1")] []) 

test> parse "<input type=\"submit\"/>" 
(Node (Name "input") [(Attr (Name "type") "submit")] []) 

作爲副作用,與非字母字符的名稱的靜默失敗與nameFromString

test> nameFromString "input " 

編輯:既然你似乎是功能模式的球迷,你可以定義發電機Node S和Attr S和在轉換函數中使用它們。

attr :: Name -> String -> Attr 
attr name val 
    | name `elem` ["type", "src", "alt", "name"] = Attr name val 

node :: String -> [Attr] -> [Node] -> Node 
node name [] nodes 
    | name `elem` ["a", "p"] = Node name [] nodes 
node name [email protected](_:_) nodes 
    | name `elem` ["img", "input"] = Node name attrPairs nodes 

node2string :: Node -> String 
node2string (node name attrs children) 
    | null children = "<" ++ name ++ attrs' ++ "/>" 
    | otherwise  = "<" ++ name ++ attrs' ++ ">" 
        ++ children' ++ "</" ++ name' ++ ">" 
where 
    name'  = name 
    attrs' = concatMap ((" " ++) . attr2string) attrs 
    children' = intercalate "" $ map (node2string) children 

attr2string :: Attr -> String 
attr2string (attr name val) = name ++ "=\"" ++ escape val ++ "\"" 
where 
    escape = concatMap (\c -> if c == '"' then "\\\"" else [c]) 

這種方法有其缺點;它適用於特定的有效名稱集,但在使用之前的謂詞(例如,all isAlpha name)時會失敗。

EDIT2: 除了這個事實,與​​條件的解決方案比你的詳細解決方案相當「漂亮」,它也以聲明的方式定義。 沒有你的評論,它不會變得清楚(很容易),你正在編碼NameChar數據類型的字母字符。另一方面,​​條件是聲明性指定所需屬性的一個很好的例子。 這是否回答你的問題?我不確定你的目標是什麼。

+0

這太棒了。我無法理解我自己怎麼沒有實現這個目標,但那可能只是因爲我放棄了早期,認爲我會失敗:) –

+0

所以基本上你還沒有限制'Node'的合法構造_set ,但你基本上限制了'Node mapping'和'String'之間的_「映射管道」。 –

+0

在這裏變得有點瑣碎,但只有一個洞察力的要求:是否有任何優勢,以目前在代碼中設計?除了從類型理論的角度來看更多的設計正確性? –

相關問題