Я правильно использую композицию функций? - PullRequest
7 голосов
/ 01 сентября 2010

Чтобы понять возможности функционального программирования, я собрал несколько основных функций, которые вы можете объединить для создания сложных регулярных выражений.Теперь после некоторого тестирования я обнаружил, что это работает, но вы можете написать какой-то ужасный код на любом языке, который будет работать.Это тот код, который вы найдете профессиональным программистом на 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()

Ответы [ 2 ]

8 голосов
/ 01 сентября 2010

Код выглядит довольно хорошо для меня.

Я не уверен, было ли это вашим намерением или совпадением, но вы реализуете что-то очень похожее на «комбинаторы синтаксического анализа», что является темой многих научных работ :-).Я думаю, что 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, чтобы принимать несколько аргументов вместо тройки (что является незначительным изменением)

Если вы хотите поиграть с этим дальше, тогда вы можете взглянуть на статью и попробовать написать F #конструктор выражений вычислений, позволяющий использовать синтаксис parser { .. }.

3 голосов
/ 01 сентября 2010

Как правило, это хороший стиль, но вы упускаете некоторые уловки и все еще имеете некоторую избыточность. Может быть, так:

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"

Вы можете предпочесть накапливать значение, представляющее вычисление, а не замыкания, потому что это значительно упрощает отладку.

...