Novos operadores de tipos
Tipos que são por natureza paramétricos como as listas e as
matrizes, podem também ser definidos em ML.
O tipo lista (list) está predefinido por questões de eficiência, mas
se assim não fosse poderia ser definido pelo mecanismo de
definição de tipos do ML.
Uma extensão natural da definição datatype inclui a definição de
operadores de tipos. Para tal basta encarar a palavra datatype
como uma definição funcional (parametrizada) em vez de como
uma definição de valor (constante).
Em vez de definirmos um só tipo iremos definir um operador que
dado um tipo dá como resultado um novo tipo.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
94
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Novos operadores de tipos
Por exemplo, suponhamos que se pretende generalizar o seguinte
tipo básico, que define uma matriz de inteiros:
datatype int_matriz = Matriz of (int list list)
Pretende-se definir matrizes cujos elementos sejam de outro tipo
qualquer, mas sem ter que definir cada um dos tipos
separadamente, ou seja, usando uma construtora polimórfica.
Através da parametrização da definição acima,
introduzir um operador de tipos em vez de um tipo fixo:
podemos
datatype  matriz = Matriz of ( list list)
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
95
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Novos operadores de tipos
Agora, para qualquer tipo T, teremos também o tipo T matriz e a
construtora é polimórfica sendo o seu tipo:
Matriz: ( list list)   matriz
Por exemplo poderemos ter:
Matriz [[1, 2], [3, 4], [5, 6]] : int matriz
Matriz [[Matriz [[true, false], [false, false]]]] : (bool matriz) matriz
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
96
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Novos operadores de tipos
Com esta facilidade para definir operadores de tipos, não será
necessário tratar as listas como um tipo especial pré-definido.
Podemos simplesmente definir:
datatype  list = :: of ( x  list)
|[]
que introduz as construtoras polimórficas
:: : ( x  list)   list
[ ]:  list
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
97
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Novos operadores de tipos
Esta capacidade do ML mostra o real poder do mecanismo de
definição de tipos.
Todos os tipos e operadores de tipos (excepto  e x) podem ser
introduzidos pelo programador.
Os tipos pré-definidos são incluídos apenas por uma questão de
eficiência. Em particular, os tipos pré-definidos int e string
poderiam ser modelados como novos tipos, no entanto isso não
seria muito conveniente.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
98
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores
Árvores aparecem com frequência nas ciências da computação e
existem diversas maneiras de as descrever.
Vamos falar das árvores, mais concretamente em algumas das
suas diversas variações: árvores binárias que só têm valores nas
folhas e árvores binárias com valores em todos os nós.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
99
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Estas árvores são chamadas binárias porque, a partir de cada nó,
existe um par de ramos que conduzem a outros dois nós.
Os nós que não têm ‘descendentes’ são chamados de folhas.
Vamos chamar ao tipo que define árvores binárias com valores
apenas nas folhas de bintree.
Mais precisamente, vamos chamar  bintree, a árvores cujos
valores que se encontram nas folhas sejam do tipo .
De forma a construirmos uma nova definição de tipo para estes
objectos, temos de considerar a forma como estes podem ser
construídos.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
100
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Pode existir um único nó, que nesse caso será considerado uma
folha e como tal (foi construído a partir de) contém um valor do
tipo .
No caso geral as árvores binárias serão compostas. Cada bintree
composta pode ser construída se juntarmos as duas bintrees mais
pequenas imediatamente abaixo do ponto de união, tal como
mostra a figura:
Lf(6)



