Páginas

Sunday, February 5, 2012

Conversão de unidades com Racket

Um rápido post para compartilhar um simples conversor de unidades escrito em Racket.
Resolvi escrever depois de ver a ideia de usar unidades junto dos valores no tutorial oficial do Erlang.

No exemplo, vamos converter centímetros em polegadas ou vice-versa. É fácil estender a ideia para converter outras unidades, como temperatura, pressão, moeda, etc.

O principal é só isso aqui:

(define (convert-length value)
  (match value
    [`(,(? number? v) centimeter) `(,(* v 2.54) inch)]
    [`(,(? number? v) inch) `(,(/ v 2.54) centimeter)]))

Ou seja, criamos uma função chamada convert-length que recebe um valor. O interessante é que esse valor não é um tipo numérico, mas sim uma estrutura de dados que contém um valor numérico e uma unidade.
Com isso, podemos ter uma única função de conversão, e sabemos sempre exatamente com que unidade estamos trabalhando.

O que é feito na função é usar casamento de padrões (pattern matching), uma funcionalidade padrão do Racket e bastante poderosa para trabalhar com estruturas de dados, para realizar a conversão apropriada de acordo com o valor de entrada.

[`(,(? number? v) centimeter) ; padrão
 `(,(* v 2.54) inch)] ; valor retornado

O primeiro padrão casa com uma lista formada por um número, armazenado na variável v, e o símbolo literal centimeter. A segunda parte da cláusula computa a conversão e cria uma estrutura com a nova unidade de medida.

O código completo:

#lang racket
(require rackunit)

;; Idea from the Erlang Tutorial
;; http://www.erlang.org/doc/getting_started/seq_prog.html#id64621
(define (convert-length value)
  (match value
    [`(,(? number? v) centimeter) `(,(* v 2.54) inch)]
    [`(,(? number? v) inch) `(,(/ v 2.54) centimeter)]
    [_ (error "Wrong value format. Value must be a list of two elements: a number and an unit, inch or centimeter.")]))

;; From Matthias Felleisein
;; http://lists.racket-lang.org/users/archive/2011-November/049154.html
(define (tee tag v)
  (displayln `(,tag ,v))
  v)

;;----------------------------------------------------------------------
;; Tests
(check-equal?
 (tee 'test-1 (convert-length '(1 centimeter)))
 '(2.54 inch))

(check-equal?
 (tee 'test-2 (convert-length '(2.54 inch)))
 '(1.0 centimeter))

(check-equal?
 (tee 'test-3 (convert-length (convert-length '(1 centimeter))))
 '(1.0 centimeter))

(check-exn exn:fail?
 (λ ()
   (convert-length 3.4)))