Um avaliador de expressões matemáticas em F# (usando Active Patterns)

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?!

Compartilhe este insight:

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

Um de nossos objetivos, nesse momento, é  melhorar a comunicação, compartilhando a visão do nosso fluxo de trabalho com a...
Mais uma vez, tive a honra de participar de um excelente bate-papo sobre microsserviços com o pessoal da Lambda 3....
Ao utilizar recursos não gerenciados, em .NET, precisamos garantir que estes sejam devidamente liberados. Para isso, quando criamos classes que...
Aqui, um registro da apresentação que realizei na abertura do IoT Weekend. O que você acha do que foi dito?
Confesso que estava sentindo falta de escrever em primeira pessoa. Há tempos, publicando apenas nos sites da EximiaCo, não encontrava,...
Mais preocupante que o posicionamento do presidente é sua falta de constância. Há dois dias, uma medida provisória proposta pelo...
Oferta de pré-venda!

Mentoria em
Arquitetura de Software

Práticas, padrões & técnicas para Arquitetura de Software, de maneira efetiva, com base em cenários reais para profissionais envolvidos no projeto e implantação de software.

× Precisa de ajuda?