6
9
3
6
6
9
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
101
3
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Isto sugere que apenas iremos necessitar de dois operadores
(construtoras do tipo) que vamos chamar de Lf e . O que é
pretendido para estas duas construtoras será:
Lf:    bintree
: ( bintree x  bintree)   bintree
em que a primeira serve para construir as folhas a partir do valor
recebido e a segunda para formar árvores binárias compostas a
partir de duas sub-árvores.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
102
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Assim, a definição para as construtoras polimórficas que
constroem o tipo recursivo bintree é a seguinte:
datatype  bintree = Lf of 
|  of ( bintree x  bintree)
infix 
Desta forma a definição da árvore apresentada no diagrama é:
val arv = Lf 6  (Lf 9  Lf 3)
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
103
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Após a introdução deste novo operador de tipos, vamos definir
algumas operações sobre árvores binárias.
As mais imediatas serão left e rigth que seleccionam,
respectivamente, a sub-árvore da esquerda e da direita que se
encontra imediatamente abaixo. Estas operações não estão
definidas para árvores que sejam apenas uma folha.
fun left (t1  t2) = t1
| left (Lf a) = error “left de Lf não está definido”
fun right (t1 t2) = t2
| right (Lf a) = error “right de Lf não está definido”
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
104
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Temos também o predicado isleaf, que indica se uma dada árvore
é ou não uma folha e as funções leftmostleaf, que retorna o valor
da folha mais à esquerda da árvore e sumofleaves, que soma
todos os valores que estão nas folhas da árvore.
fun isleaf (t1  t2) = false
| isleaf (Lf v) = true
fun leftmostleaf (t1  t2) = leftmostleaf t1
| leftmostleaf (Lf v) = v
fun sumofleaves (t1  t2) = sumofleaves t1 + sumofleaves t2
| sumofleaves (Lf v) = v
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
105
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Existem ainda funções de ordem superior com árvores binárias,
analogamente às que existem para listas (map, accumulate, etc.).
Muitas operações sobre árvores binárias envolvem que se
perscrute a árvore, ao mesmo tempo que se aplica uma qualquer
função f aos valores das folhas. Deve existir ainda uma função
binária g que combina os valores retornados por cada incursão
nas sub-árvores esquerda e direita.
Assim define-se uma função de âmbito geral btreeop:
fun btreeop f g (Lf a) = f a
| btreeop f g (t1  t2) = g (btreeop f g t1) (btreeop f g t2)
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
106
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Note-se que operações tais como leftmostleaf e sumofleaves
podem facilmente ser definidas à custa de btreeop:
val sumofleaves = btreeop id plus
À medida que a árvore é perscrutada, a função identidade é
aplicada aos valores encontrados nas folhas (retornando os
valores inalterados) e a função plus é aplicada aos resultados
retornados pela incursão nas sub-árvores esquerda e direita,
somando, assim, todos os valores das folhas.
Isto é análogo ao uso de reduce plus 0 para somar todos os
elementos de uma lista.
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
107
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores binárias com valores nas folhas
Outra operação genérica sobre árvores binárias, btreemap, que
aplica uma função às folhas da àrvore (análoga ao map no caso
das listas) também pode ser definida como um caso especial de
btreeop:
fun join t1 t2 = t1  t2
fun btreemap f = btreeop (Lf ° f) join
A função join é apenas uma versão ‘curried’ do construtor .
O que está a ser feito é decompor a árvore, até chegar às folhas,
aplicar a função f aos valores das folhas e depois voltar a compor
a árvore, agora já com os valores modificados!
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
108
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores com valores em todos os nós
Vamos agora estudar as árvores binárias com valores também
nos nós internos, que a partir de agora chamaremos apenas de
árvores.
Uma definição de tipo para estes objectos será:
datatype  tree = Empty
| Tr of ( tree x  x  tree)
A primeira construtora é uma constante que representa o caso
especial da árvore sem nós.
A segunda construtora constrói uma árvore a partir de 2 subárvores e o nó que as une tem um valor do tipo .
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
109
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores com valores em todos os nós
Para estas árvores consideramos que estamos na presença de
uma folha quando ambas as suas sub-árvores têm o valor Empty.
Para poupar na escrita definimos uma função (que não é uma
construtora) que retorna uma folha:
leaf:    tree
fun leaf a = Tr(Empty, a, Empty)
A árvore
6
Departamento de Matemática e Engenharias
Elsa Carvalho
val t1 = Tr (leaf 6, 1, Tr (leaf 9, 2, leaf 3))
2
9
Universidade da Madeira
será representada por
1
3
110
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Árvores com valores em todos os nós
Também para estas árvores vamos definir algumas funções úteis.
Começamos por definir a operação análoga ao map (para listas) e
a btreemap (para árvores binárias), que será expressa da
seguinte forma:
fun treemap f Empty = Empty
| treemap f (Tr (t1, a, t2))
= Tr (treemap f t1, f a, treemap f t2)
Universidade da Madeira
Departamento de Matemática e Engenharias
Elsa Carvalho
111
Programação em Lógica e Funcional (2000/01)
(Actualizado em 2005/06)
Download

5 - Universidade da Madeira