2010-09-01 5 views
7

Nel tentativo di comprendere le funzionalità della programmazione funzionale, ho raggruppato alcune funzioni di base che è possibile comporre insieme per creare espressioni regolari complesse. Ora dopo alcuni test ho trovato che funziona, ma puoi scrivere un codice orribile in qualsiasi lingua che funzioni. È questo il tipo di codice in cui potresti trovare un programmatore F # professionale o sto abusando della funzione?Sto usando correttamente la composizione delle funzioni?

Nota:test è specificamente ciò a cui mi riferisco.

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() 

risposta

8

Il codice mi sembra abbastanza buono.

Non sono sicuro che questa fosse una tua intenzione o una coincidenza, ma stai implementando qualcosa di molto simile a "parser combinators", che è un argomento di molti studi accademici :-). Penso che Monadic Parser Combinators sia abbastanza leggibile (ha degli esempi in Haskell, ma dovresti essere in grado di tradurli in F #).

Riguardo all'operatore di composizione della funzione. Generalmente non sono un grande fan dell'uso eccessivo dell'operatore, perché spesso offusca il codice. Tuttavia, nel tuo esempio ha un buon senso perché puoi facilmente immaginare che >> significhi "questo gruppo dovrebbe essere seguito da quel gruppo", che è facile da interpretare.

L'unica piccola modifica che vorrei fare è scegliere qualche bella operatore personalizzato per l'operazione disjunction e definire alcune operazioni più primitivi, in modo che è possibile scrivere ad esempio questo:

// 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 

questo è più descrizione di livello superiore, quindi rimuove alcuni degli operatori >> in favore di funzioni che sono più descrittive (e incapsulano >>). Ho modificato anche quantify per prendere più argomenti invece di un triplo (che è una modifica minore)

Se vuoi giocare ulteriormente, puoi dare un'occhiata all'articolo e provare a scrivere F # computation expression builder che ti consentirebbe di utilizzare la sintassi parser { .. }.

+1

È bello sapere che sto facendo progressi nelle mie capacità di programmazione funzionale. Mi hai davvero entusiasmato di provare a racchiudere tutto nell'elegante sintassi delle espressioni computazionali. :) Comunque grazie per il tuo consiglio e il documento * (Sono un fan di tutto ciò che Erik Meijer.) *. – ChaosPandion

+0

Carta interessante; grazie per aver pubblicato il link – TechNeilogy

3

Questo è generalmente un buon stile ma ti mancano alcuni trucchi e hai ancora un bel po 'di ridondanza. Forse più simile a questo:

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" 

Si potrebbe preferire di accumulare un valore che rappresenta un calcolo piuttosto che chiusure perché rende il debugging molto più facile.

+0

Wow, mi sento un po 'stupido per non avere questa funzione 'valida'. Grazie per i vostri suggerimenti. – ChaosPandion