Este post foi originalmente publicado em 2015 (infelizmente, a postagem original não está mais disponível)
Nesse post vamos implementar, passo-a-passo, um avaliador de expressões matemáticas em F#. Para isso, utilizaremos “Active Patterns” – um dos conceitos mais interessantes da linguagem.
Se não está familiarizado com o conceito de “Active Patterns” recomendo fortemente que leia a documentação antes de prosseguir.
Reconhecendo dígitos
Vamos começar a implementação de nosso avaliador de expressões por algo bastante trivial: reconhecer um dígito.
open System let (|Digit|_|) = function | x::xs when Char.IsDigit(x) -> Some(Char.GetNumericValue(x), xs) | _ -> None "10" |> List.ofSeq |> (|Digit|_|)
O que fizemos? Criamos um “active pattern” que verifica se o primeiro caractere de uma lista de caracteres é um dígito. Em caso positivo, retorna uma tupla com a versão “numérica” e a lista dos caracteres que completam a cadeia.
Reconhecendo a parte inteira de um número
Agora que conseguimos reconhecer um dígito, já podemos reconhecer a parte inteira de um número.
let (|IntegerPart|_|) input = match input with | Digit(h, t) -> let rec loop acc = function | Digit(x, xs) -> loop ((acc * 10.) + x) xs | xs -> Some(acc, xs) loop 0. input | _ -> None "10" |> List.ofSeq |> (|IntegerPart|_|) ".34" |> List.ofSeq |> (|IntegerPart|_|)
Bacana! Começamos aqui a ver o poder dos “Active Patterns”. Começamos nossa implementação verificando se a lista de caracteres começa com um dígito. Em caso positivo, iniciamos um loop acumulando um “inteiro” enquanto forem encontrados outros dígitos.
Reconhecendo a parte fracionária de um número
Para reconhecer a parte fracionária usamos uma lógica bem semelhante.
let (|FractionalPart|_|) = function | '.'::t-> let rec loop acc d = function | Digit(x, xs) -> loop ((acc * 10.) + x) (d * 10.) xs | xs -> (acc/d, xs) Some(loop 0. 1. t) | _ -> None "10" |> List.ofSeq |> (|FractionalPart|_|) ".34" |> List.ofSeq |> (|FractionalPart|_|)
A única diferença é que começamos verificando se o primeiro caractere é um ‘.’.
Reconhecendo números
Agora que já conseguimos reconhecer a parte inteira e a parte fracionária, vamos criar mais um “Active Pattern” para juntar as duas partes.
let (|Number|_|) = function | IntegerPart(i, FractionalPart(f, t)) -> Some(i+f, t) | IntegerPart(i, t) -> Some(i, t) | FractionalPart(f, t) -> Some(f, t) | _ -> None "10" |> List.ofSeq |> (|Number|_|) ".35" |> List.ofSeq |> (|Number|_|) "10.35" |> List.ofSeq |> (|Number|_|)
Um número é criado se a cadeia de caracteres 1) começar com uma parte inteira seguida de uma parte fracionária; 2) começar com uma parte inteira; 3) começar com uma parte fracionária.
Implementando a avaliação de expressões
Temos números. Agora chegou a hora de suportarmos operações matemáticas com eles.
Esse código é baseado no exemplo disponível no site F# News. Entretanto, um pouquinho aprimorado 😉
let evaluate expression = let rec (|Expr|_|) = function | Mult(e, t) -> let rec loop l = function | '+'::Mult(r, t) -> loop (l + r) t | '-'::Mult(r, t) -> loop (l - r) t | [] -> Some(l, []) | _ -> None loop e t | _ -> None and (|Mult|_|) = function | '+'::Atom(e, t) -> Some(e, t) | '-'::Atom(e, t) -> Some(-e, t) | Atom(l, '*'::Mult(r, t)) -> Some(l * r, t) | Atom(l, '/'::Mult(r, t)) -> Some(l / r, t) | Atom(e, t) -> Some(e, t) | _ -> None and (|Atom|_|) = function | Number(e, t) -> Some(e, t) | '('::Expr(e, ')'::t) -> Some(e, t) | _ -> None expression |> List.ofSeq |> (|Expr|_|) evaluate "2+2/2"
Lindo, não?!