2010-09-01 14 views
7

En un esfuerzo por comprender las capacidades de la programación funcional, reuní algunas funciones básicas que puede componer juntas para crear expresiones regulares complejas. Ahora, después de algunas pruebas, he encontrado que esto funciona, pero puedes escribir un código horrible en cualquier idioma que funcione. ¿Es este el tipo de código que encontraría un programador profesional de F # escribiendo o estoy abusando de la función?¿Estoy utilizando correctamente la composición de funciones?

Nota:test es específicamente a lo que me refiero.

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

Respuesta

8

El código se ve bastante bien para mí.

No estoy seguro de si esta era su intención o una coincidencia, pero está implementando algo muy similar a "los combinadores de analizadores", que es un tema de muchos trabajos académicos :-). Creo que Monadic Parser Combinators es bastante legible (tiene ejemplos en Haskell, pero debería poder traducirlos a F #).

En cuanto al operador de composición de funciones. Por lo general, no soy un gran admirador de usar demasiado el operador, porque a menudo ofusca el código. Sin embargo, en su ejemplo tiene sentido porque puede imaginar fácilmente que >> significa que "este grupo debe ser seguido por ese grupo", que es fácil de interpretar.

El único cambio menor que iba a hacer es elegir algún operador costumbre agradable para la operación disjunction y definir algunas operaciones más primitivas, por lo que se puede escribir, por ejemplo, esto:

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

Esto es más descripción de nivel superior, por lo que elimina algunos de los operadores >> en favor de funciones que son más descriptivas (y encapsula >>). También cambié quantify para tomar múltiples argumentos en lugar de un tripple (que es un cambio menor)

Si quieres jugar con esto más, entonces puedes echar un vistazo al artículo y tratar de escribir el constructor de expresiones F # que le permitiría usar la sintaxis parser { .. }.

+1

Es bueno saber que estoy haciendo progresos en mis habilidades de programación funcional. Me entusiasma mucho tratar de abarcar todo en la elegante sintaxis de la expresión computacional. :) De todos modos, gracias por tu consejo y el papel * (soy un fan de cualquier cosa Erik Meijer.) *. – ChaosPandion

+0

Interesante documento; Gracias por publicar el enlace. – TechNeilogy

3

Este es un buen estilo en general, pero le faltan algunos trucos y todavía tiene un poco de redundancia. Tal vez la misma familia:

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" 

Es posible que prefiera para acumular un valor que representa un cálculo en lugar de cierres porque hace que la depuración mucho más fácil.

+0

Guau, me siento un poco tonto por no encontrar esa función 'válida '. Gracias por tus sugerencias – ChaosPandion

Cuestiones relacionadas