2010-09-01 23 views
7

爲了理解函數式編程的功能,我將幾個基本函數組合在一起構建複雜的正則表達式。現在經過一些測試後,我發現這種方法可行,但是你可以用任何可用的語言編寫一些可怕的代碼。這是你會找到一個專業的F#程序員寫的代碼或我濫用該功能?我是否正確使用函數組合?

注意:test是我特指的。

type State = { input:string; index:int; succeeded:bool } 
type Matcher = State -> State 

let term (cs:char Set) = 
    fun s -> 
     if s.succeeded && s.index < s.input.Length && cs.Contains s.input.[s.index] then 
      { input = s.input; index = s.index + 1; succeeded = true } 
     else 
      { input = s.input; index = s.index; succeeded = false } 

let quantify (term, min, max) = 
    let rec inner (s:State, count) = 
     if s.succeeded && s.index < s.input.Length && count <= max then 
      inner (term { input = s.input; index = s.index + 1; succeeded = true }, count + 1) 
     elif count >= min && count <= max then 
      { input = s.input; index = s.index - 1; succeeded = true }  
     else 
      s   
    fun s -> inner (s, 0) 

let disjunction leftTerm rightTerm = 
    fun s -> 
     let left = leftTerm s 
     if not left.succeeded then 
      let right = rightTerm s 
      if not right.succeeded then 
       { input = s.input; index = s.index; succeeded = false } 
      else 
       right 
     else 
      left 

let matcher input terms = 
    let r = terms { input = input; index = 0; succeeded = true } 
    if r.succeeded then r.input.Substring (0, r.index) else null 

let test = // (abc|xyz)a{2,3}bc 
    disjunction // (abc|xyz) 
     (term (set "a") >> term (set "b") >> term (set "c")) 
     (term (set "x") >> term (set "y") >> term (set "z")) 
    >> quantify (term (set "a"), 2, 3) // (a{2,3}) 
    >> term (set "b") // b 
    >> term (set "c") // c 

let main() : unit = 
    printfn "%s" (matcher "xyzaabc" test) 
    System.Console.ReadKey true |> ignore 

main() 

回答

8

該代碼對我來說看起來不錯。

我不確定這是你的意圖還是巧合,但是你正在實現一些與「分析器組合器」非常相似的東西,這是許多學術論文的主題:-)。我認爲Monadic Parser Combinators是非常可讀的(它在Haskell中有例子,但你應該能夠將它們翻譯成F#)。

關於函數組合運算符。我通常不太喜歡使用操作符,因爲它經常混淆代碼。然而,在你的例子中,它很有意義,因爲你很容易想象到>>意味着「這個組應該被該組跟隨」,這很容易解釋。

,我會做的只有輕微的變化是選擇爲disjunction操作一些不錯的運營商定製,並確定了幾個基本的操作,讓你可以寫例如這樣的:

// Test against several terms in sequence 
let sequence terms = (fun state -> terms |> Seq.fold (>>) state) 
// Test for a substring 
let substring s = sequence [ for c in s -> term (set [c]) ] 

let test = // (abc|xyz)a{2,3}bc 
    (substring "abc" <|> substring "xyz") 
    >> quantify 2 3 (term (set "a")) // (a{2,3}) 
    >> substring "bc" // bc 

這是更更高級別的描述,所以它刪除了一些>>運算符,以支持更具描述性的函數(並封裝了>>)。我也改變了quantify以取代多個參數而不是tripple(這是一個小小的改變)

如果你想進一步玩,那麼你可以看看文章,並嘗試編寫F#計算表達式生成器將允許您使用parser { .. }語法。

+1

很高興知道我在功能編程技巧方面取得了進展。你讓我非常興奮,試着把所有的東西都包裝進優雅的計算表達式語法中。 :)無論如何感謝您的建議和論文*(我是Erik Meijer的粉絲。)*。 – ChaosPandion 2010-09-01 03:25:02

+0

有趣的紙張;感謝張貼的鏈接。 – TechNeilogy 2010-09-01 14:08:38

3

這通常是很好的風格,但你錯過了一些技巧,仍然有相當多的冗餘。也許更像是這樣的:

let valid (s: State) = s.succeeded && s.index < s.input.Length 
... 
let disjunction leftTerm rightTerm s = 
    let left = leftTerm s 
    if left.succeeded then left else 
    let right = rightTerm s 
    if right.succeeded then right else 
     { s with succeeded = false } 
... 
let test = 
    let f s = set s |> term 
    let (++) s t = f s >> f t 
    disjunction ("a" ++ "b" ++ "c") ("x" ++ "y" ++ "z") 
    >> quantify (f "a", 2, 3) 
    >> "b" ++ "c" 

您可能更喜歡積累代表計算的值而不是閉包,因爲它使調試更容易。

+0

哇,我覺得有點愚蠢,因爲沒有提出「有效」的功能。感謝您的建議。 – ChaosPandion 2010-09-01 15:07:07