
























Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Neste documento, aprenderemos sobre a linguagem clean e suas funções para trabalhar com listas. A estrutura de uma lista em clean é [c:r], onde c é a cabeça e r o resto da lista. A cabeça é o primeiro elemento, e a cauda é a lista que contém todos os elementos da lista, exceto a cabeça. Existem diferentes tipos de listas, como listas de reais, strings, caracteres, listas de listas, e listas definidas por um intervalo. A compreensão de listas em clean é uma notação para criar listas com base em uma regra. Além disco, aprenderemos a definir funções com listas e as funções primitivas existentes em clean para manipula-las.
Tipologia: Notas de estudo
1 / 32
Esta página não é visível na pré-visualização
Não perca as partes importantes!

























Freqüentemente em programação nos deparamos com situações onde desejamos modelar uma coleção de objetos. Vejamos dois exemplos:
As listas em CLEAN representam coleções de objetos de um determinado tipo e, portanto, é uma ferramenta bastante útil quando desejamos modelar coleções de objetos.
As listas são importantes para a programação funcional assim como a teoria de conjuntos é importante para muitos dos ramos da matemática.
A linguagem CLEAN possui várias funções que trabalham com listas, não sendo necessário, portanto, que recriemos todas elas. Estas funções básicas, denominadas de primitivas , estão no módulo StdList fornecido juntamente com o CLEAN. Elas estão prontas para serem utilizadas quando você carrega o módulo StdEnv digitando import StdEnv logo após o nome do módulo o qual deverá possuir o mesmo nome do arquivo .icl criado para armazenar seus programas.
1 - Lista : uma lista é uma seqüência finita ou não de elementos colocados entre colchetes e separados por vírgula. Os elementos de uma lista devem ser do mesmo tipo.Uma Lista em CLEAN possui a seguinte estrutura: [c:r], onde c é a cabeça da lista (o primeiro elemento dela) e r o resto da lista (sua calda).
2 - Lista vazia : uma lista vazia é representada por [ ] , ou seja, o abrir e fechar de colchetes com tantos espaços em branco internamente quanto se deseje.
Exemplos de listas finitas:
[ 1 , 2,3,4 ] CAUDA DA LISTA = [ 2 , 3 , 4 ]
Comparando a lista [1,2,3,4] com [c:r] temos que: c = 1 e r = [2,3,4]
CAUDA DA LISTA = [['d','e']]
LISTA = ['a','b']
3 - Listas Definidas por Intervalo : uma forma diferente de se construir uma lista faz uso de uma notação de intervalo. A notação de intervalo é definida por duas expressões numéricas separadas por dois pontos delimitadas por colchetes.
Exemplos de listas de inteiros definidas por um intervalo
Entendendo o programa: 1- Criamos um função chamada patternMatch cujo argumento é uma lista r que possui três elementos [x,y,z]. A forma de CLEAN expressar o que foi dito é: r =: [x,y,z]. O operador =: atribui a r a estrutura da lista [x,y,z]. 2- Ao executarmos esta função: Start = patternMatch [2,4,6] , pelo pattern match de r=: [x,y,z] com a lista [2,4,6] , teremos que x=2 , y=4 e z=6 , assim, obtemos o resultado de yz = 64 = 24**.
Assim, para separar a cabeça e a cauda de uma lista, em CLEAN, basta comparar, fazer o pattern match da lista desejada com [c:r]. Desta forma, dado uma lista list = [1,2,3,4,5] , se a compararmos com a definição genérica de uma lista [c:r] , para que [1,2,3,4,5] seja igual a [c:r], concluímos que c = 1 e r = [2,3,4,5]. Como list = [1,2,3,4,5] e list = [c:r] , conclui-se, pelo pattern match , que c = 1 e r = [2,3,4,5].
Como já vimos no capítulo INTRODUÇÃO, CLEAN é uma linguagem fortemente tipada, sendo esta uma característica importante quando se deseja programar em uma linguagem que evite efeitos colaterais indesejados. Você deverá declarar o tipo de uma função quando desejar que ela só aceite como argumento e resposta os tipos de dados que você estipulou. Isto evita que um programa seja compilado e algum programador menos avisado tente utilizar sua função com tipos de dados diferentes. Se você não declarar o tipo de sua função, o CLEAN deduzirá por si só. Neste caso, o mesmo só verificará se você não está utilizando a função a cada momento com um tipo diferente. Assim, se você utilizar uma vez argumentos inteiros para uma determinada função f você não poderá utilizar, em um mesmo programa, no mesmo módulo, argumentos que não sejam inteiros para f.
Quando você for declarar o tipo de uma função cujos argumentos utilizem o tipo lista, proceda de forma similar a aquela mostrada nos exemplos mostrados a seguir.
Exemplo 1: Listas de caracteres
Declaração de tipo de uma função f que aceita como argumento de entrada uma lista de caracteres e devolve como resposta uma lista de caracteres.
f :: [Char] -> [Char]
Exemplo 2: Listas de inteiros e reais
Declaração de tipo de uma função f que aceita como argumentos de entrada uma lista de inteiros e uma lista de reais e devolve como resposta uma lista de inteiros.
f :: [Int] [Real] -> [Int]
Exemplo 3: Listas de strings
Declaração de tipo de uma função f que aceita como argumento de entrada uma lista de strings e devolve como resposta um string.
f :: [String] -> String ou^1 f :: [{#CHAR}] -> {#Char}
Esta é sem dúvida alguma a ferramenta mais eficiente desta linguagem. Além de permitir a criação de listas de elementos gerados por funções extremamente complexas, de uma forma clara e simples, o código gerado é compacto e veloz.
A Notação Zermelo-Frankel Na teoria de conjuntos a seguinte notação é comumente utilizada na definição de conjuntos:
S = { x^3 | x ∈Ν }
A expressão descrita anteriormente é normalmente denominada de uma compreensão de conjunto. Ela deve ser lida da seguinte forma: S é o conjunto é
(^1) Ver o livro sobre vetores para ver porque o Tipo String pode ser declarado como {#Char}
como o símbolo ∈ não está disponível no teclado, indica-se pertinência utilizando- se o sinal de menor ( < ) seguido do sinal de menos ( - ). Uma expressão x <− xs é denominado de gerador.
Tal como na Matemática, CLEAN permite que se utilizem condições para filtrar (eliminar) elementos do contradomínio. Vamos ver um exemplo ilustrativo:
Exemplo: vamos gerar uma lista cujos elementos sejam o quadrado dos números pares que estão no intervalo de 1 a 10.
Da matemática temos: S = { x^2 | 1 =< x >= 10 & x é par } Em CLEAN^2 temos: S = [xx \ x <-[1..10] | isEven x] Obs*. A função isEven é uma primitiva do CLEAN que testa se um valor é ou não par.
INTERVALOS REAIS
Muitas vezes precisamos gerar intervalos de números reais em nossas funções, como é o caso do exemplo de integração numérica que mostraremos logo a seguir. Antes do exemplo, vamos ver como tratar listas com intervalos reais. Os intervalos de reais podem ser obtidos de duas maneiras:
∈ + +×⋅⋅−
[ , 2 ]
x a ha hbh
b
a
Veja como é simples implementar esta complexa função, em CLEAN, utilizando a notação Zermelo-Frankel para listas.
(^2) Observe o quanto aderente, o quão parecido, é a notação em CLEAN à definição
matemática.
(^3) Se você não tem ainda algum conhecimento sobre integrais, você pode saltar este exemplo.
Onde: fn = função escolhida para integrar a = início do intervalo de integração b = fim do intervalo de integração h = incremento
Observe que o programa expressa de forma sucinta a fórmula matemática do método dos trapézios. O valor na parte inferior da figura mostra o resultado da integral da função co-seno para o intervalo de 0.0 a 5.231 com incremento igual a 0.001.
UTILIZANDO MAIS DE UM GERADOR AO MESMO TEMPO Numa compreensão de listas, após as duas barras invertidas ( \ ) pode aparecer mais de um gerador separados por uma vírgula. Quando isto ocorre temos uma combinação ortogonal de geradores. Ao se utilizar uma combinação ortogonal de geradores, a expressão colocada na frente das duas barras invertidas é calculada para toda possível combinação das variáveis correspondentes. O exemplo, a seguir, torna mais claro o que foi dito:
Este programa devolve a lista [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)] , ou seja, ele pega o primeiro elemento do primeiro gerador ( x<-[1..3] ) e devolve todas as combinações com os elementos do segundo gerador ( y<-[4..6] ), ou seja: (1,4),(1,5) e (1,6) , depois pega o segundo elemento do primeiro gerador e devolve todas as combinações com os elementos do segundo gerador, ou seja: (2,4),(2,5) e (2,6) , o processo continua até que o programa tenha obtido todas as combinações possíveis de elementos do primeiro gerador como elementos do
Assim, quando a menor lista é totalmente percorrida, todos os geradores combinados com & param de gerar elementos.
Podemos estabelecer condições para os valores a serem gerados por uma combinação de geradores. A condição, mais uma vez, deve ser separada dos geradores por uma barra vertical. O programa devolve a lista [(2,1),(2,2),(4,1),(4,2),(4,3),(4,4)]. Observe que a visibilidade da variável x (denominada de escopo de x ) não se restringe somente ao lado esquerdo da compreensão. Ela também pode ser manipulada no lado direito do gerador introduzindo x. Entretanto y não pode ser utilizada nos geradores precedendo-a, isto é, y pode somente ser usada em (x,y) e na condição. O símbolo <− que define a pertinência é especialmente utilizado na compreensão de listas e não constitui um operador.
Vamos mostrar como projetar e implementar funções em CLEAN que trabalhem com listas, e, posteriormente, vamos apresentar as funções primitivas existentes no CLEAN criadas para esta finalidade. Vamos fazer isto através de vários exemplos que serão mostrados a seguir. Não declararemos o tipo de cada função aqui criada devido as mesmas trabalharem com vários tipos diferentes. Nossa preocupação maior, neste momento, é mostrar como manipular listas em CLEAN. Não se esqueça de criar um arquivo .icl e um módulo de execução para você testar as funções apresentadas. Crie, por exemplo um arquivo com o nome listas.icl e digite o seguinte cabeçalho:
Como você vai testar várias funções, não se esqueça que cada módulo de execução só pode ter um Start ativo ao mesmo tempo^4. Se você tiver mais que um, coloque todos como comentário e deixe apenas um ativo.
Exemplo:
Apenas um Start
Obs. : // no início da linha indica que a mesma é um comentário
EXEMPLO: vamos construir uma função em CLEAN que devolve a cabeça de uma lista. Vamos dar a ela o nome de car.
Função = car | Argumento = uma lista | Formato = car x
A figura a seguir descreve um programa ilustrando a criação e o uso da função car. O valor devolvido decorrente da execução do programa aparece na parte inferior desta figura.
Observe que na linha quatro do programa está a definição da função car. Assim ao digitar esta linha no ambiente Clean criamos a função car. O argumento de car é [c:r]. O argumento está entre colchetes para indicar que o mesmo deve ser uma
(^4) Na realidade pode-se ter mais de um Start, mas, como Start é uma função, todos os Starts deverão possuir o
mesmo tipo de dados com resultado, e, nestes casos, o primeiro Start encontrado será o avaliado por CLEAN.
Na linha quatro do programa aparece a definição da função cadr que tem como argumento uma lista [c:r]. A resposta é o car (a cabeça) da lista r que é a cauda da lista dada [c:r]. O cdr da lista [1,2,3] é a lista de inteiros [2,3] que corresponde à cauda da lista [1,2,3]. A cabeça da cauda [2,3], obtida usando-se a função car , corresponde ao inteiro 2 que aparece na parte inferior da figura (resposta de cadr de [1,2,3] ).
EXEMPLO: vamos criar uma função que devolve a cauda da cauda de uma lista. Vamos dar a ela o nome de de cddr.
Função = cddr | Argumento = uma lista | Formato = cddr x
A figura a seguir descreve um programa ilustrando a criação e o uso da função cddr. O valor devolvido decorrente da execução do programa aparece na parte inferior desta figura.
Na linha quatro do programa aparece a definição da função cddr que tem como argumento uma lista [c:r]. A resposta a esta função é o cdr da lista r que é a cauda da lista de entrada [c:r]. A cauda da cauda da lista [1,2,3] é a lista de inteiros [3] que aparece como resposta na parte inferior da figura.
Outras funções para trabalhar com cabeça e cauda de listas:
caar [c:r] = car c - Devolve a cabeça da cabeça de uma lista (neste caso a cabeça da lista tem que ser uma lista cadddr [c:r] = car (cdr (cdr r)) - Devolve a cabeça da cauda da cauda da cauda de uma lista
EXEMPLO: vamos criar no CLEAN uma função que pega um elemento x e o coloca na cabeça da lista y. Vamos dar a esta função o nome de consLista.
Função = consLista x y | Argumento = duas listas | Formato = consLista x y
Criando a função consLista.
Para criá-la, digite no ambiente do CLEAN consLista x y = [x:y] Se digitarmos: Start = consLista 1 [2,3,4,5] Teremos como resposta [1, 2, 3, 4, 5]
Observe que é relativamente simples criar algumas funções em CLEAN e utilizá- las na implementação de outras funções. CLEAN já possui várias funções que trabalham com listas. Como exemplo podemos citar a função hd^5 (que representa um acrônimo para a palavra head em inglês que traduzimos para cabeça) e a função tl^6 (que representa um acrônimo para a palavra tail em inglês que traduzimos como cauda).
EXEMPLO: Suponha que queiramos definir uma função somaLista que some todos os elementos de uma lista de inteiros. O seu tipo seria declarado como segue.
somaLista:: [Int] -> Int
Para uma lista com um número pequeno de elementos poderíamos defini-la da seguinte forma:
somaLista [x,y,z] = x+y+z
(^5) Equivalente à função car criada anteriormente (^6) Equivalente à função cadr criada por nós
EXEMPLO: definir uma função que calcule o tamanho de uma lista de inteiros.
comp :: [Int] -> Int comp [] = 0 comp [x:y] = 1 + comp y
EXEMPLO: definição de ++ para listas de inteiros.
Para fazer recursão em duas listas temos quatro casos que correspondem a combinações de casos base e indutivo para cada lista:
[ ] ++ [ ] =... [ ] ++ [y:ys] =... [x:xs] ++ [ ] =... [x:xs] ++ [y:ys] =...
Se começarmos assim poderemos encontrar algumas redundâncias e eliminá-las. Os dois primeiros casos não precisam ser separados. A seguinte regra os cobre:
[ ] ++ ys = ys
A terceira e a quarta regra podem também ser combinadas:
[x:xs] ++ ys = [x : [xs++ys]]
Desta forma apenas recursão sobre a primeira lista é suficiente para definir esta função em particular.
EXEMPLO: a ordenação por Inserção. Devemos projetar uma função que ordene uma lista de inteiros em ordem crescente. Esta função terá o seguinte tipo:
ordIns :: [Int] -> [Int]
Um exemplo de uso: ordIns [7, 3, 9, 2] deve gerar [2, 3, 7, 9]
Como em todos os exemplos anteriores, nossa primeira intuição conduz a dois casos:
ordIns [] =... ordIns [x:xs] =...
O primeiro caso é fácil desde que a lista vazia é trivialmente ordenada. Assim sendo temos que:
ordIns [ ] = [ ]
Para listas não vazias nós temos acesso direto à cabeça x e à cauda xs. Por exemplo:
insere x [] = [x]
Se a lista não é vazia, nós temos acesso direto a sua cabeça. Nós podemos perguntar: "x pertence antes ou após a cabeça da lista?"
insere x [y:ys] | x <= y = [x :[ y : ys]] | otherwise = [y : (insere x ys)]
O casamento de padrões é um meio poderoso para se definir funções em CLEAN. Entretanto, devemos ter em mente que:
EXEMPLO: findReps [x : x : xs] = [x : findReps xs] é ILEGAL.
EXEMPLO: acha cod ((cod,nome,valor):reste) = (nome, valor) também é ILEGAL.
A solução é usar guardas para testar por igualdade ao invés de esperar que o casamento de padrões faça isto por você.
acha codBarra [(cod, nome, valor) : resto] | codBarra == cod = (nome, valor) |...
Vamos continuar explorando padrões através de mais um exemplo.
EXEMPLO: definindo a função somaPares.
O tipo da função somaPares é dado a seguir.
somaPares :: [(Int,Int)] -> [Int]
Exemplo de uso da função somaPares :
somaPares [(1, 9), (37, 8)] => [10, 45]
O caso base é padrão:
somaPares [] = []
É possível escolher uma variedade de padrões para o caso indutivo. Em geral, é melhor escolher o padrão mais preciso. Vejamos algumas alternativas e a melhor forma a ser usada.
somaPares1 [p:ps] = [(fst p + snd p) : somaPares1 ps]
O padrão [p:ps] casa-se com qualquer lista não vazia. Os componentes da sua cabeça são extraidos por fst e snd.
somaPares2 [p:ps] = [(m + n) : somaPares2 ps] where (m, n) = p
Outra vez o padrão [p:ps] casa-se com qualquer lista não vazia, mas os componentes da cabeça são obtidos por casamento de padrões na definição (m,n) = p dentro da cláusula where.
somaPares [(m,n):ps] = [m+n : somaPares ps]
Aqui, o padrão mais preciso foi usado, conduzindo a uma definição mais simples e clara da função.
CLEAN faz o casamento de padrões numa ordem seqüencial:
meuZip [x:xs] [y:ys] = [(x,y) : meuZip xs ys] meuZip xs ys = []