• Nenhum resultado encontrado

Tratamento de exceções

No documento Programação Funcional Usando Haskell (páginas 156-159)

Para construir bons programas, é necessário especificar o que o programa deve fazer no caso de acontecer algumas situações anômalas. Estas ocorrências são conhecidas como exceções, normal- mente indesejáveis, e podem ser de vários tipos: a tentativa de divisão por zero, cálculo de raiz quadrada de número negativo ou a aplicação da função fatorial a um número negativo, tentativa de encontrar a cabeça ou a cauda de uma lista vazia, entre outros.

Existem basicamente três técnicas paraa resolver estes tipos de problemas, conhecidas como técnicas de tratamento de erros. A solução mais simples é exibir uma mensagem informando o tipo da exceção e parar a execução do programa. Isto pode ser feito através de uma função de erro.

error :: String -> t

Uma tentativa de avaliar a expressão error "Circulo com raio negativo" resultará na mensagem

Program error: Circulo com raio negativo. que é impressa e a execução do programa é abortada.

O problema com esta técnica é que todas as informações usuais da computação calculadas até o ponto em que ocorreu a exceção são perdidas, porque o programa é abortado. Em vez disso, o erro pode ser tratado de alguma forma, sem parar a execução do programa. Isto pode ser feito através das duas técnicas a seguir.

5.5.1 Valores fictícios

A função tail é construída para retornar a cauda de uma lista finita não vazia e, se a lista for vazia, reportar esta situação com uma mensagem de erro e parar a execução. Ou seja,

tail :: [t] -> [t] tail (a : x) = x

tail [ ] = error "cauda de lista vazia"

No entanto, esta definição poderia ser refeita da seguinte forma:

tl :: [a] -> [a] tl (_:xs) = xs tl [ ] = [ ]

Desta forma, todas as listas passam a ter uma resposta para uma solicitação de sua cauda, seja ela vazia ou não. Se a lista não for vazia, sua cauda será reportada normalmente. Se a lista for vazia, o resultado será o valor fictício

. De forma similar, a função de divisão de dois números inteiros pode ser feita da seguinte forma, envolvendo o caso em que o denominador seja zero:

divide :: Int -> Int -> Int divide n m

|(m /= 0) = n ’div’ m |otherwise = 0

Para estes dois casos, a escolha dos valores fictícios é óbvia. No entanto, existem casos em que esta escolha não é simples, nem mesmo possível. Por exemplo, na definição de uma função que retorne a cabeça de uma lista. Qual deve ser o valor fictício a ser adotado neste caso?

Para resolver exceções deste tipo, temos de recorrer a um artifício mais elaborado. A solução adotada é definir a função hd, acrescentando mais um parâmetro.

hd :: a -> [a] -> a hd y (x:_) = x hd y [ ] = y

Esta técnica é mais geral e consiste na definição de uma nova função para o caso de ocorrer uma exceção. Em nosso caso, a função hd de um argumento foi modificada para ser aplicada a dois argumentos. De maneira geral, constrói-se uma função com uma definição para o caso de surgir uma exceção e outra definição para o caso em que ela não ocorra.

fErr y x

|cond = y

|otherwise = f x

Esta técnica funciona bem em muitos casos. O único problema é que não é reportada nenhuma mensagem informando a ocorrência incomum. Uma outra abordagem, mais elaborada e desejável, consiste em processar a entrada indesejável.

5.5.2 Tipos de erros

As técnicas anteriores para se tratar as exceções se baseiam no retorno de valores fictícios, caso elas aconteçam. No entanto, em Haskell, é possível se adotar uma outra bem mais elaborada. Baseia-se no retorno de um valor erro como resultado. Isto é feito através do tipo Maybe.

data Maybe t = Nothing | Just t

deriving (Eq, Ord, Read, Show)

Na realidade Maybe t é o tipo t com um valor extra, Nothing, acrescentado. Vamos redefinir a função de divisão, divide, transformando-a em errDiv da seguinte forma:

errDiv :: Int -> Int -> Maybe Int errDiv n m

| (m /= 0) = Just (n ’div’ m) | otherwise = Nothing

Para o caso geral, utilizando uma função f, devemos fazer fErr x

| cond = Nothing | otherwise = Just (f x)

O resultado destas funções agora não são do tipo de saída original, digamos t, mas do tipo Maybe t. Este tipo, Maybe, nos permite processar um erro. Podemos fazer duas coisas com ele:

1. podemos “repassar” o erro através de uma função, que é o efeito da função mapMaybe, a ser vista a seguir ou

2. podemos “segurar” a exceção, que é o papel da função maybe, também definida a seguir. A função mapMaybe repassa o valor de um erro, através da aplicação de uma função g. Suponhamos que g seja uma função do tipo a → b e que estejamos tentando usá-la como operador sobre um tipo Maybe a. Caso o argumento seja Just x, g será aplicada a x, ou seja, g x do tipo b. Se, no entanto, o argumento for Nothing, então Nothing é o resultado.

mapMaybe :: (a -> b) -> Maybe a -> Maybe b mapMaybe g Nothing = Nothing

mapMaybe g (Just x) = Just (g x)

Para segurar um erro e resolvê-lo, deve-se retornar um resultado do tipo b, a partir de uma entrada do tipo Maybe a. Neste caso, temos duas situações:

• no caso Just, aplica-se a função g de a para b;

• no caso Nothing, temos de apresentar o valor do tipo b que vai ser retornado.

A função de alta ordem que realiza este objetivo é maybe, cujos argumentos, n e f, são usados nos casos Nothing e Just, respectivamente.

maybe :: b -> (a -> b) -> Maybe a -> b maybe n f Nothing = n

Vámos acompanhar as funções mapMaybe e maybe em ação, nos exemplos que seguem. No primeiro deles, a divisão por zero nos retorna Nothing que vai sendo “empurrado” para a frente e retornar o valor 56.

maybe 56 (1+) (mapMaybe (*3) (errDiv 9 0)) = maybe 56 (1+) (mapMaybe (*3) Nothing) = maybe 56 (1+) Nothing

= 56

No caso da divisão ser normal, o valor a ser retornado é Just 9. Este resultado é multiplicado por 3 e maybe, no nível externo, adiciona 1 e remove o Just.

maybe 56 (1+) (mapMaybe (*3) (errDiv 9 1)) = maybe 56 (1+) (mapMaybe (*3) (Just 9)) = maybe 56 (1+) (Just 27)

= (1+) 27 = 28

A vantagem desta técnica é que podemos definir o sistema sem gerenciar os erros e depois adicionar um gerenciamento de erro usando as funções mapMaybe e maybe juntamente com as funções modificadas para segurar o erro. Separar o problema em duas partes facilita a solução de cada uma delas e do todo.

Exercício.

Defina uma função process :: [Int] − > Int − > Int − > Int de forma que process l n m toma o n-ésimo e o m-ésimo elementos de uma lista l e retorna a sua soma. A função deve retornar zero se quaisquer dos números n ou m não forem índices da lista l. Para uma lista de tamanho p, os índices são: 0, 1, ..., p-1, inclusive.

No documento Programação Funcional Usando Haskell (páginas 156-159)