Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf ·...

141
Tutorial de Mozart-Oz Por Pedro Miguel Félix Alípio Agosto de 2000 Para a disciplina de Projecto do 5º Ano do Curso de Engenharia Informática Ramo Computadores e Sistemas Pedro Miguel Félix Alípio Nº 920385 E-mail: [email protected]

Transcript of Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf ·...

Page 1: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

Tutorial de Mozart-Oz

Por Pedro Miguel Félix Alípio

Agosto de 2000

Para a disciplina de Projecto do 5º Ano do Curso de Engenharia Informática Ramo Computadores e Sistemas

Pedro Miguel Félix Alípio Nº 920385 E-mail: [email protected]

Page 2: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

2

Índice

1. INTRODUÇÃO 7

1.1 - Os principais paradigmas da computação 7

1.2 História do Mozart-Oz 8

1.3 Resumo das Características do Oz 3 9

2. A LINGUAGEM OZ 11

2.1 O Modelo de programação do Oz 11

2.2 Comparação entre o Oz e o Prolog 14

2.3 Comparação entre o Oz e o Java 14

3. O OZ DISTRIBUÍDO 16

3.1 Características do Oz Distribuído 18

3.2 O grafo de distribuição 19

3.3 A Arquitectura de Implementação 20 3.3.1 O Motor Estendido 21 3.3.2 A Camada de Protocolo 21 3.3.3 A Camada de gestão de memória 22 3.3.4 Camada de Rede 22

3.4 Computação Aberta 22 3.4.1 Conexões e tickets 22 3.4.2 Servidores de computação 23

3.5 Detecção e Tratamento de Falhas 23 3.5.1 O Principio da detenção 24 3.5.2 Falhas no grafo da distribuição 25 3.5.3 Handlers e watchers 25

3.6 Controlo de Recursos e Segurança 26 3.6.1 Sites Virtuais 27

4. TUTORIAL DE OZ 28

Page 3: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

3

4.1 O Ambiente de Desenvolvimento 28 4.1.1 Arrancar o ambiente de desenvolvimento 28 4.1.2 O primeiro programa 29 4.1.3 Principais características do OPI 30

4.2 Introdução á programação em Oz 34

4.3 Os tipos básico de dados 35 4.3.1 Instanciação de variaveis 35 4.3.2 Números 35 4.3.3 Literais 36 4.3.4 Registos e Tuplos 36 4.3.6 Listas 38 4.3.7 Strings Virtuais 39

4.4 Igualdade e Teste de Igualdade 39 4.4.1 Teste de Igualdade 40

4.5 Estruturas Básicas de Controlo e Procedimentos 41 4.5.1 A instrução vazia 41 4.5.2 A instrução condicional if 41 4.5.3 Procedimentos 43 4.5.4 Lexical Scoping 45 4.5.5 Semântica dos procedimentos 45 4.5.6 Inicialização de variáveis 46 4.5.7 Pattern Matching 47 4.5.8 Aninhamento de instruções 49 4.5.9 Procedimentos como valores 50 4.5.10 Abstracções de controlo 51

4.6 Tratamento de Excepções 52 4.6.1 As excepções do sistema 53

4.7 Módulos e Interfaces 54

4.8 Concorrência 56 4.8.1 Tempo 58 4.8.2 Comunicação entre streams 58 4.8.3 Prioridades das Threads 60 4.8.4 Execução orientada ao pedido 61 4.8.5 Detecção de terminação de threads 62 4.8.6 Composição concorrente 63

4.9 Tipos de dados baseados em estados 64 4.9.1 Ports 64 4.9.2 Comunicação Cliente-Servidor 65 4.9.3 As células 66 4.9.4 Chunks 67 4.9.5 Locks 68

Page 4: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

4

4.10 Classes e Objectos 69 4.10.1 Implementação de Classes utilizando estruturas mais primitivas 69 4.10.2 Implementação de Objectos utilizando estruturas mais primitivas 70 4.10.3 As Classes em Oz 70 4.10.4 Chamadas estáticas a métodos 71 4.10.5 Herança de classes 72 4.10.6 Propriedades 73 4.10.7 Classes parametrizadas 75 4.10.8 Métodos públicos e privados 76 4.10.9 Valores por omissão dos argumentos 77

4.11 Objectos e Concorrência 78 4.11.1 Trocas atómicas em atributos 78 4.11.2 Locks de threads reentrantes 78 4.11.3 Aplicando locks a objectos 79 4.11.4 Canal FIFO concorrente 80 4.11.6 Eventos 80 4.11.7 Objectos Activos 81

4.12 Arrays e Dicionários 83

4.13 Programação Lógica 84 4.13.1 Armazenamento de restrições 84 4.13.2 Espaços computacionais 84 4.13.3 Vinculação e desvinculação de restrições 85 4.13.4 Disjunções 85 4.13.6 Execução orientada à determinação 86 4.13.7 Lógica Condicional 87 4.13.8 Expressões condicionais paralelas 87 4.13.9 Programas não deterministicos e busca 88 4.13.10 Exemplos 90

5. TUTORIAL DE OZ DISTRIBUÍDO 93

5.1 Nomes Globais 93 5.1.1 Conectar aplicações através de tickets 94 5.1.2 Estruturas de dados persistentes usando pickes 95 5.1.3 Computações Remotas e Functors 95 5.1.4 Servidores 96 5.1.5 Servidor de computações 97 5.1.6 Servidor de computações com funtors 97 5.1.7 Servidor dinamicamente extensível 98

5.2 Agentes Móveis 99 5.2.1 Instalação de Agentes 99 5.2.2 Programação de agentes 101 5.2.3 A Definição do servidor de agentes 102

5.3 Tolerância a falhas 103

Page 5: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

5

5.3.1 O servidor de “Olá Mundo” com tolerância a falhas 103 5.3.2 Tolerância a falhas em objectos estacionários 106 5.3.3 Tolerância a falhas usando guardas 107 5.3.4 Tolerância a falhas baseada em excepções 109

6 PROGRAMAÇÃO GRÁFICA 111

6.1 Toplevel e Objectos Widget 111 6.1.1 Frames 111 6.1.2 Labels 112 6.1.3 Imagens 113 6.1.4 Mensagens 113

6.2 Gestores de Geometria 113 6.2.1 O Packer 113 6.2.2 Grids 115

6.3 Outros Widgets 115 6.3.1Botões e Acções 116 6.3.2 CheckButtons, RadioButtons e variáveis 116 6.3.3 Menus 117 6.3.4 Eventos 118 6.3.5 Entries 119 6.3.6 Scales 120 6.3.7 Listboxes 120 6.3.8 Manipular o toplevel 121 6.3.9 DialogBoxes predefinidas 121

6.4 O Canvas 121 6.4.1 Criar um gráfico de barras 122 6.4.2 Eventos 123

6.5 Caixas de Texto 124 6.5.1 Manipulação do texto 124 6.5.2 Etiquetas de texto e marcas 124

6.6 Ferramentas para o Tk 125 6.6.1 Dialogs 125 6.6.2 Mensagens de Erro 126 6.6.3 Barras de Menus 126 6.6.4 Listas de Imagens 127

7. CONCLUSÃO 128

APÊNDICE A 129

Os programas dos testes de performance entre o Oz e o Java 129 A.1 Produtor Consumidor Centralizado em Java 129 A.2 Produtor Consumidor Centralizado em Oz 131

Page 6: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

6

A.3 Produtor Consumidor Distribuído em Java 131 A.4 Produtor Consumidor distribuído em Oz 135

APÊNDICE B 137

Nomes de Cores 137

APÊNDICE C 138

Glossário de alguns termos 138

BIBLIOGRAFIA 140

Page 7: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

7

1. Introdução As linguagens de programação descrevem comportamentos

computacionais baseando-se em diferentes filosofias ou paradigmas. Uma distinção mais rude dos paradigmas divide-os em duas categorias : programação baseada e não baseada em estados. A programação baseada em estados representa dados que mudam ao longo do tempo, enquanto que, a não baseada em estados, representa dados que são imutáveis depois de serem criados. A distinção entre estas duas filosofias é importante pois a programação baseada em estados apresenta um modelo de programação que consiste numa aproximação ao mundo real, enquanto o modelo não baseado em estados permite uma maior simplicidade no raciocínio. A evolução das linguagens de programação baseadas em estados culminou no modelo orientado a objectos, enquanto as não baseadas em estados (linguagens declarativas) deram origem à computação directa e indirecta. Da computação directa nasceram as linguagens funcionais. Da computação indirecta surgiram as linguagens lógicas.

A programação multi-paradigma consiste na integração de vários paradigmas num único modelo. Neste modelo, as várias formas de computação podem ser entendidas, como partes de uma filosofia única. Esta forma de computação permite um estilo natural de programação, isto é, adaptar a cada problema especifico que surge no decorrer da elaboração de uma aplicação, o paradigma mais adequado à implementação de um algoritmo para o resolver. Por exemplo, para problemas relacionados com avaliação de funções, usar-se-ia o paradigma funcional, para problemas relacionados com informação parcial, usar-se-ia o paradigma da programação baseada em restrições, para problemas em que seja necessário representar entidades do mundo real ou estados, usar-se-ia o paradigma da programação orientada a objectos.

1.1 - Os principais paradigmas da computação Os principais paradigmas da computação que são utilizados no Oz

são os seguintes:

PPrrooggrraammaaççããoo LLóóggiiccaa A programação lógica consiste na realização de computações como

deduções. As características principais destas linguagens utilizadas pelo Oz são a utilização de variáveis lógicas e a pesquisa de soluções (por exemplo: backtracking). Este tipo de programação deu mais tarde origem à computação especulativa com base em informação parcial, ou seja, a computação baseada em restrições. A linguagem mais conhecida que implementa este paradigma é o Prolog.

PPrrooggrraammaaççããoo FFuunncciioonnaall As principais características das linguagens funcionais usadas pelo Oz são

a utilização de entidades first-class (funçoes, variáveis, etc... podendo ser passadas como argumento), sintaxe composicional e lexical scoping (está relacionado com o

Page 8: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

8

espaço contextual que a variável abrange). Estes conceitos irao ser estudados em maior detalhe ao longo deste documento. Uma das linguagens que implementa este paradigma é o Haskell.

PPrrooggrraammaaççããoo OOrriieennttaaddaa aa OObbjjeeccttooss Actividades computacionais são organizadas em entidades chamadas de

objectos, que encapsulam estados e métodos que servem para os manipular. Com frequência é implementada a possibilidade de herança para facilitar o desenvolvimento incremental e reutilização do código. Utilizar objectos é uma forma bastante poderosa de implementação de modelos baseados em estados ou computação imperativa, e são principalmente utilizados em aplicações que se relacionam com o mundo exterior.

1.2 História do Mozart-Oz O Oz e o sistema Mozart foram desenvolvidos pelos grupos de

investigação Smolka no DFKI (Centro Alemão de Pesquisa para a Inteligência Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação), e finalmente por Peter Van Roy no UCL (Universidade Católica de Louvain).

A primeira versão do Oz foi o Oz 1, e foi lançado em 1995. Caracterizava-se sobretudo, por basear-se num modelo de concorrência, que assume que qualquer expressão pode potencialmente ser executada de modo concorrente. Mais tarde surgiu outra versão Oz 2, que a principal novidade que trazia eram melhoramentos relativamente ao modelo de concorrência anterior. Em vez de cada expressão ser executada de modo concorrente, as threads passam a ser criadas explicitamente. O modelo de concorrência do Oz 1, dificultava bastante o controlo sobre os recursos partilhados e o debug dos programas. Passou-se assim a um modelo em que só existe concorrência quando esta for desejada.

Finalmente surgiu o Oz 3 e o sistema Mozart, a última versão da linguagem multi-paradigma Oz. As principais diferenças da versão anterior estão relacionadas com a Introdução de functors, que consistem em componentes de software espalhados por diferentes URLs, e as futures utilizadas na sincronização do fluxo dos dados na internet.

Page 9: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

9

1.3 Resumo das Características do Oz 3

PPrrooggrraammaaççããoo Programação Orientada a Objectos

O Oz é uma linguagem concorrente orientada a objectos. Esta linguagem pode inicialmente ser programada de modo similar a outras, como por exemplo o Java. O programador, através da experiencia, tenderá a simplificar os seus programas, usando os poderosos conceitos de execução dependente no fluxo de dados e procedimentos first-class. O Oz possui as estruturas básicas encontradas em todas as linguagens orientadas a objectos, tais como : classes, objectos, métodos, atributos e herança.

Programação Concorrente O Oz é iminentemente uma linguagem concorrente. O sistema Mozart

implementa um sistema de threads ultraleves utilizando um escalonamento preemptivo baseado na divisão do tempo (time slice) de processamento pelas threads – Fair scheduling. Possui dois mecanismos de sincronização : baseados no fluxo dos dados ou de forma transparente pela utilização de variáveis lógicas.

Multi-Paradigma Quase todas as linguagens de programação têm um modelo baseado num

só paradigma. O Oz usa de forma coerente e simples os paradigmas: funcional, orientado a objectos, e programação lógica. Isto é possivel porque possui uma implementação muito geral e poderosa do paradigma de programação concorrente baseada em restrições.

IInnffeerrêênncciiaa Programação Baseada em Restrições

O Oz uma poderosa linguagem de programação por restrições com variáveis lógicas, domínios finitos, conjuntos finitos, árvores relacionais e restrições de registo. O sistema é competitivo em desempenho, com as soluções comerciais, mas é mais expressivo e flexível, disponibilizando espaços computacionais fist-class, estratégias programáveis de pesquisa de soluções, uma ferramenta gráfica para a exploração interactiva das árvores de pesquisa, motores de pesquisa paralelos explorando a possibilidade de distribuir a computação na rede e um interface por programação para construir eficientes sistemas novos de restrições.

Programação Lógica O Oz implementa tanto programação declarativa lógica directa como

indirecta. Possui poderosas ferramentas construídas sobre os conceitos espaços de computação first-class e disjunções. Torna o Mozart ideal para a implementação de sistemas Multi-Agentes Inteligentes ou sistemas de pesquisa paralelos.

DDiissttrriibbuuiiççããoo Sistemas Distribuídos Abertos

O sistema Mozart é a plataforma ideal para este tipo de sistemas porque torna a rede completamente transparente. Cria a ilusão de uma área de armazenamento comum, que se encontra na realidade estendida a varias localizações. O suporte para isso, são protocolos bastante eficientes. O Mozart tem

Page 10: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

10

controlo absoluto sobre as comunicações na rede permitindo o uso de modo bastante eficiente os recursos por ela disponibilizados. Permite também fácil desenvolvimento de aplicações robustas, com mecanismos de tolerância a falhas.

Programação Baseada em Componentes Distribuídos O Mozart permite o desenvolvimento especificações de componentes first-

class (functors) e componentes (modules). Os módulos facilitam o desenvolvimento de aplicações. Estes componentes podem ser acedidos através de URLs, absolutas e relativas, é carregados quando necessários (lazy). Permite a implementação de políticas de segurança flexíveis pelos gestores do modulo.

Agentes Móveis Tendo uma tecnologia de componentes dinâmicos, suporte para

computação aberta, e todas as outras características, o Mozart é uma plataforma ideal para programação de agentes móveis. Uma computação é capaz de criar computações num espaço distribuído e dinâmico.

PPllaattaaffoorrmmaa Compatibilidade

Tal como o Java, o Oz é do tipo “escreve um vez, executa em qualquer sitio” é possui mecanismos locais e distribuídos de garbage colection. O Oz possui uma virtual machine, que é portável e pode ser executada em sistemas Unix ou Windows.

Interface Gráfica O sistema Mozart tem uma biblioteca orientada a objectos que disponibiliza

funcionalidades de interface gráfico baseadas em Tcl/Tk. Módulos de Expansão Nativos

O sistema Mozart foi projectado para que fosse facilmente expandido de forma a aumentar as suas funcionalidades através de DLLs.

Page 11: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

11

2. A Linguagem Oz O Oz é uma linguagem poderosa que se baseia num pequeno conjunto de

conceitos simples. Este capítulo tenta explicar o modelo de programação do Oz,e ainda compará-lo com outras linguagens semelhantes.

Apesar das raízes do Oz serem a programação lógica concorrente baseada em restrições, esta linguagem pretende ir muito mais longe. O objectivo é criar uma infra-estrutura sólida para qualquer tipo de computação, não apenas para a programação declarativa.

2.1 O Modelo de programação do Oz

Figura 1 – Threads e Armazenamento

O modelo do Oz é constituído por uma área de armazenamento abstracta

acedida por threads de fluxo de dados. As thread executam sequências de expressões e bloqueiam quando na falta de disponibilidade dos dados. A área de armazenamento não é a memória física. Só autoriza operações que sejam validas para as entidades envolvidas, isto é, não permite apontadores tipo linguagem C, nem casting. A área de armazenamento tem três componentes: a área de restrições, que contem as variáveis e os valores a elas atribuídos (instanciações); a área de procedimentos, que contem a definição dos procedimentos; e finalmente a área das células, que contém apontadores mutáveis (“Cells”). A área de armazenamento de restrições e a área de armazenamento de procedimentos são monotónicas ou monotonas, isto é, os dados nelas contidos não podem ser alterados ou removidos, apenas se podem adicionar. As threads, bloqueiam com a não disponibilidade de dados que necessitem na área armazenamento de restrições.

X=23 Z Y=Pessoa(idade:27)

S1 S1 SnThreads de Fluxo de Dados

Área de armazenamento abstacto

Bloqueiam quando não tem disponiveis os dados

Executa sequências de expressões

Não é memória fisica Contém variáveis e

atribuições Apenas admite operações

válidas para as entidades envolvidas

Page 12: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

12

<Expressão> ::= <Expressão1> <Expressão2>

| X = f(l1:Y1 ... ln:Yn)| X = <número>| X = <atomo>| X = <booleano>| {NewName X}| X = Y| local X1 ... Xn in S1 end| proc {X Y1 ... Yn} S1 end| {X Y1 ... Yn}| {NewCell Y X}| {Exchange X Y Z}| {Access X Y}| if B then S1 else S2 end| thread S1 end| try S1 catch X then S2 end| raise X end

Figura 2 – Oz Programming Model

As threads executam uma linguagem que consiste num forma reduzida do

Oz designada de OPM (Oz Programming Model). Na figura 1, encontra-se descrito o sintaxe desta linguagem. As sequências de expressões são reduzidas de forma sequencial na thread. Os valores (Registos, números, etc ...), são introduzidos explicitamente e podem ser igualados a variáveis. Todas as variáveis são lógicas e definidas num espaço explicito definido pela palavra local.

Os procedimentos são criados em tempo de execução através da palavra proc, e referenciados por uma variável. Na área de armazenamento são representados na forma ξ:z/E, em que ξ é o nome, o z um argumento formal (variável), e o E é o corpo do procedimento. A redução de um procedimento na forma “proc{x y} E”, processa-se escolhendo um nome ξ, indicando na área de armazenamento de restrições que “x = ξ”, escrevendo de seguida na área de armazenamento de procedimentos o novo procedimento (“ξ:z/E”). A chamada ao procedimento x (“ {x y} “), bloqueia enquanto não estiver na área de armazenamento de procedimentos a definição ξ:z/E para a variável x, que por sua vez está definida na área de armazenamento de restrições sob a forma de x=ξ. Neste caso desta chamada a redução é feita substituindo todas as ocorrências de z no corpo do procedimento E por y.

As variáveis de estados são criadas explicitamente através de NewCell, que cria uma célula que consiste num apontador alterável para área de armazenamento de restrições. As células podem ser alteradas através de Exchange e Access.

As condições são implementadas através da palavra chave case que bloqueia, enquanto a condição, na área de armazenamento de restrições, não for verdadeira ou falsa. A palavra chave if é reservada para aplicações baseadas em restrições.

As threads, são criadas de forma explicita através da palavra thread, e ficam com um identificador próprio.

O tratamento de excepções é feito através das palavras try que determina o espaço de abrangência e catch que indica a acção a tomar caso ocorra nesse espaço uma excepção.

As expressões do Oz completo são executadas traduzindo essas expressões noutras na forma reduzida ou OPM. O Oz completo disponibiliza outras estruturas, tais como objectos, classes, locks reentrantes, e ports. Que consistem no seguinte:

Page 13: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

13

OObbjjeeccttooss Um objecto é essencialmente um procedimento com um argumento

(“{Obj M}”) que referencia uma célula escondida pelo lexical scoping. Essa célula detém o estado do objecto. O argumento M indexa o método na tabela de métodos do objecto. O método é um procedimento que a partir de um determinado estado calcula um novo estado.

CCllaasssseess A classe é um registo uma tabela de métodos e os nomes dos

atributos. Os conflitos de herança múltipla são resolvidos na altura da criação da criação da classe, de modo a ser possível construir a tabela de métodos.

LLoocckkss RReeeennttrraanntteess Um lock reentrante consiste num procedimento com um argumento (“

{Lck P} “) usado para efectuar exclusão mútua (por exemplo podem ser usados para exclusão mútua nos corpos dos métodos, criado uma espécie de entrys de objectos protegidos do ADA95). A thread que acede ao lock pode reentrar nele. Isto permite a utilização de Nesting. O lock é libertado quando a thread no seu corpo terminar, ou se surgir uma excepção durante a execução, que faça abandonar corpo do lock.

PPoorrttss Os Ports canais assíncronos que suportam um modelo de

comunicação um para muitos. Estas estruturas encapsulam streams, que consistem em listas com a cauda não instanciada. A operação ( “{Send P M}” ) adiciona M ao fim do stream encapsulado por P. Envios sucessivos partindo da mesma thread, aparecem no stream, com a mesma ordem de envio.

Mais tarde irá ser detalhado como se implementa cada uma destas

estruturas.

Page 14: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

14

2.2 Comparação entre o Oz e o Prolog Pode dizer-se que o Oz é um sucessor do Prolog, pois esta linguagem

pode ser utilizada para resolver os mesmo tipo de problemas, que o Prolog e as linguagens lógicas baseadas em restrições têm sido usadas. Tal como o Prolog o Oz tem um subconjunto de instruções declarativas e um sistema arbitrário de restrições. O desempenho do Oz está muito próximo com os melhores sistemas de Prolog emulados. O Oz não possui um sintaxe reflectivo, isto é, dados e programas com o mesmo sintaxe, nem possui mecanismos de meta-programação como por exemplo, o Assert no prolog, nem um sintaxe definivel pelo utilizador. O motivo do êxito do Prolog é o nível elevado de abstracção do seu subconjunto declarativo, mas a tudo o que está fora desse domínio foi dada muito pouca atenção.

Programação lógica Concorrente Oz Restrições Não, excepto no AKL Solver com ask e tell Controlo Fine-grainded Thread explicitas de

fluxo de dados, pequisa encapsulada

High-order Restrita Procedimentos first-class e lexical scoping

Estados Objectos baseados em streams Objectos, células

Tabela 1 – Comparação entre as linguagens lógicas concorrentes e o Oz

2.3 Comparação entre o Oz e o Java O Mozart e o Java, de modo geral em aplicações centralizadas têm

desempenhos comparáveis, embora o Mozart tenha melhores mecanismos de fluxo de dados e multi-thread.

Java Mozart

Tempo de Execução 17.6 segundos 3.9 segundos

Linhas de código 108 28

Tabela 2: Estatísticas relativas às diferença de desempenho entre o Oz e o Java executando uma aplicação Produtor/Consumidor (jdk 1.2 e mozart 1.0.1 num solaris UltraSparc II)

Em aplicações distribuídas o desempenho do Java é muito menor que o do

Mozart. A principal razão é que para cada elemento que o Produtor produz, é enviada uma mensagem de rede, e a thread é sincronizada depois do retorno de uma chamada a RMI (Remote method evocation). O sistema Mozart agrupa automaticamente grupos de elementos que o Produtor produz, consequentemente reduzindo o numero de mensagens na rede. Para além disso, no Mozart apenas o consumidor necessita ser sincronizado. O java pode ainda ser optimizado para a lógica Produtor/Consumidor, mas isso iria fazer com que o programa fosse bastante maior e mais complexo, e tornaria o programa distribuído cada vez mais distinto da versão centralizada. Conclui-se que a programação distribuída eficiente é consideravelmente mais difícil de implementar em Java que em Mozart.

Page 15: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

15

Java Mozart

Tempo de Execução 1 hora 8.0 segundos

Linhas de código 220 32

Tabela 3 – Resultados da aplicação Produtor/Consumidor distribuida.

Page 16: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

16

3. O Oz Distribuído Cada vez mais os computadores são ligados em redes. A transmissão de

informação de um ponto para outro qualquer no mundo, tornou-se trivial. A internet, construída sobre a família de protocolos TCP/IP, tem vindo a duplicar o numero de servidores desde 1981. O trabalho colaborativo que inicialmente consistia em trocas de e-mail e newsgroups, usa agora tecnologias como workflow, multimédia, e ambientes verdadeiramente distribuídos. Fontes de informação heterogéneas e fisicamente separadas, são ligadas. Surgem os agentes, permitindo a delegação de tarefas na rede. Criam-se protocolos seguros que possibilitam o comercio electrónico.

Apesar de todo este desenvolvimento espantoso, a computação distribuída continua um grande desafio. Um sistema distribuído é um conjunto de processos autónomos, interligados por uma rede. Para especificar que os processos não correm necessariamente na mesma máquina, deu-se-lhes o nome de sites. Um sistema como este é muito diferente de um só processo a correr numa máquina. Estes sistemas são inerentemente concorrentes e não deterministicos. Não existe informação global nem tempo global. Os atrasos na comunicação entre os processos é imprevisível. Existe uma grande probabilidade de falhas localizadas. O sistema é partilhado, tendo os utilizadores, de estar protegidos dos outros utilizadores e dos seus agentes computacionais.

Uma aplicação distribuída deve ter bom desempenho, ser segura, permitir comunicação de forma simples com outras aplicações. Actualmente, o desenvolvimento de uma aplicação distribuída com estas características, requer conhecimentos muito para lá dos que são necessários para desenvolver uma aplicação numa máquina só. Por exemplo, uma aplicação Cliente-Servidor pode ser desenvolvida em Java-RMI. Uma determinada aplicação pode comunicar com outra através de um implementação de CORBA (por exemplo o Orbix). Nestes casos, ambas as ferramentas são insatisfatórias. A reorganização da aplicação para suportar uma estrutura distribuída implica rescrever toda a aplicação. O java não usa threads de fatias temporais, por isso, a reorganização em Java exige profundas alterações na aplicação. Para além disso, cada nova funcionalidade que é acrescentada, por exemplo adicionar tolerância a falhas, a complexidade da aplicação aumenta bastante. Para se dominar cada problema, é necessário estudar e compreender várias ferramentas complexas para além do ambiente base do Java, isto é, alguém que apenas saiba desenvolver aplicações centralizadas não está qualificado para desenvolver aplicações distribuídas.

Algumas soluções foram tentadas para integrar mecanismos de resolução de problemas de várias áreas, numa única plataforma. Por exemplo, a Ericsson Open Telecom Platform (OTP), baseada na linguagem Erlang, integra uma solução para estruturas distribuídas e tolerância a falhas. O Erlang é transparente quanto á utilização da rede a nível de processo, isto é, as mensagens entre processos são enviadas da mesma forma independentemente dos processos estarem ou não na mesma máquina. A OTP vai bastante mais longe que plataformas mais populares tais como o Java, e tem sido usado em produtos comerciais relacionados com telecomunicações (Software para centrais telefónicas PBX, routers ATM, etc...) .

O sucesso do Erlang sugere aumentar o alcance das ferramentas de desenvolvimento de aplicações a outras áreas da computação distribuída. Existem

Page 17: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

17

quatro áreas, nomeadamente estrutura de distribuição, computação aberta, tolerância a falhas e segurança. Se for incluída a funcionalidade da aplicação, existirão cinco principais preocupações:

Funcionalidade: O que é que a aplicação faz, ignorando tudo o que diz respeito á distribuição.

