Free Palestine and Lebanon 🍉 Stop the Genocide
Haskell Logo

Programação Funcional

Questão 1: enumFromTo

Voltar

Apresente uma definição recursiva da função (pré-definida) enumFromTo :: Int -> Int -> [Int] que constrói a lista dos números inteiros compreendidos entre dois limites.

Exemplo

> enumFromTo 1 5
[1,2,3,4,5]

Resolução

Clica para revelar

enumFromTo :: Int -> Int -> [Int]
enumFromTo start end 
    | start > end = []
    | otherwise = start : enumFromTo (start + 1) end

Explicação

Clica para revelar

Existem várias formas de definir funções recursivas. A que vou usar nesta explicação consiste em encontrar o “padrão” da recursividade, isto é, o que é que muda entre diferentes chamadas recursivas da função.

Qual deverá ser o resultado de enumFromTo 5 5? Exato, apenas [5].

E de enumFromTo 4 5? [4,5].

Ou seja, quando chamamos a função enumFromTo start end, a lista resultante começa sempre com start.

Temos assim uma definição preliminar da função: enumFromTo start end = start : ??? (: é um operador que coloca um elemento no início de uma lista).

Vamos aplicar esta nossa solução a um exemplo concreto: enumFromTo 1 5 irá resultar em 1 : ???.

Pode parecer que chegámos a um entrave, mas nós sabemos o valor de ???. Neste caso, ??? = [2,3,4,5], isto é, o resto da lista.

Agora vamos pensar ao contrário… o que é que produz o resultado [2,3,4,5]? Exatamente, enumFromTo 2 5! Por outras palavras, sabemos que enumFromTo 1 5 = 1 : enumFromTo 2 5 = 1 : enumFromTo (1 + 1) 5.

Já podemos então definir a nossa função como: enumFromTo start end = start : enumFromTo (start + 1) end.

Visto que esta função é recursiva, precisamos ainda de um caso de paragem. Para lá chegar, podemos pensar da seguinte forma: quando é que paramos de adicionar 1 a start? Aqui a resposta é “quando for igual a end”, pois se os dois valores forem iguais iremos ter uma lista de apenas um elemento.

Chegamos assim à definição (quase) final da função:

enumFromTo start end 
    | start == end = [start]
    | otherwise = start : enumFromTo (start + 1) end

Os caracteres | chamam-se “guardas”, podes aprender o que são e como as usar aqui.

Apesar desta definição funcionar para a maioria dos casos, não funciona para todos. E se start for maior que end? Se experimentássemos correr enumFromTo 5 1 com a nossa solução atual, o nosso computador nunca mais iria sair do sítio. Porquê? Porque iria estar constantemente a adicionar uma unidade a start, mas como este já é superior a end, nunca iriam ser iguais. Desta forma, uma solução mais completa para esta questão seria:

enumFromTo start end 
    | start > end = []
    | start == end = [start]
    | otherwise = start : enumFromTo (start + 1) end

Por outras palavras, se o limite inferior for maior do que o limite superior, o resultado é uma lista vazia (um comportamento alternativo seria ordenar de forma decrescente neste cenário, mas nesta questão apenas pretendemos ordenar de forma crescente).

Por intuição, vemos que a segunda condição agora é redundante, por isso podemos removê-la de forma segura.

enumFromTo start end 
    | start > end = []
    | otherwise = start : enumFromTo (start + 1) end