Estrutura de distribuição: Repartir a aplicação por vários sites. Computação Aberta: Aplicações independentes que comunicam entre si. Tolerância a falhas: A aplicação continuar a providenciar um serviço

mesmo que ocorram falhas parciais. Segurança: A capacidade da aplicação continuar a providenciar um

serviço mesmo que haja interferência intencional. Uma parte muito importante da tolerância a falhas e segurança é o controlo dos recursos.

Uma solução possível para integrar estas preocupações, é separar a

funcionalidade das outras (figura 2). Idealmente, o maior volume da aplicação deveria ser ocupado com o código relacionado com as suas funcionalidades, as outras preocupações deveriam ser, nada mais, do que pequenos módulos adicionais.

O primeiro passo para atingir isso, será separar a funcionalidade da estrutura de distribuição. O sistema deverá ser transparente relativamente ao uso da rede. Para isso as computação de aplicações centralizadas e distribuídas terão de ser semelhantes, ou seja, deverá ser possível programar a aplicação sem ter em conta a existência de uma rede de computadores (network-transparent system). Outro aspecto importante, é manter, por parte do programador, o controlo sobre a localização das computação e das comunicações na rede, decidindo onde são executadas e detendo o controlo sobre a mobilidade e replicação dos dados e do código (network-aware system).

Figura 2 – Simplificação da programação distribuída

Funcionalidade

Estrutura de distribuição

Computação Aberta

Tolerância a falhas

Controlo de recursos e segurança

Funcionalidade

Estrutura de distribuição

Computação Aberta

Tolerância a falhas

Controlo de recursos e segurança

Não afectam a funcionalidade

Page 18: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

18

3.1 Características do Oz Distribuído O Oz distribuído separa a funcionalidade da estrutura de distribuição da

aplicação. Esta linguagem consiste apenas na expansão do Oz centralizado. A conversão de aplicações programadas em Oz centralizado para o Oz distribuído, é uma tarefa muito simples. Existem três características que tornam o Oz uma linguagem muito poderosa para implementar sistemas distribuídos:

O Oz tem uma base formal solida que não prejudica a expressividade

nem a eficiência na implementação. O Oz baseia-se em high-order, state-aware, computação baseada em restrições concorrente. O Oz apresenta-se ao programador como uma linguagem concorrente orientada a objectos, com capacidades idênticas às linguagens modernas como o Java. Para além das principais técnicas da programação concorrente orientada a objectos, o Oz, disponibiliza alguns novos mecanismos que o Java, por exemplo, não possui.

O Oz é uma linguagem state-aware e orientada ao fluxo dos dados, que permitem ao programador, o controlo sobre as comunicações de rede de uma forma natural. State-aware quer dizer que a linguagem distingue entre dados que não referem estados (procedimentos ou valores), que podem ser copiados de forma segura entre sites (máquinas), e dados que referem estados (Objectos), que num dado instante apenas podem residir num site. A sincronização pelo fluxo dos dados permite distribuir cálculos, o que é bastante importante na tolerância de falhas, nomeadamente na construção de réplicas.

O Oz disponibiliza segurança a nível da linguagem, isto é, a referencia a qualquer entidade é passada e criada de forma explicita. Uma aplicação é incapaz de forjar ou aceder a referencias que não lhe tenham sido atribuídas explicitamente. A representação da entidades da linguagem é inacessível ao programador. O Oz usa uma área de armazenagem abstracta com lexical-scoping, e procedimentos first-class. Estas características são fundamentais para implementar uma linguagem com capacidade de ter uma política de segurança dentro dela própria.

A separação entre a funcionalidade e a estrutura de distribuição de uma

aplicação impõe grandes restrições a uma linguagem de programação. Seria praticamente impossível em C++ por causa da semântica ser informal e bastante complexa, e também porque o programador tem acesso total a todas as representações de entidades (ex: usando endereços de memória). Em Oz é possível porque implementa as características atrás descritas. Não foi necessário alterar a semântica da linguagem além de pequenas modificações para permitir a distribuição (por exemplo, os ports foram alterados para o modelo assíncrono de comunicação entre sites). Continua-se a trabalhar para separar a funcionalidade das outras três preocupações restantes. Actualmente, o Oz distribuído, disponibiliza a semântica de linguagem para o Oz e para complementos de quatro formas:

Tem construções para exprimir a estrutura da distribuição de forma

independente da funcionalidade. Tem primitivas de computação aberta, baseadas no conceito de tickets.

Isto permite aplicações de execução independente se ligarem e trocarem dados e código.

Tem primitivas para detecção e tratamento de falhas, baseadas nos conceitos de handlers e watchers, que permitem um primeiro nível de tolerância a

Page 19: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

19

falhas. Suporta uma política de segurança dentro da própria linguagem e possui

primitivas para o controlo de recursos baseadas no conceito de site virtual. No Oz distribuído, o desenvolvimento de uma aplicação esta separado em

duas partes independentes. Em primeiro lugar, apenas a arquitectura lógica da tarefa é considerada. A aplicação é desenvolvida em Oz, sem que se tenha em atenção a distribuição da sua computação por diversos sites. Assim pode-se verificar a segurança e a durabilidade da aplicação executando em apenas um site. Depois, a aplicação é tornada eficiente pela especificação dos comportamentos de rede nas suas entidades. Em particular, a mobilidade das entidades com estados (objectos), deve ser especificada. Por exemplo, alguns objectos podem ser colocados em certos sites, enquanto outros podem ter um caracter móvel.

O Oz distribuído expande o Oz com quatro algoritmos complexos. Três deles foram desenvolvidos para entidades especificas da linguagem, nomeadamente variáveis lógicas, objectos-registos, e objectos-estados de objectos. As variáveis lógicas são instanciadas através do variable binding protocol. Os objectos-registos são replicados pelos sites através do lazy replication protocol. Os objectos-estados são movidos entre sites através do mobile state protocol. O quarto protocolo é um algoritmo de recolha de lixo (garbage collection) distribuído. Este mecanismo é uma parte da gestão de entidades distribuídas, serve portanto de suporte aos outros protocolos.

3.2 O grafo de distribuição

Figura 3 – As entidades da linguagem representadas com nós do grafo

O modelo pode ser entendido de uma forma simples mas precisa usando o

conceito de grafo de distribuição. O grafo de distribuição pode ser obtido em dois passos a partir de um estado arbitrário do sistema. O primeiro passo é independente da distribuição. Modela-se o estado de execução utilizando um grafo chamado grafo de linguagem, onde cada entidade da linguagem com a excepção dos objectos que correspondam a nós (Figura 3).

No segundo passo, introduz-se a noção de site. Assume-se um conjunto finito de sites e coloca-se cada nó no seu site respectivo (Figura 4). Se um nó for referenciado por pelo menos outro nó noutro site, organiza-se um conjunto, por exemplo para N2, cria-se o conjunto {P1,P2,P3,M}. A este conjunto dá-se o nome de estrutura de acesso do nó. Uma estrutura de acesso e constituída por um nó proxy Pi para cada site que referencia o nó e um nó de gestão M para toda a estrutura. O grafo que resulta, contendo tanto os nós locais como as estruturas de acesso necessárias, é chamado de grafo de distribuição. Os exemplos do funcionamento dos protocolos serão apresentados segundo esta notação.

A cada estrutura de acesso é dada um endereço global único no sistema global. Este endereço global codifica diversa informação incluindo o site de gestão.

Variável não instanciada

Registo com campos

Thread com referências

Célula Procedimento com referências externas

Page 20: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

20

Os nós proxy são unicamente identificados pelo par (endereço global, site). Em cada site, os endereços globais indexam uma tabela que referencia o proxies. Cada site tem pelo menos um proxy. As mensagens entre nós são enviadas nas estruturas de acesso. No corpo das mensagens existem referencias a nós no site destino. Esses nós são identificados pelo endereços globais das suas estruturas de acesso. Quando as mensagens chegam, os nós são pesquisados na tabela do site.

Os procedimentos e os outros valores (registos, números, etc) são copiados de forma eager, isto é, não resultam em estruturas de acesso. Um procedimento é enviado uma vez só para um site e apenas existe uma cópia nesse site. O procedimento consiste numa clausula e um bloco de código, sendo-lhes atribuído um endereço global. As mensagem apenas contêm o endereço global. Depois de serem recebidas imediatamente é feito o pedido dos blocos de código e clausulas que faltam.

Figura 4 – Grafo da linguagem e Grafo da distribuição

3.3 A Arquitectura de Implementação A figura 5, mostra a arquitectura de implementação do Oz distribuído. De

seguida irão ser detalhadas todas as camadas desta implementação.

Figura 5 – A arquitectura de implementação

N1 N2 N3

Site 1 Site 2 Site 3 Site 1 Site 2 Site 3

N1 P1 P2 P3 N3

M

Estrutura de acesso para N2

Camada de Rede

Camada de gestão de memória

Camada de protocolo

Motor estendido

Rede

Page 21: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

21

3.3.1 O Motor Estendido

Figura 6 – Instanciação de uma variável

Este motor expande o Oz centralizado sob a forma de um interface para a

camada de protocolo. O motor reconhece certos eventos e passa-os á camada de protocolo. Um exemplo típico é a instanciação de variáveis (figura 6) através dos seus proxies. A camada de protocolo pode também desencadear operações do motor, por exemplo à chegada do conteúdo de uma célula vindo de outro site, ou a passagem de procedimentos da rede para o motor.

3.3.2 A Camada de Protocolo

Os nós no grafo são modelados como objectos concorrentes. Estes objectos trocam mensagens através do grafo. Os algoritmos que controlam essas trocas de mensagens são chamados de protocolos de distribuição da implementação. Estes protocolos definem o comportamento do sistema. Cada estrutura de acesso tem o seu próprio protocolo. Existem três tipos:

Estrutura de acesso de variáveis. Representa a variável globalizada.

Os proxies representam a variável em cada site. Os proxies tornam-se referencias quando a estrutura de acesso da variável é fundida com outra estrutura de acesso. O papel do nó Gestor (normalmente representado por M no grafo), é esperar o primeiro pedido de fusão e pedir a todos os proxies que modifiquem os gestores. O conjunto dos proxies formam um grupo de multicast. Este protocolo é usado para adicionar informação á área de armazenamento de restrições.

Estrutura de acesso de procedimentos. Representa os procedimentos

globalizados no sistema. Sendo os procedimentos informação sem dependência de estados podem ser replicados para outros sites quando necessários. Este protocolo assegura que cada clausula e bloco de dados do procedimento seja único em cada site. O nó gestor gere os pedidos para as cópias. Este nó esta associado a um nó local que representa a estrutura ou o procedimento.

Estrutura de acesso de mobilidade. Representa um célula ou um port.

O estado é localizado, isto é, encontra-se sempre num determinado site, e pode ser

P

M P

TP

1

4

3

3

2 3

Site1 Site2 Site3

1.A thread inicia o processo de instanciação e bloqueia. 2.O proxy faz um pedido de instanciação 3.O nó gestor (M) concede a intanciação e efectua um multicast para todos os proxies 4.O proxy informa a thread permitindo que ela continue a execução

Page 22: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

22

movido de um proxy para outro. O proxy que necessitar de ler um estado pede ao gestor, este decide quem irá aceder a seguir e envia uma mensagem ao proxy respectivo.

3.3.3 A Camada de gestão de memória Esta camada converte as estruturas de distribuição do grafo em

sequências de bytes para ser enviadas através da rede. As mensagens trocadas entre nós do grafo de distribuição podem

referenciar subgrafos. Quando os subgrafos são transportados de um site para outro, os seu nós tornam-se membros das estruturas de acesso que têm nós tanto no site emissor como no receptor.

As estruturas de acesso são identificadas por um endereço único na rede chamado endereço de rede (network address). Este endereço é recuperado quando a estrutura de acesso que identifica deixa de ser acessível localmente de nenhum dos seus sites. Esta situação é detectada por mecanismos locais de garbage collection com um mecanismo de crédito. Cada endereço de rede é criado com um numero grande fixo de créditos. O nó gestor inicialmente detém os créditos e atribui-os a qualquer site (incluindo mensagens em transito) que têm o endereço de rede. Quando um site deixa de referenciar localmente a estrutura de acesso os créditos são devolvidos ao gestor. Quando o gestor recupera todos os créditos e deixa de ser referenciado localmente o endereço de rede é recuperado (libertado).

3.3.4 Camada de Rede A camada de rede é implementa uma cache de conexões TCP, garantindo

confiança e eficiência na transferência de informação entre sites numa qualquer rede local ou wan. Assume-se que não existem barreiras temporais nem ordem relativa das mensagens (FIFO) para todos os protocolos de distribuição excepto para estruturas de acesso fixas. Para enviar mensagens de tamanhos variáveis provenientes de threads concorrentes baseadas em fatias temporais, a implementação gere os seus próprios buffers e usa chamadas ao sistema não bloqueantes do tipo send e receive.

3.4 Computação Aberta Computação distribuída aberta consiste na execução de aplicações

independentes que interagem umas com as outras. Normalmente, isto implica que o sistema tenha que ter a mesma base, isto é, a utilização dos mesmos frameworks ou linguagens utilizadas pelas aplicações para interagir. Os exemplos mais típicos são formatos comuns na transferencia de informação, protocolos comuns para comercio electrónico, etc... Para isto ser possível é necessário que as aplicações consigam estabelecer conexões com computações que foram iniciadas de forma independente noutra localização na rede. É também necessário que a aplicação possa iniciar novas computações distribuídas.

3.4.1 Conexões e tickets O Oz distribuído usa um mecanismo baseado em tickets para estabelecer

Page 23: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

23

conexões entre sites independentes. O sistema, na fase final do seu desenvolvimento deve garantir segurança, tanto a nível das conexões como a nível dos tickets. O site servidor cria o ticket através do qual os sites clientes podem estabelecer a conexão. O ticket consiste numa string de caracteres que pode ser guardada e transportada de qualquer forma que suporte texto, por exemplo por correio electrónico, modem, etc...

O ticket identifica o site servidor e a entidade da linguagem á qual será feita uma referencia remota. Podem ser feitas conexões independentes a diferentes entidades num mesmo site. O estabelecimento de uma conexão gera uma conexão a nível da camada de rede (por exemplo TCP), e gera também no espaço computacional do Oz, uma referencia no site cliente a uma entidade da linguagem no lado do site servidor. Isto pode ser implementado de várias formas, passando procedimentos sem argumentos, unificando duas variáveis, ou passando um port que será utilizado para posteriormente para enviar valores. Depois de uma conexão inicial ter sido criada, todas as outras que a aplicação necessitar podem ser geradas a partir das abstracções disponibilizadas pela linguagem. Por exemplo, é possível definir uma classe C num site, passar C para outro site, definir a classe D derivada de C nesse site, e finalmente passar D para o site inicial.

Existem dois tipos de tickets: one-shot tickets para conexões um para um, e many-shot tickets para conexões muitos para um.

3.4.2 Servidores de computação O Oz possibilita a criação de servidores de computação. Servidores de

computação são aplicações que usam a rede para aumentar a velocidade da computação. Estes servidores podem ser acedidos como objectos, e são implementados utilizando um mecanismo de tickets. Depois de um servidor ser inicializado podem lhe ser atribuídas tarefas. Estas tarefas são passadas ao servidor sob a forma de procedimentos.

A inicialização dos servidores é feita em dois passos : primeiro é criado um site independente pelo potencial cliente, de seguida é estabelecida a conexão entre o potencial cliente e o servidor. Isto é feito passando um ticket one-shot do potencial cliente para o servidor.

3.5 Detecção e Tratamento de Falhas Uma aplicação é tolerante a falhas quando continua a cumprir as

finalidades para que foi desenvolvida apesar de acontecerem falhas acidentais em componentes dessa aplicação. Existem muito pouco trabalho no sentido de integrar abstracções de tolerância a falhas numa linguagem de programação de modo a que se possa construir um sistema tolerante a falhas. A maior parte do trabalho foi concentrado em áreas como a persistência e transacções, adicionando modelos destes conceitos ás linguagens de programação. No entanto é possível suportar tolerância a falhas de um modo mais simples.

O Oz foi expandido de modo a suportar falhas parciais em sites e detectar e tratar entidades individuais da linguagem. Existem meios para o programador decidir qual a acção a executar no caso de uma falha. Esses meios são implementados através de “handlers” e “watchers” sobre as entidades da linguagem.

Page 24: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

24

3.5.1 O Principio da detenção A tolerância a falhas é algo que por vezes está para lá dos limites da

abstracção. Vejamos o seguinte exemplo : A maior parte dos sistemas não lidam com o tempo de uma forma correcta. O que fazem é deixar que uma camada mais baixa tomar uma decisão irreversível. Relativamente a um time-out não deixam o sistema prosseguir. Se houver um time-out numa camada baixa, por exemplo na camada de transporte da rede, irá atravessar todas as camadas até aparecer no nível mais alto ao utilizador. Não existe a possibilidade de comunicar com camada onde ser verificou o time-out, o que limita bastante a flexibilidade do sistema. Deveria ser possível construir um sistema, que permitisse ao utilizador aguardar, em vez de abortar. Em muitos casos não é oferecida esta possibilidade.

O principio da detenção diz que um comportamento anormal de uma qualquer camada deverá ser contido numa camada mais alta. Isto é, uma camada onde se verifique uma situação anormal não deve tomar decisões irreversíveis, estas devem ser decididas pelo programador, ele é que deverá decidir que camada é indicada para tratar o problema. Por exemplo, na questão do time-out, o programador deveria ter a oportunidade de decidir, se queria ou não um time-out numa camada mais baixa, e deveria ser ele a decidir qual a acção a tomar caso existisse time-out e fosse ultrapassado.

Page 25: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

25

3.5.2 Falhas no grafo da distribuição

Figura 7 – Detecção remota de falhas no Oz distribuído

A causa de uma falha no Oz distribuído está relacionada com falhas num

ou mais sites, ou com a falha de uma área da rede. Estas situações revelam-se nas estruturas de acesso do grafo da distribuição. Uma estrutura de acesso é afectada se tem pelo menos um nó que se situe num site com falha ou se tem pelo menos uma ligação estraves de uma área da rede com falha. Muitas vezes estruturas de acesso continuam a funcionar apesar de serem afectadas. Por exemplo, um objecto pode continuar a ser usado apesar de ter uma referencia remota a um site em falha. Uma estrutura de acesso falha quando os sites não conseguem operar nela. Isto pode suceder quando os componentes principais da estrutura de acesso falham, por exemplo, quando falha o nó de gestão. Tendo em conta que as redes podem ter falhas temporárias, também as falhas das estruturas de acesso podem ter um caracter temporário ou permanente.

3.5.3 Handlers e watchers O Oz distribuído detecta falhas ao nível das estruturas de acesso,

P

M

P

P

M

P

P

P M

P

P

Site A Site B Site C

Site D Site E Site F

Site em falha

Site normal

Nó afectado

Nó normal

Page 26: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

26

revelando-se na linguagem sob a forma de entidades da linguagem (objectos, variáveis e ports). Quando há um problema a efectuar uma operação efectuada sobre uma entidade, essa operação por omissão, bloqueia indeterminadamente. Qualquer outro comportamento deve ser explicitamente especificado pelo programador. Para isso devem ser instalados watcher e handlers na entidade. Um handler é invocado se for detectado um erro quando é tentada a execução de uma operação sobre uma entidade (detecção lazy). Um watcher é invocada quando um erro é detectado para uma entidade, mesmo que nenhuma operação seja tentada sobre essa entidade (detecção eager).

A semântica dos handlers e dos watchers é bastante simples. Se a tentativa de execução de uma operação falha, se existir um handler com uma condição de trigger que seja verificada, é efectuada uma chamada a esse handler. O funcionamento é idêntico para os watchers, se o sistema verificar uma falha de uma determinada entidade, todos os watchers são verificados e aquelas que tiverem condições de trigger que sejam verdadeiras, são executados em threads. Os handlers e os watchers tem dois argumentos: o primeiro diz respeito à entidade que falhou e o segundo ao tipo de erro.

Os handlers podem ser instalados em entidades por site e por thread. Os handlers baseados em threads sobrepõem-se aos baseados em sites, isto é, se forem ambos aplicados os sistema apenas responde ao handler baseado em threads. Os watchers podem ser instalados por site ou por entidade.

São passados três argumentos para utilizar os handlers e os watchers: a entidade, informação de controlo, e o próprio handler ou watcher. A informação de controlo consiste no tipo de erro que o handler ou o watcher é suposto detectar. No caso dos handlers a informação de controlo também indica se o handler é baseado em site ou em threads, e se depois do handler ser executado se a operação deve ser tentada de novo.

3.6 Controlo de Recursos e Segurança Uma aplicação é considerada segura se continua a cumprir os objectivos

para que foi desenvolvida, apesar de falhas intencionais nos seus componentes. O controlo de recursos é bastante semelhante à tolerância a falhas. A principal diferença diz respeito ao tipo de falhas que são detectadas e tratadas, isto é, no caso do controlo de recursos, as falhas são malignas e intencionais. As técnicas de detenção e replicação devem também ser usadas para o controlo de recursos e segurança. O controlo de recursos é uma questão fundamental para a segurança porque muitas vezes são utilizadas técnicas de sobrecarga dos recursos para provocar falhas intencionais (“denial of service”).

Os recursos encontram-se divididos entre recursos do site e recursos da rede. Os recursos do site incluem os recursos computacionais (memória e processador) e outros recursos como sistemas de ficheiros e periféricos. Os mesmos recursos do site aparecem por vezes representados de formas distintas nas várias camadas do site, por exemplo, o programa em Oz, o emulador, e o sistema operativo. A questões de segurança também são distintas em cada camada:

Segurança da linguagem é uma característica da linguagem. É

implementada através de meios de restrição do acesso aos dados. No Oz o acesso sem restrições à memória não é permitido, apenas é permitido o acesso a dados aos quais foram atribuídas referencias explicitas.

Page 27: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

27

Segurança da implementação é uma característica da linguagem no processo de implementação. Protege as computações e os dados de tentativas de interferência com o programas compilados (Oz bytecode). As duas aplicações mais importantes deste nível de segurança são: na integridade do site e o no controlo de recursos. Os problemas de segurança surgem quando o código é passado directa ou indirectamente de um site para outro. Por exemplo, enviado um procedimento para um servidor o executar (directa), ou evocando um método de um objecto móvel (indirecta). A solução para impedir que o código enviado seja malicioso é a implementação de técnicas de verificação do código bytecode e o uso de autenticação a nível da compilação.

Segurança do sistema operativo e da rede não é uma propriedade da linguagem, mas do sistema operativo e da rede. Protegem as computações e os dados de serem corrompidos por tentativas de interferência com o emulador de Oz e com o run-time system de um processo do sistema operativo, e também tentativas de interferência com sistema operativo e com a rede. A segurança da rede pode ser conseguida através da utilização de TCP/IP seguro.

3.6.1 Sites Virtuais Um site pode criar vários sites na mesma máquina, isto é virtuais, que se

comportam exactamente da mesma forma que os sites normais, com a excepção que o site master monitoriza e controla os slaves. Se um slave falha o master é notificado. O master controla os recursos dos slaves, incluindo os recursos computacionais e outros recursos como o acesso a sistemas de ficheiros. Por exemplo, a um site slave pode ser dada a possibilidade de criar e apagar ficheiros num determinado directório e em nenhum sitio mais.

Para além das limitações impostas pelo master, um site slave comporta-se exactamente da mesma forma que um site normal. Pode partilhar entidades com o master ou com um site noutra localização da rede. Como os sites virtuais residem na mesma máquina as comunicações são mais eficientes porque não existe camada de rede. Para tirar partido da protecção e controlo de recursos do sistema operativo, um slave normalmente reside num processo diferente do master.

Os sites virtuais podem ser usados para tirar partido dos recursos em máquinas de memória partilhada com multiprocessadores. Basta alocar um site a cada processador. Não tendo camada de rede, o paralelismo numa máquina deste tipo, é bastante mais eficiente que em rede.

Page 28: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

28

4. Tutorial de Oz Neste tutorial irá ser introduzida a linguagem Oz, assim como o seu

ambiente de trabalho. O conteúdo irá incidir sobre as características fundamentais da linguagem Oz, nomeadamente sobre as características funcionais, concorrência, objectos e classes, objectos e concorrência e programação lógica. Num outro capitulo irá ser abordado o Oz distribuído.

4.1 O Ambiente de Desenvolvimento O ambiente de desenvolvimento do Oz é chamado de OPI (Oz

Programming Interface). Este ambiente de desenvolvimento usa o Emacs, expandindo os seus menus e coloração sintáctica, de forma a facilitarem a programação em Oz.

4.1.1 Arrancar o ambiente de desenvolvimento Sob ambientes Unix, o OPI pode ser arrancado através do commando oz

<nome do ficheiro.oz> na shell. Sob o Windows confesso que não experimentei, mas o processo é simples basta instalar o Emacs para Windows, correr os ficheiros de instalação do Oz, que estão disponíveis no site oficial, e será acrescentado um grupo de programas ao menu do Windows. Eu usei o Xemacs e o aspecto do OPI é o seguinte:

Figura 8 – O aspecto do OPI, usando o Xemacs

Page 29: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

29

A janela inicial está separada em dois buffers. O buffer de cima com o nome de oz, é onde é escrito o programa que pode ser executado interactivamente. O buffer de baixo contém as mensagens do compilador.

Para trabalhar com o Emacs é aconselhável a leitura de um tutorial desta ferramenta.

4.1.2 O primeiro programa Comecemos com o o tradicional “Olá mundo”, para isso basta escrever no

buffer de cima o seguinte código:

{Show ‘Olá Mundo’} Isto é nada mais, que a invocação de um procedimento em Oz, isto

é, a chaveta significa que se vai chamar o procedimento Show com o argumento ‘Olá mundo’. Para se poder executar este primeiro programa, basta ir ao menu Oz no Emacs e escolher feed line para que o compilador compile a linha.

Figura 9 – Compilação do “Olá mundo” No buffer do compilador aparece uma mensagem que indica que a linha

compilada foi aceite pelo compilador. Embora possa parecer estranho não termos nenhum resultado aparente, mas o que se passa efectivamente é que o resultado encontra-se noutro buffer chamado Oz Emulator. Se escolhermos a opção do menu Oz Show Hide e de seguida Emulator, veremos o resultado.

Page 30: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

30

Figura 10 – O buffer “Oz Emulator” contendo o resultado da execução.

4.1.3 Principais características do OPI

Edição de Código O OPI contem um modo de edição Oz que é adicionado ao Emacs, e

consiste em indentação automática e coloração do sintaxe do Oz. Teclas de Atalho

Todas as acções de interacção com o Oz podem ser atribuídas a teclas de atalho, por exemplo:

Ctr

+ . Ctr

+ l Compila a linha corrente

Ctr + .

Ctr + r

Compila a região seleccionada

Ctr + .

Ctr + b

Compila o buffer

M + Ctr + x

Compila o parágrafo (limitado por linhas vazias)

Ctr + .

Ctr + p

Compila o parágrafo

Ctr + .

c Mostra o buffer do compilador

Ctr + .

e Mostra o Emulator Buffer

Tabela 4 – Teclas de atalho do OPI

Erros do Compilador O OPI tem mecanismos bastante simples para procurar e indicar os erros

de compilação. A melhor maneira de perceber como funciona é através de um exemplo. Se introduzirmos o seguinte código com erros:

Local A B in

A = 3Proc {B}

{Show A + “Teste” }end{B 7}

end

Page 31: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

31

Ao compilar serão reportados dois erros como se pode verificar.

Figura 11 – Erros na compilação

Se for primido Ctr + x e de seguida `, o cursor no buffer do programa irá deslocar-se para a posição onde foi detectado o primeiro erro.

Figura 12 – Localização do primeiro erro no buffer do programa O erro diz-nos que obviamente um inteiro não pode ser adicionado a um

Page 32: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

átomo. Se for primido de novo Ctr + x e depois `, o cursor vai posicionar-se no próximo erro do buffer do programa.

Figura 13 – Segundo erro encontrado no programa Este erro sucedeu porque o procedimento B não leva argumentos.

O Browser O Oz tem bastantes ferramentas gráficas de apoio ao desenvolvimento. O

browser é uma ferramenta que serve para obter uma visualização dos resultados da execução de um programa através de um interface gráfico.

Se compilarmos a seguinte linha: {Browse ‘Olá mundo’}

Iremos obter o seguinte:

Figura 13 – O browser Compilemos o seguinte código:

O browse

encontram instancvalor das variávei

declare W H in{Browse foo(comprimento:W altura:H area:thread W*H end)}

32

r irá mostrar os nomes das vaiáveis porque estas não se iadas e no caso da área irá mostrar _ porque não conhecendo o

s não pode efectuar o calculo.

Page 33: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

33

Figura 14 – O browser mostrando variáveis não instanciadas

Se igualarmos o W a 3, o browser, será automaticamente refrescado e colocará o valor 3 em vez de W.

Figura 15 – O browser mostrando uma variável não instanciada e outra instanciada. Finalmente se instanciarmos H com o valor 5, iremos obter o resultado da

área porque a variáveis que esse calculo depende estão todas instanciadas.

Figura 16 – O browser mostrando variável instanciada. O browser permite-nos ver a evolução da instanciação de termos ou

variáveis ao longo da execução de computações concorrentes (threads).

Page 34: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

34

4.2 Introdução á programação em Oz A melhor maneira de começar a programar em Oz é começar pela

programação sequencial. Segundo esta abordagem, uma computação pode ser entendida como um conjunto de instruções executadas sequencialmente. A este conjunto de instruções dá-se o nome de thread. A thread pode aceder a uma área de armazenamento para manipular informação, isto é, para ler, adicionar ou actualizar dados. O acesso à informação é feito a partir de variáveis visíveis à thread, quer directa ou indirectamente. As variáveis em Oz são variáveis lógicas, isto é só podem ser instanciadas um vez. Este tipo de variáveis é bastante usada em linguagens lógicas com por exemplo no Prolog. O uso de variáveis que não possam ser alteradas, não implica que não possam representar estados, pois podem ser atribuídas a células. As células podem ser modificadas.

A expressão:

local X Y Z in S end Define as variáveis lógicas X,Y e Z dentro do espaço léxico S. Ou seja S

corresponde a um conjunto de instruções onde essas variáveis podem ser acedidas. As variáveis começam por uma letra maiúscula e seguidas de caracteres alfanuméricos. As variáveis também podem ter a forma de uma string limitada por apóstrofos. Imediatamente depois de serem declaradas as variáveis não têm nenhum valor associado, dizem-se não instanciadas. Qualquer variável em Oz deve ser definida antes de ser utilizada excepto, como veremos mais tarde em algumas situações no pattern matching.

Outra forma de definir variáveis é:

declare X Y Z in S A diferença está relacionada com a área de acção das variáveis. Em vez

de estarem definidas num espaço limitado pela palavra chave end, neste caso através do declare, as variáveis irão ter um alcance global, a não ser que existam blocos que entretanto definam variáveis como o mesmo nome.

Page 35: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

35

4.3 Os tipos básico de dados

Figura 17 – Os tipos de dados do Oz

A figura 17, mostra a hierarquia que existe entre os tipos de dados no Oz.

Qualquer variável pode instanciar valores de qualquer dos tipos representados. A maior parte dos tipos são comuns noutras linguagens de programação com a excepto os Chunk, Cell, Space, FDInt e Name. Ao longo deste tutorial irão ser explicados na devida altura. O tipo da variável é determinado quando é instanciada, isto é, dependendo do valor que se lhe atribuir, assim será o seu tipo.

4.3.1 Instanciação de variáveis A forma mais comum de se instanciar uma variável é através do operador

de igualdade =. Se tivermos uma variável X e a quisermos instanciar com o valor 1, poderia ser feito da seguinte maneira:

local X in

X=1end

Se a variável X já estivesse instanciada com o valor 1, esta operação seria

interpretada com um teste à variável X. Se X fosse instanciada com um valor diferente de 1, seria levantada a excepção correspondente.

4.3.2 Números O Oz suporta números inteiros e de virgula flutuante. Os inteiros têm um

precisão infinita, isto é, podem tem um qualquer tamanho. A representação dos valores inteiros pode ser feita utilizando várias notações, nomeadamente a notação binária, octal, decimal e hexadecimal. Para representar um octal, número deve ser precedido de um O, para representar um hexadecimal o número deve ser precedido de 0x. Os caracteres são inteiros entre 0 e 255 e podem ter uma

Valor

Number

Register

Pro edure

Cell

Chunk

Space

Thread

ByteString

BitString

Int

Float

FDInt Char

Tuple Literal

Atom

Name

Bool

Unit

Array

Dictionary

BitArray

Class

Object

Lo k

Port

Page 36: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

36

representação sintáctica, por exemplo o caracter t pode ser representado por &t, ou nova linha pode ser representado por &\n.

Os números de virgula flutuante distinguem-se dos inteiros por terem um ponto decimal. O sinal menos é representado por ~. Vejamos alguns exemplos:

~3.141 4.5E3 ~12.0e~2 No Oz não existe conversão automática de tipos, isto é, 5.0 = 0 irá levantar

uma excepção. Obviamente existem métodos de conversão de tipos para permitir este tipo de comparações. As operações sobre números encontram-se no módulo Number.

4.3.3 Literais Existem dois tipos de literais: os átomos e os nomes. O átomo é um

entidade simbólica representada por um conjunto de caracteres, em que o primeiro caracter é minúsculo, ou então, quaisquer caracteres limitados por apóstrofos. Por exemplo:

a foo ‘=’ ‘:=’ ‘Oz 3.0’ ‘Ola Mundo’ Os átomos possuem uma ordenação baseada num ordem lexicográfica. Outro tipo de literais são os nomes. Os nomes consistem em sequências

únicas de caracteres. A criação de um nome é feita usando o procedimento {NewName X}, onde X ficará com o nome gerado. Os nomes gerados não podem ser forjados ou visualizados e são normalmente usados para questões relacionadas com segurança. Um subtipo particular dos nomes são os valores booleanos, que consistem em dois nomes protegidos, isto é, não podem ser redefinidos e são representados pelas palavras reservadas true e false. O tipo unit é também um nome, que é usado para sincronização em programação concorrente. O código seguintes mostra a definição destes tipos de dados.

local X Y B in

X = foo{NewName Y}B = true{Browse [X Y B]}

end

4.3.4 Registos e Tuplos Os registos são entidades estruturadas compostas. São constituídos por

um identificador e um número fixo de componentes ou argumentos. Também existem registos com um numero variável de componentes que são chamados de registo abertos. Vejamos o exemplo da definição de um registo:

tree(chave:K valor:V esquerda:LT direita:RT)

Neste caso o registo tem quatro argumentos e um identificador tree. Cada argumento é constituído pelo par propriedade:campo. No exemplo as propriedades são: chave, valor, esquerda e direita. Os campos são K,V,LT e RT. Se omitirmos as

Page 37: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

37

propriedades teremos uma estrutura que normalmente é designada por tuplo.

tree(K V LT RT)

É exactamente o mesmo que ter o seguinte registo:

tree(1:K 2:V 3:LT 4:RT)

Vejamos o seguinte exemplo:

declare T K V LT RT W inT = tree(chave:K valor:V esquerda:LT direita:RT)K = pedroV = 13LT = nilRT = nilW = tree(K V LT RT){Browse [T W]}

O resultado será :

[tree(chave:pedro valor:13 esquerda:nil direita:nil) tree(pedro13 nil nil)]

Grande parte das operações que podem ser executadas sobre os registos

encontram-se no modulo Record. Vejamos agora algumas operações básicas que podem ser feitas sobre estas estruturas de dados.

Para seleccionar um campo de uma determinada propriedade de um

registo usa-se o operador infixo ponto (“.”). Por exemplo:

{Browse T.chave} % mostra pedro no ecrã{Browse W.1} % também mostra pedro no ecrã

O arity de um registo é a lista das suas propriedades ordenadas lexicograficamente. O procedimento {Arity R X} obtém o arity do registo R e coloca-o na variável X. Por exemplo:

local X in {Arity T X} {Browse X} endlocal X in {Arity W X} {Browse X} end

O resultado será :

[chave valor esquerda direita] % no primeiro caso[1 2 3 4] % no caso do tuplo

Outra operação que pode ser bastante útil é a selecção condicional de um campo de um registo. O procedimento CondSelect recebe um registo R, a propriedade F, um valor de campo por omissão D e o resultado é colocado num ultimo argumento X. Funciona da seguinte maneira: Se a propriedade F existe em R então X fica com o valor de R.F, senão X fica com o valor de D.

local X in {CondSelect W chave omissao X} {Browse X} endlocal X in {CondSelect T chave omissao X} {Browse X} end

Na primeira expressão, como não existe a propriedade chave, porque W é um tuplo, o resultado será “omissao”. Na segunda expressão, o registo T possui uma propriedade chave, então X fica com o valor da chave que é “pedro”.

Page 38: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

38

Um operador bem comum na utilização dos tuplos é o #. Este operador define um tuplo com dois ou mais elementos. Por exemplo a expressão 1#2#3 define o tuplo ‘#’(1 2 3). Se desejarmos criar tuplos com um ou sem elementos, devemos defini-lo da forma habitual, ou seja: ‘#’ (X) para um elemento ou ‘#’() para um tuplo sem elementos.

A operação {AdjoinAt R1 F X R2} instancia R2 com o registo resultante da

adição a R1 da propriedade F e do campo X. Se a propriedade F já existir em R1 em R2 aparecerá com o valor de X.

A operação {AdjoinList R LP S} recebe um registo R, uma lista de pares

propriedade campo, e retorna um novo registo S tal que: o identificador de R é o mesmo de S; Todas as propriedades que não existam em R são adicionados a partir da lista LP, caso existam, o valor que irá passar para S será o que está definido no par propriedade campo na lista LP. Vejamos um exemplo:

local S in

{AdjoinList tree(a:1 b:2) [a#3 c#4] S}{Show S}

end% O resultado seria S=tree(a:3 b:2 c:4)

4.3.6 Listas As listas não pertencem a um tipo de dados no Oz. São uma estrutura

conceptual. Podem ser representadas de várias maneiras: o átomo nil representa uma lista vazia; por elementos separados pelo operador “|” em que o elemento da esquerda é a cabeça da lista e o da direita a cauda; uma lista fechada pode ser representada entre parênteses recto com os valores separados por espaços; ou pela notação de registos. Vejamos os exemplos:

1|2|3|nil % lista com os valores 1 2 e 3

[1 2 3] % lista fechada com os valores 1 2 e 3

1|2|X % lista com os dois primeiros elementos e Xinstancia a cauda

‘|’(1 ‘|’(2 X))

Existe outra notação para um tipo especial de listas que são as cadeias de caracteres ou strings. As strings são representadas por um conjunto de caracteres limitados por aspas. Por exemplo a string:

“oz 3.0”

É convertida na lista:

[79 90 32 51 46 48] ou [&o &z & &3 &. &0]

Page 39: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

39

4.3.7 Strings Virtuais Uma string virtual é um tuplo especial que representa concatenação virtual,

isto é a concatenação só é efectuada quando necessário. As strings virtuais podem ser usadas em ficheiros, sockets e janelas. Todos os átomos podem ser usados excepto o nil e o #. O operados “#” pode ser usado para criar as strings virtuais. Por exemplo o seguinte tuplo:

123#”-“#23#” = “#100

Resulta na string:

“123-23 = 100”

Nota: Recomenda-se a consulta do manual, pois associado a cada tipo de dados existe um módulo da linguagem com várias operações, entre as quais conversões de tipos.

4.4 Igualdade e Teste de Igualdade Até agora já foi visto como se instancia uma variável usando o operador de

igualdade. No entanto, convém entender o que está por de trás de uma atribuição ou instanciação. Quando por exemplo X é igualado a Y (“X=Y”). Existe uma área de armazenamento que consiste num array dinâmico em que cada vez que é definida uma variável é acrescentada uma posição a esse array contendo o valor unknown (desconhecido). Uma posição do armazenamento contendo este valor corresponde a uma variável não instanciada. Quando se tenta igualar duas variáveis, o que realmente se passa é uma fusão das duas variaveis, isto é, a operação X=Y, tentará fundir os nós correspondentes na área de armazenamento. Este processo é chamado de Incremental Tell ou operação de unificação.

Se X e Y referencia o mesmo nó na área de armazenamento. A

operação é executada com sucesso Se o nó X for não instanciado e Y estiver instanciado, todas as

referencias a X são substituídas por Y e X é descartado – Processo de fusão. Se X e Y referenciam nós diferente na área de armazenamento

contendo os registo Rx e Ry respectivamente: Se Rx e Ry tiverem diferentes identificadores de registo, diferentes

aritys, ou ambas as situações é completada a operação e levantada uma excepção.

Se não, os argumentos de Rx e Ry com o mesmo atributo são fundidos.

Page 40: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

40

Na fusão de dois grafos (Registos), podem existir ciclos. No entanto a

operação de fusão guarda informação sobre os pares, aos quais foi feita uma tentativa de fusão anterior, permitindo que a operação seja correctamente executada.

Quando uma variável deixar de ser acessível os mecanismos de garbage collection irão libertar o nó que ela referenciava.

Vejamos algumas algumas operações de igualdade (unificações) válidas:

local X Y Z inf(1:X 2:b) = f(a Y)f(Z a) = Z{Browse [X Y Z]}

end

% O resultado será [a b R14=f(R14 a)] no browser.% R14 = f(R14 a) é um grafo cíclico

Nota: Para poder ver a representação finita de Z (grafo cíclico), terá de

ser seleccionado no Browser, o menu Option, Representation field e depois escolher Minimal Graph.

O próximo exemplo mostra uma operação de igualdade errada:

local X Y Z inX = f(c a)Y = f(Z b)X = Y

End

Note-se que a atribuição de “c” a “Z” é correcta, mas quando tenta atribuir, “a” a “b”, é levantada um excepção.

4.4.1 Teste de Igualdade O operador de teste de igualdade é o “==”. Vejamos como funciona o

teste de igualdade partindo de um exemplo:

local X Y inX=1{Show X==1} %% escreve true{Show X==2} %% escreve false{Show X==f(a b)} %% escreve false{Show Y==1} %% Não faz nada

end Quando se verifica uma igualdade o resultado será true. Caso não se

verifique o resultado será false. Existe um terceira situação que é quando uma variável não instanciada é testada. Neste caso, como a igualdade não pode ser testada, a thread bloqueia até conhecer o valor da variável.

Quando duas estruturas (tuplos, registos) são testadas, pode passar-se o seguinte:

Retorna true se os grafos gerados tiverem a mesma estrutura e se cada par propriedade campo corresponder a valores idênticos ou ao mesmo nó (unificação).

Page 41: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

41

Retorna falso se as estruturas forem distintas ou existir alguma desigualdade nos pares propriedade campo.

Suspende a execução quando faz o teste de um par com outro não instanciado.

É importante referir que os tipos de dados que foram discutidos até agora

possuem igualdade estrutural. Vejamos alguns exemplos:

5 == 5.0 %% retorna false porque numeros devirgula flutuante e inteiros nunca são iguais

f(A B) == f(C D) %% retorna true se A==C e B==D

local X Y in{Show f(a b)==f(a b)} %% escreve true{Show f(a b)==f(a c)} %% escreve false{Show f(a b)==f(c d)) %% escreve false{Show f(a _)==f(a b)} %% suspende!!!

end

4.5 Estruturas Básicas de Controlo e Procedimentos O execução de um programa em Oz é feita sequencialmente, isto é, é

executada uma sequência de instruções:

S1 S2 S3 … Sn Apesar da execução das thrads ser sequencial, existe uma grande

diferença das linguagens convencionais. As threads podem ser suspensas, o que significa que por exemplo se a execução for suspensa na execução de S1, S2 só será executada quando forem satisfeitas as necessidades de dados que S1 tem retomando a execução. Pode acontecer que S2 nem seja executado caso S1 levante uma excepção.

4.5.1 A instrução vazia A instrução de Oz correspondente à instrução vazia é o skip.

4.5.2 A instrução condicional if A instrução condicional if funciona de forma bastante semelhante a todas

as outras linguagens de programação, e tem o seguinte sintaxe:

If B then S1 else S2 end

Em que B é um valor booleano, ou expressão que resulte um valor booleano.

Page 42: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

42

A semântica da instrução if é a seguinte: Se B for true a instrução S1 é executada Se B for false a instrução S2 é executada Se B for um valor não booleano é levantada uma excepção Se B for uma variável não instanciada a thread suspende.

Os operadores disponíveis para fazer comparações são os seguintes:

== Igual a\= Diferente de=< Menor ou igual a=> Maior ou igual a> Maior que< Menor que

Tabela 5 – Operadores booleanos

Exemplo:

declare X Y ZX = 5Y = 10if X >= Y then Z = X else Z = Y

end Abriviações: Em Oz existe a palavra reservada elseif: Pode-se escrever o seguinte

código:

if B1 then S1 elseif B2 then S2 else end

Em vez de: if B1 then S1else if B2 then S2

else S3 endend

Pode-se escrever a instrução if sem a parte do else:

if B1 then S1 end

É equivalente a: if B1 then S1 else skip end

Page 43: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

43

4.5.3 Procedimentos Os procedimentos podem ser passados a outros procedimentos ou

atribuídos a registos ou a variáveis (first-class), e são definidos da seguinte maneira:

declareproc {P X1 … Xn} S end

São chamados da seguinte forma:

{P Y1 … Yn} % Y1 ... Yn são os argumentos

Passados como argumento

{Q P 1}

Guardados numa qualquer estrutura de dados

X=rec(P)

Vejamos um exemplo:

declare Max X Y Zproc {Max X Y Z}

if X >= Y thenZ = X

else Z = Y endendX = 5 Y = 10{Max X Y Z}{Show Z} %% escreve 10

Os procedimentos não retornam valores directamente. Para retornar

valores, basta definir como argumentos que irão ser instanciadas dentro do procedimento com os resultados. Quando o procedimento for chamado no lugar desses argumentos colocam-se variáveis não instanciadas. Depois de executada a chamada essas variáveis irão conter os valores retornados. Por exemplo:

{Max In1 In2 Return}

Vejamos um exemplo de um procedimento que retorna dois valores:

declare Two F Sproc {Two In First Second}First=In.1Second= In.2

end{Two f(a b) F S} % F é instanciado com a, S com b

É recomendado usar as convenções do Oz, ou seja, os argumentos que

retornam valores devem sempre ser retornados ao fim, e sempre que quisermos definir um procedimento que apenas retorne um valor, devemos usar uma função em vez de um procedimento.

Page 44: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

44

Figura 18 – Função vs. Procedimento Os procedimentos como são first-class, podem ser atribuídos a qualquer

entidade do Oz. Para isso utiliza-se uma notação designada de procedimentos anónimos. O símbolo “$” é usado para indicar que um determinado procedimento é anónimo.

P = proc{$ X1 ... Xn} S end Neste caso, a variável P irá ser instanciada com um procedimento. Esta

variável pode ser utilizada da mesma forma que qualquer outra variável lógica e pode também ser utilizada para chamar o procedimento utilizando a forma já conhecida de:

{P X1 ... Xn}

Sendo estas variáveis que instanciam procedimentos tratadas de igual modo às outras, podem surgir algumas questões. Por exemplo no que diz respeito ao teste de igualdade. Vejamos um exemplo:

declareproc {Max X Y Z}if X >= Y then Z = Xelse Z = Y end

endproc {Max2 X Y Z}if X >= Y then Z = Xelse Z = Y end

endproc {Min X Y Z}if X >= Y then Z = Yelse Z = X end

endAlias=Max{Show Max==Min} %% escreve false{Show Max==Max2} %% escreve false{Show Max==Alias} %% escreve true

Só a ultima igualdade é verdadeira porque é a única que possui igualdade estrutural.

Vejamos um exemplo que ilustra como podemos passar directamente o resultado de um procedimento a outro procedimento:

{Show {Max3 1 5 4 $}} %% mostra 5

Se em vez de se colocar uma variável não instanciada no argumento de retorno, se colocar o símbolo “$”, o valor é automaticamente passado ao procedimento Show.

Vejamos agora um exemplo da atribuição de procedimentos a estruturas de

fun {Max X Y} proc{Max X Y Aux}if X >= Y then X if X >= Y then Aux=Xelse Y else Aux=Yend end

end end

Page 45: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

45

dados.

declarefun {Max A B}…end

fun {Min A B}…end

MyMathModule=math(max:Max min:Min)declare Max Min{MyMathModule.max 1 3 Max} %% Max=3{MyMathModule.min 1 3 Min} %% Min=1

Finalmente, um exemplo de uma função que recebe uma função (Less) e que retorna uma função que no programa é atribuída a P.

declarefun {GenMax Less}

fun{$ A B}if {Less A B} then Belse Aend

endendP={GenMax fun{$ A B} A<B end}{Show {P 5 3}} %%% Shows 5

4.5.4 Lexical Scoping Num conjunto de instruções S, algumas ocorrências das variáveis estão

ligadas sintacticamente, isto é, só fazem sentido dentro do espaço desse conjunto de instruções, outras estão livres. Uma ocorrência de uma variável X está ligada sintacticamente se:

Se estiver dentro da definição de um procedimento sendo X um

argumento. Se estiver dentro do espaço definido pelas instruções de declaração de

variáveis (por exemplo: local). Caso contrário, encontra-se livre.

As ocorrências de variáveis livres são eventualmente instanciadas na

pela instrução de atribuição mais próxima.

4.5.5 Semântica dos procedimentos Para o procedimento definido por:

P = proc{$ X1 ... Xn} S end

A variável P deve ter sido definida anteriormente. A expressão anterior irá dar origem à uma expressão lambda seguinte:

Page 46: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

46

•(X1 … Xn).S.

P é instanciado com esta abstracção ou clausula que contem o código e

referências às entidades referenciadas pelas variáveis livres dentro do procedimento.

4.5.6 Inicialização de variáveis Como já vimos anteriormente as variáveis ou registos são inicializados

através do operador “=” e definidas através da instrução local ou declare. O que ainda não vimos foi que as variáveis também podem ser inicializadas no momento da sua definição.

Apenas as variáveis correspondentes ao lado direito das expressões são

inicializadas. Vejamos um exemplo.

localY = 1

inlocal

M = f(M Y)[X1 Y] = LL = [1 2]

in{Show [M L]}

endend

A variável Y definida no primeiro local é invisível dentro da instrução local

interior. O resultado que irá resultar será o seguinte: [R1=f(R1 2) [1 2]]

Como L é igualado a [1 2], a X1 é consequentemente atribuído o valor do primeiro elemento de L e a Y o segundo. Logo o tuplo cíclico M, terá o seu segundo elemento igualado a 2. Este conjunto de instruções e semelhante a ter:

local Y in

Y = 1local M X1 Y L in

M = f(M Y)L = [X1 Y]L = [1 2]{Show [M L]}

endend

O símbolo “!” pode ser utilizado para suprimir a inicialização de uma

variável. Vejamos o seguinte exemplo:

Page 47: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

47

local

Y = 1in

localM = f(M Y)[X1 !Y] = LL = [1 2]

in {Show [M L]}end

end Neste exemplo a inicialização de Y é suprimida, logo Y ficará com o valor

atribuído no espaço léxico exterior, que é 1. É semelhante a ter :

local Y inY = 1local M X1 L in

M = f(M Y)L = [X1 Y]L = [1 2]{Show [M L]}

endend

Repare-se que neste exemplo, no segundo local não é definida a variável

Y, o que significa que o valor que Y irá ter dentro dessa área, será o valor que Y tem na área exterior, ou seja 1.

4.5.7 Pattern Matching A implementação de reconhecimento de padrões (pattern matching), é

feita utilizando a instrução case. O seu sintaxe é o seguinte:

case E ofPattern1 then S1

[] Pattern2 then S2[] ...else S end

As variáveis introduzidas (que ainda não foram declaradas) nos patterns, têm um espaço de acção dentro das respectivas instruções (S1,S2).

Imaginemos que E é comparado com uma expressão V. A semântica da

instrução case é a seguinte:

A comparação de V com os patterns é feita de modo sequencial pela ordem que foram definidos.

O teste de comparação de V com um pattern é feita de esquerda para a direita e utilizando o critério do primeiro em profundidade.

Se V for comparado com um pattern sem que nenhuma variável seja instanciada a instrução correspondente será executada.

Se V for comparado com um pattern e apenas algumas das variáveis forem instanciadas a thread fica suspensa.

Se a comparação de V com um pattern falha, V será comparado com o próximo pattern, caso todos falhem será executada a instrução relativa ao else.

A parte do else pode ser omitida, mas caso todos os patterns falhem

Page 48: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

48

será levantada uma excepção. Pode ser suprimida a introdução de uma nova variável local usando o

operador “!” da seguinte forma: case f(X1 X2) of f(!Y Z) then ... else ... end

Vejamos este exemplo que consiste num procedimento que serve para

introduzir valores numa árvore binária.

proc {Insert Key Value TreeIn ?TreeOut}case TreeInof nil then TreeOut = tree(Key Value nil nil)[] tree(K1 V1 T1 T2) then

if Key == K1 thenTreeOut = tree(Key Value T1 T2)

elseif Key < K1 then T inTreeOut = tree(K1 V1 T T2){Insert Key Value T1 T}

else T inTreeOut = tree(K1 V1 T1 T){Insert Key Value T2 T}

endend

end O símbolo “?” serve para indicar que se trata de um argumento de retorno

(output).

Page 49: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

49

4.5.8 Aninhamento de instruções O aninhamento (nesting) é um processo que consiste em colocar

instruções dentro de outras instruções. Para se entender este conceito mais facilmente vejamos o seguinte exemplo:

% São definidas as seguintes variáveis transitóriaslocal T0 T1 T2 T3 in

{Insert a 3 nil T0}{Insert b 15 T0 T1}{Insert c 10 T1 T2}{Insert d 7 T2 T3}

end

Como o Oz possui sintaxe para chamar procedimentos dentro de

procedimentos, pode-se substituir a seguinte expressão

local Y in{P ... Y ...}{Q Y ... }

end por

{Q {P ... $ ...} ... } O símbolo “$” indica que o valor que está nesse argumento, é o valor que

é passado ao procedimento exterior. No caso do nosso exemplo, o conjunto de chamadas ao procedimento

insert poderia ser substituído por:

{Insert d 7{Insert c 10

{Insert b 15 {Insert a 3 nil}}}}}

O aninhamento também pode ser aplicado a listas, tuplos ou registos. Por exemplo:

Zs = X|{SMerge Xr Ys $}

Que substitui a seguinte expressão:

local Zr inZs = X|Zr{SMerge Xr Ys Zr}

end

Vejamos um exemplo de um procedimento que faz a fusão entre duas listas:

proc {SMerge Xs Ys Zs}

case Xs#Ysof nil#Ys then Zs=Ys[] Xs#nil then Zs=Xs[] (X|Xr) # (Y|Yr) then

if X=<Y thenZs = X|{SMerge Xr Ys $}

else Zr inZs = Y|{SMerge Xs Yr $}

Page 50: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

50

endend

end

4.5.9 Procedimentos como valores O procedimento BinaryTree verifica se uma determinada estrutura é ou

não uma árvore binária. O argumento B será instanciado com true ou false se a estrutura for ou não uma árvore binária.

local

proc {And B1 B2 ?B}if B1 then B = B2 else B = false end

endin

proc {BinaryTree T ?B}case Tof nil then B = true[] tree(K V T1 T2) then

{And {BinaryTree T1}{BinaryTree T2} B}

else B = false endend

end

A chamada {And {BinaryTree T1}{BinaryTree T2} B} vai fazer “trabalho” desnecessário, isto é, atendendo às regras do aninhamento, a segunda expressão vai ser avaliada mesmo que a primeira seja falsa.

O que deve ser feito para que isso não suceda é definir um procedimento AndThen, que recebe como argumentos dois procedimentos, onde só executa o segundo se o primeiro retornar true. Este processo chama-se Lazy e só é possível porque os procedimentos em Oz, assim como as outras entidades são High-Order.

local

proc {AndThen BP1 BP2 ?B}if {BP1} then B = {BP2} else B = false end

endin

proc {BinaryTree T ?B}case Tof nil then B = true[] tree(K V T1 T2) then

{AndThenproc{$ B1} {BinaryTree T1 B1} endproc{$ B2} {BinaryTree T2 B2} end

B}else B = false end

endend

Page 51: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

51

4.5.10 Abstracções de controlo Já existem definidas em vários módulos várias abstracções de controlo,

por exemplo no módulo Control ou no módulo List. O facto de os procedimentos em Oz serem High-Order pode ser aproveitado para criar abstracções de controlo. Por exemplo podemos facilmente criar uma abstracção para percorrer todos os elementos de uma lista, aplicando a cada elemento um procedimento P.

proc {ForAll Xs P}

case Xsof nil then skip[] X|Xr then

{P X}{ForAll Xr P}

endend

O Oz possui uma instrução para iterações.

for <iteradores> do <S> end Onde os iteradores são:

X in L itera sobre uma lista L. X in I..J itera sobre os inteiros, de I até J inclusive. X in I..J;K itera sobre os inteiros, de I até J inclusive, com incrementos

de K Exemplo:

for X in [1 2 3] do Show end ou utilizando o nosso exemplo inicial:

{ForAll [1 2 3] Show}

Vejamos agora outro exemplo de iteração:

localproc {HelpPlus C To Step P}

if C=<To then{P C} {HelpPlus C+Step To Step P}

endendproc {HelpMinus C To Step P}

if C>=To then{P C} {HelpMinus C+Step To Step P}

endend

inproc {For From To Step P}

if Step>0 then {HelpPlus From To Step P}else {HelpMinus From To Step P} end

endend{For 3 7 2 proc{$ X} {Show X} end}%% ou alternativamente

Page 52: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

52

for X in 3..7;2 do {Show X} end

4.6 Tratamento de Excepções Para levantar uma excepção definida pela expressão E, utiliza-se a

seguinte expressão:

raise E end Vejamos um exemplo:

proc {Eval E ?R}case Eof plus(X Y) then {Browse X+Y}[] times(X Y) then {Browse X*Y}else raise illFormedExpression(E) endend

end O tratamento de falhas é baseado no conjunto de instruções try e catch, e

são usadas de forma semelhante a outras linguagens. O sintaxe é o seguinte:

try S catchPattern1 then S1Pattern2 then S2

…Patternn then Sn

end

A semântica é a seguinte:

Se não for levantada nenhuma excepção a instrução S é executada normalmente.

Se S levanta uma excepção, essa excepção é comparada com os pattens. Se alguma dessas comparações for verificada é executada a instrução correspondente.

Se a comparação com os patterns não se verificar, a excepção e propagada para fora da instrução try catch e eventualmente tratada pelo sistema

Exemplo:

Page 53: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

53

try{ForAll

[plus(5 10) times(6 11) min(7 10)] Eval}catch

illFormedExpression(X) then{Browse ’** X **’}

end Na conjunto try catch também podemos garantir que uma determinada

instrução seja executada sendo ou não sendo levantada uma excepção. Isto pode ser feito através da palavra finally, que depois de ser executada a instrução correspondente a excepção levantada, ou se nenhuma excepção for levantada, depois de ser executada a instrução sob a palavra try, será executada a instrução indicada a seguir a esta palavra.

try S catch

Pattern1 then S1Pattern2 then S2

…Patternn then Sn

finally Sfinalend

Vejamos um exemplo: Assumindo que F é um ficheiro aberto, o procedimento Process/1 manipula

o ficheiro e o procedimento CloseFile/1 fecha o ficheiro. Este programa garante que o ficheiro seja fechado quer seja ou não

levantada uma excepção.

try{Process F}

catchillFormedExpression(X) then {Browse ’** X **’}

finally {CloseFile F}end

4.6.1 As excepções do sistema As excepções levantadas pelo Oz são registos que podem ter os seguintes

identificadores: failure, error, e system. failure – Indica uma tentativa de execução de uma igualdade

inválida. error – Indica um erro de runtime que não deveria ter sucedido, pode

ser consequencia por exemplo da adição de um inteiro a um átomo. system – Indica um erro de runtime a nível interno do sistema do Oz,

pode surgir devido a um ficheiro ou janela fechados imprevisivelmente, ou devido a falhas de comunicação entre processos.

Page 54: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

54

proc {One X} X=1 end proc {Two X} X=2 endtry {One}={Two}catch failure(...) then {Show caughtFailure}end

O pattern failure(...) irá capturar qualquer excepção cujo identificador seja

failure. A excepção não é tratada, apenas é escrita no emulator-buffer a mensagem de erro correspondente e depois termina a execução. Numa aplicação compilada a mensagem aparecerá no standard error e depois termina a execução. Este comportamento pode ser alterado para algo mais desejável para algumas aplicações, por exemplo para aplicações com tolerância a falhas.

4.7 Módulos e Interfaces

Os módulos são componentes de software first-class que consistem num conjunto de entidades Oz (procedimentos, objectos, e outros valores) que são agrupados para disponibilizar certas funcionalidades. Um módulo é constituído por um conjunto de entidades privadas, isto é, que não são visíveis do exterior, e por um interface que possibilita o acesso ás funcionalidades do módulo. O lexical scoping e certas estruturas de dados como por exemplo os registos facilitam a construção destes componentes.

declare Listlocal

proc {Append ... } ... endproc {Partition ... } ... endproc {Sort ... } ... {Partition ...} ... endproc {Member ...} ... end

inList = 'export'(append: Append

sort: Sortmember: Member... )

end

Pode-se aceder às funcionalidades da seguinte forma:

{Browse {List.append [1 2 3] [3 4]}} Outra forma de criar módulos é usando functors. Os functors especificam a

criação de um módulo partindo de outros módulos (import), especificando o seu interface (export), e a definição das suas funcionalidades (define). O functor correspondente ao exemplo anterior seria:

functorexportappend:Appendsort:Sortmember:Memberdefineproc {Append ... } ... endproc {Partition ...} ... endproc {Sort ... }

... {Partition ...} ...end

Page 55: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

55

proc {Member ...} ... endend% guardado em /home/person/List.oz% compilado no ficheiro /home/person/List.ozf

Como os functors são entidades first-class podem ser definidas da

seguinte maneira:

define

functor LFexportappend:Appendsort:Sortmember:Memberdefineproc {Append ... } ... endproc {Partition ...} ... endproc {Sort ... }

... {Partition ...} ...endproc {Member ...} ... end

end

declare[List]= {Module.link [LF]}

Os módulos podem ser construídos a partir dos functors através da

seguinte expressão:

declare [List]= {Module.link [“/home/person/list.ozf”]} Quando o functor é referenciado são executadas as instruções entre o

define e o end. Como já foi dito anteriormente podem-se criar functors partindo de outros

functors. Para importar os functors basta usar a palavra chave import na definição do functor da seguinte maneira:

functorimport

BrowserFO at 'file:///home/person/FileOperations.ozf'

define{Browser.browse {FO.countLines '/etc/passwd'}}

end

Page 56: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

56

4.8 Concorrência Como já foi dito anteriormente, existe uma área de armazenamento onde

toda a informação é guardada monotónicamente, isto é, apenas é adicionada. As threads adicionam informação a esta área através da operação tell e testam a informação através da operação ask. Quando as threads testam a informação podem ficar suspensas. Depois de serem lançadas, as thrads alternam entre os estados de execução e suspensão um numero arbitrário de vezes até terminarem. Conceptualmente a área de armazenamento está sempre a crescer, mas existem mecanismos de garbage collection (recolha de lixo), que removem a informação que deixou de ser acessível.

Em Oz uma thread pode ser lançada da seguinte maneira:

Thread S end

As threads são executadas de forma concorrente. Todas as threads em execução que não estão bloqueadas é-lhes eventualmente alocada um tempo de processamento. Isto significa que as threads são executadas de modo fair (justo).

Cada thread é uma thread de fluxo de dados (dataflow), o que significa que bloqueiam quando têm necessidade de dados.

declare X0 X1 X2 X3 inthread

local Y0 Y1 Y2 Y3 in{Browse [Y0 Y1 Y2 Y3]}Y0 = X0+1 % inicialmente a thread suspende aquiY1 = X1+Y0Y2 = X2+Y1Y3 = X3+Y2{Browse completed}

endend{Browse [X0 X1 X2 X3]}

Se forem inseridos um de cada vez os seguintes valores:

X0 = 0X1 = 1X2 = 2X3 = 3

A thread irá suspender na linhas seguinte à medida que as suas necessidades de dados vão sendo preenchidas.

Vejamos agora um exemplo:

fun {Map Xs F}case Xsof nil then nil[] X|Xr then thread {F X} end |{Map Xr F}end

end

Page 57: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

57

proc {Map Xs F Rs}case Xsof nil then Rs = nil[] X|Xr then R Rr in

Rs = R|Rrthread R = {F X} endRr = {Map Xr F}

end

Se escrevermos as seguintes instruções:

declareF X Y Z{Browse thread {Map X F} end}

Uma thread executando a função Map será criada, mas como a variável X

não está instanciada a thread será imediatamente suspensa. Se escrevermos :

X = 1|2|Yfun {F X} X*X end

X será instanciado com a lista [1 2 Y] e F com a função X*X. Os requisitos

da thread encontram-se satisfeitos e serão criadas duas novas threads para os dois primeiros elementos da lista. Como o terceiro valor da lista não está instanciado a nova thread suspende. Se escrevermos:

Y = 3|ZZ = nil

Por fim todas as necessidades de informação das threads são

completadas, retomando a execução. O resultado será a lista [1 4 9]. O Programa que se segue, é uma forma muito ineficiente de calcular

séries de Fibonacci, pois cria um número exponencial de threads. No entanto pode ser útil para verificar o número de thread suportados pelo sistema. Por exemplo pode-se tentar:

{Fib 20} Se continuar a funcionar deve tentar-se um valor maior que 20. Entretanto

deve ser executado o programa panel, que pode ser acedido pelo menu Oz do OPI. O objectivo é determinar o limite de threads que o sistema suporta, para que os programas concorrentes possam ser planeados de uma forma mais modular.

fun {Fib X}

case Xof 0 then 1[] 1 then 1else thread {Fib X-1} end + {Fib X-2} end

end

Page 58: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

58

Figura 19 – O Oz panel

4.8.1 Tempo O Oz implementa algumas funcionalidades de soft real-time. Destacam-se

os procedimentos: {Alarm I ?U} cria imediatamente a sua própria thread e instancia U com

unit depois de I milisegundos. {Delay I } suspende a execução da thread por pelo menos I milisegundos.

localproc {Ping N}

if N==0 then {Browse 'ping terminated'}else {Delay 500} {Browse ping} {Ping N-1} end

endproc {Pong N}

{For 1 N 1proc {$ I} {Delay 600} {Browse pong} end }

{Browse 'pong terminated'}end

in{Browse 'game started'}thread {Ping 50} endthread {Pong 50} end

end

4.8.2 Comunicação entre streams O facto de o Oz ser uma linguagem dependente do fluxo de dados

possibilita uma facil implementação de threads que comunicam segundo o modelo produtor - consumidor. Um stream é uma lista que é incrementada por uma thread (produtor) e consumida por uma ou várias threads (consumidores). Todos os consumidores consomem os mesmos elementos do stream.

Page 59: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

59

fun {Generator N}

if N > 0 then N|{Generator N-1}else nil end

endlocal

fun {Sum1 L A}case Lof nil then A[] X|Xs then {Sum1 Xs A+X}end

endin

fun {Sum L} {Sum1 L 0} endend local L in

thread L = {Generator 150000} end{Browse {Sum L}}

end Este programa gera uma lista com os valores menores que um

determinado número (150000) e soma-os. O resultado deverá ser 11250075000. Um produtor vai incrementado um stream (lista), isto é feito de uma forma

eager, ou seja :

fun {Producer ...}... volvo|{Producer ...} ...

end O consumidor espera que os elementos cheguem ao stream.

proc {Consumer Ls ...}case Ls of volvo|Lr then

’Consume volvo’...end{Consumer Lr}

end

Por causa do comportamento dependente do fluxo de dados enquanto não chegar a stream o próximo elemento o consumidor fica suspenso. A chamada recursiva permite o consumidor repetir o processo.

No exemplo seguinte cada vez que o consumidor recebe mil automóveis escreve uma mensagem.

fun {Producer N}

if N > 0 thenvolvo|{Producer N-1}

else nil endendproc {Consumer Ls}

proc {Consumer Ls N}case Lsof nil then skip[] volvo|Lr then

if N mod 1000 == 0 then{Browse ’driving a new volvo'}

end{Consumer Lr N+1}

else {Consumer Lr N} endend

in {Consumer Ls 1}end

Este exemplo pode ser executado da seguinte forma:

Page 60: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

60

{Consumer thread {Producer 10000} end}

4.8.3 Prioridades das Threads Depois de executar o exemplo anterior, se verificarmos no panel o

comportamento da memória, facilmente concluiremos que não é um comportamento correcto. O motivo tem haver com a passagem assíncrona de mensagens. Se o produtor envia mensagens, isto é, cria novos elementos no stream, de uma forma mais rápida que o consumidor consegue consumir, será necessária o recurso intenso a buffers. Uma solução possível para o problema é atribuir ao consumidor uma prioridade maior, que corresponde a maior tempo de processador.

Existem três nível de prioridade: Alta, média e baixa (por omissão). O nível de prioridade determina a frequência com que é alocado um time-slice à thread. Em Oz uma thread de maior prioridade não pode impedir que uma de menor prioridade seja executada. Todas são executadas de forma fair (Justa).

Cada thread tem um nome único. Para obter o nome da thread que está em execução usa-se o procedimento Thread.this/1. Obtendo a referencia a uma thread, através do seu nome, é possível efectuar certas operações. Estas operações estão no modulo Thread. Por exemplo:

fun {Thread.state T} %% Retorna o estado da threadproc{Thread.injectException T E} %% Injecta a excepção E na threadTfun {Thread.this} %% Retorna a referencia à threadproc{Thread.setPriority T P} %% P pode ser high, medium ou lowproc{Thread.setThisPriority P} %% o mesmo que na anterior massobre a thread correntefun{Property.get priorities} %% devolve as racios dasprioridadesproc{Property.put priorities(high:H medium:M)} %% especifica asracios das prioridades

O procedimento:

{Property.put priorities(high:X medium:Y)}

Irá colocar o racio temporal do processador em X:1 entre threads de alta prioridade e média prioridade, e irá também colocar o racio Y:1 entre as threads de média prioridade e baixa prioridade. X e Y são valores inteiros. Por exemplo:

{Property.put priorities(high:10 medium:10)}

Aplicando isto ao programa produtor-consumidor, poderemos resolver o problema da utilização dos buffers. Esta expressão estabelece que as racios são 10:1 entre a alta prioridade é a média e 10:1 entre a média e a baixa.

Page 61: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

61

Vejamos a nova versão do programa produtor consumidor.

local L in{Property.put ’threads’ priorities(high:+10

medium:+10)}thread

{Thread.setThisPriority low}L = {Producer 5000000}

endthread

{Thread.setThisPriority high}{Consumer L}

endend

4.8.4 Execução orientada ao pedido Outra maneira de resolver o problema da sobreutilização de memória no

problema produtor-consumidor é através de execução orientada ao pedido (lazy), isto é, o produtor apenas produz um elemento se for requerido pelo consumidor.

Para isso o consumidor vai construindo o stream com variáveis não instanciadas. O produtor aguarda o aparecimento destas variáveis no stream, para depois lhes atribuir valores.

O produtor será algo semelhante a:

proc {Producer Xs}case Xs of X|Xr then I in

’Produce I’X=I ...{Producer Xr}

endend

E consumidor seria:

proc {Consumer ... Xs}X Xr in...Xs = X|Xr’Consume X’...{Consumer ... Xr}

end Vejamos agora o exemplo do Volvo completo:

localproc {Producer L}

case L of X|Xs then X = volvo|{Producer Xs}[] nil then {Browse 'end of line'}end

endproc {Consumer N L}

if N==0 then L = nilelse

X|Xs = L incase X of volvo then

if N mod 1000 == 0 then{Browse 'riding a new volvo'}

end{Consumer N-1 Xs}

Page 62: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

62

else {Consumer N Xs} endend

endin

{Consumer 10000000 thread {Producer $} end}end

Existe ainda outra maneira de resolver o problema. Existe outra forma de

executar computações orientadas ao pedido, através do uso de futures e da primitiva ByNeed. Uma future consiste na propriedade de leitura de uma variável. Para criar a future da variável X usa-se o símbolo “!!” e é atribuída a Y.

Y = !!X

Uma thread quando tenta usar o valor da future, suspende até que a

variável da future seja instanciada. Pode-se executar um procedimento orientado ao pedido através da

operação {ByNeed +P ?F}. Esta operação recebe um procedimento e retorna uma future F. Quando uma thread tenta aceder ao valor de F, o procedimento {P X} é chamado e o seu resultado X é atribuído a F. Por exemplo:

declare Y{ByNeed proc($ X) X = 1 end Y}{Browse Y}

O Y passa a ser uma future e irá ser instanciada com o valor 1. Outra

maneira de aceder a Y, esperando que Y tenha um valor é através da operação {Wait Y}.

Vejamos o exemplo do Volvo usando ByNeed:

localproc {Producer Xs}

Xr inXs = volvo|{ByNeed {Producer Xr} $}end

endproc {Consumer N Xs}

if N>0 thencase Xs of X|Xr then

if X==valvo thenif N mod 1000 == 0 then

{Browse 'riding a new volvo'}end

end{Consumer N-1 Xr}

else {Consumer N Xr} endend

endin

{Consumer 10000000 thread {Producer $} end}end

4.8.5 Detecção de terminação de threads Uma questão que pode surgir relativamente ao uso de threads é, como se

pode fazer uma thread principal aguardar que todas as threads lançadas a partir dela, terminem para só depois prosseguir a execução.

Page 63: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

63

Isto é uma situação particular da detecção de terminação de threads e ter outra thread á espera desse evento.

Mais uma vez, sendo o Oz uma linguagem dependente do fluxo dos dados, isto pode ser feito de forma bastante simples.

thread T1 X1 = unit endthread T2 X2 = X1 end

...thread TN XN = XN-1 end{Wait XN}MainThread

Quando as threads terminam as variáveis X1,X2,...,Xn são fundidas

contendo o valor unit. A operação {Wait Xn} espera que a variável Xn seja instanciada, ou seja indica que a ultima thread da sequência foi terminada.

4.8.6 Composição concorrente Em Oz existe uma forma de simplificar o problema anterior recorrendo ao

composição concorrente. Para isso usa-se a primitiva da linguagem Conc, que recebe um único parâmetro que consiste numa lista de procedimentos sem argumentos. Quando o Conc é executado, todos os procedimentos existentes na lista, são lançados de forma concorrente. Só é executada a próxima instrução depois de todos esses procedimentos terminarem.

{Conc [proc{$} S1 end

proc{$} S2 end...

proc{$} Sn end]} O Conc pode ser definido da seguinte forma:

localproc {Conc1 Ps I O}

case Ps of P|Pr thenM inthread {P} M = I end{Conc1 Pr M O}

[] nil then O = Iend

endin

proc {Conc Ps}{Wait {Conc1 Ps unit $}}end

end Vejamos um exemplo da utilização do Conc:

declareproc {Ping N}

if N==0 then {Browse 'ping terminated'}else {Delay 500} {Show ping} {Ping N-1} end

endproc {Pong N}

{For 1 N 1proc {$ I} {Delay 600} {Show pong} end }

end{Conc [proc{$} {Ping 500} end

Page 64: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

64

proc{$} {Pong 500} end]}{Show pingPongFinished}

4.9 Tipos de dados baseados em estados O Oz disponibiliza um conjunto de tipos de dados para representar

estados. Esse tipos de dados são entre outros ports, objectos, arrays, e dicionários (tabelas de hash). Este conjunto de dados é abstracto porque caracterizam-se por apenas serem manipulados por um conjunto de operações relativas a cada tipo. As suas implementações são escondidas, e existem de facto várias implementações embora os seus comportamentos sejam os mesmos. Por exemplo, os objectos são implementados de forma diferente dependendo do nível de optimização do compilador. Cada membro é sempre único sendo-lhe atribuído um nome do Oz depois da sua criação. Um membro é criado por uma operação explicita. Existe sempre a operação de teste de tipo. Os membros deixam de existir quando deixam de ser acessíveis.

4.9.1 Ports Os ports são canais de comunicação assíncronos que podem ser

partilhados por vários emissores. Os ports têm sempre um stream associado. A operação {Port.new S ?P} cria um port P e associa-o a um stream S. A operação {Port.send P M} acrescenta no fim do stream associado a P a

mensagem M. As inserções nos ports são sempre feitas no fim. A operação {Port.is P ?B} verifica se P é um port. Para proteger o stream S

de ser instanciado outra vez, S e uma future. A semântica é a seguinte: As mensagens enviadas pela mesma thread chegam com a ordem

de envio, mas mensagens enviadas por threads diferentes podem chegar com uma ordem arbitraria.

Page 65: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

65

declare S PP = {Port.new S}{Browse S}{Port.send P 1}{Port.send P 2}

Executando este código, verificamos que S está sempre a crescer. O resultado produzido será:

S<Future>1|_<Future>1|2|_<Future>

Os ports são abstracções mais expressivas que a comunicação de streams

pura, pois podem ser partilhados por várias threads e podem ser embebidos noutras estruturas de dados.

Os ports são o principal mecanismo para comunicação entre threads existente no Oz.

Relativamente à thread, a ordenação das mensagens é do tipo FIFO. Vejamos o seguinte exemplo:

declare S PP = {Port.new S}{Browse S}thread...{Port.send P a1}...{Port.send P a2}end...{Port.send P b1}...{Port.send P b2}

Ordem possivel de chegadaa1 a2 b1 b2a1 b1 a2 b2b1 a1 a2 b2

Ordem de chegada Impossívela1 a2 b2 b1b1 a2 a1 b2

4.9.2 Comunicação Cliente-Servidor Os ports podem ser usados como pontos de entrada de comunicações

para servidores. Vejamos um exemplo de um servidor de uma fila FIFO: Usa dois ports, um para inserir elementos na fila através do procedimento

put, e outro para retirar um elemento da fila usando get. O servidor é insensível à ordem de relativa de chegada dos pedidos de get

e put. Os pedidos de get podem chegar mesmo que a fila esteja vazia. Um servidor é criado por {NewQueueServer ?Q}, que retorna um registo Q

com duas propriedades put e get, tendo cada uma um procedimento com um argumento. Uma thread cliente pode aceder a estes serviços, invocando esses procedimentos. Os valores são retornados para variáveis lógicas.

declare fun {NewQueueServer}

Page 66: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

66

Given GivePort={Port.new Given}Taken TakePort={Port.new Taken}

inthread Given=Taken endqueue(put:proc{$ X} {Port.send GivePort X} end

get:proc{$ X} {Port.send TakePort X} end)end

Ou alternativamente:

declarefunctor NewQueueServerexport

put: proc {$ X} {Port.send GivePort X} endget: proc {$ X} {Port.send TakePort X} end

defineGiven GivePort={Port.new Given}Taken TakePort={Port.new Taken}thread Given = Taken end

end

Figura 20 – Funcionamento do servidor de fila FIFO O programa funciona da seguinte maneira: {Q.put I0} {Q.put I1} ...{Q.put In}

adiciona os elementos I0, I1, ..., In ao stream Given. O resultado é I0|I1|...|In <future1>. {Q.get X0} {Q.get X1} ... {Q.get Xn} irá adicionar os elementos X0 , X1, ..., Xn ao stream Taken. O resultado é o stream X0|X1|...|Xn <future2>. A igualdade Given = Taken irá instanciar os Xi com os Ii.

4.9.3 As células As células (cells) referenciam uma célula de memória que tem um

componente mutável. Funcionam como as variáveis nas linguagens convencionais. O procedimento {NewCell X ?C} cria uma célula com o valor inicial X. C é instanciado com a Célula.

=

GivePort

TakePort

E1 E2 E3 <Future 1>

X1 X2 <Future 2>

X1=E1 X2=E2

Page 67: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

67

Podem efectuar-se as seguintes operações com as células: Operação Descrição{NewCell X ?C} Cria uma célula com o conteúdo X{IsCell +C} Testa se C é uma célula{Exchange +C X Y} Troca conteúdo de C de X para Y{Access +C X} Devolve o conteúdo de C em X{Assign +C Y} Modifica o conteúdo de C para Y

Tabela 6 – Operações sobre células O exemplo que se segue faz uso de células e iteradores high-order para

acumular o valor de uma célula.

declare C = {NewCell 0}{For 1 10 1

proc {$ I}O N in{Exchange C O N}N = O+I

end}{Browse {Access C}} %% o resultado será 55

4.9.4 Chunks Os chunks são estruturas semelhantes aos registos só que: O identificador

do chunk é um nome Oz e não existe operação de arity sobre os chunks. Isto permite que certos componentes do chunk possam ser escondidos se uma propriedade do chunk é um nome Oz que é apenas visível através do lexical scoping, por operações definidas pelo utilizador sobre o chunk.

Um chunk pode ser criado da seguinte maneira:

{NewChunk Record ?Chunk}

Esta expressão cria um chunk com a mesma estrutura do registo mas com um identificador único.

local X in

{Browse X={NewChunk f(c:3 a:1 b:2)}}{Browse X.c}

end

Irá mostrar no browser o seguinte:

<Ch>(a:1 b:2 c:3)3

Os chunks e as células são tipos de dados primitivos do Oz, isto é, todos

os outros tipos de dados que representam estados existentes no Oz, podem ser construídos com base nestes dois tipos.

Page 68: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

68

4.9.5 Locks Os locks são os mecanismos existentes em Oz para garantir acesso

exclusivo a secções criticas, que correspondem a uma zona de código Oz. Quando uma thread entra numa secção critica, o lock é activado e é garantido o acesso exclusivo da thread ao recurso. Quando a execução abandona a região correspondente à secção critica, quer na sequência normal de execução, que por intervenção de uma excepção, o lock é libertado. Uma thread que concorra para obter um lock que está ocupado, bloqueia até que o esse lock seja libertado. Os locks são reentrantes como no java. Os locks não são primitivos do Oz, isto é, podem ser implementados através de células.

Para aplicar um lock a uma secção critica S, faz-se o seguinte:

lock L then S end O L é a expressão associada ao lock. Assumindo que T é uma

thread que executa a expressão anterior, se L estiver livre, T executa S e L fica ocupado. Se L estiver ocupado, T bloqueia e fica no fim da fila aguardando a sua vez para ocupar L. Todas as outras threads bloqueiam enquanto uma thread executar S.

Existem as seguintes operações sobre locks:

{Lock.new ?L} cria um novo lock L .{Lock.is E} devolve true se E for um lock.

Para os locks simples a reentrância não é suportada. Isto é, se a mesma

thread tentar readquirir o lock bloqueia. O locks simples podem ser implementados através do seguinte código:

proc {NewSimpleLock ?Lock}

Cell = {NewCell unit}in

proc {Lock Code}Old New intry

{Exchange Cell Old New}{Wait Old}{Code}

finally New=unit endend

end Code é um procedimento sem argumentos que corresponde à secção

critica. O lock é representado sob a forma dum procedimento, que quando aplicado ao code, tenta deter o lock esperando que a variável Old seja instanciada com unit. Note-se que o lock é libertado quando a execução de code termina de forma normal ou por meio de uma excepção.

Page 69: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

69

4.10 Classes e Objectos As classes em Oz são chunks que contêm : Uma colecção de métodos

numa tabela de métodos; uma descrição dos atributos que serão possuídos por cada instancia da classe, cada atributo representa estados e é acedido pelo nome do atributo que consiste num átomo ou num nome Oz; descrição das propriedades que cada instancia da classe possui, as propriedades são componentes imutáveis (variáveis), que são acedidas pelo nome da propriedade que consiste num átomo ou um nome Oz.

As classes em Oz não representam estados. Ao contrário de linguagens como o Java e o Smalltalk, são apenas descrições de como os objectos da classe se devem comportar.

4.10.1 Implementação de Classes utilizando estruturas mais primitivas Vejamos um exemplo de uma classe counter. Esta classe tem um único

atributo que pode ser acedido pelo átomo val. Tem uma tabela de métodos com três métodos acedidos pelas propriedades browse, init e inc do chunk. Um método é um procedimento que recebe uma mensagem que consiste num registo, um parâmetro com o estado do objecto, e o próprio objecto representado internamente por self.

declare Counterlocal

Attrs = [val]MethodTable =

m(browse:MyBrowse init:Init inc:Inc)proc {Init M S Self}

init(Value) = M in{Assign S.val Value}

endproc {Inc M S Self}

X inc(Value) = M in{Access S.val X} {Assign S.val X+Value}

endendproc {MyBrowse M=browse S Self}

{Browse {Access S.val}}end

inCounter =

{NewChunk c(methods:MethodTable attrs:Atts)}end

Page 70: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

70

4.10.2 Implementação de Objectos utilizando estruturas mais primitivas Existe um procedimento genérico que cria o objecto a partir da classe. O

iterador Record.ForAll/2 itera sobre os campos de um registo. NewObject devolve um procedimento Object que identifica o objecto. O estado do objecto apenas é visível no interior de Object.

fun {NewObject Class InitialMethod}

State = {MakeRecord state Class.attrs}proc{Object M}

{Class.methods.{Label M} M State Object}end

in{Record.forAll State

proc{$ A} X in A={NewCell X} end}{Object InitialMethod}Object

end

Este programa pode ser testado da seguinte maneira:

declare C{NewObject Counter init(0) C}{C inc(6)} {C inc(6)}{C browse}

4.10.3 As Classes em Oz O Oz implementa programação orientada a objectos utilizando as

metodologias vistas anteriormente, mas possui no seu sintaxe vocabulário especifico e a nível da implementação, possui certas optimizações para que a invocação de métodos em objectos, tenha o mesmo desempenho que a simples chamada a um procedimento.

A classe Counter definida anteriormente poderia ser rescrita sobre a forma:

class Counterattr valmeth browse

{Browse @val}endmeth inc(Value)

val <- @val + Valueendmeth init(Value)

val <- Valueend

end

Page 71: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

71

Uma classe é definida da seguinte forma:

class X ... end

Os atributos são definidos usando um declarador de atributos antes da declaração dos métodos.

attr A1 A2 … An

Depois declaram-se os métodos usando a seguinte forma para cada um

deles:

meth E S end A expressão E é um registo que especifica a “cabeça” do método e

consiste num registo cujo identificador corresponde ao nome do método.Para se aceder a um atributo A usa-se @A. Para atribuir um valor a um atributo A usa-se A <- E. Uma classe pode ser anónima como os procedimentos. Define-se da seguinte forma:

X = class $ ... end

O exemplo a seguir mostra como um objecto pode ser criado a partir de uma classe usando o procedimento New/3, cujo primeiro argumento é a classe, o segundo é o método inicial, e o terceiro é o resultado, isto é, o objecto. O procedimento New é o procedimento genérico de criação de objectos a partir de classes.

declare C = {New Counter init(0)}{C browse}{C inc(1)}{C browse}

4.10.4 Chamadas estáticas a métodos Assumindo que existe uma classe C e um método cuja “cabeça” seja m(...),

uma chamada estática a um método tem a seguinte forma:

C, m(...)

Uma chamada estática a um método só pode ser usada no interior da definição da classe. A chamada ao método recebe o próprio objecto denominado de self como argumento implícito. O método m pode estar definido na classe C ou numa classe de hierarquia superior (super classe). Vejamos um exemplo:

declare functor ListFexport

append:Appendmember:MemberBlength:Length

defineclass ListClass from BaseObject

meth append(Xs Ys $)case Xsof nil then Ys[] X|Xr then X|(ListClass , append(Xr Ys $))

Page 72: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

72

endendmeth member(X L $) … endmeth length(Xs $) … endO = {New ListClass noop}fun {Append X Y} {O append(X Y $)} endfun {MemberB X L} {O member(X L $)} endfun {Length L} {O length(L $)} end

end Este exemplo é bastante interessante, porque para além de demonstrar a

utilização de chamadas estáticas a métodos, também demonstra como as classes podem ser usadas como especificações de módulos, isto é, a classe ListC define procedimentos comuns de tratamento de listas como métodos. Para além disso também se pode ver um exemplo de herança de classe em Oz através da expressão :

class ListClass from BaseObject

4.10.5 Herança de classes

class Accountattr balance:0meth transfer(Amount)

balance<- @balance+Amountendmeth getBal(B)

B = @balanceend

endA={New Account transfer(100)}

Extensão conservadora:

class VerboseAccountfrom Accountmeth verboseTransfer(Amount)

{self transfer(Amount)}{Show @balance}

endend

Extensão não conservadora:

class AccountWithFeefrom VerboseAccountattr fee:5meth transfer(Amount)

VerboseAccount,transfer(Amount-@fee)end

end

Page 73: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

73

As classes podem herdar de uma ou várias classes definidas depois da palavra chave from. A classe B é uma superclasse de A se: B aparece depois de from na declaração da classe A, ou B é uma superclasse de uma classe que aparece depois da palavra chave from na declaração de A.

Os métodos (atributos e propriedades) disponíveis (visíveis) na classe C estão definidos segundo uma relação de precedência sobre os métodos sobre os métodos que aparecem na hierarquia da classe: relação de sobrecarga: Um método na classe C sobrecarrega um qualquer método de uma superclasse, se esse método tiver o mesmo identificador que o do método da superclasse.

Para a herança de classes ser válida não pode haver relações cíclicas entre as classes.

Figura 21 – Relação válida de herança de classes

Figura 22 – Relação de herança invalida (Relação cíclica)

4.10.6 Propriedades As propriedades são componentes das classes e são especificados da

seguinte forma:

class C from …feat a1 … an…

end Tal como nos registos, as propriedades dos objectos têm um campo

associado. Esse campo consiste num variável lógica que pode ser instanciada com qualquer valor Oz, isto é, com células, registos, objectos, classes, etc... Para se aceder ao valor da propriedades usa-se o operador infixo “.”.

class ApartmentC from BaseObject

meth init skip endendclass AptC from ApartmentC

featstreetName: yorkstreetNumber:100wallColor:whitefloorSurface:wood

endApt = {New AptC init}{Browse Apt.streetName}

Page 74: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

74

As propriedades são inicializadas na altura da definição da classe: Todas

as instancias da classe irão ter as propriedades com os valores definidos na classe. No exemplo que se segue irá ser escrito o nome york duas vezes.

declare Apt1 Apt2Apt1 = {New AptC init}Apt2 = {New AptC init}{Browse Apt1.streetName}{Browse Apt2.streetName}

Propriedades não instanciadas:

class MyAptC1 from ApartmentCfeat streetName

end Quando uma instancia de uma classe é criada, o campo da propriedade

não instanciada é uma nova variável. Exemplo:

declare Apt3 Apt4Apt3 = {New MyAptC1 init}Apt4 = {New MyAptC1 init}Apt3.streetName = kungsgatanApt4.streetName = sturegatan

Neste exemplo a propriedade streetName do objecto Apt3 é instanciado

com com o átomo kungsgatan e a propriedade streetName do objecto Apt4 é instanciada com o átomo sturegatan.

Quando as propriedades são instanciadas com um valor Oz, por exemplo

uma variável, todas as instancias irão partilhar a mesma variável.

class MyAptC1 from ApartmentCfeat streetName:f(_)

enddeclare Apt1 Apt2Apt1 = {New MyAptC1 init}Apt2 = {New MyAptC1 init}{Browse Apt1.streetName}{Browse Apt2.streetName}Apt1.streetName = f(york)

Neste exemplo a propriedade streetName de Apt2 irá ter o mesmo valor de

streetName de Apt1.

Page 75: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

75

4.10.7 Classes parametrizadas A forma mais comum de tornar as classes genéricas é definindo uma

classe com alguns métodos definidos mas sem estarem especificados. Mais tarde esses métodos serão especificados nas subclasses.

Em Oz, existe uma forma muito natural de definir classes genéricas. Como as classes são entidades first-class, pode criar-se uma função que receba um determinado número de argumentos e retorne uma classe. O exemplo que se segue a função SortClass recebe uma classe (que pode possui um operador de ordenação) como argumento e retorna uma classe de ordenação.

fun {SortClass Type}

class $ from BaseObjectmeth qsort(Xs Ys)case Xsof nil then Ys = nil[] P|Xr then S L in

{self partition(Xr P S L)}ListC, append({self qsort(S $)} P|{self

qsort(L $)} Ys)end

endmeth partition(Xs P Ss Ls)case Xsof nil then Ss = nil Ls = nil[] X|Xr then Sr Lr in

case Type,less(X P $) thenSs = X|Sr Lr = Ls

elseSs = Sr Ls = X|Lr

end{self partition(Xr P Sr Lr)}

endend

endend

Podemos criar uma classe com os operadores. Por exemplo para inteiros e racionais:

class Int

meth less(X Y $)X<Y

endendclass Rat from Object

meth less(X Y $)’/’(P Q) = X’/’(R S) = Y

inP*S < Q*R

endend

O exemplo pode ser usado da seguinte maneira:

Page 76: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

76

Para inteiros:

{Browse {{New {SortClass Int} noop} qsort([1 2 5 3 4]$)}}

Para racionais:

{Browse {{New {SortClass Rat} noop}qsort([’/’(23 3) ’/’(34 11) ’/’(47 17)] $)}}

4.10.8 Métodos públicos e privados Os métodos podem ser identificados por variáveis em vez de por literais.

Caso sejam identificados por variáveis, os métodos são privados.

class C from ...meth A(X) ... endmeth a(...) {self A(5)} ... end....

end

O método A(X) apenas é visível do interior da classe. Esta notação é representada de uma forma mais primitiva da seguinte forma:

local A = {NewName} in

class C from …meth !A(X) … endmeth a(…){self A(5)} … end…

endend

Muitas das linguagens de programação orientada a objectos também possuem o conceito de métodos protegidos. Um método protegido consiste num método que apenas é acessível na classe onde está definido, ou nas classes descendentes. Como os atributos apenas são visíveis dentro da própria classe ou nas classes descendentes, para criar métodos protegidos basta em primeiro lugar tornar os métodos privados e depois guarda-los em atributos.

local ProtMeth in

{NewName ProtMeth}class C2

attr protMeth:ProtMethmeth !ProtMeth

...endmeth otherMethod

{self @protMeth}end

endend

Page 77: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

77

Se for criada uma classe C2 descendente de C1, o método protegido pode ser chamado da seguinte forma:

class C1 from Cmeth b(...) L=@protMeth in {self L(5)} ... end...end

4.10.9 Valores por omissão dos argumentos Um método pode ter valores por omissão para os seus argumentos. Por

exemplo:

meth m(X Y d1:Z<=0 d2:W<=0) ... end Para este exemplo, se as propriedades d1 e d2 não forem especificadas

estes argumentos irão ter o valor 0. Vejamos um exemplo que ilustra alguns dos conceitos aprendidos até

agora sobre objectos:

class BoundedPoint from Pointattr

xbounds: 0#0ybounds: 0#0boundConstraint: BoundConstraint

meth init(X Y xbounds:XB <= 0#10 ybounds:YB <= 0#10)Point,init(X Y) % call your superxbounds <- XBybounds <- YB

endmeth move(X Y)

if {self BoundConstraint(X Y $)} thenPoint,move(X Y)

endendmeth BoundConstraint(X Y $)

(X >= @xbounds.1 andthenX =< @xbounds.2 andthenY >= @ybounds.1 andthenY =< @ybounds.2 )

endmeth display

Point,display{self DisplayBounds}

endmeth DisplayBounds

X0#X1 = @xboundsY0#Y1 = @yboundsS = "xbounds=("#X0#","#X1#"),ybounds=("#Y0#","#Y1#")"

in{Browse S}

endend

Page 78: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

78

4.11 Objectos e Concorrência As threads podem comunicar através da passagem de mensagens ou

através de um espaço partilhado (exemplo: objectos partilhados). A comunicação através de objectos partilhados requer a capacidade de construir objectos com operações concorrentes, de forma que o estado do objecto se mantenha coerente depois de efectuadas essas operações. Em Oz o acesso exclusivo a um objecto está separado do sistema do objecto. Isto permite-nos fazer operações atómicas sobre conjuntos de objectos, o que é bastante importante por exemplo em bases de dados distribuídas.

4.11.1 Trocas atómicas em atributos Um lock simples pode ser implementado usando a seguinte expressão:

Old = lck <- New

Esta operação é semelhante à operação Exchange aplicada às células, isto é, efectua trocas atómicas em atributos de objectos.

class SimpleLock

attr lck: unitmeth init skip endmeth lock( Code)Old New intry

Old = lck <- New{Wait Old} {Code}

finally New= unit endend

end

4.11.2 Locks de threads reentrantes Como já foi visto, a unidade computacional em Oz é a thread.

Consequentemente terá de existir um mecanismo de exclusão mútua que garanta o acesso exclusivo de uma thread a um recuso. Um lock de thread reentrante permite à mesma thread de reentrar numa secção critica dinâmica aninhada (nested) sob o mesmo lock. Este lock apenas pode ser adquirido por uma thread de cada vez. Threads que tentem adquirir o mesmo lock concorrentemente ficam em fila de espera. Quando o lock é libertado, a thread que está em primeiro lugar na fila será a próxima a adquiri-lo.

Vejamos um exemplo:

Page 79: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

79

class ReentrantLock from SimpleLock

attr Current: unitmeth lck( Code)

ThisThread = {Thread. this} incase ThisThread == @Current then

{Code}else

tryCode1 = proc {$}

Current <- ThisThread{Code}

endin SimpleLock, lck{ Code1}finally Current <- unit end

endend

end

4.11.3 Aplicando locks a objectos Podemos declarar na classe que os seus objectos possuem um lock por

omissão quando estes são criados. Uma classe com a declaração implícita de um lock é feita da seguinte forma:

class C from ....prop locking....end

Isto não fecha automaticamente o objecto quando um dos seus métodos é

invocado, tem de ser usada a expressão

lock S end

dentro do método para seja garantido o acesso exclusivo quando S é

executado. É importante relembrar que os locks são de threads reentrantes, isto

significa que: Se em todos os objectos que construímos colocarmos lock S end em todos os métodos e executarmos o programa com apenas uma thread, terá de ter o mesmo comportamento que antes.

Vejamos agora um exemplo de como pode refinar uma classe para poder ser usada de forma concorrente:

class Counter

attr valmeth browse

{Browse @val}endmeth inc(Value)

val <- @val + Valueendmeth init(Value)

val <- Valueend

Page 80: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

80

end

class CCounter from Counterprop lockingmeth inc(Value)

lock Counter,inc(Value) endendmeth init(Value)

lock Counter,init(Value) endend

end

4.11.4 Canal FIFO concorrente Trata-se de um exemplo de concorrência com objectos, em que existe um

canal concorrente que é partilhado por um número arbitrário de threads e funciona da seguinte maneira: Qualquer thread produtora pode colocar informação no canal assincronamente; As threads consumidoras têm que aguardar que exista informação no canal; As threads que esperam são servidas de forma fair (justa). A sincronização é feita através de variáveis lógicas. Os métodos put/1 e get/1 respectivamente inserem e esperam que exista um elemento no canal. Se existirem múltiplas threads a concorrerem pelo canal irão reservar o seu lugar no canal, garantido justiça no acesso ao canal.

A expressão {Wait I} é feita fora da secção critica para evitar deadlocks.

class Channel from BaseObjectprop lockingattr f rmeth init

X in f <- X r <- Xendmeth put( I)

X in lock @r= I|X r<- X endendmeth get(? I)

X in lock @f= I|X f<- X end {Wait I}end

end

4.11.6 Eventos A classe seguinte define a noção e operações de evento (notify(Event) e

wait(Event) ) através da classe Channel.

class Event from Channelmeth wait

Channel , get(_)endmeth notify

Channel , put(unit)end

end

Page 81: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

81

Vejamos o exemplo do unit buffer. O comportamento é bastante semelhante ao do canal FIFO relativamente ás threads consumidoras. Cada thread consumidora espera que o buffer esteja cheio. Relativamente às threads produtoras, apenas uma pode inserir um item no buffer vazio. As outras threads produtoras ficam suspensas, até que, o elemento seja consumido.

Veja-se que não é necessário utilizar locks directamente neste programa. A combinação de variáveis lógicas com objectos tornam bastante simples e clara, a programação de problemas deste tipo.

class UnitBuffer from BaseObject

attr prodq buffermeth init

buffer <- {New Channel init}prodq <- {New Event init}{@ prodq notify} % Buffer está vazio

endmeth put(I)

{@ prodq wait} {@ buffer put(I)}endmeth get(?I)

{@ buffer get(I)} {@ prodq notify}end

end

Se quisermos um tamanho variável do buffer podemos passá-lo como

argumento ao método init e modificar da seguinte maneira este método: Criar um ciclo que mande N notificações de buffer livre.

class BoundedBuffer from UnitBuffer

attr prodq buffermeth init( N)

buffer <- {New Channel init}prodq <- {New Event init}{For 1 N 1 proc {$ _} {@ prodq notify} end}

endend

4.11.7 Objectos Activos Um objecto activo é uma thread (processo) cujo comportamento é descrito

por uma classe. A comunicação com objectos activos é feita através de mensagens. Um objecto activo reage às mensagens executando o método correspondente à mensagem recebida. Estes objectos executam um método de cada vez, portanto o lock não é necessário para os métodos executados pelo objecto activo. A interface é feita usando ports de Oz. Os cliente enviam mensagens para o objecto activo, enviando mensagens para o port que lhe está associado.

Vejamos como pode ser construída esta abstracção: Tendo em conta que, os objectos activos podem ser usados como servidores recebendo mensagens de clientes através de uma rede, chamamos a esta abstracção de abstracção de servidor. Para criar um servidor S com a classe Class executa-se o seguinte S={Newserver Class init}, em que init é o construtor. Para se ficar com uma ideia mais clara, a função a seguir, consiste numa primeira aproximação á criação dum servidor. Esta função cria um port chamado Port, um objecto chamado Object, e

Page 82: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

82

por ultimo uma thread que trata as mensagens recebidas no port, chamando o método correspondente.

fun {NewServer Class init}

S % O stream do portPort = {NewPort S}Object = {New Class init}

inthread

{ForAll Sproc{$ M} {Object M} end}

endPort

End

No próximo exemplo adicionou-se a possibilidade de terminar a thread construindo um método protegido close que é acessível aos métodos da classe. Para saltar fora do ciclo de recepção de mensagens, usa-se um mecanismo baseado em tratamento de excepções.

local

CloseException = {NewName}class Server

attr close: Closemeth Close raise CloseException end end

endin

fun {NewServer Class init}S % O stream do portPort = {NewPort S}Object = {New class $ from Server Class end init}

inthread

try {ForAll Sproc{$ M} {Object M} end}

catch !CloseException then skip endendPort

endend

Page 83: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

83

4.12 Arrays e Dicionários Os arrays em Oz podem ser criados da seguinte maneira:

Arr={NewArray 1 100 nil} Cria um array de 100 elementos, indexado de 1 a 100 e inicializado a nil. Para se inserir um elemento no array basta fazer:

{Array.put Arr 1 a} ou Arr.1:=a

Para se ler um valor do array faz-se o seguinte:

X={Array.get Arr 1} or X=Arr.1

A expressão seguinte cria um registo (tuplo) que é a cópia sem ser baseada em estados do array.

R={Array.toRecord +Label +Array}

Um dicionário é uma estrutura de dados parecida aos arrays onde existe

uma associação entre chaves e entradas. Pode ser criado da seguinte forma:

Dict={NewDict} A expressão seguinte mostra como adicionar uma entrada no dicionário:

{Dictionary.put Dict Chave Entrada} ouDict.Chave:=Entrada

Para ler um elemento a partir da chave pode fazer-se:

X={Dictionary.get Dict Chave} ou X=Dict.Chave A próxima expressão é idêntica à anterior, caso exista uma entrada para

essa chave, caso contrário devolve um valor por omissão.

X={Dictionary.condGet Dict Chave Default}

A expressão seguinte devolve uma lista com todas as chaves:

X={Dictionary.keys Dict}

A expressão seguinte devolve uma lista na forma Chave#Entrada:

X={Dictionary.entries Dict}

Finalmente a expressão seguinte cria uma cópia do dicionario sob a forma de tuplo:

R={Dictionary.toRecord +Label +Dict}

Page 84: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

84

4.13 Programação Lógica Existem muitos tipos de problemas, nomeadamente no campo da

inteligência artificial, que são resolvidos recorrendo a mecanismos de busca e propagação de restrições. Para facilitar a programação deste tipo de problemas, é fundamental que a linguagem possa abstraccionar os detalhes da busca, através de não determinismo. O Prolog é a linguagem mais conhecida desenvolvida para resolver este tipo de problemas. Iremos ver que o Oz também possui excelentes características para a programação lógica e para a programação baseada em restrições.

4.13.1 Armazenamento de restrições As threads partilham um espaço de armazenamento onde as variáveis são

instanciadas sob a forma de igualdades: X1=U1, ..., Xn=Un onde Xi são as variáveis e Ui são entidades Oz ou variáveis. O armazenamento de restrições contém valores Oz correspondentes a registos, números, nomes ou variáveis, nomes únicos que identificam os procedimentos, células e vários tipos de chunks (classes, objectos, functors, etc). Conceptualmente o armazenamento é uma formula conjuntiva do tipo: Y1...Yn: X1=U1 e ... e Xn=Un, onde Xi são as variáveis, Ui são os valores Oz, e Yi são todas as uniões entre as variáveis Xi e os valores Oz Yi. A área de armazenagem de computação do Oz é constituída pela área de armazenagem de restrições, de procedimentos e de células.

4.13.2 Espaços computacionais Um espaço computacional consiste num espaço de armazenamento de

computações e um conjunto de threads. Até agora, apenas foi usado um só espaço computacional. Em programação lógica surgem estruturas mais elaboradas devido a múltiplas computações embebidas. Existem as seguintes regras para a estrutura dos espaços computacionais:

Existe sempre um espaço computacional de topo, onde as threads

interagem com o exterior. Uma thread que tente adicionar restrições (instanciações) inconsistentes neste espaço será levantada uma excepção, garantindo a consistência deste espaço.

Uma thread pode criar directa ou indirectamente um espaço computacional. O novo espaço computacional será filho e tendo como pai o espaço computacional corrente, isto é, é criada uma hierarquia de espaços computacionais.

As threads e as variáveis apenas pertencem a um espaço computacional.

Uma thread num espaço computacional pode aceder as suas variáveis e às dos seus antecedentes. Mas uma thread num espaço computacional pai não pode ver as variáveis de um espaço computacional filho.

Uma thread num espaço filho pode adicionar restrições (instanciações) a variáveis que lhe são visíveis, isto é, pode instanciar variáveis no espaço corrente e

Page 85: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

85

nos seus antecessores.

4.13.3 Vinculação e desvinculação de restrições Uma condição C é vinculada ao armazenamento σ se C, for uma formula

lógica e estiver implícita no armazenamento σ. Isto é, adicionando C ao armazenamento σ não acrescenta informação ao que já lá existe.

Uma condição C é desvinculada ao armazenamento σ se uma contradição de C estiver existir no armazenamento σ. Uma restrição desvinculada é inconsistente com a informação que já existe no armazenamento.

Sendo o armazenamento de restrições uma formula lógica, pode também falar-se em vincular um armazenamento de restrições a outro.

Por exemplo, se considerarmos o armazenamento σ = X=1 e ... e Y=f(X Z)

e as seguintes condições:

X=1 é vinculado porque esta atribuição não acrescenta informação ao espaço σ.

Existe pelo menos um U tal que Y=f(1 U), esta condição também é vinculada porque não adiciona informação ao armazenamento. Já existe uma condição X=1 e independentemente do valor que Z venha a ter a condição é satisfeita.

Y=f(1 2) não é vinculado porque esta condição irá aumentar informação existente no armazenamento, nomeadamente a informação Z=1.

X=2 e Y=f(3 U) ambas são desvinculadas do armazenamento σ, porque contradizem a informação lá existente. Isto é, X=2 contradiz X=1 e Y=f(3 U) também contradiz X=1. Será levantada uma excepção, que no nível superior aparecerá ao utilizador sobre a forma de mensagem de erro.

4.13.4 Disjunções Em Oz as disjunções são especificada usando a instrução or. As

expressões disjuntivas são expressas na forma de clausulas. Uma clausula é composta por um guarda G e por corpo S1.

or

G1 then S1[] G2 then S2

. …[] GN then SN

end Assumindo que é executa numa thread num espaço SP, a semântica

da instrução or é a seguinte:

A thread bloqueia. São criados N espaços SP1, SP2, ..., SPn com uma thread associada a

cada executando cada um dos guardas G1, G2, ..., Gn. A execução da thread principal fica bloqueada até que pelo menos um

dos espaços computacionais filhos não falhe. Se todos os espaços filhos falharem a thread pai levantada uma

Page 86: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

86

condição de falha no seu espaço. Se o espaço da thread for o topo da hierarquia de espaços, é levantada uma excepção. Caso não seja o topo, simplesmente fica um espaço falhado.

Se restar um espaço que não tenha falhado é fundido com o espaço pai. A execução da thread inicial é retomada executando o corpo da clausula correspondente ao guarda associado ao espaço que não falhou.

Pode abreviar-se a seguinte expressão:

or...[] Gi then skip...end

da seguinte forma:

or...[] Gi...end

A instrução or não introduz o conceito de não determinismo. No prolog não existe uma instrução correspondente ao or. O disjuntor P ;

Q cria um ponto de escolha sujeito a backtraking.

4.13.6 Execução orientada à determinação Este processo consiste em sincronizar computações, baseando-se na

indecisão sobre qual a disjunção a seguir, tendo a thread que aguardar que certas variáveis existentes nos guardas dessas disjunções sejam conhecidas.

proc {Ints N Xs}

or N = 0 Xs = nil[] Xr in

N > 0 = true Xs = N|Xr{Inst N-1 Xr}

endendlocal

proc {Sum3 Xs N R}or Xs = nil R = N[] X|Xr = Xs in

{Sum3 Xr X+N R}end

endin proc {Sum Xs R} {Sum3 Xs 0 R} endendlocal N S R in

thread {Ints N S} endthread {Sum S {Browse}} endN = 1000

end

Neste exemplo a thread que executa Ints, suspende até que N seja

Page 87: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

87

conhecido, porque não consegue escolher uma disjunção. De forma similar Sum3 irá esperar que S seja conhecido. Como o S é criado incrementalmente o comportamento de Sum3 será uma secessão de suspensões e execuções. O programa começa realmente a funcionar quando N é instanciado com o valor 1000.

4.13.7 Lógica Condicional Lógica condicional em Oz é exprimida pela expressão:

cond X1 ... XN in S0 then S1 else S2 end Assumindo que a thread executa num espaço SP, esta instrução tem a

seguinte semântica:

A thread fica bloqueada. Um espaço computacional SP1 é criado com uma só thread que executa

o guarda cond X1 ... XN in S. A execução da thread pai fica suspensa até que SP1 seja vinculado ou

desvinculado. Estas condições podem nunca ocorrer, por exemplo, se existir uma thread suspensa ou se existir uma thread em execução permanente.

Se a SP1 for desvinculado a thread pai continua com a execução de S2. Se SP1 for vinculado, é fundido com com SP e a thread pai continua

com a execução de S1. A instrução cond é parecida com as expressões condicionais do Prolog (

P->Q ; R ). O Oz é um pouco mais cuidadoso no espaço de abrangência das variáveis, tendo que ser declaradas explicitamente.

4.13.8 Expressões condicionais paralelas Uma expressão condicional paralela é expressa da seguinte forma:

cond G1 then S1[] G2 then S2...else SN end

Uma expressão condicional paralela e executada avaliando todas as condições G1, G2, ..., G(N-1), de forma arbitraria e concorrente, em que cada thread tem o seu próprio espaço. Se o espaço Gi for vinculado, a instrução Si é escolhida pela thread pai. Se todas os espaços falharem, a instrução SN é escolhida. Em caso contrário a thread fica suspensa.

Page 88: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

88

Um exemplo tipico de programação lógica concorrente é a fusão binária. A fusão binária consiste um fundir dois streams, em que a ordem de chegada dos elementos dos dois streams determinam a ordem em que estarão na lista resultante.

proc {Merge Xs Ys Zs}cond

Xs = nil then Zs = Ys[] Ys = nil then Zs = Xr[] X Xr in Xs = X|Xr then Zr in

Zs = X|Zr {Merge Xr Ys Zr}[] Y Yr in Ys = Y|Yr then Zr in

Zs = Y|Zr {Merge Xs Yr Zr}end

end

Normalmente a fusão binária é ineficiente. Uma maneira mais eficiente é usando células e streams.

proc {MMerge STs L}

C = {NewCell L}proc {MM STs S E}

case STsof ST|STr then M in

thread{ForAll ST proc{$ X} ST1 in {Exchange C

X|ST1 ST1} end}M=S

end{MM STr M E}

[] nil then skip[] merge(STs1 STs2) then M in

thread {MM STs1 S M} end{MM STs2 M E}

endend

inthread {MM STs unit E} endthread if E==unit then L = nil end end

end

Para obter fusão binária basta fazer {MMerge [X Y] Z}.

4.13.9 Programas não deterministas e busca O Oz permite a programação não deterministica e de busca ao mesmo

nível que o Prolog, embora haja algumas diferenças. Por exemplo o Prolog, por omissão já implementa uma estratégia de busca conhecida por backtraking. O Oz vai mais longe pois permite ao programador desenvolver a sua própria estratégia de busca, de forma a separar da programação ortogonal a programação não deterministica.

O Oz possui uma instrução que define um ponto de escolha, a estratégia de busca pode ser programada separadamente.

A instrução dis permite definir um ponto de escolha.

Page 89: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

89

proc {Append Xs Ys Zs}dis

Xs = nil Ys = Zs then skip[] X Xr Zr in

Xs = X|Xr Zs = X|Zr then{Append Xr Ys Zr}

endend

É idêntico ao predicado append/3 do Prolog.

append([], Ys, Ys).append([X|Xr], Ys, [X|Zr]) :- append(Xr, Yr, Zr).

Tal como o or, o dis pode ser abreviado da seguinte maneira:

proc {Append Xs Ys Zs}dis

Xs = nil Ys = Zs[] X Xr Zr in

Xs = X|Xr Zs = X|Zr then{Append Xr Ys Zr}

endend

Se este procedimento for chamado da seguinte maneira:

local X in{Append [1 2 3] [a b c] X}{Browse X}

end

Neste caso o comportamento será igual ao da instrução or, isto é, X será instanciado deterministicamente com a lista [1 2 3 a b c]. Mas se for chamado na forma:

local X Y in

{Append X Y [1 2 3 a b c]}{Browse X#Y}

end

O comportamento é semelhante á instrução or. A thread bloqueia quando tenta executar {Append X Y [1 2 3 a b c]}. No entanto existe um diferença que reside criará um ponto de escolha com duas alternativas:

X = nil Y = [1 2 3 a b c] then skip Xr Xr in

X = 1|Xr Zr = [2 3 a b c] then {Append Xr Y Zr}

Page 90: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

90

4.13.10 Exemplos Definição de uma gramática:

Sentence(P) --> NounPhrase(X P1 P) VerbPhrase(X P1)NounPhrase(X P1 P) --> Determiner(X P2 P1 P) Noun(X P3)RelClause(X P3 P2)NounPhrase(X P P) --> Name(X)VerbPhrase(X P) --> TransVerb(X Y P1) NounPhrase(Y P1 P)| InstransVerb(X P)RelClause(X P1 and(P1 P2)) --> [that] VerbPhrase(X P2)RelClause(_ P P) --> []Determiner(X P1 P2 all(X imp(P1 P2))) --> [every]Determiner(X P1 P2 exits(X and(P1 P2))) --> [a]Noun(X man(X)) --> [man]Noun(X woman(X)) --> [woman]name(john) --> [john]name(jan) --> [jan]TransVerb(X Y loves(X Y)) --> [loves]IntransVerb(X lives(X)) --> [lives]

proc {Sentence P S0#S}X P1 S1 in{NounPhrase X P1 P S0#S1}{VerbPhrase X P1 S1#S}

endproc {NounPhrase X P1 P S0#S}

choiceP2 P3 S1 S2 in{Determiner X P2 P1 P S0#S1}{Noun X P3 S1#S2}{RelClause X P3 P2 S2#S}

[] {Name X S0#S}P1 = P

endend

proc {VerbPhrase X P S0#S}choice

Y P1 S1 in{TransVerb X Y P1 S0#S1}{NounPhrase Y P1 P S1#S}

[] {IntransVerb X P S0#S}end

end

proc {TransVerb X Y Z S0#S}S0 = loves|SZ = loves(X Y)

End

proc {IntransVerb X Y S0#S}S0 = lives|SY = lives(X)

End

proc {Name X S0#S}S0 = X|S

Page 91: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

91

choiceX = john

[]X = jan

endend

proc {Noun X Y S0#S}choice

S0 = man|SY = man(X)

[] S0 = woman|SY = woman(X)

endend

proc {Determiner X P1 P2 P S0#S}choice

S0 = every|SP = all(X imp(P1 P2))

[] S0 = a|SP = exists(X and(P1 P2))end

end

proc {RelClause X P1 P S0#S}P2 inchoice

S1 inS0 = that|S1P = and(P1 P2){VerbPhrase X P2 S1#S}

[] S0 = SP = P1

endend

declareproc {Main P}

{Sentence P [every man that lives loves a woman]#nil}end

Instrução dis:

declare Edgeproc {Connected X Y}

dis{Edge X Y}

[] Z in {Edge X Z} {Connected Z Y}end

end

proc {Edge X Y}dis

X = 1 Y = 2[] X = 2 Y = 1[] X = 2 Y = 3[] X = 3 Y = 4[] X = 2 Y = 5[] X = 5 Y = 6[] X = 4 Y = 6[] X = 6 Y = 7

Page 92: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

92

[] X = 6 Y = 8[] X = 1 Y = 5[] X = 5 Y = 1

endend{ExploreOne

proc {$ L}X Y inX#Y = L {Connected X Y}

end}{Browse

{SearchAllproc {$ L}

X Y inX#Y = L {Connected X Y}

end}

} Negação:

proc {NotP P}{SearchOne proc {$ L} {P} L=unit end $} = nil

end

proc {ConnectedEnh X Y Visited}dis

{Edge X Y}[] Z in

{Edge X Z}{NotP proc{$} {Member Z Visited} end}{ConnectedEnh Z Y Z|Visited}

endend

Page 93: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

93

5. Tutorial de Oz Distribuído O Mozart-Oz possui um modelo de programação distribuída que consegue

separar a funcionalidade da aplicação da sua estrutura distribuída. Possui também mecanismos para tolerância a falhas, computação aberta, e algum suporte para segurança. Para as próximas versões irá ser dada uma maior atenção aos aspectos ainda pouco desenvolvidos deste sistema de programação, como por exemplo, a segurança.

Neste tutorial podemos encontrar informação relativa à programação distribuída em Mozart-Oz, nomeadamente, sobre as abstracções de programação distribuída em Oz, objectos estacionários, agentes móveis, tolerância a falhas, etc ..., sempre acompanhando os novos conceitos que são introduzidos, com exemplos práticos da sua aplicação.

A distribuição nesta linguagem é garantida por quatro módulos:

Connection – Disponibiliza o mecanismo básico (conhecidos por tickets) para a conexão entre duas aplicações

Remote – Permite que uma aplicação possa criar um site e ligar-se a ele. Um site pode ser criado na mesma máquina ou num máquina remota.

Pickle – Permite guardar ou ler informação de URLs e ficheiros. Fault – Possui mecanismos de tratamento e detecção de falhas.

Para uma melhor compreensão dos assuntos referidos neste capítulo

recomenda-se a leitura do capítulo 3, onde é abordado o Oz distribuído de uma forma teórica.

5.1 Nomes Globais Existem dois tipos de nomes globais em OZ:

Referências internas – São referências que existem dentro dum espaço computacional do Oz. Todas as estruturas de dados do Oz são endereçadas por estas referências. Estas referências correspondem as apontadores e apontadores de rede em outras linguagens de programação, embora se encontrem protegidos do programador como por exemplo no Java.

Referências externas – Podem existir em qualquer sitio, isto é, quer no interior ou exterior de espaços computacionais do Oz. Possuem nomes globais externos conhecidos. São representados por strings, logo podem ser comunicados e guardados de diferentes formas, por exemplo: páginas Web, espaços computacionais do Oz, etc ... São necessários para que uma aplicação comunique com o exterior.

Page 94: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

94

Existem três tipos de nomes globais externos:

Ticket – É uma string que referencia uma qualquer entidade da linguagem numa aplicação em execução. Os tickets são criados no interior de uma aplicação Oz em execução e podem ser usados para conectar aplicações.

URL – É uma string referencia um ficheiro numa rede. Em Mozart um ficheiro pode ser um pickle. Um pickle pode conter procedimentos, classes, functors, etc ...

Hostname – É uma string que segue o sintaxe do DNS, que identifica o nome de uma máquina. O hostname pode ser usado para iniciar um processo noutra máquina.

5.1.1 Conectar aplicações através de tickets Se por exemplo tivermos um stream e desejarmos que outras aplicações o

possam partilhar, basta associa-lo a um ticket. Se as outras aplicações conhecerem o ticket podem comunicar e aceder ao stream.

Existem a seguintes operações sobre tickets:

{Connection.offer X T} Cria um ticket T para X. X pode ser uma qualquer entidade Oz. O ticket só pode ser utilizado uma vez, caso se tente usar mais de uma vez é gerada uma excepção.

{Connection.offerUnlimited X T} Cria um ticket T para a entidade X. O ticket pode ser usado um número de vezes ilimitado.

{Connection.take T X} Cria uma referência de uma entidade Oz para o ticket T. Isto quer dizer que, se a aplicação que criar a referência estiver noutra máquina irão haver comunicações pela rede.

No exemplo seguinte é criado um ticket para um stream:

declare Stream Tkt in{Connection.offerUnlimited Stream Tkt}{Show Tkt}

O ticket fica com um valor semelhante a : ’x-ozticket://193.10.66.30:9002:SpGK0:U4v/y:s:f:xl’ Vejamos agora um exemplo de uma outra aplicação, que sendo executada

noutra localização referencia o mesmo stream:

declare Stream in{Connection.take’x-ozticket://193.10.66.30:9002:SpGK0:U4v/y:s:f:xl’Stream}{Browse Stream}

Page 95: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

95

5.1.2 Estruturas de dados persistentes usando pickes Uma aplicação pode guardar e ler qualquer estrutura de dados que não

seja baseada em estados num ficheiro. Os ficheiros podem estar em localizações remotas, sendo acedidos na forma de URLs. O modulo Pickle implementa estas funcionalidades.

O exemplo seguinte ilustra como pode ser criada uma função e guardada num ficheiro:

declarefun {Fact N}

if N=<1 then 1 else N*{Fact N-1} endend{Pickle.save Fact "~pvr/public_html/fact"}

Como o ficheiro está localizado numa área publica de html, pode ser

acedido da seguinte forma:

declareFact={Pickle.load "http://www.info.ucl.ac.be/~pvr/fact"}{Browse {Fact 10}}

5.1.3 Computações Remotas e Functors Uma aplicação pode iniciar um computação numa máquina remota usando

os recursos dessa máquina e continuar a interagir com a aplicação. As computações são definidas como functors, pois o functor permite especificar computações e recursos. O functor é a especificação dum módulo e da definição dos recursos que esse módulo necessita de forma explicita.

R={New Remote.manager init(host:"sinuhe.sics.se")}F=functor export x:X define X={Fact 30} end

M={R apply(F $)}

{Browse M.x}

Em primeiro lugar é criada uma referencia á máquina remota. Depois

constroi-se a computação propriamente dita, que consiste num functor que é atribuído a F. O resultado X é devolvido ao lado cliente no módulo M.

Outra forma é usando o functor com uma referencia externa:

declare F M X inF=functor define {Fact 30 X} endM={R apply(F $)}{Browse X}

Page 96: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

96

5.1.4 Servidores Servidores são computações que estão em permanente execução e

garantem serviços a clientes. Vejamos partindo dum exemplo simples, como podem ser criados servidores com o Mozart-Oz.

% Cria um servidor

declare Str Prt Srv in{NewPort Str Prt}thread

{ForAll Str proc {$ S} S="Olá mundo" end}endproc {Srv X}

{Send Prt X}end

% O servidor fica disponivel através de URL:% (passando o nome do ficheiro que também é acessível porURL)

{Pickle.save {Connection.offerUnlimited Srv}"/usr/staff/pvr/public_html/hw"}

Um cliente poderia aceder a este servidor da seguinte maneira:

declare Srv inSrv={Connection.take {Pickle.load

"http://www.info.ucl.ac.be/~pvr/hw"}}local X in

{Srv X}{Browse X}

end

Irá mostrar “Olá mundo” na janela do browser. O cliente conectando-se recebe uma referencia do servidor. Isto faz com

que os espaços computacionais do servidor e do cliente sejam fundidos num só, o que faz com que o cliente e o servidor possam comunicar como se estivessem no mesmo processo.

No exemplo anterior utilizou-se um port para recolher as mensagens dos

clientes. Vejamos agora o mesmo exemplo utilizando objectos estacionários. O servidor seria :

declareclass HelloWorld

meth hw(X) X="Hello world" endendSrv={NewStat HelloWorld hw(_)} % Requer um método inicial

O cliente poderia chamar o servidor através de {Srv hw(X)}. As entidades estacionárias são muito importantes. O Oz possui duas

formas de criar entidades estacionárias. A primeira é:

Page 97: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

97

DeclareObject={NewStat Class init}

Quando executado num site, o NewStat, recebe uma classe e um método inicial e cria um objecto que é estacionário nesse site.

5.1.5 Servidor de computações Um servidor de computações pode ser escrito da seguinte forma, usando

objecto estacionários:

declareclass ComputeServer

meth init skip endmeth run(P) {P} end

endC={NewStat ComputeServer init}

O servidor pode ser disponibilizado através de uma URL como nos exemplos anteriores. Vejamos agora como um cliente pode usar o servidor de computações:

declarefun {Fibo N}

if N<2 then 1 else {Fibo N-1}+{Fibo N-2} endend

% Executa a primeira computação remotamentelocal F in

{C run(proc {$} F={Fibo 30} end)}{Browse F}

end

% Executa a segunda computação localmentelocal F in

F={Fibo 30}{Browse F}

end

Para executar uma computação remota basta usar {C run(P)}. Sendo o Oz completamente transparente o procedimento P pode ter qualquer instrução de Oz, mas se usar recursos deve ser executado remotamente através de funtors como já foi visto anteriormente.

5.1.6 Servidor de computações com funtors Vejamos como um cliente pode executar um servidor de computações num

site remoto.

Page 98: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

98

declareR={New Remote.managerinit(host:"rainbow.info.ucl.ac.be")}

declare F CF=functor

export cs:CSdefine

class ComputeServermeth init skip endmeth run(P) {P} end

endCS={NewStat ComputeServer init}

end

C={R apply(F $)}.cs % Arranca o servidor

O cliente pode usar o servidor da seguinte forma:

local F in{C run(proc {$} F={Fibo 30} end)}{Browse F}

end

5.1.7 Servidor dinamicamente extensível O Oz permite fazer actualizações num servidor, sem que se tenha de o

parar. Em Java não é possível fazer actualizações deste tipo. Em Mozart-Oz esta actualização pode até ser feita interactivamente. Um servidor destes pode ser desenvolvido da seguinte forma:

declareproc {NewUpgradableStat Class Init ?Upg ?Srv}

Obj={New Class Init}C={NewCell Obj}

inSrv={MakeStatproc{$ M} {{Access C} M} end}Upg={MakeStatproc{$ Class2#Init2} {Assign C {New Class2 Init2}}end}

end

Retorna um servidor Srv e um procedimento estacionário Upg que serve para actualizar o servidor. O servidor é actualizável porque todas as chamadas ao objecto são feitas indirectamente através de uma célula C.

Para criar o servidor basta fazer:

declare Srv Upg inSrv={NewUpgradableStat ComputeServer init Upg}

Vejamos agora do lado cliente, como se pode actualizar o servidor sem o

parar. No exemplo cria-se um novo objecto partindo de uma classe nova.

Page 99: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

99

declareclass CComputeServer from ComputeServer

meth run(P Prio<=medium)thread

{Thread.setThisPriority Prio}ComputeServer,run(P)

endend

endSrv2={Upg CComputeServer#init}

O método run é sobrecarregado por um novo método que recebe dois

parâmetros : P que é a computação a executar remotamente e Prio que é a prioridade da thread que executa a computação P.

5.2 Agentes Móveis Um Agente é uma computação distribuída que está organizada como um

conjunto de tarefas. Uma tarefa é uma computação que usa recursos de um determinado site. Recursos são por exemplo: sistemas de ficheiros, periféricos, acessos ao sistema operativo, etc... Uma tarefa pode iniciar tarefas noutros sites com os recursos a ser usados bem especificados. O comportamento distribuido do agente é decidido pelo próprio agente.

Por exemplo, um agente A delega concorrentemente 10 tarefas a sites

remotos e espera que todas sejam completadas para continuar. Os servidores de agentes são representado por AS0 a AS9 e o seu trabalho é representado por functors contento um procedimento (P0 a P9) de um argumento que irá ser instanciado com o resultado.

declareA=functor

defineX0 ... X9{AS0 functor define {P0 X0} end}...{AS9 functor define {P9 X9} end}{Wait X0} ... {Wait X9}...

end

5.2.1 Instalação de Agentes Vejamos um exemplo simples dum agente que se desloca a um sitio,

interroga o sistema operativo e depois regressa. Em primeiro lugar necessitamos de saber como instalar o servidor de agentes para que o agente se possa deslocar. Só depois podemos programar o agente.

Page 100: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

100

Para que o agente se possa deslocar a um determinado site, tem de haver nesse site algo que o possa receber. Esse algo são os servidores de agentes. Estes servidores aceitam functors (que representam agentes ou partes de agentes) e aplicam-nos ao site. Para instalar um servidor de agentes num site usamos o functor AgentServer. Quando o AgentServer é instalado num site, cria o módulo AS nesse site com as seguintes operações:

O servidor de agentes é acedido por AS.server. Um calculo é iniciado

assincronamente chamando {AS.server F} onde F é o functor onde está especificado o calculo e os recursos que usa.

A operação {AS.publishserver FN} cria um ficheiro FN com o ticket do servidor de agentes.

A operação {AS.getserver UFN ?AS} quando lhe é passada uma URL ou o nome de um ficheiro UFN retorna uma referência ao respectivo servidor de agentes.

Se assumirmos que a seguinte URL é conhecida:

http://www.info.ucl.ac.be/~pvr/agents.ozf

e que referencia o funtor AgentServer. O código seguinte cria um servidor

de agentes local e torna-o acessível através da URL:

http://www.info.ucl.ac.be/~pvr/as1 Vejamos:

declare GetServer inlocal

% le o AgentServer:AgentServer={Pickle.load

“http://www.info.ucl.ac.be/~pvr/agents.ozf”}% Instala o AgentServer localmente: (cria um servidor

de agentes)[AS1]={Module.apply [AgentServer]}% Publica o servidor de agentes{AS1.publishserver “/usr/staff/pvr/public_html/as1”}

inGetServer=AS1.getserver

End

Criemos agora um segundo agente. Este será remoto e acessível através da URL

http://www.info.ucl.ac.be/~pvr/as2

localRF=functor import Pickle Module export done:Ddefine

AgentServer={Pickle.load“http://www.info.ucl.ac.be/~pvr/agents.ozf”}

[AS2]={Module.apply [AgentServer]}{AS2.publishserver

“/usr/staff/pvr/public_html/as2”}endRM={New Remote.manager init}M={RM apply(RF $)}

inskip

end

Page 101: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

101

Podemos aceder aos servidores de agentes da seguinte maneira:

declareServer1={GetServer “http://www.info.ucl.ac.be/~pvr/as1”}Server2={GetServer “http://www.info.ucl.ac.be/~pvr/as2”}

5.2.2 Programação de agentes Agora que já estão instalados os servidores podemos começar a

programar o agente. O agente irá interrogar o sistema operativo e irá retornar as horas.

declare D1 in{Server2 functor import OS define D1={OS.time} end}{Browse D1}

Primeiro cria a variável D1 e depois executa um agente num site remoto. O

agente apenas necessita de um recurso (OS), que consiste num módulo que disponibiliza acesso a funções do sistema operativo. Como o agente é criado assincronamente, quando o Browse for executado, a variável D1 pode ainda não estar instanciada. Isto não é necessariamente um problema pois no Oz as operações suspendem enquanto as variáveis que necessitam não estiverem instanciadas. Para garantir que a variável D1 é mesmo instanciada pode usar-se {Wait D1}.

Modifiquemos agora o primeiro exemplo de forma a obter um agente isolado, isto é, o agente é um functor que pode ser compilado e executado isoladamente ou por outra aplicação. Assumimos que o functor AgentServer está numa localização standard.

functor

import System AgentServerdefine

GetServer=AgentServer.getserverServer2={GetServer

"http://www.info.ucl.ac.be/~pvr/as2"}D1{Server2 functor import OS define D1={OS.time} end}{System.show D1}

end

A única diferença do exemplo anterior é que neste exemplo usa o GetServer para aceder ao servidor de agentes.

Se o functor AgentServer não estiver numa localização standard, o agente isolado têm que explicitamente referenciar a sua localização.

Page 102: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

102

functor

import System Pickle Moduledefine

AgentServer={Pickle.load"http://www.info.ucl.ac.be/~pvr/agents.ozf"}

[AS]={Module.apply [AgentServer]}GetServer=AS.getserverServer2={GetServer

"http://www.info.ucl.ac.be/~pvr/as2"}D1{Server2 functor import OS define D1={OS.time} end}{System.show D1}

end

5.2.3 A Definição do servidor de agentes A ferramenta que atribui funcionalidade aos agentes é o AgentServer.

Quando instalado, este functor cria um servidor de agentes, com um procedimento de publicação, um procedimento de acesso a qualquer servidor de agentes publicado.

functorimport Module Connection Pickleexport

server:ASpublishserver:PublishServergetserver:GetServer

defineS P={NewPort S}proc {InstallFunctors S}

case Sof F|S2 then

try[_] = {Module.apply [F]}

catch _ then skip end{InstallFunctors S2}else skip end

endthread {InstallFunctors S} endproc {AS F} {Send P F} endT={Connection.offerUnlimited AS}% O servidor fica disponivel pelo ficheiro FN:% O servidor de agentes é assincronoproc {PublishServer FN}

{Pickle.save T FN}end% Acesso ao servidor que está no ficheiro/URL UFN:proc {GetServer UFN AS}

tryT={Pickle.load UFN}

inAS={Connection.take T}

catch _ thenraise serverUnavailable end

endend

end

Page 103: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

103

Existem duas formas de usar o AgentServer. Ou se compila com o

compilador e se copia o ficheiro .ozf para uma área publica (public_html), ou se quisermos usar o AgentServer interactivamente no OPI, temos de alterar o código da seguinte maneira:

declareAgentServer=

functor... (corpo do functor)end

{Pickle.save AgentServer"/usr/staff/pvr/public_html/agents.ozf"}

5.3 Tolerância a falhas Iremos ver agora como se podem desenvolver aplicações robustas com

tolerância a falhas.

5.3.1 O servidor de “Olá Mundo” com tolerância a falhas O servidor deve continuar a funcionar mesmo que haja uma falha num dos

seus clientes. O cliente deve ser informado de uma falha no servidor em tempo finito através de uma excepção serverError.

Em seguida iremos ver como se pode construir o servidor de “Olá mundo”

com através do modelo básico de falhas. Neste modelo o sistema gera excepções quando existem tentativas de fazer operações sobre entidades que têm problemas relacionado com a distribuição. Estas excepções assumem a forma de system(dp(conditions:FS ...) ...), onde FS é a lista de estados das falhas. Por omissão o sistema gera excepções se os estados das falhas forem tempFail ou permFail.

Vejamos as seguintes abstracções:

{SafeSend Prt X} – envia para um port e gera uma excepção serverError se é permanentemente impossivel.

{SafeWait X T} – espera que X seja instanciado e gera uma excepção caso isso seja permanentemente impossível ou o tempo T seja ultrapassado.

A vantagem de criar estas abstracções é que possibilita-nos ter um

programa quase igual ao programa sem ser tolerante a falhas. Vejamos o servidor com tolerância a falhas:

Page 104: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

104

declare Str Prt Srv in{NewPort Str Prt}thread

{ForAll Strproc {$ S}

tryS="Hello world"

catch system(dp(...) ...) then skip endend}

endproc {Srv X}

{SafeSend Prt X}end

{Pickle.save {Connection.offerUnlimited Srv}"/usr/staff/pvr/public_html/hw"}

A instanciação de S está na expressão try ... catch para que possa tolerar falhas dos clientes.

Vejamos um cliente:

declare Srvtry X in

trySrv={Connection.take {Pickle.load

"http://www.info.ucl.ac.be/~pvr/hw"}}catch _ then raise serverError endend{Srv X}{SafeWait X infinity}{Browse X}catch serverError then

{Browse ’Server down’}end

O cliente efectua duas operações distribuídas, o send, que é substituído

pelo safeSend dentro de Srv, e o wait que é substituído pelo SafeWait. Se houver problemas no envio ou na recepção de uma mensagem é gerada uma excepção serverError. Também são geradas excepções se existirem falhas na altura do estabelecimento de conexão, isto é, na execução das operações Connection.take e Pickle.load.

Vejamos agora a definição das operações SafeSend e SafeWait. As funções FOneOf e FSomeOf serão definidas depois.

declareproc {SafeSend Prt X}

try{Send Prt X}

catch system(dp(conditions:FS ...) ...) thenif {FOneOf permFail FS} then

raise serverError endelseif {FOneOf tempFail FS} then

{Delay 100} {SafeSend Prt X}else skip end

endend

Page 105: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

105

Gera uma excepção serverError quando existe uma falha permanente no servidor e quando existe uma falha temporária volta a tentar de 100 em 100 ms.

declarelocal

proc {InnerSafeWait X Time}try

cond {Wait X} then skip[] {Wait Time} then raise serverError endend

catch system(dp(conditions:FS ...) ...) thenif {FSomeOf [permFail remoteProblem(permSome)]

FS} thenraise serverError end

if {FSomeOf [tempFail remoteProblem(tempSome)]FS} then

{Delay 100} {InnerSafeWait X Time}else skip end

endend

inproc {SafeWait X TimeOut}

Time inif TimeOut\=infinity then

thread {Delay TimeOut} Time=done endend{Fault.enable X ’thread’(this)[permFail remoteProblem(permSome) tempFail

remoteProblem(tempSome)] _}{InnerSafeWait X Time}

endend

Gera uma excepção serverError quando há uma falha permanente no

servidor e tenta de 100 em 100 ms quando a falha é temporária. A variável X existe apenas no cliente e no servidor. Quando sucede remoteProblem(permFail:_ ...)

significa que o servidor avariou. Para impedir que o cliente fique a funcionar indefinidamente introduz-se um timeout.

As funções FOneOf e FsomeOf foram criadas para simplificar a verificação

dos estados das falhas. A função {FOneOf permFail AFS} é true se o estado da falha permFail no

conjunto de falhas AFS.

declarefun {FOneOf F AFS}

case AFS of nil then false[] AF2|AFS2 then

case F#AF2of permFail#permFail(...) then true[] tempFail#tempFail(...) then true[] remoteProblem(I)#remoteProblem(I ...) then trueelse {FOneOf F AFS2}end

endend

Page 106: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

106

A função {FSomeOf [permFail remoteProblem(permSome)] AFS} retorna true se permFail ou remoteProblem(permSome) ou ambos ocorrem no conjunto AFS.

declarefun {FSomeOf FS AFS}

case FS of nil then false[] F2|FS2 then{FOneOf F2 AFS} orelse {FSomeOf FS2 AFS}end

end

5.3.2 Tolerância a falhas em objectos estacionários Os objectos estacionários têm de ter comportamentos bem definidos

quando existem falhas. A chamada C={NewSafeStat Class Init} cria um novo servidor C. Se não existirem problemas com a distribuição, a semântica da

chamada {C Msg} é igual à execução centralizada do objecto incluindo as excepções geradas.

Se houver um problema na distribuição que impeça de completar a chamada {C Msg} será gerada a excepção remoteObjectError.

Se existir um problema na comunicação com o cliente, o servidor tenta a comunicação por um curto período de tempo e depois desiste. Isto não irá afectar a execução do servidor.

Em primeiro lugar vamos criar um objecto estacionário remoto. A classe

seguinte define um contador.

declareclass Counter

attr imeth init i<-0 endmeth get(X) X=@i endmeth inc i<-@i+1 end

end O functor definido a seguir cria uma instancia da classe usando o

NewSafeStat, Note-se que o objecto ainda não é criado.

declareF=functor

import Faultexport statObj:StatObjdefine

{Fault.defaultEnable nil _}StatObj={NewSafeStat Counter init}

End O módulo Fault é importado porque queremos garantir que irá ser usado o

Fault do site onde o functor é instalado.

Page 107: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

107

De seguida iremos criar um site remoto e uma instancia da classe Counter chamada StatObj. A classe Remote.manager dá-nos a possibilidade de criar de várias formas um site remoto. Neste exemplo usamos a opção fork:sh, que cria um processo novo na mesma máquina. O processo é acessível através do gestor de módulo MM, que permite instalar functors no site remoto.

declareMM={New Remote.manager init(fork:sh)}StatObj={MM apply(F $)}.statObj

Finalmente podemos chamar o objecto. A chamada é feita dentro de try catch para demonstrar a tolerância a falhas. Se “matarmos” o processo remoto, veremos que continua a funcionar.

try

{StatObj inc}{StatObj inc}{Show {StatObj get($)}}

catch X then{Show X}

end

5.3.3 Tolerância a falhas usando guardas A melhor maneira mais simples de implementar tolerância a falhas em

objecto estacionários através de guardas (Guards). Um guarda vigia uma computação. Se existir uma falha de distribuição o guarda termina essa computação.

O procedimento {Guard E FS S1 S2} guarda a entidade para os estados de falhas FS dentro da instrução S1, substituindo S1 por S2 caso uma falha ocorra.

A definição de NewSafeStat é idêntica à definição de NewStat só que a primeira usa guardas em todas as suas operações distribuídas.

O exemplo seguinte mostra um objecto estacionário baseado em guardas.

proc {MakeStat PO ?StatP}S P={NewPort S}N={NewName}

in% Interface com o Servidor:% o cliente gera uma excepção se houver um problema% no servidorproc {StatP M}R in

{Fault.enable R ’thread’(this) nil _}{Guard P [permFail]

proc {$}{Send P M#R}if R==N then skip else raise R end end

endproc {$} raise remoteObjectError endend}

end % Implementação do Servidor% O servidor termina o pedido do cliente se% se houver um problema com o clientethread{ForAll S

proc{$ M#R}thread RL in

try {PO M} RL=N catch X then RL=X end{Guard R [permFail

Page 108: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

108

remoteProblem(permSome)]proc {$} R=RL endproc {$} skip end}

endend}

end end

proc {NewSafeStat Class Init Object}Object={MakeStat {New Class Init}}

end Vejamos agora a definição de Guarda. O guarda permite-nos substituir

uma instrução S1 por outra S2 caso exista uma falha. O procedimento {Guard E FS

S1 S2} primeiro desliga todas as excepções em E. Depois executa S1 com um watcher local W. Se o watcher for chamado durante a execução de S1, então S1 é interrompido e é gerada uma excepção N. Consequentemente S2 é executado.

declareproc {Guard E FS S1 S2}

N={NewName}T={Thread.this}proc {W E FS} {Thread.injectException T N} end

in{Fault.enable E ’thread’(T) nil _}try

{LocalWatcher E FS W S1}catch X then

if X==N then{S2}

elseraise X end

endend

endUm watcher local é um watcher que é instalado apenas durante a

execução de uma instrução. Quando a instrução termina ou é gerada uma excepção o watcher é removido.

O procedimento {LocalWatcher E FS W S} vigia a entidade E para os estados de falhas FS com o watcher W durante a execução de S.

declareproc {LocalWatcher E FS W S}

{Fault.installWatcher E FS W _}try

{S}finally

{Fault.deInstallWatcher E W _}end

end

Page 109: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

109

5.3.4 Tolerância a falhas baseada em excepções Iremos ver como implementar o NewSafeStat usando tolerância a falhas

baseada em excepções. Este modelo de tolerância a falhas é usado por outras linguagens de programação com por exemplo o Java.

declareproc {MakeStat PO ?StatP}

S P={NewPort S}N={NewName}EndLoop TryToBind

in% Interface do cliente com o servidor% O cliente chama o servidor% Sincronização do cliente com o servidor

% Implementação do servidor% Ciclo principal do servidor% Sincronização com o cliente

end

proc {NewSafeStat Class Init ?Object}Object={MakeStat {New Class Init}}

end

Primeiro o cliente envia uma mensagem ao servidor com uma variável de

sincronização. Esta variável é usada para sinalizar que o servidor terminou a chamada ao objecto. Se foram geradas excepções, estas são passadas ao cliente. Se existir uma falha permanente no envio, é gerada a excepção remoteObjectError, caso exista uma falha temporária o envio e retentado de 100 em 100 ms.

% O cliente chama o servidorproc {StatP M}R in

try{Send P M#R}

catch system(dp(conditions:FS ...) ...) thenif {FOneOf permFail FS} then

raise remoteObjectError endelseif {FOneOf tempFail FS} then

{Delay 100}{StatP M}

else skip endend{EndLoop R}

end O cliente espera que o servidor instancie uma variável de sincronização.

No caso de existir uma falha permanente é gerada uma excepção, caso exista uma falha temporária espera 100 ms e tenta de novo.

Page 110: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

110

% Sincronização do cliente com o servidorproc {EndLoop R}

{Fault.enable R ’thread’(this)[permFail remoteProblem(permSome) tempFail

remoteProblem(tempSome)] _}try

if R==N then skip else raise R end endcatch system(dp(conditions:FS ...) ...) then

if {FSomeOf [permFail remoteProblem(permSome)] FS}then

raise remoteObjectError endelseif {FSomeOf [tempFail remoteProblem(tempSome)]

FS} then{Delay 100} {EndLoop R}

else skip endend

end

O servidor cria uma nova thread para cada cliente.

% Ciclo principal do servidorthread

{ForAll Sproc {$ M#R}

threadtry

{PO M}{TryToBind 10 R N}

catch X thentry

{TryToBind 10 R X}catch Y then skip end

endend

end}

end

% Sincronização com o clienteproc {TryToBind Count R N}

if Count==0 then skipelse

tryR=N

catch system(dp(conditions:FS ...) ...) thenif {FOneOf tempFail FS} then

{Delay 2000}{TryToBind Count-1 R N}

else skip endend

endend

Page 111: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

111

6 Programação Gráfica Em Mozart a programação do interfaces gráficas é baseada em Widgets

que consistem em objectos que representam entidades graficas, como por exemplo janelas, menus, botões, etc... As janelas são descritas composicionalmente através de hierarquias de objectos e estão sujeitas modificações dinâmicas e interactivas.

Este tipo de programação consiste num interface orientado a objectos para o Tk. Todas as características do Oz são aplicáveis a este tipo de programação, tais como concorrência e entidades first-class. Do Tk o interface herda um conjunto de abstracções bastante poderosas.

O objectivo deste capitulo é apenas de introduzir uma ideia geral de como pode ser construído um interface gráfico em Mozart-Oz.

6.1 Toplevel e Objectos Widget O objecto TopLevel consiste num contentor de outros objectos e pode ser

inicializado da seguinte maneira:

W={New Tk.toplevel tkInit(width:150 height:50)}

Podem enviar-se mensagens para um widget para poder alterar uma das suas propriedades, por exemplo para mudar a cor do fundo do TopLevel faríamos:

{W tk(configure background:purple)}

Por exemplo a mensagem tkClose serve para fechar o widget, removendo-o consequentemente do ecrã.

6.1.1 Frames Os frames têm características idênticas ao toplevel, a diferença é que os

frames são contentores dentro de outros widgets. As opções relief e border permitem dar um aspecto tridimensional ao

frame. A opção relief pode ter os seguintes valores: groove, ridge, flat, sunken, e raised.

Fs={Map [groove ridge flat sunken raised]fun {$ R}

{New Tk.frame tkInit(parent:W width:2#c height:1#crelief:R borderwidth:4)}

end}{{Nth Fs 3} tk(configure background:black)}{Tk.send pack(b(Fs) side:left padx:4 pady:4)}

Este exemplo cria um frame para cada tipo de relief.

Page 112: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

112

Todos os widgets necessitam uma referencia a um widget pai com a excepção do toplevel.

6.1.2 Labels Um label serve para escrever texto ou uma imagem (bitmap) numa janela.

Vejamos um exemplo em que é criado um label com uma imagem e outro com texto.

L1={New Tk.label tkInit(parent:W bitmap:info)}L2={New Tk.label tkInit(parent:W text:’Labels: bitmapsand text’)}{Tk.send pack(L1 L2 side:left padx:2#m pady:2#m)}

Existem alguns bitmaps predefinidos, vejamos este exemplo:

{List.forAllInd [error gray75 gray50 gray25 gray12hourglass info questhead question warning]proc {$ I D}

R=(I-1) div 5C=(I-1) mod 5

in{Tk.batch [grid(row:R*2 column:C

{New Tk.label tkInit(parent:W bitmap:D)})grid(row:R*2+1 column:C

{New Tk.label tkInit(parent:W text:D)})]}end}

As fonts podem ser especificadas de uma forma dependente da plataforma, por exemplo se a plataforma for unix as fonts podem ser especificadas por um dos nomes que o comando xlsfonts devolve.

No exemplo seguinte podemos ver um outro método de especificar fonts independentemente da plataforma.

{ForAll [times helvetica courier]

proc {$ Family}{ForAll [normal bold]proc {$ Weight}

F={New Tk.font tkInit(family: Familyweight: Weightsize: 12)}

L={New Tk.label tkInit(parent: Wtext: ’A ’#Weight#’

’#Family#’ font.’font: F)}

in{Tk.send pack(L)}

end}end

Page 113: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

113

6.1.3 Imagens A diferença entre as imagens e os bitmaps é que as imagens podem ter

mais de duas cores. Podemos usar os labels para mostrar imagens numa janela.

D ={Property.get ’oz.home’}#’/doc/wp/’I ={New Tk.image tkInit(type:photo format:ppmfile:D#’truck-left.ppm’)}L1={New Tk.label tkInit(parent:W image:I)}L2={New Tk.label tkInit(parent:W image:I)}L3={New Tk.label tkInit(parent:W image:I)}{Tk.send pack(L1 L2 L3 padx:1#m pady:1#m side:left)}

As imagens podem ser de dois tipos :

photo – pode mostrar imagens com dois formatos: gif e ppm. bitmap – o ficheiro referenciado tem que estar no formato bitmap.

As imagens podem em vez de serem lidas de ficheiros, podem ser lidas a

partir de uma URL da seguinte maneira:

{New Tk.image tkInit(type:photo format:gifurl:’http://foo.com/bar.gif’}

6.1.4 Mensagens As mensagens servem para mostrar texto em várias linhas. O proximo

exemplo mostra deferentes combinações de justificações e do aspecto condicionado pela opção aspect.

S =’Text extending over several lines.’Ms={Map [left#200 center#100 right#50]

fun {$ J#A}{New Tk.message tkInit(parent:W text:S justify:J

aspect:A)}end}

{Tk.send pack(b(Ms) side:left padx:2#m pady:2#m)}

6.2 Gestores de Geometria Os gestores de geometria servem para determinar quanto espaço os

widgets ocupam e em que posição aparecem no ecrã.

6.2.1 O Packer

O Packer suporta uma organização de widgets em linhas e colunas. Vejamos um exemplo de aplicação do packer:

Page 114: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

114

fun {NewLabels}W={New Tk.toplevel tkInit(background:white)}

in{Map [’label’ ’Second label widget’ ’3rd label’]

fun {$ A}{New Tk.label tkInit(parent:W text:A)}end}

end

Para os labels aparecerem no toplevel o packer pode ser invocado da seguinte maneira:

[L1 L2 L3] = {NewLabels}{Tk.send pack(L1 L2 L3)}

O packer também pode ser chamado usando um batch tickle. Um batch tickle é um tuplo com um identificador b e argumento é uma lista de tickles. Os tickles são mensagens enviadas ao motor gráfico.

{Tk.send pack(b({NewLabels}))}

Existe uma opção (side) no packer que permite escolher a orientação dos widgets. O valor por omissão é top.

No exemplo seguinte os labels em vez de serem desenhados de cima para baixo, são desenhados da esquerda para a direita.

{Tk.send pack(b({NewLabels}) side:left)}

As áreas ocupadas com os widgets podem ser expandidas de duas

formas: interna ou externa. Espaço adicional externo pode ser especificado com as opções padx e pady, e corresponde ao espaço disponibilizado pela janela pai em redor dos widgets. Os valores usado nestas opções devem ser distâncias de ecrã. Distâncias de ecrã são distancias em pixeis, a não ser que antes tenham um c, m, i ou um p, que significam que as distancias são respectivamente em centímetros, milímetros, polegadas e pontos de impressão. As distancias de ecrã podem ser especificadas com strings virtuais. Para especificar espaço adicional interno, ou seja, o espaço que cada cada um expande nas suas 4 bordas, usa-se o ipadx e o ipady.

{Tk.send pack(b({NewLabels}) padx:1#m pady:1#m)}{Tk.send pack(b({NewLabels}) ipadx:2#m ipady:2#m)}

A opção anchor em que posição dentro da parcela do packer o widget irá

ser colocado. As posições podem ser center, n, s, w, e, nw, ne, sw, and se. Por omissão o widget é colocado na posição central.

O espaço expandido pode ser preenchido à totalidade do espaço da

parcela para isso usa-se a opção fill. Os valores possiveis são x,y e both.

{Tk.send pack(b({NewLabels}) anchor:w padx:1#m pady:1#m)}

{Tk.send pack(b({NewLabels}) fill:x)}

Page 115: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

115

6.2.2 Grids As grids organizam os widgets na forma de grelhas. Para cada widget

poder ser posicionado na grelha é necessário passar-lhe o número de linha e colunas desejadas. Todas as posição na mesma coluna têm o mesmo tamanho e na mesma linha têm a mesma altura. As grids também suportam as opção que vimos anteriormente para o packer.

proc {GL W R C S}

L={New Tk.label tkInit(parent:W text:S)}in

{Tk.send grid(L row:R column:C padx:4 pady:4)}end{GL W 1 1 nw} {GL W 1 2 north} {GL W 1 3 ne}{GL W 2 1 west} {GL W 2 3 east}{GL W 3 1 sw} {GL W 3 2 south} {GL W 3 3 sw}

A opção columnspan e rowspan permite que o widget ocupe mais de uma coluna ou mais de uma linha.

Vejamos um exemplo:

{Tk.send grid({New Tk.label tkInit(parent:W text:’Upperleft’)}

row:1 rowspan:2column:1 columnspan:2padx:4 pady:4)}

{GL W 1 3 ne} {GL W 2 3 east}{GL W 3 1 sw} {GL W 3 2 south} {GL W 3 3 sw}

A opção sticky especifica simultaneamente o widget ocupa todo o espaço

da parcela e a sua posiçao dentro desta.

{Tk.send grid({New Tk.label tkInit(parent:W text:’Upperleft’)}

row:1 rowspan:2column:1 columnspan:2sticky: nsepadx:4 pady:4)}

6.3 Outros Widgets A seguir iremos ver alguns dos widgets interactivos disponibilizados pelo

módulo Tk do Mozart-Oz.

Page 116: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

116

6.3.1Botões e Acções Os botões são bastante similares aos labels, a grande diferença é que os

botões desencadeiam acções: quando se carrega no botão do rato por cima do botão é desencadeada uma acção que consiste ou num procedimento ou num par objecto e mensagem.

As acções podem ser alteradas ou apagadas com o método tkAction. Vejamos um exemplo em que a acção do botão B1 é eliminada, e a acção do botão B2 é alterada.

{B1 tkAction}{B2 tkAction(action: B1 # tkClose)}

Vejamos um exemplo de botões:

B1={New Tk.button tkInit(parent: Wtext: ’Press me!’

action: proc {$}{Browse pressed}

end)}B2={New Tk.button tkInit(parent: W

bitmap: erroraction: W#tkClose)}

{Tk.send pack(B1 B2 fill:x padx:1#m pady:1#m)}

6.3.2 CheckButtons, RadioButtons e variáveis Os checkbuttons são usados para escolhas binárias, isto é, podem estar

activados ou desactivados. O estado deste tipo de botões é dado por uma variável de tickle. Uma variável de tickle é um objecto tickle que disponibiliza métodos para interrogar e modificar o estado do botão.

Os radiobuttons são usados para escolhas não binárias, isto é, vários botões são agrupados e escolhendo um dos botões do grupo todos os outros serão desactivados. Usam uma variável de tickle para representar o estado do grupo de botões.

Vejamos um exemplo de uma interrogação o estado dos checkbuttons ou dos radiobuttons:

{Browse state(bold: {V1 tkReturnInt($)}==1family: {V2 tkReturnAtom($)})}

Vejamos um exemplo de checkbuttons e de radiobuttons:

V1={New Tk.variable tkInit(false)}C ={New Tk.checkbutton tkInit(parent:W variable:V1

text:’Bold’ anchor:w)}V2={New Tk.variable tkInit(’Helvetica’)}Rs={Map [’Times’ ’Helvetica’ ’Courier’]

fun {$ F}{New Tk.radiobutton tkInit(parent:W

variable:V2 value:Ftext:F anchor:w)}

end}{Tk.batch [grid(C padx:2#m columnspan:3)

grid(b(Rs) padx:2#m)]}

Page 117: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

117

As acções nestes tipos de botões podem ser usadas da mesma forma que nos botões convencionais.

Como já foi visto anteriormente a forma de aceder ao valor das variáveis

de tickle é através dos métodos tkReturnInt e tkReturnAtom. Vejamos um exemplo:

fun {GetWeight}

if {V1 tkReturnInt($)}==1 then bold else normal endendF={New Tk.font tkInit(size:24

family: {V2 tkReturn($)}weight: {GetWeight})}

L={New Tk.label tkInit(parent:W text:’A test text.’font:F)}{C tkAction(action: proc {$}

{F tk(configure weight:{GetWeight})}{L tk(configure font:F)}

end)}{List.forAllInd [’Times’ ’Helvetica’ ’Courier’]proc {$ I Family}

{{Nth Rs I} tkAction(action: proc {$}{F tk(configure family:Family)}{L tk(configure font:F)}

end)}end}

As opções dos widgets também podem ser interrogadas. Para isso usa-se

o seguinte:

{T tkReturnListAtom(configure bg:unit $)}

Neste exemplo estamos a interrogar a opção bg do widget T. Vejamos que fazer para interrogar os parâmetros de um widget. O exemplo

seguinte interroga a posição e a geometria de um widget T.

{Browse {Map [rootx width rooty height]fun {$ A}

{Tk.returnInt winfo(A T)}end}}

6.3.3 Menus As entradas de menus não são widgets. Não são geridos pelo gestor de

geometria. Quando uma entrada menu é criado é imediatamente visualizado num menu pai.

O próximo programa cria dois widgets M1 e M2. A primeira entrada de menu está configurada para mostrar M2 em cascata. A opção tearoff:false impede que o menu seja cria numa janela própria.

Cs =[’Wheat’ ’Firebrick’ ’Navy’ ’Darkorange’]M1 ={New Tk.menu tkInit(parent:W tearoff:false)}

Page 118: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

118

M2 ={New Tk.menu tkInit(parent:M1 tearoff:false)}E1 ={New Tk.menuentry.cascade

tkInit(parent:M1 label:’Background Color’ menu:M2)}E2 ={New Tk.menuentry.separator tkInit(parent:M1)}E3 ={New Tk.menuentry.command

tkInit(parent:M1 label:’Quit’ action: W#tkClose)}V ={New Tk.variable tkInit(Cs.1)}CEs={Map Cs fun {$ C}

{New Tk.menuentry.radiobuttontkInit(parent:M2 label:C var:V val:C

action: W#tk(configure bg:C))}end}

Normalmente os menus não são visíveis. Para que o menu seja por exemplo visível no canto superior esquerdo faz-se:

{M1 tk(post 0 0)}

No módulo Tktools existem abstracções para criar de forma bastante simples barras de menus.

Para criar menus popup usa-se o comando tk_popup. Recebe como parâmetros o widget do menu e as coordenadas onde é suposto aparecer. De seguida iremos ver como se associam eventos a widgets, por exemplo, para fazer aparecer o menu quando se pressiona o botão direito do rato.

6.3.4 Eventos Para associar uma acção a um widget usa-se o comando tkbind. A acção é

invocada quando um determinado evento sucede. Vejamos o que teríamos de fazer para que, no exemplo anterior, o menu

seja visualizado quando se pressionar o botão do rato sobre o toplevel.

{W tkBind(event: ’<Button-1>’args: [int(x) int(y)]

action: proc {$ X Y}TX={Tk.returnInt winfo(rootx W)}TY={Tk.returnInt winfo(rooty W)}

in{Tk.send tk_popup(M1 X+TX Y+TY)}

end)}

A seguir à opção event é especificado o tipo de evento chamado de

padrão de evento, na opção args é especificada uma lista com os paramentos do evento. Os padrões têm a seguinte forma:

’<’ Um caracter ’>’ – Significa que a teclacorrespondente a esse caracter foi primida

’<’#Modifier#’-’#Modifier#’-’#Type#’-’#Detail#’>’ou ’<’#Type#’>’ ou apenas ’<’#Detail#’>’

Page 119: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

119

Exemplos de modifiers: Control Shift Lock Meta Alt Button1, B1 Button2, B2Button3, B3 Button4, B4 Button5, B5 Double Triple

Exemplos de tipos de eventos: Key, KeyPress Tecla primidaKeyRelease Tecla libertadaButton, ButtonPress Botão do rato primidoButtonRelease mouse Botão do rato libertadoEnter mouse pointer O ponteiro do rato entrou num widgetLeave mouse pointer O ponteiro do rato saíu de um widgerMotion mouse pointer O ponteiro do rato foi movido nointerior de um windget

Se não for especificada nenhuma acção na respectiva opção, a acção que anteriormente estava associada a esse evento será eliminada.

Para adicionar acções a um evento usa-se a opção append, que significa que a nova acção especificada não irá ser sobreposta à anterior.

Os Listeners garantem que as mensagem das acções são processadas

pela ordem que são invocadas. Podem ser criados derivando uma classe da classe Tk.listener.

L ={New class $ from Tk.listenermeth b1 {Browse b1} endmeth b2 {Browse b2} end

end tkInit}B1={New Tk.button tkInit(parent:W text:’One’ action:L#b1)}B2={New Tk.button tkInit(parent:W text:’Two’ action:L#b2)}{Tk.send pack(B1 B2 side:left)}

Neste exemplo garantimos que os métodos b1 e b2 são executados com a mesma ordem que os botões correspondentes são primidos.

6.3.5 Entries

Uma entry permite-nos inserir uma linha de texto. Vejamos um exemplo:

L={New Tk.label tkInit(parent:W text:’File name:’)}E={New Tk.entry tkInit(parent:W width:20)}{Tk.batch [pack(L E side:left pady:1#m padx:1#m)

focus(E)]}

O comando focus serve para atribuir o foco à entry para que o texto digitado apareça nesse widget.

Para ler o conteúdo da entry usa-se o comando get da seguinte forma:

{Browse {E tkReturnAtom(get $)}}

Page 120: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

120

6.3.6 Scales O scale widget, permite seleccionar um determinado valor movendo um

slider que desliza sobre uma barra. Cada vez que o slider é movido, um acção associada é invocada com um único argumento, que é o valor para o qual o slider aponta. Vejamos um exemplo que cria três barras com as corres RGB.

L ={New class $ from Tk.listener

attr red:0 green:0 blue:0meth bg(C I)C <- I {F tk(configure bg:c(@red @green @blue))}

endend tkInit}

F ={New Tk.frame tkInit(parent:W height:2#c)}Ss={Map [red green blue]fun {$ C}

{New Tk.scale tkInit(parent:W orient:horizontal length:8#clabel: C ’from’:0 to:255action: L # bg(C)args: [int])}

end}{Tk.send pack(b(Ss) F fill:x)}

6.3.7 Listboxes Uma listbox mostra uma lista de strings e permite o utilizador seleccionar

uma delas. Para associar a uma listbox um elevador, usa-se o procedimento Tk.addYScrollbar. Este procedimento associa os eventos do elevador às strings visíveis na listbox. Vejamos um exemplo:

L={New Tk.listbox tkInit(parent:W height:6)}{L tkBind(event: ’<1>’

action: proc {$}I={L tkReturn(curselection $)}C={L tkReturn(get(I) $)}

in{L tk(configure bg:C)}

end)}S={New Tk.scrollbar tkInit(parent:W)}{ForAll NomesDeCores proc {$ C}{L tk(insert ’end’ C)}

end}{Tk.addYScrollbar L S}{Tk.send pack(L S fill:y side:left)}

Page 121: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

121

6.3.8 Manipular o toplevel Existem vários comandos para manipular a janela toplevel. Por exemplo o

comando seguinte:

{Tk.send wm(iconify T)}

Inconifica a janela toplevel T. Para desiconificar faz-se:

{Tk.send wm(iconify T)}

Estes comando correspondem a opções quando o toplevel é criado. Por exemplo:

W={New Tk.toplevel tkInit(title:’Meu Titulo’)}

Cria uma janela toplevel com o titulo ‘Meu Titulo’. Para se poder criar um toplevel que não apareça imediatamente quando é

criado no ecrã, e só apareca depois de todos os widgets que irá conter serem criados, faz-se da seguinte maneira:

W={New Tk.toplevel tkInit(withdraw:true)}

Para que o toplevel seja visualizado faz-se:

{Tk.send wm(deiconify W)}

6.3.9 DialogBoxes predefinidas O tk proporciona algumas DialogBoxes predefinidas para algumas

operações, por exemplo para seleccionar um ficheiro, pode ser utilizado o commando tk_getOpenFile, para gravar um ficheiro, pode ser utilizado o comando tk_getSaveFile. Vejamos um exemplo:

case {Tk.return tk_getOpenFile}

of nil then skipelseof S then {Browse file({String.toAtom S})}

end

6.4 O Canvas Os widgets Canvas servem para manipular entidades gráficas, tais como

linhas, arcos, pontos, etc...

Page 122: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

122

6.4.1 Criar um gráfico de barras Para ser mais fácil entender como funciona o canvas iremos partir de um

exemplo prático. Antes de um elemento ser criado pelo método bars, o canvas é configurado de forma a que o gráfico de barras caiba na zona definida com elevadores.

O método drawBars cria para cada elemento da lista Ys um rectângulo e um texto correspondendo a um determinado valor. O valor de O é usado como opção na criação do rectângulo. Este valor depende de Tk.isColor que é true se o ecrã for colorido e false se não for colorido. Isto serve para determinar as corres dos rectângulos. Se o ecrã for a preto e branco os rectângulos são preenchidos com listas.

Cada canvas é identificado por um valor inteiro que pode ser obtido pelo método tkReturnInt.

Para ser mais fácil manipular elementos ou grupos de elementos do canvas existe o conceito de etiquetas (Tags) que podem ser manipuladas como objectos. Uma etiqueta pode ser criada através da classe Tk.canvasTag. Por exemplo:

R={New Tk.canvasTag tkInit(parent:C)}{C tk(create rectangle 10 10 40 40 fill:red tags:R)}

Se quisermos adicionar outro elemento à etiqueta podemos fazer:

{C tk(create oval 20 20 40 40 tags:R)}

Para manipular simultaneamente todos os elementos de uma etiqueta podemos fazer por exemplo:

{R tk(move 40 0)}

Para configurar um elemento do canvas pode usar-se o comando itemconfigure que é semelhante ou configure para os widgets. As cores da oval e do rectângulo do exemplo anterior podem ser modificadas usando:

{R tk(itemconfigure fill:wheat)} % preto e branco comlistas

{O tk(itemconfigure fill:blue)} % cor azul

Para eliminar um determinado elemento do canvas pode fazer-se:

{O tk(delete)}

Apaga todas as ovais associadas à etiqueta O. Vejamos agora o exemplo do programa do gráfico de barras:

Page 123: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

123

localO=if Tk.isColor then o(fill:wheat)else o(stipple:gray50 fill:black)endD=10 D2=2*D B=10

inclass BarCanvas from Tk.canvas

meth DrawBars(Ys H X)case Ys of nil then skip[] Y|Yr then{self tk(create rectangle X H X+D H-Y*D2 O)}{self tk(create text X H+D text:Y anchor:w)}{self DrawBars(Yr H X+D2)}

endendmeth configure(SX SY)

{self tk(configure scrollregion:q(B ~B SX+B SY+B))}endmeth bars(Ys)

WY=D2*({Length Ys}+1) HY=D2*({FoldL Ys Max 0}+1)in

{self configure(WY HY)}{self DrawBars(Ys HY D)}

endend

end Pode ser utilizado da seguinte forma:

C={New BarCanvas tkInit(parent:W bg:white width:300 height:120)}H={New Tk.scrollbar tkInit(parent:W orient:horizontal)}V={New Tk.scrollbar tkInit(parent:W orient:vertical)}{Tk.addXScrollbar C H} {Tk.addYScrollbar C V}{Tk.batch [grid(C row:0 column:0)

grid(H row:1 column:0 sticky:we)grid(V row:0 column:1 sticky:ns)]}

{C bars([1 3 4 5 3 4 2 1 7 2 3 4 2 45 6 7 7 8 4 3 5 6 7 7 8 4 3])}

6.4.2 Eventos De modo similar aos widgets, também se podem utilizar eventos

associados etiquetas. Um evento associado a uma etiqueta significa que fica associado a todos o elementos que ela referencia. Vejamos a atribuição de eventos a uma oval:

Colors={New class $ from BaseObject

attr cs:(Cs=red|green|blue|yellow|orange|Csin

Cs)meth get(?C)

Cr in C|Cr = (cs <- Cr)end

end noop}{O tkBind(event: ’<3>’

action: proc {$}{O tk(itemconfigure

fill:{Colors get($)})}end)}

Page 124: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

124

6.5 Caixas de Texto As caixas de texto, ao contrario das entries, suportam texto em mais de

uma linha. Existem vários comando para a manipulação do texto.

6.5.1 Manipulação do texto Para inserir texto numa caixa de texto usa-se o seguinte:

T={New Tk.text tkInit(parent:W width:28 height:5 bg:white)}{T tk(insert ’end’ "Este texto irá aparecer na caixa de texto.")}

Para não serem tomados em conta os espaços entre as palavras, mas preservando os limites das palavras, faz-se o seguinte:

{T tk(configure wrap:word)}

Porções do texto podem ser associadas a tickles p(L C), em que L é a

linha e C a coluna. As porções texto pode ser acedidas usando o seguinte:

{T tkReturnAtom(get p(1 4) p(1 9) $)}

As porções também podem ser usadas para determinar onde iremos inserir texto.

{T tk(insert p(1 4) "texto")}

Da mesma forma podemos eliminar texto. No exemplo o texto ente as posições p(1 4) e p(1 14) é eliminado.

{T tk(delete p(1 4) p(1 14))}

Para se impedir que o utilizador insira texto faz-se:

{T tk(configure state:disabled)}

6.5.2 Etiquetas de texto e marcas Tal como os canvas as caixas de texto suportam etiquetas. A diferença é

que as etiquetas, em vez de referenciarem elementos, referenciam conjuntos de caracteres.

Vejamos como podem ser criadas as etiquetas:

Page 125: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

125

B={New Tk.textTag tkInit(parent:T foreground:brown)}

Todos os caracteres associados a esta etiqueta irão ser castanhos. Pode-se adicionar texto a uma etiqueta da seguinte maneira:

{B tk(add p(1 10) p(1 15))}

O próximo exemplo mostra como se pode alterar a configuração de uma etiqueta.

{B tk(configure font:{New Tk.font tkInit(size:18)})}

O comando insert também pode ser utilizado para inserir ou adicionar texto

directamente a uma etiqueta.

{T tk(insert ’end’ "\nTexto1 ")}{T tk(insert ’end’ "Texto2" B)}{T tk(insert ’end’ " Texto3.")}

As marcas são idênticas às etiquetas, só que em vez de referenciarem

caracteres, referenciam posições no texto. A classe que suporta as marcas é Tk.textMark.

6.6 Ferramentas para o Tk O módulo TkTools possui diversas abstracções que facilitam a

implementação de interfaces gráficos.

6.6.1 Dialogs Dialogs ou caixas de dialogo consistem em janelas que possuem alguma

informação gráfica e botões. Para criar este tipo de janelas usa-se a classe TkTools.dialog.

A criação da dialog contem uma opção tiltle para o titulo. Os botões são especificados em listas de pares, onde o primeiro par é o botão mais à direita. Estes pares são constituídos pelo identificador do botão e a acção correspondente. A acção pode ser um procedimento ou por exemplo o átomo tkClose que envia uma mensagem para fechar a dialog. Pode também ser um tuplo com o identificador tkClose, que significa que antes de ser fechada a dialog é executado o argumento do tuplo, que pode ser um procedimento. A opção default especifica qual o botão escolhido por omissão. Vejamos um exemplo de uma dialog para apagar um ficheiro.

D={New TkTools.dialog

tkInit(title: ’Eliminar um Ficheiro’buttons: [’Ok’ #

proc {$}try

Page 126: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

126

{OS.unlink {E tkReturn(get $)}}{D tkClose}

catch _ then skipend

end’Cancelar’ # tkClose]default: 1)}

L={New Tk.label tkInit(parent:D text:’Nome doFicheiro:’)}E={New Tk.entry tkInit(parent:D bg:wheat width:20)}{Tk.batch [pack(L E side:left pady:2#m) focus(E)]}

6.6.2 Mensagens de Erro Para produzir mensagens de erro existe a classe TkTools.error que é uma

subclasse de TkTools.dialog. Vejamos um exemplo da utilização desta classe:

E={New TkTools.errortkInit(master:W

text: ’Erro na configuração do sistema: ’#’demasiada memória.’)}

6.6.3 Barras de Menus Uma barra de menus é um frame com vários widgets botões de menus.

Cada widget botões de menu têm associados um menu. O menu contém items de menus ou entradas, que podem ser radiobuttons, checkbutton, comandos, separadores, ou outros menus associados em cascata. Podem ser usados aceleradores de teclado que podem ser usados para invocar opções do menu.

Para criar barras de menus, o módulo TkTools tem o procedimento TkTools.menubar, onde são especificados os menus e os aceleradores de teclado.

Vejamos um exemplo:

V={New Tk.variable tkInit(0)}B={TkTools.menubar W W

[menubutton(text:’Test’ underline:0menu: [command(label: ’About test’

action: Browse#aboutkey: alt(a)

feature: about)separatorcommand(label: ’Quit’

action: W#tkClosekey: ctrl(c)

feature: quit)]feature: test)

menubutton(text:’Options’ underline:0menu: [checkbutton(label:

’Incremental’var: {New

Tk.variable tkInit(false)})separatorcascade(label:

’Size’menu:

[radiobutton(label:’Small’

Page 127: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

127

var:Vvalue:0)radiobutton(label:’Middle’var:V value:1)radiobutton(label:’Large’var:V value: 2)])])]nil}

F={New Tk.frame tkInit(parent:W width:10#c height:5#c bg:ivory)}{Tk.send pack(B F fill:x)}

A opção feature associa propriedades aos botões de menu e aos items de menu, de forma a que seja acessíveis. Podem ser usadas por exemplo para desactivar uma opção:

{B.test.about tk(entryconfigure state:disabled)}

A propriedade menu permite aceder a submenus:

{B.test.menu tk(configure tearoff:false)}

Um menu pode ser expandido da seguinte forma:

A={New Tk.menuentry.command tkInit(parent:B.test.menubefore:B.test.quitlabel: ’Exit’)}

A opção pode ser removida com:

{A tkClose}

6.6.4 Listas de Imagens Uma maneira de criar imagens é usando TkTool.images. Recebe uma lista

de URLs e retorna uma registo de imagens, onde os campos são átomos derivados das URLs. O tipo das imagens depende da extensão das URLs. Vejamos um exemplo:

U=’http://www.mozart-oz.org/home-1.1.0/doc/wp/’I={TkTools.images [U#’wp.gif’

U#’queen.xbm’U#’truck-left.ppm’]}

Page 128: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

128

7. Conclusão Penso que este tutorial é uma boa ferramenta para introduzir o sistema de

programação Mozart-Oz. Obviamente, devido à vastidão de assuntos que são incluídos neste tutorial, não é possível detalhá-los a todos com maior profundidade. No entanto, escolhi focar as questões que me pareceram mais fundamentais, quer para entender como se programa com esta poderosa linguagem de programação, que é o Oz, quer para compreender os modelos subjacentes a essa programação.

Tive algumas duvidas em incluir o capitulo dedicado à programação gráfica, pois não me pareceu um aspecto fundamental da introdução à programação neste sistema. Decidi incluí-lo porque hoje em dia, quando se estuda uma linguagem de programação e não se foca os aspecto gráficos dessa linguagem é normalmente desmotivador. Para além disso, é relativamente fácil programar interfaces gráficos em Mozart, porque as abstracções de programação são simples de compreender e são baseadas no Tk.

O modelo de programação distribuída é na minha opinião aquilo que torna este sistema de programação realmente brilhante. Isto é obviamente possível graças às extraordinárias características que a linguagem de programação Oz possui, nomeadamente o facto de tudo em Oz ser first-class, o que possibilita que aplicações distribuídas sejam praticamente iguais a aplicações centralizadas, isto é, separa-se completamente as questões da distribuição da funcionalidade dos programas.

Eu penso que o Oz marcará certamente uma nova era na programação. As linguagens de programação convencionais não conseguem dar respostas a todos os problemas. Existe nos dias de hoje uma tendência para a programação de computações móveis e programação de agentes que possam existir em qualquer site e movimentarem-se numa rede local ou na internet. Creio que o Oz, apesar de algumas lacunas que ainda tem, nomeadamente em questões de segurança, é a ferramenta mais apropriada para o desenvolvimento deste tipo de programas.

Vimos também neste tutorial a comparação entre o desempenho do Oz e do Java, numa aplicação produtor / consumidor. Os resultados demonstram a ineficiência do Java para este tipo de problemas, e a ineficiência é ainda maior quando a aplicação é distribuída. O Oz vem provar que o java é uma linguagem desactualizada e inadequada aos problemas que a computação distribuída propõe nos dias de hoje.

Foi para mim um grande prazer desenvolver este tutorial. Nunca tinha tido contacto com este sistema de programação antes. Espero que seja também do agrado das pessoas que o vierem a ler e a utilizar como ferramenta. Espero também que o Mozart tenha um excelente futuro. Parabéns aos professores Seif Haridi, Peter Van Roy, Per Brand e a tantos outros que trabalharam neste excelente projecto.

Page 129: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

129

Apêndice A Os programas dos testes de performance entre o Oz e o Java

A.1 Produtor Consumidor Centralizado em Java

import java.util.*;

/**This is where the example is run.

*/public class ProdCons {

public static void main(String[] args) {long starttime = (new Date()).getTime();Buffer buffer=new Buffer();Consumer c=new Consumer(buffer);Producer p=new Producer(buffer,1000000);

c.start();p.start();

try {c.join();

} catch (InterruptedException e) {}try {

p.join();import java.util.*;

/**The buffer class acts as the glue between the Producer and theConsumer. Here the Producer puts his products and from herethe consumer retrievs them

*/public class Buffer {

LinkedList list = new LinkedList();

public synchronized void put(Integer i) {list.addLast(i);notifyAll();

}

public synchronized Integer get() {if (list.size() == 0)

try {wait();

} catch (InterruptedException e) {}return (Integer) list.removeFirst();

}}

} catch (InterruptedException e) {}

long endtime = (new Date()).getTime();System.out.println("Time (milliseconds): "+

Long.toString(endtime-starttime));

Page 130: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

130

System.out.println("The sum is: "+c.getSum());}

}

public class Producer extends Thread {Buffer buffer;int times;

Producer(Buffer buffer, int times) {this.buffer = buffer;this.times = times;

}public void run() {for (int i=times;i>0;i--) {

Integer n = new Integer(i);buffer.put(n);

}buffer.put(null); // Tells the consumer that we are done.

}}

public class Consumer extends Thread {Buffer buffer;long sum;

Consumer(Buffer buffer) {this.buffer = buffer;sum = 0;

}

public void run() {Integer i;

while(true) {i = buffer.get();if (i==null)break;

elsesum += i.intValue();

}}

public long getSum() {return sum;

}}

Page 131: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

131

A.2 Produtor Consumidor Centralizado em Oz

declare S T1 T2 Res

proc{Producer Stream Cur No}if No>0 then

Stream1 inStream=Cur|Stream1{Producer Stream1 Cur+1 No-1}

elseStream=nil

endend

fun{Consumer Stream Acc}case Stream of S1|Ss then

{Consumer Ss Acc+S1}[] nil then

Accend

end

{Property.get time T1}

threadRes={Consumer S 0}{Property.get time T2}{Show result(res:Res time:T2.total-T1.total)}

end

{Producer S 1 1000000}

A.3 Produtor Consumidor Distribuído em Java

import java.rmi.Remote;import java.rmi.RemoteException;

public interface Buffer extends Remote {public void put(Integer i) throws RemoteException;public Integer get() throws RemoteException;

public void waitForStart(Waiter w) throws RemoteException;public void start() throws RemoteException;

}

import java.rmi.*;import java.rmi.registry.*;import java.rmi.server.*;import java.util.*;

public class BufferImpl extends UnicastRemoteObjectimplements Buffer {

LinkedList list = new LinkedList();Waiter w;

BufferImpl() throws RemoteException {

Page 132: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

132

super();}

public void put(Integer i) {putSynch(i);

}

// Java does not allow remote methods to be synchronized,// therefore we have to synchronize locally.private synchronized void putSynch(Integer i) {list.addLast(i);notifyAll();

}

public Integer get() {return getSynch();

}

// Java does not allow remote methods to be synchronized,// therefore we have to synchronize locally.private synchronized Integer getSynch() {if (list.size() == 0)

try {wait();

} catch (InterruptedException e) {}return (Integer) list.removeFirst();

}

///////////////////////// For timing purposespublic void waitForStart (Waiter w) {this.w=w;

}

public void start() {try {

w.startup();} catch (RemoteException e) {

System.err.println("Could not start "+w);}

}//////////////////////

public static void main(String[] args) {if (System.getSecurityManager() == null) {

System.setSecurityManager(new RMISecurityManager());}String name = "//" + args[0] + "/Buffer";try {

// Create a "name-server" and store infomation of// this buffer there.LocateRegistry.createRegistry(1099);BufferImpl bufferImpl = new BufferImpl();Naming.rebind(name, bufferImpl);

} catch (Exception e) {System.err.println("BufferImpl exception: " +

e.getMessage());}

}}

import java.rmi.*;

Page 133: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

133

import java.util.*;

public class Consumer extends Thread {Buffer buffer;long sum;

Consumer(Buffer buffer) {this.buffer = buffer;sum = 0;

}

public void run() {Integer i;

try {while(true) {i = buffer.get();if (i==null) {

break;}else

sum += i.intValue();

}} catch (RemoteException e) {

System.err.println("Consumer exception: " +e.getMessage());

}}

public long getSum() {return sum;

}

// Now the consumer is a standalone application.public static void main(String[] args) {if (System.getSecurityManager() == null) {

System.setSecurityManager(new RMISecurityManager());}try {

String name = "//" + args[0] + "/Buffer";// Find the buffer from the "name-server"Buffer buffer = (Buffer) Naming.lookup(name);Consumer c = new Consumer(buffer);

long starttime = (new Date()).getTime();buffer.start();c.start ();try {c.join();

} catch(InterruptedException e2) {}long endtime = (new Date()).getTime();System.out.println("Time (milliseconds): "+

Long.toString(endtime-starttime));System.out.println("The sum is: "+c.getSum());

} catch (Exception e) {System.err.println("Consumer exception: " +

e.getMessage());e.printStackTrace();

}}

}

Page 134: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

134

import java.rmi.*;

public class Producer extends Thread {Buffer buffer;int times;

Producer(Buffer buffer, int times) {this.buffer = buffer;this.times = times;

}

public void run() {try {

for (int i=times;i>0;i--) {Integer n = new Integer(i);buffer.put(n);

}buffer.put(null);

} catch (RemoteException e) {System.err.println("Producer exception: " +

e.getMessage());}

}

// Now the producer is a standalone application.public static void main(String[] args) {if (System.getSecurityManager() == null) {

System.setSecurityManager(new RMISecurityManager());}try {

String name = "//" + args[0] + "/Buffer";// Find the buffer from the "name-server"Buffer buffer = (Buffer) Naming.lookup(name);Producer p = new Producer(buffer, 1000000);buffer.waitForStart(new ProdWaiter(p));

} catch (Exception e) {System.err.println("Producer exception: " +

e.getMessage());e.printStackTrace();

}}

}

import java.rmi.*;import java.rmi.server.*;

/**This is simply a construction to start the Producer at a givenmoment, to make it possible to time the process.

*/public class ProdWaiter extends UnicastRemoteObject implements Waiter{

Producer p;

ProdWaiter(Producer p) throws RemoteException {this.p=p;

}

public void startup() {p.start();

}

Page 135: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

135

}

import java.rmi.Remote;import java.rmi.RemoteException;

/**This is simply a construction to start the Producer at a givenmoment, to make it possible to time the process.

*/public interface Waiter extends Remote {

public void startup() throws RemoteException;}

/* java.policy */grant {

permission java.net.SocketPermission “*:1024-65535”,“connect,accept”;

permission java.net.SocketPermission “*:80”, “connect”;};

A.4 Produtor Consumidor distribuído em Oz

declare S TrigFileName='/home/perbrand/public_html/ticket'{Save {Connection.offer S#Trig} FileName}

proc{Producer Stream Cur No}if No>0 then

Stream1 inStream=Cur|Stream1{Producer Stream1 Cur+1 No-1}

elseStream=nil

endend

{Wait Trig}{Producer S 1 1000000}

/*Trig=unit*/

functorimport Connection Pickle Application Systemdefine

FileName={Application.getArgs record('url'(single type:atom))}.url_#Trig ={Connection.take {Pickle.load FileName}}{System.show initiating}Trig=unit{System.show done}{Application.exit 0}

end

declare S T1 T2 TrigFileName='http://www.sics.se/~perbrand/ticket'S#Trig={Connection.take {Load FileName}}

fun{Consumer Stream Acc}case Stream of S1|Ss then

Page 136: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

136

{Consumer Ss Acc+S1}[] nil then

Accend

end

{Wait Trig}{Property.get time T1}Res={Consumer S 0}{Property.get time T2}{Show result(result:Res time:T2.user -T1.user)}

Page 137: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

137

Apêndice B Nomes de Cores

___ [aliceblue antiquewhite aquamarineazure beige bisqueblack blanchedalmond blueblueviolet brown burlywoodcadetblue chartreuse chocolatecoral cornflowerblue cornsilkcyan darkblue darkcyandarkgoldenrod darkgray darkgreendarkgrey darkkhaki darkmagentadarkolivegreen darkorange darkorchiddarkred darksalmon darkseagreendarkslateblue darkslategray darkslategreydarkturquoise darkviolet deeppinkdeepskyblue dimgray dimgreydodgerblue firebrick floralwhiteforestgreen gainsboro ghostwhitegold goldenrod graygreen greenyellow greyhoneydew hotpink indianredivory khaki lavenderlavenderblush lawngreen lemonchiffonlightblue lightcoral lightcyanlightgoldenrod lightgoldenrodyellow lightgraylightgreen lightgrey lightpinklightsalmon lightseagreen lightskybluelightslateblue lightslategray lightslategreylightsteelblue lightyellow limegreenlinen magenta maroonmediumaquamarine mediumblue mediumorchidmediumpurple mediumseagreen mediumslatebluemediumspringgreen mediumturquoise mediumvioletredmidnightblue mintcream mistyrosemoccasin navajowhite navynavyblue oldlace olivedraborange orangered orchidpalegoldenrod palegreen paleturquoisepalevioletred papayawhip peachpuffperu pink plumpowderblue purple redrosybrown royalblue saddlebrownsalmon sandybrown seagreenseashell sienna skyblueslateblue slategray slategreysnow springgreen steelbluetan thistle tomatoturquoise violet violetredwheat white whitesmokeyellow yellowgreen]

Page 138: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

138

Apêndice C Glossário de alguns termos

Agentes – Computações que exibem pelo menos os seguintes

comportamentos: autonomia, comportamento reactivo, comportamento pró-activo e comportamento social.

Agentes Inteligentes – Agentes que exibem comportamentos

semelhantes à inteligência humana (raciocínio, aprendizagem). Agentes Móveis – Agentes com a capacidade de se deslocarem entre

localizações e com autonomia para decidir a que localização se deslocam. Aninhamento ou Embutimento de Instruções – Consiste em embutir

instruções dentro de outras instruções. Avaliação Eager – A avaliação de alguns ou todos os parâmetros de uma

função é iniciada antes do seu valor ser requerido. Um exemplo tipico é o call-by-value em que todos os argumentos são passados depois de avaliados.

Avaliação Lazy – A avaliação de uma expressão é apenas feita se for

necessária para que a função retorna um valor. Em situações em que o valor de uma expressão é necessário mais do que uma vez, o resultado da primeira avaliação é relembrado.

Computação Aberta – São computações (aplicações) que são

desenvolvidas de forma a poderem comunicar com outras, independentemente da plataforma e da linguagem de implementação. A comunicação é feita através de sockets ou ficheiros.

Full Laziness – É a modificação de um programa tendo em vista a

optimização da avaliação lazy, de modo a que todas as subexpressões no corpo da função que não dependem dos argumentos, sejam apenas avaliados uma vez.

Handlers – Mecanismos de detecção de falhas de distribuição que

consistem em executar um procedimento quando uma instrução sobre uma determinada entidade distribuída falha.

High Order – As funções ou procedimentos podem receber como

argumento outras funções ou procedimentos e podem também retornar outras funções ou procedimentos.

Lambda-Calculus – Um ramo da matemática desenvolvido por Alonzo

Church nos anos 30 e anos 40, relacionado com a aplicação das funções aos seus argumentos. O puro Lambda-Calculus não possui constantes, nem números nem operadores. Consiste em abstracções lambda (funções), variáveis e aplicação de uma função noutras. Todas as entidades são representadas por funções. Por

Page 139: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

139

exemplo um número natural pode ser representado como uma função que aplica o seu primeiro argumento ao seu segundo N vezes (Inteiro de Church). As linguagens funcionais são extensões ao Lambda-Calculus introduzindo constantes e tipos.

Linguagens Declarativas – Computação directa (Linguagens de programação Funcional) e computação indirecta (Linguagens de programação em Lógica).

Lógica Determinista – Programação lógica em que a sequência de

execução do algoritmo é conhecida. Lógica não Determinista – Programação lógica em que a sequência de

execução do algoritmo é desconhecida, isto é, existem vários caminhos alternativos partindo de um ponto de escolha.

Multi-Agentes – Vários Agentes em cooperação e/ou competição. Objectos Estacionários – Objectos que permanecem numa localização

(site). Objectos Móveis – Objectos que se movimentam entre sites. OPI – Oz Programming Interface. É o ambiente de desenvolvimento do

Mazart-Oz. Consiste em vários programas utilitários e na integração com o Emacs. Procedimentos Anónimos – É uma forma de definir um bloco de

instruções. Isto permite que estes blocos de instruções sejam embutidos dentro de chamadas a procedimentos.

Servidores de Computação – São aplicações que aceitam pedidos de

computação remota processam essas computações e devolvem um resultado. Servem para tirar partido dos recursos da rede para melhorar o desempenho do sistema.

Servidores Dinamicamente Extensível – São servidores que podem ser

actualizados sem que tenham que ser desligados. Threads – Unidades computacionais da programações concorrente que

consistem num conjunto de instruções executadas em concorrência. Tickets – São strings que servem para identificar uma determinada

entidade distribuída. Valores First-Class – Um valor é first-class quando pode ser passado

como parâmetro ou devolvido por uma função e pode também ser guardado em estruturas de dados. O conceito é semelhante a high order.

Watchers – Mecanismos de detecção de falhas de distribuição que

consistem em executar um determinado procedimento quando uma determinada entidade distribuída falha independentemente se é executada ou não uma instrução sobre essa entidade.

Page 140: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

140

Bibliografia Site Oficial – www.mozart-oz.org Jason Cecil, Mike Reily, Jason Sutton, Mike Tolliver; A Comparative Report

of Simple Procedural vs. Functional Programming Languages and Functional vs. Logic-Based Programming Languages; 24/4/2000

Constraint Logic Programming; Revista Byte; 2/1995 The Journal of Functional Programming; MIT press Gert Smolka; The Oz programming Model; DFKI; 1995 Seif Haridi, Peter Van Roy, Per Brand e Christian Schulte; Programming

Languages for Distributed Applications; 1998 Seif Haridi, Peter Van Roy, Gert Smolka ; An Overview of the Design of

Distributed Oz; 1997 Seif Haridi, Peter Van Roy, Per Brand e Christian Schulte, Denis Duchier,

Martin Henz; Logic Programming in Oz and its relation to multiparadigm programming; 2000

Seif Haridi, Nils Fransén; Tutorial of Oz; 2/2000 Seif Haridi, Peter Van Roy, Per Brand; Distributed Programming in Mozart -

ATutorial Introduction; 2/2000 Christian Schulte; Window Programming in Mozart; 2/2000

Page 141: Tutorial de Mozart-Oz - Departamento de Engenharia ...paf/proj/Set2000/Tutorial de Mozart Oz.pdf · Artificial), pelo Seif Haridi no SICS (Instituto Sueco de Ciências de Computação),

141