om – uma linguagem de programaÇÃo multiparadigmactp.di.fct.unl.pt/~amd/publications/1999-tese...

275
ARTUR MIGUEL DE ANDRADE VIEIRA DIAS OM – UMA LINGUAGEM DE PROGRAMAÇÃO MULTIPARADIGMA Dissertação apresentada para obtenção do Grau de Doutor em Informática pela Universidade Nova de Lisboa, Faculdade de Ciências e Tecnologia. Lisboa 1999

Upload: others

Post on 24-May-2020

17 views

Category:

Documents


0 download

TRANSCRIPT

ARTUR MIGUEL DE ANDRADE VIEIRA DIAS

OM – UMA LINGUAGEM DEPROGRAMAÇÃO MULTIPARADIGMA

Dissertação apresentada para obtenção

do Grau de Doutor em Informática

pela Universidade Nova de Lisboa,

Faculdade de Ciências e Tecnologia.

Lisboa1999

Autor: Artur Miguel de Andrade Vieira Dias

Título: OM – Uma Linguagem de Programação Multiparadigma

Orientador: António Beça Gonçalves Porto

Instituição: Universidade Nova de Lisboa

Faculdade de Ciências e Tecnologia

Departamento de Informática

Endereço: Quinta da Torre

2825-114 Monte da Caparica

Portugal

Copyright: Universidade Nova de Lisboa

Local: Lisboa

Data: 1999

Agradecimentos

É com muito gosto que agradeço a todas as pessoas e instituições que me ajudaram a concreti-

zar a presente dissertação:

A António Porto, meu orientador, agradeço a possibilidade que me deu de enveredar pela

linha de investigação desta tese, pela sua ajuda, disponibilidade e liberdade de investigação

proporcionada.

A Luís Caires, agradeço o seu interesse pelo meu trabalho e as diversas discussões que

manteve comigo.

A Margarida Mamede, agradeço o incentivo e o excelente ambiente proporcionado no nos-

so gabinete de trabalho. Agradeço também a sua ajuda na revisão da versão final desta tese.

A Luís Monteiro, Presidente do Departamento de Informática, agradeço todo o apoio que

me dispensou, incluindo a flexibilização da minha carga horária ligada ao serviço decente.

A todos os meus colegas do Departamento de Informática, agradeço os incentivos recebi-

dos.

À minha familia, agradeço o apoio e incentivo a este trabalho.

Ao Ministério da Educação, agradeço a bolsa que me concedeu, ao abrigo do programa

PRODEP II do Fundo Social Europeu.

Sumário

Esta tese explora a ideia da criação duma linguagem de programação multiparadigma estatica-mente tipificada, na qual se pretende que os paradigmas participantes sejam integrados, não deforma ad-hoc, mas sim de forma uniforme, com base num mecanismo primitivo de extensãosemântica. OM é o nome da linguagem concreta que se desenvolve, e modo, ou mecanismodos modos, é o nome do mecanismo primitivo de extensão semântica que se propõe.

O núcleo da linguagem OM é orientado pelos objectos e suporta os seguintes elementosprimitivos principais: objectos mutáveis com componentes privadas, classes, herança, subti-pos, métodos binários, polimorfismo paramétrico, componentes de classe e mecanismo dosmodos. Ao longo da tese, este núcleo linguístico é construído de forma gradual, em paralelocom o desenvolvimento do seu modelo tipificado: primeiro introduz-se um cálculo-lambda po-limórfico de ordem superior, designado por F+, e depois, sobre este, ergue-se, a pouco e pouco,o edifício da linguagem e do modelo. A noção de modo surge no culminar deste processo,resultando natural a sua descrição no modelo. No âmbito deste modelo, sugere-se ainda umasolução para a conhecido problema da tensão entre o mecanismo de herança e a relação desubtipo, um problema recorrente nas linguagens orientadas pelos objectos estaticamente tipifi-cadas com mecanismos de herança flexíveis.

O mecanismo dos modos é uma ferramenta de programação de nível meta que permite im-por propriedades arbitrárias às entidades tipificadas dos programas (i.e. variáveis, expressões,parâmetros e resultados), dependendo do modo com que essas entidades estão declaradas. Porexemplo, as propriedades específicas duma variável inteira com modo lógico (i.e. umavariável declarada com tipo log Int) são diferentes das propriedades específicas duma variávelinteira com modo constante (i.e. uma variável declarada com tipo const Int). Num modo consi-deram-se duas facetas: uma dinâmica e outra estática. A faceta dinâmica refere-se ao compor-tamento dinâmico dos objectos de funcionalidade modificada que o modo implementa. A face-ta estática refere-se às propriedades estáticas das entidades tipificadas, propriedades que sãodescritas usando regras de conversão implícita de tipo (regras de coerção) e regras de reinter-pretação da sintaxe. Para tratar estes dois tipos de regras, o mecanismo dos modos incorporaum sistema de coerções extensível, assim como um esquema simples de sobreposição de sin-taxe focado nas operações primitivas da linguagem (i.e. atribuição, aplicação, envio de men-sagem, etc.). A biblioteca padrão da linguagem OM disponibiliza cinco modos, já definidos, –const, value, lazy, log, gen – e não é um sistema fechado.

O sistema de coerções extensível não é trivial e a sua versão mais intuitiva é indecidível.Este problema de indecidibilidade só se resolveu com a descoberta duma solução pragmática,conciliável com a utilização pretendida para o sistema na linguagem.

Abstract

This thesis explores the idea of creating a statically typed multiparadigm programming langua-ge where the participant paradigms are integrated, not in an ad-hoc fashion, but in an uniformmanner, using a primitive semantic extension mechanism. OM is the name of the concrete lan-guage that is developed, and mode, or mode mechanism, is the name of the primitive semanticextension mechanism that is proposed.

The core of language OM is object-oriented and includes the following main primitive ele-ments: mutable objects with hidden components, classes, inheritance, subtypes, binary meth-ods, parametric polymorphism, class components and mode mechanism. Throughout the the-sis, this linguistic core is built gradually, in parallel with the development of its typed model:first, a high-order polymorphic lambda-calculus, called F+, is introduced, and then, on top ofit, the edifice of the language and the model raised, little by little. The concept of mode emer-ges in the culmination of this process, and its description in the model results as natural. An-other matter that this model deals with is the well known problem of the tension between theinheritance mechanism and the subtyping relation, a recurrent problem in statically typed ob-ject-oriented languages with a flexible inheritance mechanism.

The mode mechanism is a meta level programming tool that allows one to impose arbitraryproperties on the typed entities of the programs (i.e. variables, expressions, parameters andresults), depending on the mode used in the declaration of these entities. For example, the par-ticular properties of an integer variable with logic mode (i.e. a variable declared with type log

Int) are different from the particular properties of an integer variable with constant mode (i.e. avariable declared with type const Int). There are two facets in a mode: one dynamic and anotherstatic. The dynamic facet is concerned with the dynamic behaviour of the objects of modifiedfunctionality that the mode implements. The static facet is concerned with the static propertiesof the typed entities, properties that are described using rules of implicit conversion of type(coercion rules) and rules of reinterpretation of syntax. In order to be able to deal with thesetwo types of rules, the mode mechanism includes a extensible coercion system as well as asimple scheme of syntax overloading focused on the primitive operations of the language (i.e.attribution, application, sending of message, etc.) The standard library of the language OM in-cludes five modes, already defined, – const, value, lazy, log, gen – and it is not a closed system.

The extensible coercion system is not trivial, and its most intuitive version is undecidable.This problem of undecidability could only be solved with the discovery of a pragmatic solu-tion, compatible with the intended use of the system in the language.

Résumé

Cette thèse explore l'idée de créer un langage de programmation multiparadigme statiquementtypé, où les paradigmes participants sont intégrés, pas d'une façon ad-hoc, mais d'une façonuniforme, en utilisant un mécanisme primitif d'extension sémantique. OM est le nom du langa-ge qu'on y développé, et mode, ou mécanisme des modes, le nom du mécanisme primitifd'extension sémantique qu'on propose.

Le noyau du langage OM est orientée-objet et supporte les éléments primitifs principauxsuivants: objets mutables avec des composants privés, classes, héritage, sous-types, méthodesbinaires, polymorphisme paramétrique, composants de classe et mécanisme des modes. Cenoyau linguistique est établi graduellement, parallèlement au développement de son modèletypé: d'abord un calcul lambda polymorphe d'ordre supérieur, appelé F+, est présenté, et puis,sur celui-ci, on érige l'édifice du langage et du modèle, peu à peu. Le concept du mode émergedans le point culminant de cette procédure et sa description dans le modèle résulte commenaturelle. Une autre matière traitée par le modèle est le problème de la tension entre le méca-nisme d'heritáge et la relation de sous-typage, un problème récurrent dans des langages orien-tée-objet statiquement typés avec des mécanismes d'héritage flexibles.

Le mécanisme des modes est un outil de programmation de niveau méta qui permet d'impo-ser les propriétés arbitraires aux entités typées des programmes (c.-à-d. variables, expressions,paramètres et résultats), selon le mode utilisé dans la déclaration de ces entités. Par exemple,les propriétés spécifiques d'une variable entière avec le mode logique (c.-à-d. une variabledéclarée avec type log Int) sont différentes des propriétés spécifiques d'une variable entièreavec le mode constant (c.-à-d. une variable déclarée avec type const Int). Dans un mode onconsidere deux facettes: une dynamique et une statique. La facette dynamique concerne lecomportement dynamique des objets avec fonctionnalité modifiée qui le mode implémente. Lafacette statique concerne les propriétés statiques des entités typées, propriétés qui sont spéci-fiées par des règles de conversion implicite de type (règles de coercition) et des règles de réin-terprétation de syntaxe. Afin de traiter ces deux types de règles, le mécanisme des modesinclut un système de coercition extensible et un système simple de superposition de syntaxefocaliée sur les opérations primitives du langage (c.-à-d. attribution, application, envoi du mes-sage, etc.) La bibliothèque standard du langage OM contient cinq modes, déjà définis, - const,value, lazy, log, gen - e ce n'est pas un système fermé.

Le système de coercition extensible n'est pas trivial, et sa version plus intuitive est indéci-dable. Ce problème a pu être resolu seulement avec la découverte d'une solution pragmatique,compatible avec l'utilisation voulue pour le système dans le langage.

Índice

Agradecimentos....................................................................................................................... iii

Sumário ......................................................................................................................................v

Abstract .................................................................................................................................. vii

Résumé ......................................................................................................................................ix

Índice .........................................................................................................................................xi

Capítulo 1 – Introdução ...........................................................................................................1

1.1 Paradigmas de programação .....................................................................................1

1.2 Linguagens extensíveis .............................................................................................2

1.3 Apresentação da linguagem e do trabalho ................................................................4

1.4 Estrutura da dissertação ............................................................................................5

1.5 Principais contribuições desta tese............................................................................7

Capítulo 2 – O Sistema F+ ........................................................................................................9

2.1 O sistema F e suas extensões ..................................................................................10

2.1.1 O sistema F ...............................................................................................10

2.1.2 O sistema Fω .............................................................................................11

2.1.3 Os sistemas F≤ e Fω≤ ..................................................................................12

2.1.4 Polimorfismo paramétrico F-restringido ..................................................13

2.2 Sintaxe de F+ ...........................................................................................................14

2.2.1 Variáveis ligadas ......................................................................................16

2.3 Sistema de tipos de F+ .............................................................................................16

2.3.1 Juízos ........................................................................................................17

2.3.1.1 Contextos ...................................................................................17

2.3.1.2 Asserções ...................................................................................17

2.3.2 Sublinguagem dos tipos............................................................................18

2.3.2.1 Apresentação das regras de boa formação dos tipos .................18

2.3.2.2 Regras de boa formação dos tipos .............................................18

2.3.3 Identidade entre tipos ...............................................................................19

xii Índice

2.3.3.1 Apresentação das regras de identidade entre tipos....................19

2.3.3.2 Regras de identidade entre tipos ...............................................20

2.3.4 Subtipos....................................................................................................21

2.3.4.1 Apresentação das regras de subtipo ..........................................21

2.3.4.2 Regras de subtipo ......................................................................23

2.3.4.3 Noção de polaridade..................................................................23

2.3.4.4 Indecidibilidade da relação de subtipo em F≤ ...........................24

2.3.4.5 F+d subsistema decidível de F+ ...................................................25

2.3.4.6 Usos distintos de F+ e de F+d ......................................................27

2.3.5 Termos .....................................................................................................27

2.3.5.1 Apresentação das regras de boa formação dos termos..............27

2.3.5.2 Regras de boa formação dos termos .........................................28

2.4 Teoria equacional para F+ .......................................................................................28

2.4.1 Apresentação das regras de identidade entre termos ...............................28

2.4.2 Regras de identidade entre termos ...........................................................29

2.5 Formas derivadas ....................................................................................................30

2.5.1 Pares ordenados........................................................................................30

2.5.2 Operador de ponto fixo e valores recursivos ...........................................31

2.5.3 Declarações locais de tipos e valores .......................................................31

2.5.4 Tipos existenciais .....................................................................................32

2.5.4.1 Exemplo de tipo existencial ......................................................32

2.5.4.2 Tipos existenciais restringidos ..................................................33

2.5.4.3 Tipos existenciais F-restringidos ..............................................33

2.5.5 Concatenação de registos .........................................................................34

2.5.6 Referências ...............................................................................................36

2.5.6.1 Sintaxe de F+& ............................................................................37

2.5.6.2 Regras de boa formação de F+& ..................................................37

2.5.6.3 Semântica de F+& ........................................................................37

2.5.7 Constante polimórfica nil .........................................................................40

2.6 Modelo semântico...................................................................................................41

Capítulo 3 – Linguagem sem objectos ..................................................................................43

3.1 A linguagem L3 ......................................................................................................44

3.1.1 Sintaxe......................................................................................................44

3.1.2 Relações binárias......................................................................................45

3.1.3 Programa ..................................................................................................45

Capítulo 4 – Objectos simples com herança ........................................................................47

4.1 Conceitos e mecanismos de L4...............................................................................48

4.1.1 Objectos ...................................................................................................48

Índice xiii

4.1.2 Tipos-objecto ............................................................................................49

4.1.3 Classes ......................................................................................................49

4.1.4 Interfaces ..................................................................................................51

4.1.5 Herança, subclasses e superclasses ..........................................................51

4.1.6 “Reutilização sem reverificação” .............................................................52

4.1.7 Subtipos ....................................................................................................53

4.2 Semântica de L4 ......................................................................................................53

4.2.1 Semântica dos tipos ..................................................................................54

4.2.2 Semântica dos termos ...............................................................................54

4.2.2.1 Semântica das classes ................................................................55

4.2.2.2 Boa formação das subclasses ....................................................55

4.2.2.3 Semântica dos outros termos .....................................................56

4.3 Discussão sobre L4 .................................................................................................56

4.3.1 Inicialização dos objectos e criação de cópias modificadas .....................57

4.3.2 Problema da perda de informação ............................................................57

4.3.3 Inflexibilidade na herança do tipo de self ................................................58

4.3.4 Métodos binários ......................................................................................59

4.3.5 Tipos dinâmicos em L4 ............................................................................60

4.3.5.1 Introdução dos tipos dinâmicos .................................................60

4.3.5.2 Operação de teste de tipo...........................................................61

4.3.5.3 Operação de despromoção de tipo.............................................62

4.3.5.4 Discussão ...................................................................................62

4.3.5.5 Utilidade dos tipos dinâmicos ...................................................63

4.3.6 Simulação em L4 do sistema de tipos do C++ .........................................63

4.4 Conclusões ..............................................................................................................64

Capítulo 5 – Tipo SAMET, relações de compatibilidade e de extensão ...........................65

5.1 Conceitos e mecanismos de L5 ...............................................................................66

5.1.1 O tipo SAMET .........................................................................................66

5.1.2 Relação de extensão entre interfaces ........................................................68

5.1.3 Tipificação aberta e tipificação fixa .........................................................69

5.2 Semântica de L5 ......................................................................................................70

5.2.1 Semântica dos tipos ..................................................................................71

5.2.2 Semântica dos termos ...............................................................................73

5.2.2.1 Semântica das classes ................................................................73

5.2.2.2 Boa formação das subclasses ....................................................73

5.2.2.3 Semântica dos outros termos .....................................................75

5.2.3 Relação de subtipo vs. relação de compatibilidade ..................................75

5.3 Propriedades das relações de compatibilidade e extensão ......................................76

5.4 O operador “+”........................................................................................................89

xiv Índice

5.4.1 Problemas que o operador “+”resolve .....................................................90

5.4.2 Ilustração duma aplicação de “+” ............................................................90

5.4.3 Eficácia da classe +c na prática................................................................91

5.4.3.1 Métodos binários transformados não redefinidos .....................92

5.4.3.2 Métodos binários transformados redefinidos ............................92

5.4.3.3 Conclusão ..................................................................................92

5.5 Discussão sobre L5 .................................................................................................93

5.5.1 Complicação da relação de extensão .......................................................93

5.5.2 Programação genérica ..............................................................................94

5.5.2.1 Tipo heterogéneo.......................................................................95

5.5.2.2 Colecções heterogéneas ............................................................97

5.6 Conclusões ..............................................................................................................98

Capítulo 6 – Polimorfismo paramétrico .............................................................................101

6.1 Conceitos e mecanismos de L6.............................................................................102

6.1.1 Classes paramétricas ..............................................................................102

6.1.2 Funções paramétricas .............................................................................103

6.1.3 Parâmetros covariantes ..........................................................................104

6.2 Semântica de L6 ...................................................................................................106

6.2.1 Semântica dos tipos e termos .................................................................106

6.2.2 Boa tipificação da instanciação com variáveis de tipo ..........................106

6.3 Discussão sobre L6 ...............................................................................................107

6.3.1 Operações dependentes do tipo-parâmetro ............................................107

6.3.2 Polimorfismo paramétrico e coerções ....................................................108

6.3.2.1 Problema e solução .................................................................108

6.3.2.2 Exemplos.................................................................................109

6.4 Conclusões ............................................................................................................111

Capítulo 7 – Componentes privadas e variáveis de instância ..........................................113

7.1 Conceitos e mecanismos de L7.............................................................................115

7.1.1 Formas de encapsulamento ....................................................................116

7.1.2 Nomeação das componentes das classes................................................117

7.1.3 Tipo externo e tipo interno .....................................................................118

7.1.4 Interfaces global, externa, interna e secreta ...........................................119

7.1.5 SELFT, SAMET e herança ....................................................................120

7.2 Semântica de L7 ...................................................................................................120

7.2.1 Semântica dos tipos................................................................................120

7.2.2 Semântica dos termos.............................................................................123

7.2.2.1 Semântica das classes..............................................................123

7.2.2.2 Boa formação das subclasses ..................................................123

Índice xv

7.2.2.3 Semântica dos outros termos ...................................................126

7.2.2.4 Função de ocultação ................................................................127

7.3 A linguagem imperativa L7& ................................................................................128

7.3.1 Variáveis de instância.............................................................................128

7.3.2 Semântica de L7& ..............................................................................................129

7.3.2.1 Tratamento dos pontos fixos ...............................................................129

7.3.2.2 Criação das variáveis de instância .......................................................130

7.3.2.3 Inicialização das variáveis de instância ...............................................131

7.3.2.4 Constante nil ........................................................................................132

7.3.3 Tipos-referência e herança .................................................................................133

7.4 Conclusões ............................................................................................................134

Capítulo 8 – Componentes de classe ...................................................................................137

8.1 Conceitos e mecanismos de L8 .............................................................................139

8.1.1 Componentes de classe e meta-objectos ................................................140

8.1.2 Utilidade das componentes de classe .....................................................140

8.1.3 Nomeação das componentes das classes ................................................141

8.1.4 Tipos-objecto e tipos-meta-objecto ........................................................142

8.1.5 Interfaces de classe .................................................................................143

8.1.6 Recursividade das classes e SELFC .......................................................143

8.1.7 Componentes de classe e herança ..........................................................144

8.1.8 Resumo dos nomes especiais .................................................................144

8.2 Semântica de L8 ....................................................................................................145

8.2.1 Semântica dos tipos ................................................................................146

8.2.2 Semântica dos termos .............................................................................147

8.2.2.1 Semântica das classes ..............................................................147

8.2.2.2 Boa formação das subclasses ..................................................147

8.2.2.3 Semântica dos outros termos ...................................................148

8.3 Discussão sobre L8 ...............................................................................................148

8.3.1 Polimorfismo de classe...........................................................................149

8.3.1.1 Definição de polimorfismo de classe ......................................149

8.3.1.2 Boa tipificação da instanciação com variáveis de tipo ............149

8.4 Conclusões ............................................................................................................150

Capítulo 9 – Modos ...............................................................................................................153

9.1 Conceitos e mecanismos de L9 .............................................................................154

9.1.1 Modos .....................................................................................................154

9.1.2 Exemplo: o modo log ..............................................................................155

9.1.2.1 Faceta dinâmica do modo log...................................................156

9.1.2.2 Faceta estática do modo log .....................................................157

xvi Índice

9.1.3 O que é um modo? .................................................................................158

9.1.4 Implementação dum modo.....................................................................159

9.2 Semântica de L9 ...................................................................................................160

9.2.1 Semântica dos tipos................................................................................160

9.2.2 Semântica dos termos.............................................................................161

9.2.2.1 Modos......................................................................................161

9.2.2.2 Instanciação dum modo ..........................................................162

9.2.2.3 Boa tipificação da equação semântica ....................................164

9.2.3 Operadores de modo ..............................................................................165

9.3 Discussão sobre L9 ...............................................................................................166

9.4 Conclusões ............................................................................................................166

Capítulo 10 – Sistema de coerções ......................................................................................169

10.1 Conceitos e mecanismos de L10.........................................................................169

10.1.1 Coerções e relação de coerção .............................................................170

10.1.2 Sistema de coerções .............................................................................170

10.1.3 Coerções de modo ................................................................................171

10.1.4 Funções de conversão sem redundância ..............................................172

10.2 O sistema natural ................................................................................................174

10.2.1 Apresentação das regras de básicas do sistema natural .......................174

10.2.2 Regras básicas do sistema natural ........................................................175

10.2.3 Regras de coerção extra .......................................................................176

10.2.4 Operadores de conversão .....................................................................177

10.2.5 Regras terminais e árvores de prova ....................................................177

10.2.6 Procedimentos de prova .......................................................................179

10.2.7 Problemas do sistema natural...............................................................181

10.2.7.1 Indeterminismo .....................................................................181

10.2.7.2 Ambiguidade .........................................................................183

10.2.7.3 Indecidibilidade.....................................................................184

10.2.7.3.1 Procedimento geral de prova ..................................184

10.2.7.3.2 Propriedade da subfórmula.....................................185

10.2.7.3.3 Indecidibilidade do sistema natural ........................187

10.3 O sistema prático ................................................................................................190

10.3.1 Apresentação das regras básicas do sistema prático ............................191

10.3.2 Regras básicas do sistema prático ........................................................191

10.3.3 Consequências da eliminação da regra da transitividade .....................192

10.3.4 Procedimentos de prova normalizado e prático ...................................193

10.3.5 Propriedades dos procedimentos de prova ...........................................194

10.3.6 Propriedades do sistema prático...........................................................199

Índice xvii

Capítulo 11 – Linguagem OM .............................................................................................211

11.1 Sintaxe da linguagem OM...................................................................................211

11.2 Nomeação das classes e tipos-objecto ................................................................212

11.2.1 Regras de nomeação .............................................................................213

11.2.2 Justificação das regras de nomeação ....................................................213

11.2.3 Aspectos práticos..................................................................................214

11.2.4 Exemplo................................................................................................214

11.3 Sobreposição da sintaxe de OM..........................................................................214

11.3.1 Atribuição de semântica às construções de semântica variável ...........215

11.3.2 Matéria-prima semântica ......................................................................216

11.4 Nível privilegiado e recursos especiais ...............................................................217

11.5 Componentes globalizadas..................................................................................218

11.5.1 Utilidade ...............................................................................................219

11.6 Resolução de nomes ............................................................................................220

11.7 Biblioteca de classes mínima ..............................................................................221

Capítulo 12 – Modos da biblioteca padrão .........................................................................229

12.1 Componentes adicionadas a $CoreObject ..............................................................229

12.2 Modo const...........................................................................................................230

12.3 Modo value...........................................................................................................231

12.4 Modo lazy ............................................................................................................233

12.5 Modo log ..............................................................................................................235

12.6 Modo gen .............................................................................................................238

12.6.1 Protótipo do modo gen ..........................................................................242

Capítulo 13 – Exemplo .........................................................................................................245

13.1 O problema dos padrões......................................................................................245

13.1.1 Padrões .................................................................................................246

13.1.2 Uso dos padrões....................................................................................247

13.1.3 A classe abstracta Pattern .......................................................................248

13.1.4 As classes concretas .............................................................................249

Conclusões .............................................................................................................................253

Bibliografia ............................................................................................................................255

Capítulo 1

Introdução“‘OM’ means the first vibration – that sound, that spirit that sets eve-

rything else into being. It is The Word from which all men and everythingelse comes, including all possible sounds that man can make vocally. It isthe first syllable, the primal word, the word of power."

John Coltrane

1.1 Paradigmas de programaçãoUm paradigma de programação é um modelo conceptual que determina uma forma particular

de abordar os problemas de programação e de formular as respectivas soluções. Cada paradig-

ma é caracterizado por um conjunto de conceitos mutuamente relacionados, ou seja, por uma

ontologia própria. Por exemplo, o paradigma de programação imperativo é caracterizado pelas

noções de memória, atribuição e sequenciação; o paradigma funcional pelas noções de função

e aplicação; o paradigma lógico pelas noções de relação e dedução lógica.

Diferentes paradigmas de programação representam visões distintas, e muitas vezes irre-

conciliáveis, do processo de resolução de problemas. Assim, o grau de sucesso de cada

paradigma de programação na resolução dum problema particular pode variar muito. Se num

determinado contexto conceptual esse problema pode ter uma solução natural e fácil de

descobrir, noutro contexto conceptual o problema pode ser de árdua resolução e exigir um

tratamento elaborado e artificial.

Assim se compreende que o grau de sucesso dum programador dependa da colecção de

paradigmas que domine e da sua arte em escolher, para cada problema, o modelo conceptual

mais indicado para tratar esse problema. Citando Robert Floyd na sua “ACM Turing Award

Lecture” intitulada “The Paradigms of Programming” [Flo79]:

“If the advancement of the general art of programming requires the continuing invention and

elaboration of paradigms, advancement of the art of the individual programmer requires that

he expand his repertory of paradigms.”

Diz-se que uma linguagem de programação suporta um dado paradigma de programação se

as construções e mecanismos dessa linguagem reflectirem directamente os conceitos do para-

digma. Uma solução elaborada segundo um certo paradigma pode ser expressa directamente

numa linguagem que suporte esse paradigma. Voltando a citar Floyd [Flo79]:

2 OM – Uma linguagem de programação multiparadigma

“I believe the continued advance of programming as a craft requires development and disse-

mination of languages which support the major paradigms of their user's communities.”

As linguagens modernas tendem a suportar mais do que um paradigma de programação.

Há exemplos em que esse efeito se obtém estendendo uma linguagem já existente: é o caso

do C++ [ES90], que resultou da incorporação, na linguagem C [KR78], de suporte para o pa-

radigma orientado pelos objectos; é também o caso da linguagem HOPE [DFP86], que nasceu

como uma linguagem funcional mas evoluiu assimilando aspectos do paradigma lógico.

Outras linguagens foram desenhadas logo de início com o objectivo de suportar um conjun-

to alargado de paradigmas. Por exemplo, na proposta inicial do sistema Andorra [Har89]

anuncia-se: “our approach has been to integrate the paradigms of Prolog, committed choice

and process description languages, concurrent objects, and constraint programming in a

single unified framework”. Nesta categoria incluem-se também as linguagens Nial [JG86] e

Leda [Bud95], as quais suportam, com variável grau de sucesso na integração, os paradigmas

imperativo, funcional, lógico e orientado pelos objectos.

Uma linguagem multiparadigma [Pla91] tem um poder expressivo acrescido relativamente

a uma linguagem uniparadigma. Numa linguagem multiparadigma a paleta conceptual à dispo-

sição do programador é mais vasta e, se a linguagem estiver bem articulada, criam-se sinergias

que fazem com que cada paradigma recolha benefícios da presença dos outros.

Para esclarecer um possível equívoco, o conceito de poder expressivo [Fel90] não deve ser

confundido com o conceito de poder computacional: repare que do ponto de vista do poder

computacional, a generalidade das linguagens de programação são universais [Chu36], logo

computacionalmente equivalentes entre si.

As principais questões específicas que surgem no contexto do estudo duma linguagem

multiparadigma são os problemas da sua consistência e clareza semântica, e também a deter-

minação de qual a melhor sintaxe para representar conceitos e mecanismos, por vezes tão dis-

pares.

1.2 Linguagens extensíveisConsideremos uma linguagem de programação universal, digamos a linguagem Pascal e ima-

ginemos uma sua versão estendida, chamada Pascal+, por exemplo. Para definir a sintaxe e se-

mântica da nova linguagem, existem diversas técnicas genéricas conhecidas: gramáticas, o mé-

todo operacional, o método axiomático, etc. Mas esqueçamos estas técnicas gerais e foquemos

a nossa atenção na possibilidade de definir a semântica da linguagem Pascal+ usando a própria

linguagem Pascal: afinal, sendo universal e estando bem definida, a linguagem Pascal deverá

também poder ser usada como veículo de especificação sintáctica e semântica. Existem pelo

menos duas soluções para este problema.

1 Introdução 3

A primeira solução consiste em escrever em Pascal um programa tradutor que aceite como

entrada qualquer programa escrito em Pascal+ e produza como resultado um programa com o

mesmo significado, mas agora totalmente reescrito em Pascal. Este programa tradutor define

efectivamente a sintaxe e a semântica da linguagem Pascal+, além de constituir também uma

implementação da linguagem estendida.

A segunda solução consiste em escrever em Pascal um interpretador de Pascal+, um pro-

grama que valide os aspectos estáticos dos programas Pascal+ aos quais seja aplicado e que

preceda seguidamente à sua execução. Também neste caso, estamos perante um programa

escrito em Pascal que, de forma efectiva, especifica a sintaxe e a semântica da linguagem

Pascal+.

No primeiro caso, dizemos que foi usada uma técnica de definição estática; no segundo

caso, foi usada uma técnica de definição dinâmica. São duas técnicas distintas, mas igualmente

eficazes. (No que diz respeito ao segundo caso, é interessante considerar a situação particular

Pascal=Pascal+: nesta situação, parece que a linguagem Pascal se define a ela própria, através

do que se convencionou chamar um interpretador metacircular. Mas não há aqui qualquer

mistério, já que foi assumido que a linguagem Pascal se encontrava completamente definida à

partida.)

Esta discussão pretende acima de tudo mostrar que existe um potencial que pode ser explo-

rado para a definição de linguagens extensíveis: basta que a linguagem inclua algum mecanis-

mo que permita interiorizar os procedimentos de tradução e interpretação externos, atrás des-

critos.

Ao longo da história das linguagens de programação têm sido propostos diversos destes

mecanismos. Um dos mais antigos é certamente o próprio mecanismo dos procedimentos:

através da definição de procedimentos é possível enriquecer o conjunto de operações disponí-

veis para serem usadas nos programas, sendo as novas operações definidas usando a própria

linguagem, como se sabe. Outro mecanismo antigo é o das syntax-macros [Lea66], que

Leavenworth apresenta como sendo um mecanismo que permite estender a sintaxe e a semân-

tica duma linguagem de alto-nível: trata-se aproximadamente da ideia que está na base do pré-

-processador da linguagem C.

Estes dois mecanismos são de natureza estática. Vejamos agora dois exemplos de natureza

dinâmica. A linguagem Lisp [Mac62] adopta uma sintaxe que não estabelece distinção entre

dados e programas. Este aspecto facilita a escrita de programas que manipulam outros progra-

mas e, em particular, facilita a escrita de interpretadores que definam semânticas alternativas

para a linguagem. Outro exemplo é o sistema CLOS [DG87], que inclui uma componente, o

“CLOS Metaobject Protocol” [KRB91], que oferece uma implementação metacircular do pró-

prio sistema CLOS. Este sistema admite a modificação dos mecanismos básicos da linguagem,

e.g. envio de mensagem, a partir do interior da própria linguagem e, inclusivamente, de forma

dinâmica.

4 OM – Uma linguagem de programação multiparadigma

1.3 Apresentação da linguagem e do trabalhoEste trabalho explora a ideia da criação duma linguagem multiparadigma e estaticamente tipi-

ficada, na qual os paradigmas suportados sejam integrados, não através da introdução directa e

ad-hoc de novas construções e mecanismos primitivos, mas antes por meio dum mecanismo

de extensão uniforme, obedecendo a princípios claros e bem definidos. Esse mecanismo de ex-

tensão deverá ser simples, preferencialmente de natureza estática ou semi-estática, e basear-se

em conceitos estabelecidos, dentro do possível.

Foi assim criada a linguagem OM, uma estrutura linguística semanticamente extensível,

com a capacidade de crescer por adição de conceitos e mecanismos de diferentes paradigmas.

O núcleo da linguagem OM apresenta-se sob a forma duma linguagem orientada pelos ob-

jectos/imperativa, baseada em classes. Esta escolha não foi casual. Em primeiro lugar, como

contraponto à extensibilidade da linguagem, é importante que o núcleo da linguagem imponha

formas de organização dos programas, fixando todos os aspectos de programação em grande.

Em segundo lugar, a generalidade das linguagens baseadas em classes incorpora já alguns as-

pectos de extensibilidade que foram por nós explorados de forma essencial: note que uma clas-

se é uma entidade extensível, por natureza.

O núcleo da linguagem suporta ainda um mecanismo de programação de nível meta, desi-

gnado por modo ou mecanismo dos modos, que é o mecanismo de extensão que propomos pa-

ra a linguagem. O mecanismo dos modos actua na linguagem através da alteração da funciona-

lidade das entidades tipificadas da linguagem, concretamente dos objectos, variáveis, expres-

sões, parâmetros de função e resultados de função. Para ilustrar a influência dos modos sobre

as variáveis, por exemplo, apresentamos dois exemplos simples: uma variável inteira com mo-

do lógico (declarada com tipo “log Int”) tem a funcionalidade das variáveis simbólicas da lin-

guagem Prolog [CM81, Hog84]; uma variável inteira com modo constante (declarada com tipo

“const Int”) é obrigatoriamente inicializada no ponto da declaração e não pode ser alterada.

Na linguagem OM é possível introduzir um número ilimitado de modos, especificando ca-

da modo um pacote de conceitos e mecanismos interligados. Um modo define-se usando a pró-

pria linguagem OM através duma construção sintáctica parecida com uma classe paramétrica.

No mecanismo dos modos há duas facetas a considerar: uma dinâmica e outra estática. A

faceta dinâmica concentra-se na questão do enriquecimento da funcionalidade dinâmica dos

objectos com modo, a qual é especificada por meio duma implementação. A faceta estática

concentra-se nas propriedades estáticas das entidades tipificadas com modo, propriedades que

são especificadas com recurso a um sistema de coerções extensível e a um esquema simples de

sobreposição de sintaxe

A biblioteca padrão da linguagem OM inclui cinco modos, já definidos, que ilustram bem

as possibilidades do mecanismo dos modos. São eles: o modo const, que introduz constantes na

linguagem; o modo value, que introduz semântica de não-partilha; o modo lazy, que introduz

1 Introdução 5

uma variante de call-by-name e a possibilidade de trabalhar com estruturas de dados infinitas;

o modo log, que introduz variáveis lógicas e unificação sintáctica e semântica; o modo gen,

que pela via da noção de gerador, introduz retrocesso (backtracking) na linguagem.

A parte mais substancial desta tese consiste na construção dum modelo teórico para uma

versão abstracta da linguagem OM que será referida por linguagem L10: o modelo define rigo-

rosamente esta linguagem, funcionando como seu suporte explicativo. No modelo consideram-

-se os seguintes elementos: objectos, classes, herança, polimorfismo paramétrico, ocultação de

informação, estado, componentes de classe e a faceta dinâmica dos modos. Como a formaliza-

ção do mecanismo dos modos requer o envolvimento, directo ou indirecto, de todos os outros

elementos do modelo, a introdução dos modos representa o culminar do processo de desenvol-

vimento do modelo.

Reflectindo o nosso objectivo de criar uma linguagem estaticamente tipificada, foi um mo-

delo tipificado aquele que desenvolvemos para a linguagem. Ao longo da última década mui-

tos investigadores têm tentado superar as muitas dificuldades envolvidas na criação de siste-

mas de tipos estáticos para linguagens orientadas pelos objectos que não interfiram excessiva-

mente na expressividade dessas linguagens [FM95]. Neste aspecto, o nosso trabalho assimila o

presente estado da arte, e melhora-o em alguns aspectos pontuais (referidos na secção 1.5).

Esta tese inclui ainda: o desenvolvimento dum sistema de coerções extensível para suporte

da faceta estática dos modos; a definição da linguagem concreta OM com base na linguagem

abstracta definida pelo modelo; a definição duma biblioteca padrão contendo diversas classes e

modos predefinidos; finalmente, um exemplo que ilustra uma aplicação prática e não trivial da

linguagem OM.

1.4 Estrutura da dissertaçãoA presente dissertação está organizada da seguinte forma.

O capítulo 2 introduz um cálculo-lambda polimórfico de ordem superior que se destina a

ser usado nos capítulos seguintes como veículo da descrição semântica da linguagem OM.

Este cálculo, designado por F+, agrupa num todo coerente elementos extraídos de diferentes

fontes da literatura e ainda alguns elementos simples da nossa responsabilidade. O capítulo 2 é

um pouco extenso porque nele se tenta antecipar a satisfação de todas as necessidades futuras

relacionadas com o sistema F+.

O capítulo 3 introduz a linguagem L3, uma linguagem simples que não é mais do que um

ponto de partida para a introdução gradual dos vários elementos da linguagem OM.

O capítulo 4 introduz a linguagem L4, uma linguagem orientada pelos objectos com um

sistema de tipos estático rudimentar, semelhante aos sistemas de tipos de linguagens como o

C++ ou o Java. L4 partilha com estas duas linguagens a característica favorável de todas as

suas subclasses serem geradoras de subtipos, relativamente ao tipo gerado por cada superclas-

6 OM – Uma linguagem de programação multiparadigma

se. Neste capítulo, apresentamos os conceitos essenciais da linguagem L4, fazemos o seu de-

senvolvimento formal (com base num modelo monomórfico), e discutimos as fragilidades do

seu sistema de tipos, entre outros aspectos.

O capítulo 5 introduz a linguagem L5, uma evolução da linguagem L4 que suporta o nome

de tipo SAMET, um nome de tipo genérico que dentro de cada classe representa o tipo externo

de self. Diversos modelos teóricos da literatura suportam variantes de SAMET, sendo bem co-

nhecido o impacto da sua introdução. Na linguagem L5, a introdução do tipo SAMET permite

contornar algumas das limitações de expressividade que o sistema de tipos de L4 determina:

em particular são alargadas as modalidades de reutilização de código. No entanto, a introdução

de SAMET cria um problema: em certas situações as subclasses deixam de gerar subtipos, o que

prejudica a aplicação de certas técnicas de programação genérica que pressupõem a existência

de subtipos. Dentro da filosofia multiparadigma da linguagem OM, “restrições arbitrárias de

expressividade não são aceitáveis”, procurámos e encontrámos uma solução para o problema.

Trabalhando com uma relação de extensão entre classes mais fraca do que é habitual, foi pos-

sível introduzir um transformador de classes “+” que possibilita a geração de versões não pro-

blemáticas de classes problemáticas.

O capítulo 6 introduz a linguagem L6, que estende L5 com uma forma de polimorfismo pa-

ramétrico restringido em que a restrição sobre os tipos-parâmetro é imposta usando a mesma

relação binária que limita o tipo gerado por uma subclasse, face à interface da sua superclasse.

Assim, esta forma de polimorfismo paramétrico permite operar de forma genérica sobre todos

os objectos gerados pelas subclasses duma dada classe (mesmo que essas subclasses não ge-

rem subtipos). Polimorfismo paramétrico é um mecanismo de programação genérica de base

estática que contribui de forma essencial para a expressividade de qualquer linguagem.

O capítulo 7 é dedicado às questões da ocultação de informação e do estado. As regras de

encapsulamento da linguagem L7 baseiam-se nas regras de encapsulamento da linguagem

Smalltalk. No entanto, relaxamos estas regras um pouco, na fase inicial de vida dos objectos,

para permitir que estes sejam inicializados por entidades exteriores. O nosso modelo mostra

em que condições é possível fazer isso.

O capítulo 8 define a linguagem L8, na qual introduzimos componentes de classe e classes

recursivas sobre o nome abstracto SELFC. As componentes de classe constituem um mecanis-

mo de utilidade geral: permitem definir construtores, definir variáveis partilhadas, e exprimir

informação logicamente associada a cada classe; são ainda exploradas na definição da parte

estática do mecanismo dos modos. Neste capítulo introduzimos ainda uma outra forma de po-

limorfismo paramétrico – polimorfismo de classe – que será a forma exclusiva de polimorfis-

mo paramétrico a adoptar na linguagem concreta final OM.

O capítulo 9 introduz a linguagem L9. É neste capítulo que se apresenta e formaliza a no-

ção de modo, ou mais exactamente a faceta dinâmica da noção de modo. O mecanismo dos

modos é o mecanismo de extensão semântica que está na base das características multipara-

1 Introdução 7

digma da linguagem OM. Actua influenciando de forma uniforme a funcionalidade das entida-

des tipificadas da linguagem.

O capítulo 10 é dedicado ao desenvolvimento do sistema de coerções extensível da lingua-

gem L10. São estudadas duas versões deste sistema. A versão final, designada sistema prático,

é decidível. O sistema prático recorre a um algoritmo de prova completo e determinista, cha-

mado de procedimento de prova prático, que incorpora uma exigência especial do mecanismo

dos modos: a geração de árvores de prova com tamanho mínimo.

O capítulo 11 descreve a linguagem prática OM e a sua biblioteca de classes, dita biblio-

teca mínima. A linguagem prática alarga os mecanismos de especificação da faceta estática

dos modos, estabelece diversos aspectos pragmáticos (uma sintaxe concreta, regras de nomea-

ção de classes e tipos, e regras de resolução de nomes) e adquire um nível privilegiado no con-

texto do qual a própria linguagem pode ser estendida ou alterada.

O capítulo 12 descreve os cinco modos da biblioteca padrão da linguagem OM.

O capítulo 13 discute a resolução, usando a linguagem OM, dum problema não trivial, es-

colhido para ilustrar a acção combinada dos paradigmas suportados.

1.5 Principais contribuições desta teseNo conhecido trabalho "Inheritance is not subtyping" [CHC90], mostra-se que quando se au-

menta a flexibilidade dum modelo de objectos para exprimir herança, a utilidade da relação de

subtipo se reduz significativamente. Isto acontece porque se perde a garantia de que as sub-

classes da linguagem sejam geradoras de subtipos. Esta circunstância levanta sérios problemas

práticos de expressividade que, paradoxalmente, são quase ignorados na literatura. A questão é

abordada por Bruce em [BPF97], mas a solução encontrada envolve a eliminação da relação

de subtipo e a sua substituição por uma relação alternativa menos satisfatória.

A primeira contribuição importante desta tese consiste numa solução para o conflito entre o

mecanismo de herança e a relação de subtipo, atrás apresentado. Efectuamos a análise do pro-

blema no contexto duma relação de herança muito geral e complexa (só com interesse teórico),

uma relação suficientemente rica para permitir a descoberta duma solução; depois simplifica-

mos a relação tendo o cuidado de não prejudicar a validade da solução. No contexto desta tese,

esta é uma questão importante pois pretendemos evitar que a linguagem OM, uma linguagem

dita multiparadigma, seja incapaz de suportar os principais idiomas de programação usados

nas linguagens C++, Java, e Smalltalk.

A segunda contribuição importante consiste na noção de modo e da sua explicação por

meio duma formalização. Acreditamos que a nossa noção de modo se encontra no ponto ideal

entre a simplicidade, naturalidade e capacidade de introduzir uma dimensão de extensibilidade

numa linguagem. Esta noção resultou dum longo processo de experimentação e amadureci-

mento. Ela foi ganhando diversas formas ao longo do tempo: mas as várias alternativas que se

8 OM – Uma linguagem de programação multiparadigma

iam apresentando eram, todas elas, ou demasiado complicadas de usar, ou demasiado compli-

cadas de explicar, ou então possuíam problemas funcionais. Relativamente à versão final fo-

ram duas as razões que nos convenceram de que teríamos encontrado uma solução razoável: a

compacidade e a razoabilidade da formulação da sua faceta dinâmica, e o facto dos aspectos

estáticos dos modos poderem ser explicados usando as noções estabelecidas de coerção e so-

breposição de sintaxe.

As duas contribuições atrás descritas são as mais importantes desta tese. Como contribui-

ções menores podemos referir:

• A solução pragmática encontrada para resolver os problemas da indecidibilidade e da

ambiguidade no nosso sistema de coerções extensível;

• A arquitectura da linguagem concreta final e sua biblioteca padrão, que julgamos serem

simples e eficazes (também resultaram dum longo processo de convergência, possivel-

mente ainda não concluído);

• O modelo semântico desenvolvido, que tem a virtualidade de cobrir de forma coerente,

organizada e bastante abordável um largo espectro de mecanismos orientados pelos ob-

jectos. O modelo combina, adaptando, diversas ideias da literatura, com algumas nos-

sas.

Capítulo 2

O Sistema F+

Neste capítulo, introduzimos um cálculo-lambda polimórfico de ordem superior que usaremos

ao longo da presente tese como modelo de fundação para a linguagem OM. Designamos este

cálculo por sistema F+. Também antecipamos, neste capítulo, a resolução e discussão de todas

as necessidades futuras relacionadas com o sistema F+ que possam ser tratadas no âmbito deste

sistema, sem necessidade de contexto suplementar.

O sistema F+ tem por base o sistema Fω de Girard [Gir72, SP94], com a seguinte lista de in-

gredientes adicionados: relação de subtipo [CW85, CG92, CMMS94], quantificação universal

F-restringida (F-bounded) [CCH+89, CHC90], quantificação existencial F-restringida, tipos

recursivos [AC93, AF96], tipos-registo simples (não extensíveis), pares ordenados de tipos e

pares ordenados de termos.

Os ingredientes que integram o sistema F+ aproximam-se dos ingredientes usados noutros

modelos tipificados para linguagens de objectos, nomeadamente nos modelos descritos em

[Car88, CW85, CHC90, ESTZ94, Bru94, PT94, ACV96, BSG95, BFSG98]. O sistema F+ li-

mita-se a concretizar algumas escolhas que se pretendem adaptadas à linguagem a formalizar.

Por exemplo, o mecanismo de herança de OM requer o uso de quantificação universal F-res-

tringida [CCH+89], enquanto que a generalidade dos modelos referidos usa, ou poderia usar,

apenas quantificação universal restringida de ordem superior [AC96b].

Não pretendemos desenvolver neste capítulo a meta-teoria de F+, nem criar um modelo se-

mântico para este sistema. Não obstante, como referência orientadora, adoptámos um modelo

semântico conhecido da literatura: o modelo de Bruce e Mitchell [BM92], um modelo abstrac-

to muito geral e com um largo espectro de aplicação. Em conformidade, integrámos em F+

apenas ingredientes claramente suportados por este modelo.

Outros critérios usados no estabelecimento de F+ foram: a expressividade, a naturalidade e

a generalidade. Relativamente à expressividade, o sistema F+ tem evidentemente de ser sufi-

cientemente expressivo para permitir a codificação de todas as construções da linguagem OM.

Quanto à naturalidade, trata-se de incluir no sistema os elementos certos, que simplifiquem e

tornem mais intuitiva a codificação de OM em termos de F+. Demos prioridade a este factor,

mesmo em detrimento do minimalismo do sistema: por exemplo, seria possível evitar usar

tipos recursivos, como faz Pierce no seu “modelo existencial” [PT94], mas isso exigiria um

tratamento menos simples e menos directo de alguns aspectos da linguagem OM. Finalmente,

10 OM – Uma linguagem de programação multiparadigma

quanto à generalidade, escolhemos sempre as formulações mais gerais incluídas na literatura e

admitidas pelo modelo semântico referido no parágrafo anterior. Por exemplo, no caso dos

tipos recursivos, das várias formulações disponíveis [AF96] escolhemos a formulação de

Amadi e Cardelli [AC96b] que trata um tipo recursivo como sendo equivalente à sua própria

expansão infinita (o que é compatível com o facto do modelo semântico prever soluções

mínimas para todas as equações de tipo da forma F(A)=A). Também no caso da quantificação

universal, nos baseámos na formulação de Ghelli [CG92], que adaptarmos ao contexto do

polimorfismo F-restringido, em vez de partirmos da formulação original, mas menos geral, de

Cardelli [CW85].

Os objectos da linguagem OM são codificados em F+ como registos recursivos de tipo

também recursivo. As classes são definidas usando quantificação universal F-restringida e o

mecanismo de herança é definido usando uma relação entre operadores de tipo que, indirecta-

mente, se baseia na relação de subtipo.

Na literatura, são apresentados outros modelos tipificados para linguagens orientadas pelos

objectos, baseados em técnicas bem diferentes da que adoptamos, nomeadamente: registos

extensíveis [Wan87, Wan89, Rém89, Mit90, CM91, Car94], cálculo de objectos primitivos

[FHM94, AC94, ACV96], método denotacional directo [Bru94], método operacional directo

[BSG95, BFSG98].

2.1 O sistema F e suas extensõesO sistema F e muitas das suas extensões-padrão estão na base do sistema F+. Por isso nada me-

lhor do que apresentar estes sistemas, mesmo que com alguma brevidade, como forma de criar

um contexto propício ao melhor entendimento de F+.

2.1.1 O sistema FO sistema F [Gir71, Rey74] é um calculo-lambda de segunda ordem que, para além da abstrac-

ção de valor, λx:τ.e, e aplicação, (f e), típicos do cálculo-lambda tipificado de primeira ordem

[Chu40], inclui uma forma de abstracção de tipo, λX.e, e a correspondente operação de aplica-

ção (ou instanciação), F[τ]. As abstracções de tipo da forma λX.e chamam-se funções polimór-

ficas e representam funções de tipos para valores. Na abstracção λX.e, tanto o termo e como o

seu tipo estão parametrizados relativamente à variável de tipo X. O tipo de λX.e denota-se

∀X.τ, onde τ é o tipo de e. Para exemplificar, a função polimórfica identidade pode escrever-se

como λX.λx:X.x, e tem tipo ∀X.X→X.

O sistema F, assim como as suas variantes Fω e F≤, que iremos referir nos pontos seguintes,

satisfazem as propriedades de confluência e da normalização forte. Portanto, todos os termos

são redutíveis a uma forma normal, que é independente das escolhas dos subtermos a reduzir,

ao longo do processo de redução.

2 O sistema F+ 11

O sistema F diz-se impredicativo (ou circular) pois uma função polimórfica pode ser apli-

cada a um tipo qualquer, inclusivamente ao seu próprio tipo, como exemplificamos aplicando

a identidade polimórfica ao seu próprio tipo: (λX.λx:X.x)[∀X.X→X].

O sistema F diz-se paramétrico porque foi concebido para suportar exclusivamente funções

polimórficas paramétricas. Uma função polimórfica diz-se paramétrica se o seu comporta-

mento genérico não depender do tipo usado na sua instanciação [Str67]. Por outras palavras,

uma função polimórfica diz-se paramétrica se for possível escrevê-la duma forma que não de-

penda do tipo dos seus parâmetros. Por exemplo, é certamente paramétrica uma função poli-

mórfica dedicada à determinação do comprimento de listas, pois esta determinação não requer

a consideração do tipo dos elementos das listas.

Um princípio geral, aplicável a todas as linguagens paramétricas, é o seguinte: A informa-

ção de tipo que ocorre nos programas é usada em tempo de compilação, mas é ignorada em

tempo de execução.

Um resultado clássico que advém da parametricidade do sistema F, indica que o tipo para-

métrico ∀X.X→X contém como único elemento, a identidade polimórfica λX.λx:X.x. É fácil ve-

rificar este facto intuitivamente: se P for uma função de tipo ∀X.X→X e se instanciarmos X

com um conjunto singular, a, descobrimos que P[a] tem de ser a função identidade sobre

a; mas como f[X] opera independentemente do tipo X, então P[X] tem ser a identidade sobre

X, independentemente do tipo X.

A natureza do conceito de parametricidade é essencialmente semântica, pelo que não deve

surpreender que a teoria sintáctica de F não consiga capturar este conceito. Em particular, não

é possível demonstrar o resultado anterior usando apenas a teoria sintáctica do sistema F.

Em 1983, Reynolds formalizou pela primeira vez, e por via semântica, uma noção de para-

metricidade para uma linguagem semelhante ao sistema F [Rey83]. Reynolds conseguiu captu-

rar o carácter paramétrico da sua linguagem removendo do modelo semântico toda a informa-

ção de tipo associada aos termos a interpretar. No seguimento do trabalho de Reynolds surgi-

ram outros estudos dentro da mesma linha [Wad89, BL90, MR91, BM92]. Mais recentemente,

surgiram abordagens sintácticas da parametricidade, que passam por complicar a teoria sintác-

tica associada aos sistemas em estudo [ACC93, CMMS94].

Todas as variantes do sistema F estudadas neste ponto e nos pontos que se seguem são

paramétricas. Para verificar este facto basta considerar que o modelo de [BM92] (cf. secção

2.6) as captura todas.

2.1.2 O sistema Fω

O sistema Fω [Gir72, SP94] estende o sistema F com operadores de tipo, os quais se destinam

a modelizar tipos parametrizados (ou seja, funções de tipos para tipos). Este sistema define um

cálculo-lambda tipificado de primeira ordem ao nível dos próprios tipos (possuindo a sua pró-

12 OM – Uma linguagem de programação multiparadigma

pria regra de redução-β) e introduz regras de boa-formação dos operadores de tipo usando um

meta-sistema de tipos.

Os meta-tipos são designados por géneros (kinds). O género dos tipos simples (tipos não

parametrizados) é denotado por ∗; o género dos operadores de tipo que geram tipos simples

quando aplicados a tipos simples é denotado por ∗⇒∗; a expressão (∗⇒∗)⇒∗ denota o género de

todos os operadores de tipo que geram tipos simples quando aplicados a operadores de tipo do

género ∗⇒∗, etc.

A forma sintáctica geral dum operador de tipo é ΛX:Κ .κ, onde X é uma variável de tipo do

género Κ, e κ é um género no qual a variável X pode ocorrer. Este operador é do género Κ⇒Κ′,

se Κ′ for o género de κ. Para dar um exemplo, o operador de tipo identidade sobre tipos sim-

ples escreve-se ΛX:∗.X e pertence ao género ∗⇒∗.

O facto de existir uma regra de redução-β associada aos tipos torna a relação de identidade

= entre tipos não trivial, pelo que esta deve ser formalizada. Por exemplo, a seguinte identida-

de de tipos deverá ser válida em todo o contexto: (λX:Κ.κ′)κ=κ′[κ/X].

Ao contrário do sistema F, o sistema Fω é predicativo pois nele os tipos estão estratificados

em universos disjuntos. Em Fω não é possível definir uma função identidade tão geral como a

apresentada na secção anterior; quanto muito é possível definir uma identidade idΚ para cada

género Κ, pertencendo, nesse caso, o tipo da identidade idΚ a um género diferente de Κ, concre-

tamente ao género Κ⇒Κ.

O sistema Fω satisfaz as propriedades da confluência e da normalização forte. Em particu-

lar, a sublinguagem dos tipos satisfaz estas propriedades o que nos permite aplicar a regra de

redução-β para tipos com toda a liberdade (isto é, a ordem das aplicações não influencia a for-

ma normal final, a qual garantidamente existe).

2.1.3 Os sistemas F≤ e Fω≤

O sistema F≤ [CW85, CG92] enriquece F com uma relação binária de subtipo ≤ e com uma no-

va forma de polimorfismo paramétrico no qual as variáveis abstraídas estão sujeitas a um limi-

te superior. As novas abstracções têm a forma geral λX≤B.e e tipo ∀X≤B.τ, onde τ é o tipo de e.

O sistema F≤ permite escrever funções aplicáveis a todos os subtipos dum tipo particular. Para

garantir que o sistema F pode ser mergulhado no sistema F≤ adiciona-se um elemento máximo

Top aos tipos de F≤. Assim os termos λX.e do sistema F são reintroduzidos em F≤ sob a forma

λX≤Top.e.

A autoria do sistema F≤ é atribuída a Cardelli. Este sistema foi motivado pelo estudo de

modelos para linguagens orientadas pelos objectos. O cálculo original (de Cardelli e Wegner

[CW85]) chamava-se “Fun” e incluia o sistema F≤ como um fragmento. O sistema foi depois

desenvolvido em [Car88, Car90, CMMS94] e ainda por Curien e Ghelli em [CG92].

2 O sistema F+ 13

Na variante padrão do sistema F≤ [CG92], a relação de subtipo prova-se indecidível [Pie93,

Ghe93].

O sistema Fω≤ resulta da extensão de Fω com: uma relação de subtipo introduzida ao nível

de cada género Κ, novas abstracções da forma λX≤B.e e um elemento máximo Top(Κ) ao nível

de cada género. A meta-teoria duma variante de Fω≤ é extensamente desenvolvida em [SP94].

2.1.4 Polimorfismo paramétrico F-restringidoSe generalizarmos o sistema F≤ por forma a permitirmos que, nas abstracções de tipo, a variá-

vel de tipo ocorra no seu próprio limite superior, como em λX≤F[X].e, então obtemos uma for-

ma de polimorfismo paramétrico designada por polimorfismo paramétrico F-restringido

[CCH+89]. No termo “F-restringido”, a letra “F” serve para indicar que o limite superior do

tipo do parâmetro da abstracção é baseado numa função F de tipos para tipos. O tipo da abs-

tracção λX≤ϕ[X].e escreve-se ∀X≤ϕ[X].τ, onde τ é o tipo de e.

Note que o polimorfismo F-restringido é uma combinação de recursão com polimorfismo

de F≤ pois o parâmetro é parcialmente caracterizado por um limite superior no qual o próprio

parâmetro pode ocorrer. Se o operador de tipo ϕ for um tipo-registo parametrizado, então a

condição X≤ϕ[X] estabelece que X deve ser um tipo-registo contendo as componentes de ϕ[X],

pelo menos. Ora essas componentes dependem de X como se pode observar. Assim, um tipo τ

que verifique a condição τ≤ϕ[τ] será muitas vezes um tipo recursivo. No mesmo sentido, note

que a equação de tipos X=ϕ[X] define um tipo recursivo, e que a inequação X≤ϕ[X] resulta dum

enfraquecimento daquela equação.

Um exemplo bem ilustrativo das possibilidades expressivas do polimorfismo paramétrico

F-restringido é a seguinte função comp:

comp ˆ = λX≤eq:X→Bool.(λx:X.λy:X.(x.eq y))

Esta função pode ser instanciada com um tipo registo X qualquer, desde que contenha uma

operação eq que permita a um elemento doe tipo X comparar-se com qualquer outro elemento

do tipo X. Quanto à função comp, esta compara dois valores genéricos, x e y, do tipo X, usando

a operação eq definida em x. Para exemplificar uma invocação de comp, tomemos o tipo

recursivo ρ:

ρ ˆ = µSAMET.a:Nat, b:Nat, eq:SAMET→Bool

e os dois valores recursivos r e s do tipo ρ:

r ˆ = rec self.a=2, b=3, eq=λx:ρ.(self.a=x.a & self.b=x.b)

s ˆ = rec self.a=5, b=7, eq=λx:ρ.(self.a=x.a & self.b=x.b)

A expressão seguinte compara estes dois valores, r e s, usando a operação de igualdade

definida em r:

comp[ρ] r s

14 OM – Uma linguagem de programação multiparadigma

Note que é simples mostrar que ρ verifica a restrição X≤eq:X→Bool. Para isso, basta subs-

tituir, na inequação, X por ρ e mostrar que ρ≤eq:ρ→Bool fazendo uma vez unfolding da ocor-

rência de ρ do lado esquerdo.

O polimorfismo paramétrico F-restringido foi descoberto por Canning, Cook, Hill, Olthoff

e Mitchell [CCH+89] ao investigarem a forma de ultrapassar as limitações de F≤ na modeliza-

ção de aspectos das linguagens orientadas pelos objectos. As limitações de F≤ só se tornam

aparentes perante objectos de tipo recursivo (que são afinal os mais comuns).

2.2 Sintaxe de F+

Nesta secção apresentamos a sintaxe de F+, um cálculo-lambda polimórfico de ordem superior

por nós proposto, e que estende o sistema Fω≤ com polimorfismo paramétrico F-restringido e

mais alguns ingredientes padrão, como sejam tipos recursivos e tipos-registo.

A gramática independente do contexto que iremos apresentar, descreve a sintaxe (livre de

contexto) dos géneros, pré-tipos e pré-termos de F+. A caracterização precisa dos tipos (i.e.

pré-tipos bem formados), e termos (i.e. pré-termos bem formados) envolve aspectos contex-

tuais que serão capturados no sistema de tipos apresentado na secção 2.3.

Consideremos primeiro a linguagem dos géneros (ou meta-tipos). Este conjunto de expres-

sões é definido indutivamente: o seu elemento mais simples, denotado por “∗”, é o conjunto de

todos os tipos da linguagem que não são operadores de tipo nem produtos de tipos. Além dis-

so, se Κ, Κ′ forem géneros, então também serão géneros Κ⇒K ′ e Κ×Κ′. “Κ⇒K′” denota o con-

junto de todos os operadores de tipo que aplicam Κ em Κ′ e “Κ×Κ′” denota o produto cartesiano

de Κ e Κ′. Esta classificação dos géneros de F+ está de acordo com o modelo semântico de

[BM92], discutido na secção 2.6, no qual as colecções ℜ e ℑ, definidas indutivamente, modeli-

zam ∗ e Κ, respectivamente.

Κ ::=∗ género dos tipos

| Κ⇒Κ′ género dos operadores| Κ×Κ′ género dos produtos

A linguagem dos pré-tipos é descrita pela gramática que se segue. Cada linha da gramática,

exceptuando a primeira, descreve uma forma distinta de pré-tipo. Não fazendo parte da gramá-

tica, indicamos para cada forma de pré-tipo qual a forma geral do género a que pertence. As

variáveis usadas na gramática obedecem às seguintes convenções de tipo e género: κ:Κ,

ϕ:Κ⇒Κ′, τ:∗, υ:∗, σ:∗.

κϕτυσ ::=X :Κ variável de tipo

| TOP(Κ) :Κ tipo máximo do género Κ| ΛX:Κ.κ :Κ⇒Κ operador de tipo| ϕ[k] :Κ aplicação de operador de tipo| <κ,κ′> :Κ×Κ′ par ordenado de tipos

2 O sistema F+ 15

| κ.n (n=1,2) :Κ tipo projecção| µX:Κ.κ :Κ tipo recursivo| ∀X≤ϕ[X].τ :∗ tipo universal F-restringido| υ→τ :∗ tipo função| l

–:τ– :∗ tipo-registo (l

–:τ– abrevia l1:τ1‚…‚ln:τn)

(Formas derivadas)| ΛX.κ :∗⇒Κ operador de tipo sobre ∗| µX.κ :∗ tipo recursivo sobre ∗| LET X:Κ=κ IN e :τ declaração local de tipo| LET REC <X1,…,Xn>:Κ1×…×Κn=κ IN e:τ declaração de tipos mutuamente recursivos| ∃X≤ϕ[X].τ :∗ tipo existencial F-restringido| τ×τ′ :∗ tipo produto| l

–:τ–⊕l′–:τ′– :∗ tipo concatenação

As duas primeiras formas derivadas de termos que ocorrem na gramática têm as seguintes

codificações triviais à custa das formas primitivas do sistema:

ΛX.κ ˆ = ΛX:∗.κµX.κ ˆ = µX:∗.κ

As restantes formas derivadas têm codificações mais complexas e serão apresentadas na sec-

ção 2.5.

A linguagem das pré-termos é descrita pela gramática que se segue. Cada linha desta gra-

mática descreve uma forma distinta de pré-termo. Não fazendo parte da gramática, indicamos

para cada forma de pré-termo a forma geral do tipo a que pertence. As variáveis que ocorrem

na gramática obedecem às seguintes convenções de tipo: e:τ‚ f:υ→τ‚ P:∀X≤ϕ[X].τ.

efP ::=x :τ variável

| λx:υ.e :υ→τ abstracção de valor (função simples)| f e :τ aplicação| λX≤ϕ[X].e :∀X≤ϕ[X].τ abstracção de tipo| P[κ] :τ[κ/X] aplicação de abstracção de tipo| l

–=e

– :l

–: τ– registo (l

–=e

– abrevia l1=e1‚…‚ln=en)

| e.l :τ selecção

(Formas derivadas)| fix :(τ→τ)→τ operador de ponto fixo| rec x:τ.e :τ valor recursivo| let x:υ=e in e′ :τ declaração local de valor| let rec <x1,…, xn>:υ1×…×υn=e in e′ :τ declaração de valores mutuamente recursivos| pack X≤ϕ[X]=σ with e :∃X≤ϕ[X].τ criação de pacote| open e as X,e′ in e ′′ :υ uso de pacote| <e,e′> :τ×τ′ par ordenado| e.n (n=1,2) :τ projecção| +[l

–:τ–‚l′–:τ′–] :l

–:τ–×l′–:τ′–→l

–:τ–⊕l′–:τ′– concatenação assimétrica

Para efeitos de levantamento da ambiguidade, nas três gramáticas anteriores consideramos

que os operadores ×, ⊕, + e . são associativos à esquerda e os operadores ⇒ e → são associati-

vos à direita. O âmbito de Λ, µ, ∀, ∃ e λ estende-se tanto quanto possível para a direita.

16 OM – Uma linguagem de programação multiparadigma

Todas as formas derivadas de termos, incluídas na parte final da gramática, serão apresenta-

das na secção 2.5.

Note que no sistema F+ existem três formas de abstracção: abstracções monomórficas λx:υ.e

(funções de valores para valores), abstracções polimórficas λX≤ϕ[X].e (funções de tipos para

valores), e operadores de tipo ΛX:Κ.κ (funções de tipos para tipos). Para cada uma destas for-

mas de abstracção existe uma forma distinta de aplicação. Repare ainda que existem duas cate-

gorias distintas de pares ordenados: pares ordenados de tipos e pares ordenados de termos. Pa-

ra cada uma destas categorias de pares ordenados existe uma operação de projecção específica.

2.2.1 Variáveis ligadasO nome das variáveis ligadas é irrelevante nos pré-termos e pré-tipos definidos pela gramática

da secção anterior. Assim os pré-termos e os pré-tipos devem ser considerados módulo reno-

meação das suas variáveis ligadas. Para obter este efeito, associamos uma regra de identidade

sintáctica a cada uma das cinco construções que introduzem variáveis ligadas:

ΛX:Κ.κ ≡ ΛY:Κ.(κ[Y/X]) onde Y∉FV(κ)

µX:Κ.κ ≡ µY:Κ.(κ[Y/X]) onde Y∉FV(κ)

∀X≤ϕ[X].τ ≡ ∀Y≤ϕ[Y].(τ[Y/X]) onde Y∉FV(ϕ) e Y∉FV(τ)

λX≤ϕ[X].e ≡ λY≤ϕ[Y].(e[Y/X]) onde Y∉FV(ϕ) e Y∉FV(e)

λx:υ.e ≡ λy:υ.(e[y/x]) onde y∉FV(e)

Nestas regras, A[Y/X] representa a substituição sem captura de X por Y em A [Bar84]. Uma ou-

tra técnica, que não usamos, de abstracção do nome das variáveis ligadas, consiste no uso de

índices de de Bruijn em vez de nomes alfabéticos [dB72].

Como é hábito nos sistemas de tipos, o sistema F+ usa contextos para registar as variáveis

ligadas introduzidas nos seus termos (cf. secção 2.3.1). Por construção, num contexto não po-

dem coexistir duas variáveis ligadas com o mesmo nome. À partida, esta restrição torna a vali-

dade dos tipos e termos dependente dos nomes das variáveis ligadas: por exemplo, o termo

λx:υ.(λx:τ.x) não admite derivação formal directa no nosso sistema pois nele ocorrem duas va-

riáveis ligadas homónimas com âmbitos não disjuntos. Mas as regras de identidade sintáctica,

atrás definidas, libertam a linguagem dessa dependência. De facto, o termo anterior é equiva-

lente ao termo λx:υ.(λy:τ.y), o qual já admite derivação formal directa.

2.3 Sistema de tipos de F+

Apresentamos nesta extensa secção o sistema de tipos de F+. O sistema de tipos de F+ compre-

ende regras de boa formação de contextos, regras de boa formação de tipos, regras de boa for-

mação de termos, uma relação de identidade entre tipos e uma relação de subtipo. Na especifi-

cação de F+ empregamos as técnicas habituais de formalização de sistemas de tipos, apresenta-

das, por exemplo, em [Car97].

2 O sistema F+ 17

2.3.1 JuízosO sistema de tipos de F+ é um sistema de prova sobre juízos com cinco formas distintas, cada

uma delas com um significado distinto:

Γ ◊ Γ é um contexto bem formado

Γ τ:Κ o tipo τ pertence ao género Κ em ΓΓ τ=τ′:Κ os tipos τ e τ′ do género Κ, são idênticos em ΓΓ τ≤τ′ o tipo τ é subtipo de τ′ em ΓΓ e:τ o termo e tem tipo τ em Γ

Os juízo válidos do sistema são aqueles para os quais se podem construir, usando as regras

do sistema, uma árvore de prova cujas folhas são todas instâncias de axiomas. Um axioma é

uma regra sem premissas.

2.3.1.1 Contextos

A parte dum juízo que precede o símbolo chama-se contexto. Um contexto Γ é uma sequên-

cia finita ordenada de variáveis de valor com os respectivos tipos, x:τ, e variáveis de tipo com

os respectivos limites superiores, X≤ϕ[X]. Num contexto todas as variáveis têm de ser distintas.

Nas expressões de tipo podem ocorrer variáveis, mas só se estas já tiverem sido introduzidas

antes, no mesmo contexto. O contexto vazio denota-se por ∅. Eis um exemplo de contexto

bem formado:

∅,x:Nat,y:Nat→Nat,Z≤Point[Z],h:Z→Nat

O conjunto das variáveis que ocorrem num contexto Γ é representado por dom(Γ). O opera-

dor vírgula é usado para representar tanto a extensão dum contexto com uma nova variável –

Γ,x:τ ou Γ,X≤ϕ[X] – como a concatenação de dois contextos – Γ,Γ′. No primeiro caso requere-se

que a variável a adicionar não pertença ao domínio de Γ; no caso da concatenação requere-se

que os domínios dos dois contextos sejam disjuntos.

As regras que definem os contextos bem formados de F+ são as seguintes:

[Contexto vazio] ∅ ◊

[Contexto x]

Γ ◊ Γ τ:∗ x∉dom(Γ)

Γ‚x:τ ◊

[Contexto X]

Γ ◊ Γ Κ género X∉dom(Γ)

Γ‚X:Κ ◊

[Contexto X≤κ]

Γ ◊ Γ κ:Κ X∉dom(Γ)

Γ‚X≤κ ◊

[Contexto X≤ϕ[X]]

Γ ◊ Γ ϕ:Κ⇒Κ X∉dom(Γ)

Γ‚X≤ϕ[X] ◊

2.3.1.2 Asserções

A parte dum juízo que sucede o símbolo chama-se asserção. Todas as variáveis livres que

ocorrem numa asserção têm de estar declaradas no contexto respectivo.

Em situações em que o contexto Γ permanece fixo, é mais fácil trabalhar com simples as-

serções, deixando o contexto implícito, do que trabalhar com juízos completos. Esta é um prá-

18 OM – Uma linguagem de programação multiparadigma

tica habitual que também seguiremos. Assim, por exemplo, o termo asserção válida referir-se-

-á a um juízo válido no qual o contexto foi deixado implícito.

2.3.2 Sublinguagem dos tiposA sublinguagem dos tipos de F+ inclui: uma forma de abstracção-lambda, uma operação de

aplicação, um construtor de pares ordenados de tipos e duas operações de projecção. A sublin-

guagem dos tipos revela-se assim, ela mesma, como um cálculo-lambda tipificado de primeira

ordem.

Apresentamos nesta secção o sistema de tipos da sublinguagem dos tipos ou seja o meta-

-sistema de tipos de F+. Os meta-tipos designam-se por géneros (kinds). Cada género represen-

ta um conjunto particular de pré-tipos bem formados ou, mais simplesmente, de tipos.

2.3.2.1 Apresentação das regras de boa formação dos tipos

A maioria das regras desta secção são auto-explicativas, pelo que só comentamos algumas de-

las.

A regra [Tipo X] mostra que o limite superior das variáveis F-restringidas é sempre baseado

num operador de tipo do género Κ⇒Κ (note bem: com domínio e contra-domínio do mesmo

género).

A regra [Tipo Top] indica que em cada género Κ existe um elemento designado por Top(Κ).

Mais adiante, quando definirmos a relação de supertipo, esse elemento será considerado super-

tipo de todos os elementos do seu género.

Na regra [Tipo µ], κ∠X é uma condição elementar que significa que em µX:Κ.κ o tipo κ é

contractivo ou não-trivial em X, isto é κ≡/X e se κ≡µY:Κ.κ′ então κ′∠X (cf. [AC93, AF96]). Um

exemplo: de acordo com esta regra, o pré-tipo µX:∗.X não está bem formado, o que é consis-

tente com a ideia de que a equação recursiva trivial X=X não tem solução canónica.

Na regra [Tipo …] é considerada a possibilidade dum tipo-registo não ter componentes.

Daí o requisito explicito do contexto Γ ser bem formado.

2.3.2.2 Regras de boa formação dos tipos

[Tipo X]

Γ‚X≤ϕ[X]‚Γ′ ϕ:Κ⇒Κ

Γ‚X≤ϕ[X]‚Γ′ X:Κ

[Tipo Top]

Γ ◊

Γ Top(Κ):Κ

[Tipo Λ]

Γ‚X:Κx κ:Κ

Γ ΛX:Κx.κ:Κx⇒Κ

[Tipo aplic Λ]

Γ ϕ:Κx⇒Κ Γ κ:Κ

Γ ϕ[κ]:Κ

[Tipo ×]

Γ κ1:Κ1 Γ κ2:Κ2Γ <κ1‚κ2>:Κ1×Κ2

[Tipo .]

Γ κ:Κ1×Κ2

Γ κ.n:Κn (n=1,2)

[Tipo µ]

Γ‚X:Κ κ:Κ κ∠X

Γ µX:Κ.κ:Κ

[Tipo ∀]

Γ‚X≤ϕ[X] τ:∗

Γ ∀X≤ϕ[X].τ:∗

2 O sistema F+ 19

[Tipo →]

Γ υ:∗ Γ τ:∗

Γ υ→τ:∗

[Tipo …]

Γ ◊ Γ τ–:∗ l

– distintos

Γ l–:τ–:∗

2.3.3 Identidade entre tiposApresentamos agora as regras que definem a relação de identidade ou igualdade entre os tipos

de F+: trata-se portanto da teoria equacional da sublinguagem dos tipos de F+.

2.3.3.1 Apresentação das regras de identidade entre tipos

As regras [Tipo= fold/unfold µ] e [Tipo= contracção µ] estabelecem que todo o tipo recursivo

µX:Κ.κ:Κ é idêntico à sua própria expansão infinita (cf. [AC93, AF96]). Para exemplificar o

uso destas duas regras, provamos agora que os dois tipos recursivos, κ1 ˆ = µY:Κ.Nat→Y e

κ2 ˆ = µZ:Κ.Nat→Nat→Z, são idênticos. Como não é possível transformar um dos tipos no outro

usando apenas a regra [Tipo= fold/unfold µ], vamos explorar um outro caminho usando a regra

[Tipo= contracção µ]. Para isso temos de encontrar um tipo κ contractivo em X tal que κ1=κ[κ1/X] e

κ2=κ[κ2/X]. No presente exemplo, esse tipo é fácil de encontrar: basta reduzir κ1 e κ2 à mesma

forma na sua parte finita através do uso repetido da regra [Tipo= fold/unfold µ] num contexto Γ

arbitrário:

κ1 = µY:Κ.Nat→Y

= Nat→(µY:Κ.Nat→Y)

= Nat→Nat→(µY:Κ.Nat→Y)

= Nat→Nat→κ1

κ2 = µY:Κ.Nat→Nat→Y

= Nat→Nat→(µY:Κ.Nat→Nat→Y)

= Nat→Nat→κ2

O tipo contractivo pretendido é κ ˆ = Nat→Nat→X. Agora, por aplicação de [Tipo= contracção µ], ob-

temos κ1=κ2, o que conclui a demonstração. Nem todas as provas são tão simples como esta:

ver, por exemplo, a demonstração de µX:Κ.X→(X→X)=µX:Κ.(X→X)→X em [AC93].

A relação de identidade, e também a relação de subtipo, são decidíveis no contexto do siste-

ma monomórfico com tipos recursivos apresentado em [AC93]. Segundo Amadio e Cardelli

este resultado de decidibilidade generaliza-se a sistemas mais complexos, incluindo sistemas

de segunda ordem. Mais precisamente, podem adicionar-se tipos recursivos a qualquer sis-

tema, que a característica de decidibilidade ou indecidibilidade do sistema original se mantém

no novo sistema.

Ainda relativamente à regra [Tipo= fold/unfold µ], vale a pena referir que ela permite tipificar

a auto-aplicação “xx”. De facto, a operação de auto-aplicação está definida para todos os ter-

mos que tenham o tipo µX:∗.X→τ, onde τ é um tipo qualquer. Isto acontece porque, muito

simplesmente, Γ µX:∗.X→τ=(µX:∗.X→τ)→τ. Esta possibilidade de tipificar a auto-aplicação tem

20 OM – Uma linguagem de programação multiparadigma

como consequência a perda na propriedade de normalização forte: se >> representar uma rela-

ção de redução derivada das regras de F+ e se Rτ=µX:∗.X→τ, então, usando >>, o termo

divergeτ=(λx:Rτ.xx)(λx:Rτ.xx) não converge para qualquer forma normal (apesar de, formalmente,

ter o tipo τ). De qualquer forma, a confluência do sistema não fica comprometida.

A regra [Tipo= β Λ] define a operação de aplicação dum operador de tipo a um tipo-

-argumento.

A regra [Tipo= η Λ], serve para introduzir uma noção de identidade entre operadores de tipo

extensionalmente idênticos, ou seja, idênticos ponto a ponto ao longo de todo o seu domínio.

Efectivamente, conjugando esta regra com a seguinte outra regra [Tipo= Λ], prova-se que se

Γ‚X:Κx ϕ[X]=ϕ′[X]:Κ então Γ ϕ=ϕ′:Κx⇒Κ.

A regra [Tipo= proj ×] define as operações de projecção de pares ordenados de tipos e a regra

[Tipo= sp ×] é uma regra de extensionalidade adaptada ao caso dos pares ordenados (usamos sp

como abreviatura de surjective pairing, nome pelo qual a versão desta regra para pares de ter-

mos é conhecida em PCF).

A regra [Tipo= permutação …] indica que a ordem das etiquetas num tipo-registo é irrele-

vante.

As regras [Tipo= Top ⇒] e [Tipo= Top ×] identificam o tipo máximo dos géneros estruturados.

A regra [Tipo= género] indica que se um tipo está em relação consigo próprio para um dado

género, então esse tipo pertence a esse género.

As regras [Tipo= refl], [Tipo= sim] e [Tipo= trans] fazem de = uma relação de equivalência.

As oito regras finais tornam = numa congruência, isto é numa relação que permite a subs-

tituição de iguais por iguais: neste caso, a substituição de uma ou mais subcomponentes dum

tipo por componentes formalmente idênticas.

2.3.3.2 Regras de identidade entre tipos[Tipo= fold/unfold µ]

Γ‚X:Κ κ:Κ κ∠X

Γ µX:Κ.κ=κ[(µY:Κ.κ)/X]:Κ

[Tipo= contracção µ]

Γ κ1=κ[κ1/X]:Κ Γ κ2=κ[κ2/X]:Κ κ∠X

Γ κ1=κ2:Κ

[Tipo= β Λ]

Γ‚X:Κx κ:Κ Γ κx:Κx

Γ (ΛX:Κx.κ)[κx]=κ[κx/X]:Κ

[Tipo= η Λ]

Γ ϕ:Κx⇒Κ X∉dom(Γ)

Γ ΛX:Κx.ϕ[X]=ϕ:Κx⇒Κ

[Tipo= proj ×]

Γ κ1:Κ1 Γ κ2:Κ2

Γ <κ1‚κ2>.n=κn:Κn (n=1,2)

[Tipo= sp ×]

Γ κ:Κ1×Κ2

Γ <κ.1‚κ.2>=κ:Κ1×Κ2

[Tipo= permutação …]

Γ ◊ Γ τ–:∗ l

– distintos π permutação de 1‚…‚n

Γ l–:τ–=lπ(1):τπ(1)‚…‚lπ(n):τπ(n):∗

2 O sistema F+ 21

[Tipo= Top ⇒]

Γ ◊

Γ Top(Κ⇒Κ′)=ΛX:Κ .Top(Κ′):Κ⇒Κ′

[Tipo= Top ×]

Γ ◊

Γ Top(Κ×Κ′)=<Top(Κ)‚Top(Κ′)>:Κ×Κ′

[Tipo= género]

Γ κ=κ:ΚΓ κ:Κ

[Tipo= refl]

Γ κ:Κ

Γ κ=κ:Κ

[Tipo= sim]

Γ κ=κ′:ΚΓ κ′=κ:Κ

[Tipo= trans]

Γ κ=κ′:Κ Γ κ′=κ′′:Κ

Γ κ=κ′′:Κ

[Tipo= Λ]

Γ‚X:Κx κ=κ′:Κ

Γ ΛX:Κx.κ=ΛX:Κx.κ′:Κx⇒Κ

[Tipo= aplic Λ]

Γ ϕ=ϕ′:Κx⇒Κ Γ κ=κ′:Κ

Γ ϕ[κ]=ϕ′[κ′]:Κ

[Tipo= ×]

Γ κ1=κ1′:Κ1 Γ κ2=κ2′:Κ2

Γ <κ1‚κ2>=<κ1′‚κ2′>:Κ1×Κ2

[Tipo= .]

Γ κ=κ′:Κ1×Κ2

Γ κ.n=κ′.n:Κn (n=1,2)

[Tipo= µ]

Γ‚X:Κ κ=κ′:Κ κ∠X κ′∠X

Γ µX:Κ.κ=µX:Κ .κ′:Κ

[Tipo= ∀]

Γ‚X≤ϕ[X] τ=τ′:∗

Γ ∀X≤ϕ[X].τ=∀X≤ϕ[X].τ′:∗

[Tipo= →]

Γ υ=υ′:∗ Γ τ=τ′:∗

Γ υ→τ=υ′→τ′:∗

[Tipo= …]

Γ ◊ Γ τ–=τ′–:∗ l

– distintos

Γ l–:τ–=l

–:τ′–:∗

2.3.4 SubtiposO tipo τ é subtipo do tipo τ′, e escreve-se τ≤τ′, se toda a expressão do tipo τ puder ser usada nos

contextos sintácticos onde se espera uma expressão do tipo τ′ (cf. regra [Termo inclusão] da sec-

ção 2.3.5). Interpretando um tipo como um conjunto de termos, a relação de subtipo corres-

ponde à relação de inclusão entre conjuntos de termos.

A noção de subtipo, permite que uma função de domínio τ′ possa ser aplicada a termos de

vários tipos τ bastando para isso que τ≤τ′. Esta é uma forma particular de polimorfismo deno-

minada de polimorfismo de inclusão e identificada pela primeira vez em [CW85].

As regras desta secção axiomatizam a relação de subtipo no sistema F+. Esta é uma ordem

parcial, conforme o indicam as regras [Sub refl], [Sub anti-sim] e [Sub trans]. Note que a introdução

duma regra de anti-simetria é, implicitamente, uma manifestação da nossa intenção de inter-

pretar a relação de subtipo como a relação de inclusão entre conjuntos de termos. Eliminando

esta regra seria possível a interpretação, mais geral, da relação de subtipo como relação de

convertibilidade entre tipos.

2.3.4.1 Apresentação das regras de subtipo

A regra [Sub =] estabelece a conexão entre os juízos da relação identidade entre tipos e os juí-

zos da relação de subtipo: se dois tipos são idênticos, então um deles é subtipo do outro.

A regra [Sub X] permite a utilização de variáveis definidas no contexto.

22 OM – Uma linguagem de programação multiparadigma

A regra [Sub Top] indica que o tipo Top(Κ), anteriormente introduzido na regra [Tipo Top], é

supertipo de todos os tipos do género Κ.

[Sub Λ] define a relação de subtipo entre operadores de tipo, ponto a ponto.

A regra [Sub →] estabelece que a relação de subtipo entre tipos funcionais requer contrava-

riância (ou antimonotonia) no tipo do argumento e covariância (ou monotonia) no tipo do re-

sultado (cf. [Car97]). A justificação intuitiva desta regra pode fazer-se considerando a seguinte

questão: se tivermos f:υ→τ e f′:υ′→τ′, em que condições é possível usar f num contexto que es-

pera uma função com o tipo de f′? Em primeiro lugar se f′ pode ser aplicada a qualquer valor

do tipo υ′ também f o deve poder ser, de onde se obtém o primeiro requisito: υ′≤υ. Em segundo

lugar, se os resultados de f′ podem ser usado em contextos que esperam termos do tipo τ′, en-

tão os resultados produzidos por f também devem aí poder ser usados, de onde se obtém o

segundo requisito: τ≤τ′.

Segundo a regra [Sub µ], o tipo µX:Κ.κ é subtipo de µY:Κ.κ′ se a partir da assunção X≤Y for

possível provar κ≤κ′. Por exemplo, se é verdade que µX:Κ .Nat→X≤µY:Κ.Nat→Y, já não é verdade

que µX:Κ.X→Nat≤µY:Κ.Y→Nat. Um detalhe interessante é o facto desta regra não ser suficiente

para provar que µX:Κ.(X→Nat) é subtipo de si próprio: é necessário usar a regra [Sub refl] para

deduzir este facto.

Introduzimos a regra [Sub κµ], não definida em [AC93], pois teremos necessidade dela no

próximo capítulo. A regra estabelece que se κ≤ϕ[κ] e se ϕ for um operador de tipo crescente

então κ≤µZ:Κ.ϕ[Z]. Note que das premissas se obtém a seguinte cadeia infinita crescente

κ≤ϕ[κ]≤ϕ[ϕ[κ]]≤…., que conduz à condição κ≤ϕn[κ], para todo o n>0, o que mostra que a asserção

κ≤µZ:Κ.ϕ[Z] faz sentido. Convém ter em mente que a relação de subtipo é formalizada em

[AC93] usando uma noção de ordem entre árvores infinitas, definida à custa de aproximações

finitas.

A regra [Sub ∀] estabelece que a relação de subtipo entre tipos universais F-restringidos re-

quer uma forma de contravariância generalizada da restrição sobre o argumento e covariância

normal no tipo do resultado. Podemos obter uma justificação intuitiva para esta regra racioci-

nando como na regra [Sub →]. Se tivermos P:∀X≤ϕ[X].τ e P′:∀X≤ϕ′[X].τ′, em que condições será

possível usar P num contexto que espera uma função polimórfica com o tipo de P′? Em primei-

ro lugar, se P′ pode ser aplicada a um X qualquer tal que X≤ϕ′[X], então P também deve poder

ser aplicada a esse mesmo X, o que só é possível se também se verificar X≤ϕ[X]: isto justifica a

condição, que chamamos de contravariância generalizada, Γ‚X≤ϕ′[X] X≤ϕ[X]. Em segundo lu-

gar, se os resultados de P′ podem ser usados em contextos onde se esperam termos do tipo τ′

(dependente de X≤ϕ′[X]), então os resultados produzidos por P também devem poder ser aí usa-

dos, de onde se obtém a segunda exigência: Γ‚X≤ϕ′[X] τ≤τ′. As duas condições que obtivemos

são as principais premissas da regra [Sub ∀]. Esta regra generaliza a regra correspondente apre-

sentada em [CG92, Car97] para tipos universais restringidos, a qual também apresenta uma

forma de contravariância nos limites dos argumentos.

2 O sistema F+ 23

Finalmente, a regra [Sub …] estabelece que se para os dois tipos-registo ρ e ρ′ tivermos

ρ≤ρ′, então ρ inclui necessariamente as etiquetas de ρ′ e os tipos das componentes de ρ′ são su-

pertipos dos tipos das componentes respectivas de ρ. Se cada registo for interpretado como

uma função com um domínio de etiquetas, esta regra tem uma analogia com [Sub →]: a contra-

variância no domínio dos registos e a covariância nos tipos das componentes homónimas.

2.3.4.2 Regras de subtipo[Sub refl]

Γ κ:ΚΓ κ≤κ

[Sub anti-sim]

Γ κ≤κ′ Γ κ′≤κ

Γ κ=κ′

[Sub trans]

Γ κ≤κ′ Γ κ′≤κ′′

Γ κ≤κ′′

[Sub =]

Γ κ=κ′:ΚΓ κ≤κ′

[Sub X]

Γ‚X≤ϕ[X]‚Γ′ ◊

Γ‚X≤ϕ[X]‚Γ′ X≤ϕ[X]

[Sub Top]

Γ κ:Κ

Γ κ≤Top(Κ)

[Sub Λ]

Γ‚X:Κ κ≤κ′

Γ ΛX:Κ.κ≤ΛX:Κ.κ′

[Sub aplic Λ]

Γ ϕ≤ϕ′ Γ ϕ[κ]:Κ

Γ ϕ[κ]≤ϕ′[κ]

[Sub µ]

Γ‚Y:Κ‚X≤Y κ≤κ′ κ∠X κ′∠Y

Γ µX:Κ.κ≤µY:Κ.κ′

[Sub κµ]

Γ κ≤ϕ[κ] Γ‚Y:Κ‚X≤Y ϕ[X]≤ϕ[Y] ϕ[Z]∠Z

Γ κ≤µZ:Κ.ϕ[Z]

[Sub ×]

Γ κ1≤κ1′ Γ κ2≤κ2′Γ <κ1‚κ2>≤<κ1′‚κ2′>

[Sub .]

Γ κ≤κ′ Γ κ′:Κ1×Κ2Γ κ.n≤κ′.n (n=1,2)

[Sub →]

Γ υ′≤υ Γ τ≤τ′ Γ υ→τ:∗

Γ υ→τ≤υ′→τ′

[Sub ∀]

Γ‚X≤ϕ′[X] X≤ϕ[X] Γ‚X≤ϕ′[X] τ≤τ′Γ ∀X≤ϕ[X].τ:∗

Γ ∀X≤ϕ[X].τ≤∀X≤ϕ′[X].τ′

[Sub …]

Γ τ1≤τ1′ … Γ τk≤τk′Γ l1:τ1‚…‚lk:τk‚…‚ln:τn:∗

Γ l1:τ1‚…‚lk:τk‚…‚ln:τn≤l1:τ1′‚…‚lk:τk′

2.3.4.3 Noção de polaridade

A contravariância (ou antimonotonia) do argumento dos tipos funcionais que é estabelecida

pela regra [Sub →] é fonte de diversas complicações: a regra é contra-intuitiva, torna a relação

de subtipo algo tortuosa, e impede a construção de modelos semânticos usando as técnicas de

aproximação habituais (o construtor → não é monótono logo, por maioria de razão, não é con-

tínuo [BM92]).

Para lidar com os problemas criados por →, é útil dispor da noção de polaridade da ocor-

rência duma subexpressão de tipo dentro duma expressão de tipo mais ampla.

Definição 2.3.4.3-1 (Polaridade) Seja σ uma subexpressão do tipo τ e seja σ′ um subtipo

genérico de σ. Seja τ′ o tipo que se obtém a partir de τ substituindo uma ocorrência particular

de σ pelo seu subtipo estrito σ′. Então essa ocorrência de σ diz-se positiva se se verificar τ′≤τ,

ou seja, se os efeitos da alteração forem covariantes com a substituição. Diz-se negativa se

τ≤τ′, ou seja, se os efeitos da alteração forem contravariantes com a substituição. No primeiro

caso também se diz que σ ocorre positivamente em τ, e no segundo caso que σ ocorre negativa-

mente em τ.

24 OM – Uma linguagem de programação multiparadigma

Vejamos alguns exemplos:

Na expressão de tipo υ→τ, a polaridade da ocorrência visível de υ é negativa, e a polaridade

da ocorrência visível de τ é positiva.

Na expressão de tipo (υ→τ)→σ, υ e σ têm polaridade positiva, e υ→τ e τ têm polaridade ne-

gativa. Note que o construtor → inverte a polaridade das ocorrências das subexpressões do do-

mínio e preserva a polaridade das ocorrências das subexpressões do contradomínio.

Na expressão de tipo µX:Κ .X→τ, a ocorrência de τ não tem polaridade, como se pode con-

cluir da aplicação da regra [Sub µ]. Note que µX:Κ.X→τ=(((…)→τ)→τ)→τ. Já na expressão de tipo

µX:Κ.τ→X, a ocorrência de τ tem polaridade negativa.

De forma geral, os construtores de tipo preservam a polaridade das subexpressões. As ex-

cepções são os tipos funcionais (ocorrências no domínio), os tipos recursivos (ocorrências em

qualquer sítio quando a variável de recursão ocorre negativamente), e ainda os tipos universais

F-restringidos (ocorrências no limite superior).

2.3.4.4 Indecidibilidade da relação de subtipo em F≤

A relação de subtipo é indecidível em F≤, ou seja, não existe qualquer algoritmo que permita

verificar se um tipo é subtipo de outro em F≤ (cf. [Pie93, Ghe93, CP94]). Para verificar a rela-

ção de subtipo em F≤ existem apenas semialgoritmos os quais, por definição, podem não ter-

minar em alguns casos.

Pierce provou a indecidibilidade da relação de subtipo em F≤ mostrando que qualquer ex-

pressão lógica envolvendo a relação de subtipo de F≤ era redutível a uma máquina de dois con-

tadores (no sentido de [HU79]), para as quais o halting problem é indecidível.

Na prática, o problema de indecidibilidade pode não ser excessivamente grave. Em primei-

ro lugar, um sistema indecidível continua a ser útil para definir a semântica de linguagens com

sistemas de tipos que possam ser demonstrados decidíveis de forma independente. Em segun-

do lugar, como afirma Pierce [Pie93], os tipos que provocam a não terminação do algoritmo

semidecidível de tipificação para F≤ [CG92] são demasiado complicados e artificiais para

surgirem por acidente numa situação real.

Finalmente, convém dizer que o problema da indecidibilidade de F≤ pode ser eliminado

através da introdução de restrições sintácticas artificiais. Por exemplo, adoptando a versão da

regra [Sub ∀] originalmente introduzida em [CW85]: esta regra impõe que duas abstracções da

forma ∀X≤υ.τ tenham de ter o mesmo limite superior para que se possam candidatar a perten-

cer à relação de subtipo (cf. [CP94, SP94]).

2 O sistema F+ 25

2.3.4.5 F+d subsistema decidível de F+

Vimos no ponto anterior que a relação de subtipo era indecidível no sistema F≤. Ora a indeci-

dibilidade de F≤ propaga-se a F+: em F+ continua a ser possível usar o procedimento de redu-

ção de Pierce, não obstante, relativamente a F≤, a regra [Sub ∀] ter sido um pouco alterada e te-

rem sido introduzidas novas regras de subtipo para as novas formas de tipos.

Nesta secção criaremos uma variante decidível de F+ a que chamaremos F+d. Existem diver-

sas formas de restringir F+ para alcançar esse objectivo (já referimos uma no final da secção

anterior). Vamos escolher a forma que melhor serve a conveniência da linguagem OM. Como

sabemos, a semântica desta linguagem será definida por tradução para F+, ou mais exactamen-

te F+d, ao longo dos próximos capítulos.

Levando em conta a conveniência da linguagem OM, a variante decidível de F+ deve ser

obtida por eliminação da regra de transitividade [Sub trans]. Por isso definimos F+d da seguinte

forma:

Definição 2.3.4.5-1 (Sistema F+d) O sistema F+

d é o subsistema de F+ que se obtém a

partir deste removendo a regra da transitividade [Sub trans].

Para começar, vejamos quais as consequências da eliminação de [Sub trans]. Na referência

[CG92], Currien e Ghelli mostraram que em F≤, a regra de transitividade é de uso redundante

na maioria das situações, podendo ser substituída pela seguinte regra, bastante mais simples:

[Sub var-trans]

Γ‚X≤υ υ≤τΓ‚X≤υ X≤τ

Esta regra define uma forma fraca de transitividade que se caracteriza por envolver sempre

uma variável de tipo definida no contexto corrente. A substituição da regra [Sub trans] também é

possível em F+, mas neste caso tem de ser trocada por uma variante um pouco mais compli-

cada, pois X pode representar um operador de tipo variável (alguma da motivação para esta re-

gra pode ser encontrada em [SP94]):

[Sub var-trans2]

Γ‚X≤ϕ[X] (ϕ[X][υ1]…[υn])≤τ Γ‚X≤ϕ[X] (ϕ[X][υ1]…[υn]):Κ

Γ‚X≤ϕ[X] X[υ1]…[υn]≤τ

De qualquer forma não pretendemos manter no sistema F+d qualquer regra de transitividade,

mesmo numa variante fraca. Mas esta discussão foi útil para percebermos que da remoção da

regra [Sub trans] não resulta qualquer perda de transitividade na relação ≤ no domínio dos tipos

concretos, isto é no domínio dos tipos que não são variáveis de tipo. Apenas as variáveis de

tipo ficam prejudicadas pela eliminação da regra de transitividade. Com efeito, agora, sem

regra [Sub trans], do contexto Γ‚X≤ϕ[X]‚Γ′ só se podem deduzir as duas asserções triviais X≤X

(usando a regra [Sub refl]), e X≤ϕ[X] (usando a regra [Sub X]).

26 OM – Uma linguagem de programação multiparadigma

Vejamos agora por que razão a remoção da regra [Sub trans] se adequa às particularidades da

linguagem OM. A linguagem OM usa construções de segunda ordem essencialmente em duas

situações: na formalização do conceito de classe (cf. capítulo 5) e na definição duma forma es-

pecífica de polimorfismo paramétrico designada por polimorfismo paramétrico ≤*-restringido

(cf. capítulo 6). Em qualquer dos casos, uma variável de tipo X é sempre introduzida sob uma

restrição da forma X≤ϕ[X], onde ϕ representa uma interface de classe. A restrição X≤ϕ[X] lê-se

assim: “a variável de tipo X representa um tipo-objecto indeterminado compatível com a inter-

face ϕ”. Portanto, enquanto X representa um tipo-objecto, ϕ[X] não representa um tipo-objecto

mas sim a funcionalidade mínima de X. Assim interessa evitar que X e ϕ[X] possam ser mistu-

rados numa aplicação duma hipotética regra de transitividade, como se se tratassem de entida-

des da mesma natureza. Ou seja: da restrição X≤ϕ[X], gostaríamos de poder deduzir apenas as

asserções triviais: X≤X (porque X é um tipo), e X≤ϕ[X] (porque precisamos de saber qual é a

funcionalidade mínima de X). A remoção da regra da transitividade permite alcançar este

objectivo.

Falta provar que F+d é decidível.

Teorema 2.3.4.5-2 (Decidibilidade da relação de subtipo em F+d) A relação de

subtipo no sistema F+d é decidível.

Prova: (Esboço) Temos recorrer a alguns resultados do futuro capítulo 10. Em primeiro lugar,

por inspecção verificamos que todas as regras de subtipo não terminais de F+d (cf. definição

10.2.5-1) obedecem à propriedade da subfórmula (cf. definição 10.2.7.3.2-1). As regras [Sub µ]

e [Sub κµ] são um caso à parte, mas serão tratáveis, à luz dos comentário sobre “tipos recursi-

vos e decidibilidade” da secção 2.3.3.1.

Consideremos agora o procedimento de prova indeterminista completo descrito na defini-

ção 10.2.7.3.1-1. Pelo teorema 10.2.7.3.2-2, esse procedimento é um algoritmo (indeterminis-

ta) completo. Portanto a relação de subtipo de F+d é decidível.

Teorema 2.3.4.5-3 (Decidibilidade de F+d) O sistema F+

d é decidível.

Prova: (Esboço) Para desenvolver um algoritmo de tipificação para F+d, ou mais exactamente

de tipificação mínima, elimina-se a regra [Termo inclusão] (esta é uma regra problemática) e

generalizam- as regras de aplicação [Termo aplic →], [Termo aplic ∀] e [Tipo aplic Λ] para compen-

sar a eliminação da regra anterior. Depois prova-se que o novo sistema de regras define um al-

goritmo determinista para atribuição de tipos mínimos a termos (cf. [CG92, SF94]).

Em [CG92] o procedimento que acabámos de descrever é apresentado em todos os seus de-

talhes para o caso do sistema F≤. Em [SP94], adapta-se esse procedimento ao caso do sistema

Fω≤ . Em ambos os casos, estão em causa exercícios não excessivamente complicados, sendo o

segundo caso um pouco mais mais rico por estarem envolvidos operadores de tipos e géneros.

2 O sistema F+ 27

Quanto a F+d, o seu tratamento é quase idêntico ao tratamento de Fω

≤ , pois as únicas novas

formas de termos a considerar são os registos e as abstracções paramétricas F-restringidas.

Para finalizar referimos brevemente os trabalhos [BCD+93] e [BCK94], nos quais se dis-

cute e prova a decidibilidade da relação de subtipo numa linguagem orientada pelos objectos

chamada TOOPLE. Na definição da semântica da linguagem usa-se polimorfismo paramétrico

F-restringido, sendo esse um aspecto em comum com a nossa linguagem OM.

No entanto a linguagem TOOPLE é definida usando os métodos operacional e denotacional

– não por tradução para um cálculo-lambda – sendo o seu sistema de tipos especificado direc-

tamente usando um sistema de Post. Consequentemente, os algoritmos de tipificação e subtipi-

ficação são definidos directamente sobre os tipos da linguagem, incluindo complexos tipos-

-objecto. Para que o sistema fique decidível introduz-se a seguinte regra: “não é permitido

introduzir uma variável de tipo que seja subtipo dum tipo-objecto”. Curiosamente, esta acaba

por ser uma forma indirecta, e diferente da nossa, de desactivar a regra da transitividade para

subtipos de TOOPLE para o caso particular das variáveis de tipo.

2.3.4.6 Usos distintos de F+ e de F+d

Como afirmámos na secção anterior, a linguagem OM, que iremos introduzir ao longo dos

próximos capítulos, será definida por tradução para o sistema F+d.

No entanto, os teoremas sobre a linguagem serão todos demonstrados no contexto do siste-

ma F+. A razão é a seguinte: a regra [Sub trans] é necessária em certas demonstrações, e só no

contexto de F+ esta regra se encontra disponível.

Mesmo que o possa parecer, esta nossa metodologia não é contraditória. Ela significa que o

modelo da linguagem OM é efectivamente definido sobre o sistema F+. Simplesmente, restrin-

ge-se um pouco a forma como F+ pode ser usado na codificação das construções de OM para

efeitos de verificação mecânica da boa tipificação dos programas escritos em OM. O sistema

F+d captura essas restrições.

De agora em diante, será muito raro referirmo-nos directamente ao sistema F+d. Usaremos o

nome F+ para designar ambos os sistemas, ficando sempre subentendido que as equações

semânticas de OM estarão escritas em F+d.

2.3.5 TermosApresentamos agora as regras que caracterizam os pré-termos bem formados da linguagem ou,

mais simplesmente, os termos da linguagem.

28 OM – Uma linguagem de programação multiparadigma

2.3.5.1 Apresentação das regras de boa formação dos termos

A regra [Termo inclusão] estabelece a conexão entre os juízos da relação de subtipo e os juízos

relativos à boa formação dos termos. Esta regra introduz polimorfismo de inclusão no sistema.

A regra [Termo aplic ∀] mostra que quando se aplica uma função polimórfica a um tipo τ,

esse tipo influencia tanto o resultado da função como o tipo desse resultado.

A regra [Termo selecção …] permite lidar directamente com registos com uma única compo-

nente e indirectamente (usando as regras [Termo inclusão] e [Sub …]) com registos com múlti-

plas componentes.

As restantes regras são auto-explicativas, pelo que não merecem comentários.

2.3.5.2 Regras de boa formação dos termos[Termo inclusão]

Γ x:τ Γ τ≤τ′

Γ x:τ′

[Termo x]

Γ‚x:τ‚Γ′ ◊

Γ‚x:τ‚Γ′ x:τ

[Termo →]

Γ‚x:υ e:τ

Γ λx:υ.e:υ→τ

[Termo aplic →]

Γ f:υ→τ Γ e:υ

Γ (f e):τ

[Termo ∀]

Γ‚X≤ϕ[X] e:τ

Γ λX≤ϕ[X].e:∀X≤ϕ[X].τ

[Termo aplic ∀]

Γ P:∀X≤ϕ[X].τ Γ υ≤ϕ[υ]

Γ P[υ]:τ[υ/X]

[Termo …]

Γ ◊ Γ e

–:τ– l

– distintos

Γ l–=e

–:l

–:τ–

[Termo selecção …]

Γ e:l:τΓ e.l:τ

2.4 Teoria equacional para F+

Propomos agora uma teoria equacional para a linguagem dos termos de F+, concretizando

assim uma semântica para F+. Usamos, como é habitual (cf. [Gun92, Win93]), um sistema de

prova sobre juízos da forma:

Γ e=e′:τ os termos e e e′ do tipo τ, são idênticos em Γ

2.4.1 Apresentação das regras de identidade entre termosAs regras [Termo= β λx], [Termo= β λX], [Termo= selecção …] definem duas operações de aplica-

ção e a operação de selecção de componente de registo.

As regras [Termo= η λx], [Termo= η λX], [Termo= ext …] são regras de extensionalidade aná-

logas à regra [Tipo= η Λ].

A regra [Termo= permutação …] indica que a ordem das etiquetas num registo é irrelevante.

Este regra pode ser deduzida de [Tipo= permutação …] e [Termo= ext …].

2 O sistema F+ 29

A regra [Termo= inclusão] é a adaptação da regra [Termo inclusão] para a relação de identidade

entre termos.

A regra [Termo= top-colapso] é uma regra característica do sistema F≤ que estabelece que dois

termos quaisquer se tornam idênticos quando vistos como elementos do tipo Top(∗). Note que,

em geral, dois termos podem ser considerados idênticos ou distintos, dependendo do tipo em

que são considerados. Por exemplo, a=1, b=2, c=3 e a=1, b=2 são idênticos no tipo

a:Nat, b:Nat, mas não são idênticos no tipo a:Nat, b:Nat, c:Nat.

A regra [Termo= tipo] permite deduzir que todos os termos que estão em relação consigo pró-

prios num dado tipo são desse mesmo tipo.

As regras [Termo= refl], [Termo= sim] e [Termo= trans] fazem de = uma relação de equivalência.

As seis regras finais destinam-se apenas a tornar a relação = numa congruência, isto é numa

relação que permite a substituição de iguais por iguais, neste caso a substituição de subtermos

por termos formalmente idênticos.

2.4.2 Regras de identidade entre termos[Termo= β λx]

Γ‚x:υ e:τ Γ a:υ

Γ (λx:υ.e)a=e[a/x]:τ

[Termo= η λx]

Γ e:υ→τ x∉dom(Γ)Γ λx:υ.(e x)=e:υ→τ

[Termo= β λX]

Γ‚X≤ϕ[X] e:τ Γ υ≤ϕ[υ]

Γ (λX≤ϕ[X].e)[υ]=e[υ/X]:υ[υ/X]

[Termo= η λX]

Γ e:∀X≤ϕ[X].τ X∉dom(Γ)

Γ λX≤ϕ[X].e[X]=e:∀X≤ϕ[X].τ

[Termo= selecção …]

Γ e

–:τ– 1≤i≤n

Γ l–=e

–.li=ei:τi

[Termo= ext …]

Γ e

–:τ–

Γ l–=(e

–.l–)=e:l

–:τ–

[Termo= permutação …]

Γ ◊ Γ e

–:τ– l

– distintos π permutação de 1‚…‚n

Γ l–=e

–=lπ(1)=eπ(1)‚…‚lπ(n)=eπ(n): l

–:τ–

[Termo= inclusão]

Γ x=x′:τ Γ τ≤τ′

Γ x=x′:τ′

[Termo= top-colapso]

Γ e:Top(∗) e′:Top(∗)

Γ e=e′:Top(∗)

[Termo= tipo]

Γ x=x:τΓ x:τ

[Termo= refl]

Γ x:τ

Γ x=x:τ

[Termo= sim]

Γ x=x′:τΓ x′=x:τ

[Termo= trans]

Γ x=x′:τ Γ x=x′′:τ

Γ x=x′′:τ

[Termo= λx]

Γ‚x:υ e=e′:τ

Γ λx:υ.e=λx:υ.e′:υ→τ

[Termo= aplic λx]

Γ f=f′:υ→τ Γ e=e′:υ

Γ (f e)=(f′ e′):τ

[Termo= λX]

Γ‚X≤ϕ[X] e=e′:τ

Γ λX≤ϕ[X].e=λX≤ϕ[X].e′:∀X≤ϕ[X].τ

[Termo= aplic λX]

Γ P=P′:∀X≤ϕ[X].τ Γ υ≤ϕ[υ]

Γ P[υ]=P′[υ]:τ[υ/X]

30 OM – Uma linguagem de programação multiparadigma

[Termo= …]

Γ e

–=e′–:τ– l

– distintos

Γ l–=e

–=l

–=e′–:l

–:τ–

[Termo= selecção …]

Γ e=e′:l

–:τ– 1≤i≤n

Γ e.li=e′.li:τi

2.5 Formas derivadasNesta secção, introduzimos um extenso rol de novas formas de termos e tipos que teremos ne-

cessidade de usar nos próximos capítulos e que podem ser codificadas dentro do sistema F+

usando as suas construções primitivas. Esses novos termos e tipos serão chamados de formas

derivadas. Note que cada forma derivada constitui apenas sintaxe de alto nível para um padrão

complexo de elementos primitivos que seria inconveniente usar directamente. Se é verdade

que, semanticamente, as formas derivadas não introduzem nada de novo, já ao nível dos con-

ceitos, as formas derivadas podem introduzir conceitos novos: por exemplo, os conceitos de

produto cartesiano de tipos, de par ordenado, de projecção, de valor recursivo, etc.

2.5.1 Pares ordenadosA codificação padrão de pares ordenados no sistema F, também válida em F+, é a seguinte

[BB85]:

τ×τ′ ˆ = ∀X:∗.(τ→τ′→X)→X

<e,e′> ˆ = λX:∗.λf:τ→τ′→X.(f e e′)e.1 ˆ = e[τ](λx:τ.λy:τ′.x)

e.2 ˆ = e[τ′](λx:τ.λy:τ′.y)

Esta codificação respeita a relação de subtipo, o que se deve ao facto de na definição de τ×τ′

cada um dos tipos componentes ocorrer duas vezes à esquerda de → o que os torna covarian-

tes. Portanto a seguinte regra pode ser deduzida da codificação efectuada:

[Sub par]

Γ τ1≤τ1′ Γ τ2≤τ2′

Γ τ1×τ2≤τ1′×τ2′

Note que a codificação de pares usando simples registos não reproduziria com exactidão a

relação de subtipo pretendida: só para dar um exemplo, teríamos sempre Γ τ1×τ2≤τ1. Já agora,

também a tentativa, inversa, de codificar registos usando tuplos daria problemas.

Tipos tuplos, tuplos de valores e tuplos de tipos são redutíveis a pares ordenados usando as

seguintes definições indutivas, onde n>2:

τ1×τ2×…×τn ˆ = τ1×(τ2×…×τn)

<x1,x2,…,xn> ˆ = <x1,<x2,…,xn>>

<τ1,τ2,…,xn> ˆ = <τ1,<τ2,…,τn>>

2 O sistema F+ 31

2.5.2 Operador de ponto fixo e valores recursivosNa secção 2.3.3.1 vimos que os tipos recursivos permitiam tipificar a auto-aplicação. Desta

forma é possível, e simples, associar um operador de ponto fixo a cada tipo τ: basta adoptar o

operador paradoxal do cálculo-lambda não-tipificado às nossas circunstâncias [Bar84, Sto77]:

fixτ:(τ→τ)→τ ˆ = λf:τ→τ.(λx:(µX:∗.X→τ).f (xx))(λx:(µX:∗.X→τ).f (xx))

O tipo deste operador fixτ é (τ→τ)→τ.

Verifiquemos agora que se trata, realmente, dum operador de ponto fixo. Seja f:τ→τ. Então:

fixτ f = (λx:(µX:∗.X→τ).f (xx))(λx:(µX:∗.X→τ).f (xx))

= f((λx:(µX:∗.X→τ).f (xx))(λx:(µX:∗.X→τ).f (xx)))

= f(fixτ f)

Vamos agora introduzir valores recursivos usando a seguinte definição:

recτ x:τ.e ˆ = fixτ (λx:τ.e)

Verifiquemos a propriedade de unfolding do operador recτ:

recτ x:τ.e = fixτ (λx:τ.e)

= (λx:τ.e)(fixτ (λx:τ.e))

= e[fixτ (λx:τ.e/x)]

= e[recτ x:τ.e/x]

2.5.3 Declarações locais de tipos e valoresIntroduzimos agora a possibilidade de nomeação de tipos através de quatro formas distintas de

definição local.

Declaração de tipo não recursivo:

LET X:K=κ IN e ˆ = e[κ/X] (note que e[κ/X] = (ΛX.e) κ)

Declaração de tuplo de tipos não recursivo:

LET <X1,…, Xn>:Κ1×…×Κn=κ IN e ˆ = eσ[κ/X] onde σ ˆ = [X.1/X1, …, X.n/Xn]

Declaração de tipo recursivo:

LET REC X:Κ=κ IN e ˆ = e[µX:Κ.κ/X]

Declaração de tipos mutuamente recursivos:

LET REC <X1,…, Xn>:Κ1×…×Κn=κ IN e ˆ = eσ[µX:Κ1×…×Κn.κσ/X]

onde σ ˆ = [X.1/X1, …, X.n/Xn]

Analogamente, vamos também permitir a nomeação de valores por meio de quatro formas

distintas de definição local:

Declaração de valor não recursivo:

32 OM – Uma linguagem de programação multiparadigma

let x:υ=e in e′ ˆ = e′[e/x] (note que e′[e/x] = (λx.e′) e)

Declaração de tuplo não recursivo:

let <x1,…, xn>:υ1×…×υn=e in e′ ˆ = e′σ[e/x] onde σ ˆ = [x.1/x1, …, x.n/xn]

Declaração de valor recursivo:

let rec x:υ=e in e′ ˆ = e′[rec x:υ.e/x]

Declaração de tuplo recursivo:

let rec <x1,…, xn>:υ1×…×υn=e in e′ ˆ = e′σ[rec x:υ1×…×υn.eσ/x]

onde σ ˆ = [x.1/x1, …, x.n/xn]

2.5.4 Tipos existenciaisDeve-se a Mitchell e Plotkin a ideia de modelizar tipos abstractos [Mor73, LSA77, Rey78,

Rey83] usando tipos existenciais [MP85, CW85].

2.5.4.1 Exemplo de tipo existencial

Antes de definirmos uma codificação para os tipos existenciais em F+, convém introduzi-los

através dum exemplo de implementação e utilização dum tipo abstracto representado por um

tipo existencial. Vamos definir um tipo abstracto contador, identificado pelo nome Cont e pos-

suindo as seguintes operações públicas: zero (contador zero), inc (incremento de contador) e eq

(comparação de dois contadores). Para representar internamente os contadores usaremos nú-

meros inteiros. A representação interna dos contadores ficará oculta dos clientes do tipo abs-

tracto os quais só terão acesso ao seu nome e, ainda, aos nomes e tipos das operações associa-

das.

O tipo existencial que captura a informação pública associada ao tipo abstracto Cont escre-

ve-se da seguinte forma:

∃Cont:∗.zero:Cont, inc:Cont→Cont, eq:Cont→Cont→Bool

Esta expressão lê-se informalmente da seguinte forma: existe um tipo abstracto, denomina-

do Cont, cuja natureza exacta se desconhece, mas que suporta uma constante zero com tipo Cont,

uma operação inc com tipo Cont→Cont e uma operação eq com tipo Cont→Cont→Bool.

Exibimos seguidamente uma implementação particular do tipo abstracto que usa o tipo-re-

presentação Nat e ainda uma utilização, por um termo cliente, desse tipo abstracto:

let P:∃Cont.zero:Cont, inc:Cont→Cont, eq:Cont→Cont→Bool

ˆ = pack Cont=Nat with zero=0, inc=λc:Cont.c+1, eq=λc1:Cont.λc2:Cont.c1=c2 in

open P as Cont,ContI in

ContI.eq ContI.zero (ContI.inc ContI.zero)

2 O sistema F+ 33

O termo da segunda linha, definido usando a construção pack, tem tipo existencial e concretiza

uma implementação do tipo abstracto pretendido. Um termo com tipo existencial designa-se

genericamente por pacote ou módulo. Na expressão acima, associamos o nome local P ao

pacote criado. A implementação tem acesso à representação interna do tipo. Na implementa-

ção, o nome Cont é considerado um simples nome alternativo para Nat.

Na terceira linha da expressão procedemos à abertura do pacote P para obtermos acesso lo-

cal ao nome Cont e às operações públicas sobre contadores (note que, no entanto, a representa-

ção interna dos contadores permanece oculta para os termos-cliente). No contexto local intro-

duzido pela construção open, isto é na quarta linha, introduzimos um termo cliente, o qual tem

a liberdade de utilizar as operações públicas do tipo abstracto. Note que o tipo do termo da

quarta linha é Bool; nunca poderia ser Cont, ou qualquer outro tipo envolvendo Cont, pois o no-

me local Cont não deve poder escapar da construção open: as regras do sistema de tipos impõem

esta restrição.

2.5.4.2 Tipos existenciais restringidos

O tipo abstracto do exemplo anterior é completamente abstracto pois nada revela aos clientes

sobre a sua representação interna. Em [CW85] consideram-se também tipos parcialmente

abstractos, que revelam aos clientes parte da sua representação interna. Esses tipos são repre-

sentados por tipos existenciais restringidos (bounded existential types). No seguinte exemplo

de tipo existencial restringido:

∃Cont≤τ.zero:Cont, inc:Cont→Cont, eq:Cont→Cont→Bool

os clientes deste tipo ficam a saber que o tipo da representação interna é subtipo de τ, apesar

de continuarem a desconhecer qual é exactamente esse tipo interno. Note que se τ for um tipo-

-registo, então todas componentes de τ ocorrem também em Cont, eventualmente sob uma for-

ma mais especializada.

2.5.4.3 Tipos existenciais F-restringidos

Neste ponto introduzimos a forma, ainda mais geral, de tipos existenciais F-restringidos, nos

quais o nome do tipo abstracto pode ser usado na sua própria restrição. Fazemos seguidamente

a codificação destes tipos usando as formas primitivas de F+ e apresentamos ainda as princi-

pais regras do sistema de tipos que lhes estão associadas (são deriváveis das regras primitivas).

A codificação dos tipos existenciais F-restringidos em formas primitivas é a seguinte:

∃X≤ϕ[X].ρ ˆ = ∀Y:Κ.(∀X≤ϕ[X].ρ→Y)→Y) onde Y∉FV(ϕ) e Y∉FV(ρ)

pack X≤ϕ[X]=σ with r ˆ = λY:Κ .λP:(∀X≤ϕ[X].ρ→Y).P[σ]r

open e:∃X≤ϕ[X].ρ as X,r in e′:υ ˆ = e[υ](λX≤ϕ[X].λx:ρ.e′)

Esta codificação adapta de forma imediata a codificação de tipos existenciais restringidos

apresentada em [Car97, GP96].

34 OM – Uma linguagem de programação multiparadigma

Apresentamos no final desta subsecção as regras do sistema de tipos de F+ relacionadas

com os tipos existenciais F-restringidos. Produzimos apenas alguns breves comentários sobre

elas:

A regra [Tipo ∃] introduz os tipos existenciais F-restringidos.

A regra [Sub ∃] estabelece que a relação de subtipo entre tipos existenciais requer uma for-

ma de covariância generalizada da restrição sobre o tipo-representação (ao contrário do que se

passa com a restrição sobre o argumento dos tipos universais – regra [Sub ∀]) e covariância

normal no tipo do resultado (exactamente como nos tipos universais). Note que enquanto os

termos com tipos universal – as funções polimórficas – se destinam a ser aplicados, os termos

com tipo existencial – os pacotes – se destinam a ser abertos.

A regra [Termo ∃] descreve a definição dum pacote. Note como no segundo antecedente da

regra todas as ocorrências do nome público X são substituídas pelo tipo tipo-representação σ.

A regra [Termo open ∃] descreve a abertura dum pacote e. Dentro da regra, e′ denota o termo

cliente do pacote, υ o seu tipo, e ρ o registo das operações públicas do pacote e. O segundo

antecedente da regra faculta ao cliente e′ acesso ao nome X e às operações definidas no pacote.

O terceiro antecedente, Γ υ:∗, obriga υ a ser um tipo completamente independente de X: assim

X é forçado a ser local à construção open não podendo escapar para o exterior.

[Tipo ∃]

Γ‚X≤ϕ[X] ρ:∗

Γ ∃X≤ϕ[X].ρ:∗

[Sub ∃]

Γ‚X≤ϕ[X] X≤ϕ′[X] Γ‚X≤ϕ[X] ρ≤ρ′Γ ∃X≤ϕ[X].ρ:∗

Γ ∃X≤ϕ[X].ρ≤∃X≤ϕ′[X].ρ′

[Termo ∃]

Γ σ≤ϕ[σ] Γ r[σ/X]:ρ[σ/X]

Γ (pack X≤ϕ[X]=σ with r):∃X≤ϕ[X].ρ

[Termo open ∃]

Γ e:∃X≤ϕ[X].ρ Γ‚X≤ϕ[X]‚r:ρ e′:υ Γ υ:∗

Γ (open e as X,r in e′):υ

2.5.5 Concatenação de registosNesta secção, introduzimos uma operação tipificada de concatenação de registos. Em capítu-

los posteriores, usaremos esta operação na formalização de herança e de modo. Esta operação

consegue ser expressa usando as limitadas primitivas sobre registos introduzidas em F+.

Usando a nomenclatura de [Rém92], a operação de concatenação que vamos introduzir é

uma operação de concatenação assimétrica pois admite componentes homónimas nos registos

a concatenar. Uma operação de concatenação simétrica só admite concatenar registos sem

componentes homónimas.

Definimos da seguinte forma o nosso operador de concatenação assimétrica ⊕ de tipos-re-

gisto:

2 O sistema F+ 35

Definição 2.5.5-1 (Concatenação de tipos-registo) Dados os tipos-registo Π e Π′,

definimos o tipo-registo concatenação Π⊕Π′ através da seguinte regra:

Π=l1:τ1‚…‚lm:τm‚h1:υ1‚…‚hk:υkΠ′=h1:υ′1‚…‚hk:υ′k‚l′1:τ′1‚…‚l′n:τ′n

Π⊕Π′ ˆ = l1:τ1‚…‚lm:τm‚h1:υ′1‚…‚hk:υ′k‚l′1:τ′1‚…‚l′n:τ′n

Nesta regra, as componentes homónimas dos tipos-registo são identificadas pela letra h e as

componentes não homónimas pelas letras l e l′.

Introduzimos agora a nossa operação de concatenação assimétrica de registos, que

representaremos através do operador binário +[Π‚Π′].

Definição 2.5.5-2 (Concatenação de registos) Dados os tipos-registo Π e Π′,

definimos a operação de concatenação assimétrica +[Π‚Π′] como:

+[Π‚Π′] ˆ = λr:Π.λr′:Π′. l1=r.l1‚…‚lm=r.lm‚h1=r′.h1‚…‚hk=r′.hk‚l′1=r′.l′1‚…‚l′n=r′.l′n

A assinatura deste operador é:

+[Π‚Π′]: Π→Π′→Π⊕Π′

O resultado de concatenar dois registos r:Π e r′:Π′ é um registo do tipo Π⊕Π′ contendo todas as

componentes estaticamente conhecidas de r e r′. Caso existam componentes estaticamente

conhecidas que sejam homónimas, os valores das componentes do segundo registo-argumento

têm precedência sobre os valores das componentes do primeiro registo-argumento.

Note que esta definição usa, como pretendíamos, as poucas operações primitivas sobre

registos de F+: a operação de construção de registo e a operação de selecção de componente.

A linguagem F+ suporta polimorfismo de inclusão, o que permite aplicar o operador +[Π‚Π′]

a registos com mais componentes do que as explicitamente indicadas nos seus tipos. A defini-

ção daquele operador é clara quanto ao tratamento dessas componentes extra: o registo-resulta-

do considera apenas as componentes explicitamente previstas nos tipos Π e Π′, ignorando

(truncando) as componentes extra. Este tratamento é essencial para a boa definição do ope-

rador: se as componentes extra do segundo argumento não fossem ignoradas então elas teriam

precedência sobre as componentes do primeiro registo, o que faria com que certas combina-

ções de registos-argumento produzissem registos-resultado incompatíveis com o tipo Π⊕Π′.

De qualquer forma, em todas as utilizações concretas do operador +[Π‚Π′], só teremos a ne-

cessidade de o aplicar a registos constantes e sem componentes extra. Estas circunstâncias par-

ticulares do uso de +[Π‚Π′] permitirão simplificar a escrita deste operador, já que a inferência

dos tipos Π e Π′ a partir dos registos-argumento se tornará imediata. Assim, usaremos o símbo-

lo + como abreviatura de +[Π‚Π′] sempre que os tipos dos argumentos sejam óbvios.

Na modelização do mecanismo de herança, toda a subclasse será definida por concatenação

do registo das componentes da superclasse com o registo das suas componentes específicas.

36 OM – Uma linguagem de programação multiparadigma

Além disso, na redefinição de componentes herdadas, as componentes da subclasse têm priori-

dade sobre as componentes da superclasse. Assim, o operador de concatenação tem as caracte-

rísticas requeridas pelo tratamento da herança. No entanto, ele também será usado noutros

contextos, por exemplo na definição do mecanismo dos modos.

Não conhecemos da literatura nenhum operador de concatenação semelhante a +[Π‚Π′], em-

bora existam técnicas alternativas usadas na definição de herança. Terminamos esta secção

referindo brevemente as alternativas principais que snao referidas na literatura.

Em [CHC90] define-se uma operação de concatenação, denotada por “with”, que preserva

todas as componentes dos registos argumentos; o problema da boa definição do operador em

presença de polimorfismo de inclusão é resolvido de forma drástica e simplista, retirando os

tipos-registos da relação de subtipo.

Nos trabalhos [Car84/88, ESTZ94, PT94] não se usa qualquer operação de concatenação:

as subclasses são obrigadas a redeclarar exaustivamente todas as componentes herdadas.

Nas abordagem não-tipificadas [CP89] e [KR93] um registo é tratado simplesmente como

uma função com domínio num conjunto de etiquetas, o que torna a operação de concatenação

de definição trivial. Nas abordagens tipificadas, o que torna os registos entidades complexas é

o facto do domínio de cada registo fazer parte do tipo do registo.

Finalmente em [Rém89, CM91] são introduzidos registos extensíveis sobre os quais é pos-

sível definir uma operação de concatenação poderosa que preserva todas as componentes dos

registos-argumentos, mesmo as componentes estaticamente desconhecidas. No entanto, esta

forma de concatenação exige um sistema de tipos com regras complexas em que os tipos

capturam não só informação positiva como também informação negativa: um tipo-registo é

caracterizado pelo conjunto das componentes que se sabe que possui e, ainda, pelo conjunto

das componentes que se sabe que não possui. Outras abordagens que usam registos extensíveis

usam row types da forma X↑l1,…,ln, representando colecções etiquetadas de tipos onde não

ocorrem l1,…,ln [Wan87, Wan89, Car94, FHM94]. De forma geral, estes complicados sistemas

de tipos pretendem resolver o clássico problema da actualização funcional (functional update)

em F≤ [CM91]: não é possível definir em F≤ uma função polimórfica do tipo ∀X≤B.X→X que

efectue a actualização funcional (cópia modificada) de objectos do tipo X, para todo o X≤B.

Felizmente que no início dos anos 90 se descobriu uma forma simples de contornar este pro-

blema: basta definir as funções polimórficas sobre interfaces em vez de sobre tipos-objectos

[Bru94, PT94].

2.5.6 ReferênciasPara modelizar objectos contendo variáveis de instância mutáveis, iremos precisar de usar um

cálculo-lambda imperativo com referências e efeitos laterais. Assim vamos agora estender o

sistema F+ com os habituais ingredientes imperativos. Chamaremos ao novo sistema F+&.

2 O sistema F+ 37

2.5.6.1 Sintaxe de F+&

Gramática dos novos tipos de F +&:

κϕτυσ ::=(Formas derivadas)

| Ref υ :∗ tipo-referência| Unit :∗ tipo cujo único elemento é ()

Gramática dos novos termos de F+&:

efP ::=(Formas derivadas)

…| ref (e:υ) :Ref υ criação de localização| deref e :υ valor de localização| e:=e′ :Unit actualização de localização| e;e′ :Unit sequenciação| () :Unit único valor do tipo Unit

2.5.6.2 Regras de boa formação de F+&

Regras adicionais de boa formação de tipos:

[Tipo Ref]

Γ υ:Κ

Γ Ref υ:∗

[Tipo Unit]

Γ ◊

Γ Unit:∗

Regras adicionais de boa formação de termos:

[Termo ref]

Γ e:υ

Γ ref (e:υ):Ref υ

[Termo deref]

Γ e:Ref υ

Γ deref e:υ

[Termo :=]

Γ e:Ref υ Γ e′:υ

Γ e:=e′:Unit

[Termo ;]

Γ e:τ Γ e′:τ′

Γ e;e′:τ′

[Termo ()]

Γ ◊

Γ ():Unit

Não são necessárias novas regras de subtipo pois a única regra de subtipo aplicável aos tipos-

-referência é a regra da reflexibilidade.

2.5.6.3 Semântica de F+&

A nova linguagem imperativa tem efeitos laterais pelo que deixa de ser confluente. Este facto

sugere a escrita duma semântica operacional determinista como forma de definição semântica

do sistema. Contudo não faremos isto. Como até aqui, vamos a investigar a possibilidade de

reduzir as novas construções ao sistema base, neste caso, traduzindo os termos de F+& para ter-

mos funcionais que manipulam explicitamente uma representação concreta do estado. Numa

forma não-tipificada, esta técnica de tradução é usada com sucesso em [SF94], por exemplo.

38 OM – Uma linguagem de programação multiparadigma

Infelizmente, o grau de sucesso que a variante tipificada permite é apenas parcial, como vere-

mos.

Tratemos em primeiro lugar do problema da representação explícita do estado.

É simples a concepção uma representação para estados que só permitam guardar valores

dum tipo único (estados monotipificados) (cf. [Sto77]): um estado monotipificado sobre um

tipo τ de F+& é uma mera função de localizações para valores do tipo τ. As localizações podem

ser representadas usando números naturais (codificáveis no sistema F cf. [BB85]). Assim, o

seguinte operador de tipo representa todos os estados monotipificados:

MonoStore ˆ = ΛX..Nat→X

Por razões técnicas, o estado politipificado que vamos agora definir só permite guardar va-

lores de um número finito de tipos. Consideremos uma godelização do conjunto (contável) dos

tipos saturados (tipos sem variáveis livres) de F+& , que atribua um índice numérico único a ca-

da um desses tipos: τ1, τ2, …. Dado um número N arbitrário, representaremos os nossos esta-

dos politipificados usando o seguinte tipo:

Store ˆ = µStore.MonoStore[τ1] × MonoStore[τ2] × …×MonoStore[τN]

Este tipo define-se recursivamente para permitir o armazenamento no estado de valores que

dependam do próprio estado.

Para aceder a cada uma das componentes monotipificadas do estado, introduzimos os se-

guintes operadores de tipo e as seguintes abstracções:

SEL ˆ = ΛX. Store→MStore[X]

UPD ˆ = ΛX. Store→MStore[X]→Store

SU ˆ = ΛX. sel:SEL[X], upd:UPD[X]

seli ˆ = λs:Store.(s.i) : SEL[τi]

updi ˆ = λs:Store.λm:MStore[τi].<s.1, …, s.i-1, m, s.i+1, …, s.n> : UPD[τi]

sui ˆ = sel=seli, upd=updi :SU[τi]

A função seli permite extrair a i-ésima componente do estado. A função updi permite alterar a

i-ésima componente do estado. O registo sui agrupa as duas funções anteriores.

Para manipular localizações e seus valores associados, definimos as seguintes abstracções:

get_val ˆ = λX.λsu:SU[X]. λs:Store.λl.Nat. ((su.sel s) l)

set_val ˆ = λX.λsu:SU[X]. λs:Store.λl.Nat.λv.X. (su.upd s ((su.sel s)|l→v))

alloc ˆ = λX.λsu:SU[X]. λs:Store.λv.X. <l, set_val[X] su s l v> onde get_val[X] su l = free

A função get_val permite obter o valor duma localização, a função set_val permite alterar o valor

duma localização, a função alloc permite reservar e inicializar uma nova localização. Estas três

funções são parametrizadas em função de X, o tipo de base da localização a manipular, e de

su:SU[X], um registo de funções que permitem o acesso e modificação da componente monoti-

2 O sistema F+ 39

pificada do estado que corresponde a X. Terminamos assim a discussão da representação do

estado.

Consideramos agora a codificação em F+ do novo tipo Unit e do seu valor (). Basta usar as

seguintes definições:

Unit ˆ = ∀X:∗.X→X

() ˆ = λX:∗.λx:X.x

Relembramos que, de acordo com a discussão sobre parametricidade que efectuámos na sec-

ção 2.1.1, o tipo ∀X:∗.X→X representa um conjunto singular em F+. Além disso, ∀X:∗.X→X

não tem subtipos estritos e o seu único supertipo estrito é Top(∗).

Na definição da tradução . , é necessário, não só dar significado às novas construções im-

perativas, como também fazer a reinterpretação de todas as formas primitivas da linguagem

base. É também importante notar que as formas derivadas da linguagem base devem ser codi-

ficadas nas formas primitivas, antes de lhes ser aplicada a tradução.

. : F +&→F+

x ˆ = λs:Store.<x,s> onde x variável

λx:υ.e ˆ = λs:Store.<λ<x,s′>:υ×Store.( e s′),s>

f e ˆ = λs:Store.let <f′,s′>= f s in f′( e s′)λX≤ϕ[X].e ˆ = λs:Store.<λX≤ϕ[X].λs′:Store.λz:SU[X].( e s′),s>

P[τ] ˆ = λs:Store.let <P′,s′>= P s in P′[τ] s′ suτl–=e

– ˆ = λs:Store.<l

–=λs′:Store. e s′––––––––––––––––

‚s>

r.l ˆ = λs:Store.let <r′,s′>= r s in <r′.l,s′>ref (e:τ) ˆ = λs:Store.let <v′,s′>= e s in alloc[τ] suτ s′ v′deref e ˆ = λs:Store.let <l,s′>= e s in <get_val s′ suτ l,s′> onde e: Ref τ

e:=ë ˆ = λs:Store.let <l ′,s′>= e s in onde e: Ref τlet <v′′,s′′>=<ë,s′> in <(),set_val s′′ suτ l′ v′′>

e;ë ˆ = λs:Store.let <v′,s′>= e s in ë s′() ˆ = λs:Store.<(),s>

Nestas equações ocorre com frequência o termo suτ. O significado deste termo é o seguinte. Se

τ for um tipo saturado, então suτ=sui onde i é o índice numérico associado a τ pela godelização.

Se τ≡X for uma variável de tipo, então suX representa o registo de funções de acesso que foi

passado por parâmetro juntamente com a variável X.

Resta ainda o caso de τ ser um tipo não saturado diferente de variável de tipo simples. Co-

mo definir suτ neste caso? Infelizmente, neste ponto encontramos uma (previsível) limitação

essencial do esquema de tradução usado que não é possível contornar. A conclusão é que a so-

lução que analisámos suporta apenas tipos referência de formas limitadas: tipos referência de-

finidos sobre tipos saturados e tipos referência definidos sobre variáveis de tipo.

Os termos de F+ resultantes da tradução . podem ser avaliados usando uma qualquer es-

tratégia de redução, visto a linguagem F+ ser confluente (embora umas estratégias possam ser

mais poderosas do que outras, já que F+ não satisfaz a propriedade da normalização forte). No

40 OM – Uma linguagem de programação multiparadigma

entanto, a escrita de . obriga a tomar decisões sobre o tratamento do estado (argumento s),

as quais induzem automaticamente uma certa ordem de avaliação ao nível da linguagem im-

perativa F+&. Optámos pela estratégia call-by-value pela razão habitual: a sequencialização das

operações sobre o estado facilita o raciocínio sobre este. Convém, no entanto, não esquecer

uma conhecida consequência desta escolha: o operador fix, que na linguagem original está

definido para todas as funções do tipo τ→τ, na linguagem com estratégia call-by-value está

definido apenas para funções com o tipo, mais específico, (υ→τ)→(υ→τ) (ver, por exemplo,

[Sch94] pág. 181, ou [Sto77] pág. 68).

Relativamente ao sistema de tipos de F+&, note que por efeito da tradução todos os termos

passam a ter tipos muito complicados. Por exemplo, assumindo que o termo e tem tipo τ em

F+, temos:

λx:υ.e : Store→((υ×Store→τ×Store)×Store)

f e : Store→τ×Store

λX≤ϕ[X].e : Store→((∀X≤ϕ[X].Store→τ×Store)×Store)

Contudo, é possível continuar a usar em F+& as regras do sistema de tipos de F+, conjunta-

mente com as regras introduzidas nesta secção, pois as múltiplas ocorrências de Store são vá-

cuas do ponto de vista da verificação da boa tipificação dos programas (por exemplo, o tipo

υ×Store→τ×Store é válido sse o tipo υ→τ for válido).

2.5.7 Constante polimórfica nilNesta secção, introduzimos a constante polimórfica nil, uma constante atómica compatível

com qualquer tipo-registo. A constante nil será usada na inicialização implícita das variáveis de

instância de tipos-registo.

A constante nil irá permitir-nos resolver três problemas:

• Na linguagem L7&, os problemas técnicos ligados à geração de objectos com variáveis

de instância (cf. secção 7.3.2.2) podem ser resolvidos fazendo com que todas as variá-

veis de instância de tipos-registo sejam pré-inicializadas com o valor nil (cf. secção

7.3.2.4).

• No contexto da linguagem L7&, dado um tipo-objecto recursivo R que contenha uma

ou mais variáveis de instância desse mesmo tipo R, todos os objectos do tipo R terão de

ser, à partida, infinitos: realmente um objecto do tipo R tem de conter objectos do tipo

R, os quais, por sua vez, têm de conter objectos do tipo R, e assim sucessivamente. Ao

ser usada como constante terminal, a constante nil permite a criação de objectos finitos.

• Considerando uma variável de tipo-registo, R, introduzida numa função polimórfica da

linguagem L6, e.g. λR≤.…, não existe à partida qualquer valor que possa ser usado na

inicialização das variáveis do tipo-registo R dentro da abstracção. A constante nil re-

solve o problema pois é compatível com qualquer tipo-registo.

2 O sistema F+ 41

Os tipos-registo originais de F+ não suportam directamente uma constante nil com as carac-

terísticas pretendidas. Por isso, precisamos de introduzir novos registos, isomorfos aos primei-

ros na medida do possível, e compatíveis com alguma definição razoável para nil.

Eis uma possibilidade. A nossa codificação dos novos registos (a negro) em F+ é a seguinte:

l–:τ– ˆ = Unit→l

–:τ–

l–=e

– ˆ = λz:Unit.l

–=τ– : Unit→l

–:τ–

e.l ˆ = (e ()).l

A codificação de nil é a seguinte:

nill–:τ– ˆ = λz:Unit.divergel–:τ– : Unit→ l–:τ–

Tecnicamente, esta última definição introduz uma família de constantes. Do lado direito da

equação, divergel–:τ– representa a computação divergente de tipo l–:τ– (cf. secção 2.3.3.1). Note

que qualquer tentativa de acesso a uma eventual componente l de nil conduz, opera-

cionalmente, a uma computação divergente, o que significa que o valor nil pode ser consi-

derado atómico, já que não há qualquer estrutura nele detectável.

2.6 Modelo semânticoFazemos aqui uma breve descrição do modelo semântico de Bruce e Mitchell [BM92], que

adoptámos como referência para a selecção dos ingredientes de F+. Este modelo de Bruce e

Mitchell providencia uma base semântica para linguagens com subtipos, tipos recursivos e po-

limorfismo paramétrico F-restringido de ordem superior.

Este modelo expande outros modelos, menos completos, anteriormente desenvolvidos:

[Cop85, Ama91, Car89, AP90]. Em todos eles, os tipos são interpretados como relações de

equivalência parciais (relações simétricas e transitivas) sobre um modelo D∞ do cálculo-lam-

bda não-tipificado. Este modelo é construído usando a tradicional técnica do limite inverso de

Scott [Sco72, Sto77]. D∞ é uma ordem parcial ω-completa. Portanto, todas as cadeias ascen-

dentes dii∈ω de elementos de D∞ têm supremo supdi, todos os elementos de D∞ são funções

contínuas (uma função f é continua se supf di=f supdi) e cada uma destas funções tem um

ponto fixo mínimo (de acordo com o teorema do ponto fixo [Sco72, Sto77]).

Um termo é interpretado em D∞ como uma função contínua. Isto significa que a informação

de tipo incluída no termo é ignorada: o termo é tratado como se fosse um termo-lambda não-

-tipificado. Esta interpretação não-tipificada dos termos é característica das linguagens para-

métricas, linguagens em que o comportamento das funções polimórficas não depende do tipo

dos elementos às quais elas são aplicadas [Str67, Rey83, Wad89, BL90, MR91, ACC93,

CMMS94]. Em [HP95, Cas96] estudam-se variantes não-paramétricas do sistema F≤.

Quanto aos tipos, eles são interpretados no modelo como relações de equivalência parcial

sobre D∞, satisfazendo certas condições particulares que aqui não referiremos: assim, todos os

42 OM – Uma linguagem de programação multiparadigma

termos dum mesmo tipo estão em relação. O género ∗ dos tipos é modelizado usando uma

colecção de relações de equivalência parcial ℜ. ℜ é a menor colecção de relações de equiva-

lência parcial contendo a relação mínima – <⊥,⊥> – e fechada para os construtores de tipo:

função, registo e quantificação F-restringida. A relação de subtipo da linguagem é interpretada

no modelo como a relação de inclusão entre elementos de ℜ.

A parte menos standard do modelo diz respeito à interpretação dos géneros (kinds). Os au-

tores do modelo introduzem as noções de função promotora (rank-increasing function) e de

conjunto escalonado (rank-ordered set). No modelo, cada género é interpretado como um con-

junto escalonado e cada operador de tipo como uma função promotora. Em [BM92], come-

ça-se por verificar que ℜ é um conjunto escalonado e que todos os construtores de tipo vistos

como operadores de tipo são funções promotoras em ℜ. Define-se, então, ℑ a colecção de con-

juntos escalonados que irá modelizar o conjunto de todos os géneros: ℑ é a menor colecção de

conjuntos escalonados contendo ℜ e fechada para as operações de ⇒ e ×, onde κ⇒κ′ é o con-

junto (escalonado) de todas as funções promotoras definidas entre os conjuntos escalonados κ

e κ′, e κ×κ′ é o habitual produto cartesiano, o qual é um conjunto escalonado. Toda esta cons-

trução garante que toda a função promotora definida sobre um conjunto escalonado tenha um

ponto fixo único. Este facto permite a definição de tipos recursivos ao nível de qualquer géne-

ro.

Capítulo 3

Linguagem sem objectos

Introduzimos, no capítulo corrente, a linguagem L3, a primeira duma sequência de linguagens

abstractas, progressivamente mais ricas, que culminará na linguagem L10 do capítulo 10. A

linguagem concreta final OM, objectivo desta tese, será depois definida com base na lingua-

gem L10. Como veículo de expressão semântica para a definição destas linguagens usaremos

F+, o cálculo-lambda polimórfico de ordem superior introduzido no capítulo anterior1.

Introduziremos cada linguagem abstracta como uma extensão própria de F+. Portanto, as

construções específicas de cada uma delas serão definidas como formas derivadas de F+. Para

isso usaremos, como no capítulo anterior, regras da forma:

nova_construção ˆ = esquema de codificação

Uma primeira consequência desta metodologia é o facto das várias linguagens nascerem

mergulhadas em F+. Por outras palavras, todas as construções de F+ serão também construções

das novas linguagens. Caso esta contaminação seja indesejada, o problema pode resolver-se

associando a cada linguagem uma gramática livre de contexto que, selectivamente, produza

apenas as construções pretendidas. Usaremos esta técnica tanto em L3 como nas linguagens

introduzidas nos próximos capítulos.

Outra consequência deste método é o facto de, através das codificações das novas constru-

ções, o sistema de tipos de F+ determinar implicitamente o sistema de tipos de cada uma das

novas linguagens. Esta consequência tem uma vantagem importante: o facto de não ser neces-

sário explicitar autonomamente o sistema de tipos de cada uma das novas linguagens. Mas tem

também uma desvantagem: o sistema de tipos que resulta automaticamente das codificações

pode ser mais liberal do que o desejado. Este último problema, resolve-se introduzindo regras

de tipo complementares que imponham restrições adicionais à linguagem. De qualquer forma

não teremos necessidade de usar esta técnica, nem em L3, nem nas linguagens subsequentes.

A linguagem L3, a que vamos dedicar a secção seguinte, é apenas uma linguagem

preliminar que estabelece um ponto de partida para as linguagens dos próximos capítulos.

1 Mais exactamente, na codificação das construções das linguagens usaremos F+d, a versão decidível do cálculo, e nas demon-

strações usaremos a versão original indecidível do cálculo (cf. secção 2.3.5.6). No entanto, para simplificar o discurso, ao lon-

go desta tese usaremos sempre o nome F+ para designar ambas as versões do cálculo.

44 OM – Uma linguagem de programação multiparadigma

Nas gramáticas das várias linguagens abstractas, usaremos as seguintes convenções de

nomeação de entidades: τ,υ:∗ representam tipos simples, ϕ:∗⇒Κ um operador de tipo definido

sobre tipos simples‚ ϒ um tipo-registo, I uma interface, f:υ→τ uma função simples, e:τ um

termo de tipo simples‚ c uma classe, o um objecto, m um modo, R um registo, P:∀X≤ϕ[X].τ um

termo abstracção de tipo, p um programa.

Usaremos ainda as duas seguintes convenções gráficas: (1) o tipo negro (bold) será usado

para assinalar tipos ou termos que, relativamente à linguagem anterior na sequência de

linguagens abstractas, sejam introduzidos pela primeira vez ou cuja semântica sofra alteração;

(2) o tipo itálico será usado para assinalar tipos técnicos e termos técnicos, entidades auxiliares

introduzidas na linguagem por razões técnicas. Os tipos e termos técnicos não fazem parte das

linguagens, tal como se pretende que elas sejam vistas pelo programador.

3.1 A linguagem L3

Sintaxe dos géneros,tipos e termos de L3

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

lτ ::= <literais dos tipos Bool,Nat>

θτ ::= <operações predefinidas sobre Bool,Nat>

p ::= prog(e)

Relações

τ≤τ′ subtipo

τ=τ′ equivalência de tipos

e=e′ equivalência de termos

Semântica dos termos

prog(e) ˆ = e

A linguagem L3 é a nossa primeira aproximação à linguagem final OM, ou seja, é a primeira

linguagem abstracta que introduzimos como antecessora da linguagem OM. A linguagem L3 é

simples, não incluindo ainda ainda qualquer suporte para mecanismos de orientação pelos ob-

jectos.

3.1.1 SintaxeOs géneros de L3 são os seguintes: o género atómico dos tipos monomórficos ∗, e os géneros

estruturados ∗⇒∗, ∗⇒∗⇒∗, ∗⇒∗⇒∗⇒∗, etc. Portanto, os géneros de L3 são mais limitados do

que os géneros de F+.

3 Linguagem sem objectos 45

Os tipos de L3 são os seguintes: tipos funcionais υ→τ, tipos-registo l–:τ–, operadores de tipo

ΛX.κ, instanciações de operadores de tipo ϕ[τ], tipo primitivo Bool, tipo primitivo Nat. Estes

dois últimos primitivos últimos são codificáveis em F+ usando as técnicas de [CMMS94],

págs. 23 e 24. Os tipos Bool e Nat ser-nos-ão úteis na apresentação de exemplos práticos com

algum realismo.

Note que a linguagem L3 não inclui tipos recursivos µX.κ. No entanto, das próximas lingua-

gens abstractas introduzirão tipos recursivos de formas restritas. É o caso da linguagem L4,

que introduz tipos-objecto recursivos, codificados em F+ usando µX.κ.e tipos-registo.

Os termos de L3 são os seguintes: abstracções monomórficas λx:υ.e, suas aplicações f e, re-

gistos l–=e

–, valores recursivos rec x:τ.e, literais primitivos lτ sobre Bool e Nat (false, true, 0, 1, 2, 3,

4, 5, …), operações primitivas θτ sobre Bool e Nat (and, or, if-then-else, …, +, -, *, /, =, …). Não

entraremos no detalhe da especificação destes literais e operações primitivas.

3.1.2 Relações bináriasNa linguagem L3, e também nas linguagens L4, L5, etc, vamos assumir a existência das se-

guintes três relações sobre tipos e termos, todas herdadas do sistema F+:

• relação de subtipo;

• relação de equivalência entre tipos;

• relação de equivalência entre termos.

Para todos os novos tipos e termos a introduzir das próximas linguagens abstractas, estas

três relações serão inferidas, considerando a codificação desses tipos e termos em F+.

3.1.3 ProgramaUm programa em L3 tem a forma prog(e), onde e é uma expressão simples. O valor de prog(e) é

o valor da expressão e. Note que se exceptuarmos a nova construção prog(e), a linguagem L3 é

uma simples restrição do sistema F+. Tal deixará de ser verdadeiro a partir da próxima lingua-

gem, L4.

Vamos permitir que um programa possa usar nomes previamente introduzidos ao nível da

meta-linguagem. Num certo sentido essas mesmas definições farão parte do programa. Eis um

exemplo dum tal programa de L3, onde diversos nomes (metavariáveis) são introduzidos:

AutoFun ˆ = ΛX.X→X :∗⇒∗NatFun ˆ = AutoFun[Nat] :∗

inc ˆ = λx:Nat.x+1 :Nat→Nat

apply ˆ = λf:NatFun.λx:Nat.(f x) :(Nat→Nat)→Nat→Nat

p ˆ = prog(apply inc 0) :Nat

46 OM – Uma linguagem de programação multiparadigma

Note como neste programa o operador de tipo AutoFun, introduzido na primeira linha, é usa-

do para definir o tipo monomórfico NatFun, na segunda linha. Note ainda como são introduzi-

dos os termos inc e apply, e como são estes usados no corpo do programa, na última linha.

O valor deste programa é 1. Este facto pode ser verificado usando as regras de identidade

entre termos de F+ da secção 2.4.1.

Capítulo 4

Objectos simples com herança

Sintaxe dos géneros,tipos e termos de L4

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | o.l |

checkType[τ] | downcastσ[τ]

Semântica dos tipos

INTERFACE(ϒc) :∗ ˆ = interface

ϒcOBJTYPE(ϒ) :∗ ˆ = tipo-objecto

µSAMET.ϒ (= ϒ[OBJTYPE(ϒ)/SAMET])CLASSTYPE(ϒc) :∗ ˆ = tipo-classe

OBJTYPE(ϒc)→OBJTYPE(ϒc)

Semântica dos termos

checkType[τ] :τ→Bool ˆ = ver secção 4.3.5

downcastσ[τ] :σ→τ ˆ = ver secção 4.3.5

class Rc :CLASSTYPE(ϒc) ˆ = λself:OBJTYPE(ϒc).

Rcclass\s Rc :CLASSTYPE(ϒs⊕ϒc) ˆ =

let S:CLASSTYPE(ϒs) = s inλself:OBJTYPE(ϒs⊕ϒc).

let super:OBJTYPE(ϒs) = (S self) insuper+Rc

*Restrição implícita: ϒs,ϒc devem ser tais que:OBJTYPE(ϒs⊕ϒc)≤OBJTYPE(ϒc)

new c :OBJTYPE(ϒc) ˆ = let gen:CLASSTYPE(ϒc) = c in

let o:OBJTYPE(ϒc) = fix gen ino

o.l :τ ˆ = let R:OBJTYPE(ϒc) = o in

R.l

Na secção 4.1, começamos por introduzir informalmente os conceitos e mecanismos essenciais

da linguagem L4. A formalização da linguagem é depois efectuada na secção 4.2, onde

também determinamos os requisitos de boa tipificação das equações semânticas. Na secção

48 OM – Uma linguagem de programação multiparadigma

4.3, discutimos o potencial de utilização prática da linguagem L4. Fazemos também o levanta-

mento de diversos problemas que atingem a linguagem e tentamos encontrar soluções para

eles no estrito âmbito de L4, sem estender a linguagem. Na secção 4.4 damos um pouco de

perspectiva sobre a linguagem L4 e relacionamos o material deste capítulo com trabalhos de

outros autores.

4.1 Conceitos e mecanismos de L4Nesta primeira secção do capítulo 4 descrevemos informalmente os conceitos e mecanismos

da linguagem funcional L4, que não são mais do que versões dos mecanismos tradicionais das

linguagens orientadas pelos objectos: objecto, classe, herança e subtipo. A descrição é sumá-

ria pois a forma que estas noções assumem em L4 não difere muito da forma que elas assu-

mem em linguagens amplamente divulgadas, como o Smalltalk [GM83, Kra83], C++ [ES90],

Modula-3 [CDJ+89], Java [Sun95, AG98], Pizza [OW97], Eiffel [Mey88, Mey92], ou Sather

[Omo92].

Ao longo deste capítulo e seguintes, tentaremos seguir a nomenclatura da linguagem

Smalltalk, no que diz respeito a construções e mecanismos orientados pelos objectos.

4.1.1 ObjectosOs objectos de L4 são termos compostos definidos recursivamente, com acesso a si próprios

através do nome ligado self. Cada objecto é caracterizado por um conjunto de expressões eti-

quetadas e não mutáveis chamadas métodos. Um método pode ser seleccionado do objecto a

que pertence, usando a sintaxe o.m, ficando assim disponível para ser avaliado.

Envio de mensagem é o nome sugestivo dado à composição das operações de selecção e de

avaliação: a dita mensagem é a etiqueta m usada para seleccionar o método e a resposta à

mensagem é o resultado da avaliação do método. Note que, em L4 e linguagens seguintes, o

envio da mesma mensagem m para dois objectos do mesmo tipo pode resultar na selecção e

avaliação de métodos distintos. Isto mostra que só em tempo de execução é possível estabele-

cer a ligação entre uma mensagem m e o método que ela selecciona. Esta é pois uma ligação

dinâmica.

Os objectos de L4 são formalizados usando registos de F+. Para exemplificar, eis um objec-

to concreto de L4:

ob ˆ = x=5, y=8, sum=self.x+self.y

A linguagem L4 é puramente funcional, i.e. não tem estado mutável. Além disso, os seus

objectos só possuem parte pública. A secção 4.3.1 mostrará que, apesar destas limitações, é

possível escrever em L4 programas não triviais e com utilidade prática.

4 Objectos simples com herança 49

Ao contrário de L4, a linguagem OM final suportará objectos mutáveis (i.e. objectos com

variáveis de instância mutáveis), e também componentes privadas, protegidas por uma barrei-

ra de encapsulamento. Sendo estes mecanismos elementares (pelo menos aparentemente), po-

derá ser questionada a razão deles não serem, desde já, incluídos na linguagem L4. A justifica-

ção é a seguinte: tratam-se, afinal, de elementos cujo tratamento é complexo e artificioso, o

que interfere negativamente na clareza da formalização de outros aspectos da linguagem, para

nós de estudo mais prioritário. A introdução e estudo destes dois mecanismos fica adiada para

o capítulo 7.

Convém, porém, antecipar um pouco de terminologia. Designaremos, de forma genérica,

por componentes dum objecto, a colecção das variáveis de instância e dos métodos desse ob-

jecto. Quanto à separação entre componentes privadas e componentes públicas, esta resulta

duma partição arbitrária que o programador tem a possibilidade de efectuar sobre o conjunto

de componentes de qualquer objecto. Por definição, as componentes privadas só são acessíveis

a partir dos métodos do próprio objecto (usando o nome self); já as componentes públicas po-

dem ser acedidas por quem tiver acesso ao objecto, incluindo o próprio objecto.

4.1.2 Tipos-objectoUm tipo-objecto regista o nome e tipo das várias componentes que integram os objectos desse

tipo. Portanto, um tipo-objecto descreve a estrutura de uma colecção de objectos; não a sua

implementação.

Em L4, um tipo-objecto tem a forma OBJTYPE(ϒ) e pode ser recursivo na variável de recur-

são SAMET (SAMET é uma abreviatura de same type). No tipo OBJTYPE(ϒ), a letra grega ϒ re-

presenta o tipo-registo das componentes do tipo-objecto.

Eis um exemplo de tipo-objecto não-recursivo, escrito usando a sintaxe de L4:

PointT ˆ = OBJTYPE(x:Nat, y:Nat, sum:Nat)

Eis agora um exemplo de tipo-objecto recursivo:

PT ˆ = OBJTYPE(x:Nat, y:Nat, sum:Nat, eq:SAMET→Bool)

Usando a relação de equivalência entre tipos de L4, um tipo-objecto pode geralmente ser

reescrito de diferentes formas, equivalentes entre si. Por exemplo, este último tipo, PT, pode

ser reescrito permutando as componentes do tipo-registo nele incluído, ou aplicando a regra de

unfolding de tipos recursivos o número de vezes que for desejado.

4.1.3 ClassesUma classe é uma estrutura sintáctica que descreve a implementação duma família de objec-

tos.

50 OM – Uma linguagem de programação multiparadigma

Formalizamos uma classe como uma função geradora de objectos extensível. Os objectos

gerados por uma classe dizem-se instâncias dessa classe. Todas as instâncias duma classe têm

a mesma estrutura – i.e. pertencem ao mesmo tipo-objecto – e a mesma funcionalidade – i.e.

contêm as mesmas componentes semânticas. As componentes semânticas duma classe consis-

tem no corpo dos métodos e, ainda, no valor inicial das variáveis de instância (a introduzir no

capítulo 7).

A formalização das classes usando funções geradoras de objectos é natural. Inclusivamente,

existem linguagens de programação, e.g. Beta [KMMN87], em que as classes são explicita-

mente tratadas como funções.

Como vimos em 4.1.2, na linguagem L4 os tipos-objecto são caracterizados unicamente por

informação estrutural. É assim possível e normal a ocorrência num programa de duas classes

com semânticas distintas (implementações distintas), mas gerando objectos do mesmo tipo.

Este dado significa que L4 separa os conceitos de classe e de tipo-objecto, seguindo as ideias

expostas em [CHC90, LP91] e adoptadas nas linguagens experimentais Emerald [BHJL86],

PolyTOIL [BSG95, BFSG98], LOOP [ESTZ94], LOOM [BPF97] e Moby [FR99]. Diverge,

porém, de linguagens, como o C++, Eiffel, Sather, nas quais um tipo-objecto é uma classe. Já

agora, a linguagem Java suporta as duas visões: em Java cada classe introduz um tipo-objecto

distinto; mas a linguagem suporta adicionalmente os chamados tipos-interface, que se baseiam

em informação estrutural.

Representamos por CLASSTYPE(ϒc) o tipo-classe das classes que têm ϒc como tipo-registo

das suas componentes. Por construção, uma classe de tipo-classe CLASSTYPE(ϒc) gera objectos

do tipo-objecto OBJTYPE(ϒc). Para exemplificar, tomemos a classe pointC:

pointC ˆ = class x=0, y=0,

sum=self.x+self.y,

eq=λa:PT.(self.x=a.x & self.y=a.y)

que depende do tipo PT assim definido:

PT ˆ = OBJTYPE(x:Nat,y:Nat,sum:Nat,eq:SAMET→Bool)

A classe pointC gera objectos do tipo PointT:

PointT ˆ = OBJTYPE(x:Nat,y:Nat,sum:Nat,eq:PT→Bool)

e tem a interface PointI:

PointI ˆ = INTERFACE(x:Nat,y:Nat,sum:Nat,eq:PT→Bool)

O tipo da classe pointC é PointCT, e escreve-se:

PointCT ˆ = CLASSTYPE(x:Nat,y:Nat,sum:Nat,eq:PT→Bool)

Neste exemplo, verifica-se a igualdade de tipos PT=PointT. Por isso podemos dizer com se-

gurança que o tipo PointT também é recursivo. Note como, no exemplo, o tipo-objecto PT foi

habilmente introduzido para que a classe gerasse um tipo-objecto recursivo: em L4, uma classe

4 Objectos simples com herança 51

não pode especificar directamente, i.e. sem usar um tipo auxiliar, que o tipo-objecto por ela

gerado é recursivo, embora em L5 já seja possível fazer isso.

Todos os objectos gerados por uma classe têm o mesmo tipo. Referir-nos-emos a esse tipo-

-objecto como o tipo-objecto gerado pela classe ou o tipo-objecto correspondente à classe.

4.1.4 InterfacesChamamos interface de classe, ou apenas interface, a toda a informação de tipo observável

numa classe. Essa informação de tipo é capturada de forma neutra, livre de qualquer interpre-

tação prematura. Uma interface é assim, essencialmente, um repositório de informação não

tratada. As interfaces estão na base da formalização dos tipos-objecto e dos tipos das classes.

Em L4, numa interface não pode ocorrer o nome SAMET.

As interfaces são representados por tipos da forma INTERFACE(ϒc). Uma classe com interfa-

ce INTERFACE(ϒc) gera objectos do tipo OBJTYPE(ϒc).

4.1.5 Herança, subclasses e superclassesUma característica essencial das classes é o facto de elas serem incrementalmente modificá-

veis. Usando o mecanismo da herança, o programador consegue criar de forma expedita uma

nova classe a partir de outra, definindo apenas as componentes da nova classe que devem ser

adicionadas ou modificadas relativamente à classe original. As componentes da classe original

que não forem redefinidas na nova classe são automaticamente herdadas por esta. Nesta situa-

ção, a classe que herda diz-se subclasse imediata da classe que fornece as componentes. Por

sua vez, esta diz-se superclasse imediata da primeira.

Num programa de L4, qualquer classe c que não tenha superclasse imediata pode ser raiz

duma hierarquia de classes: a hierarquia das classes definidas, directa ou indirectamente, a par-

tir de c por meio de herança. O primeiro nível da hierarquia é constituído pela classe c apenas;

o segundo nível é constituído por todas as subclasses imediatas de c; o terceiro nível é consti-

tuído por todas as subclasses imediatas destas; e assim sucessivamente. Justifica-se, assim, a

generalização das noções de subclasse imediata e de superclasse imediata: define-se a relação

binária de subclasse como sendo o fecho reflexo-transitivo da relação de subclasse imediata e

define-se a relação binária de superclasse como sendo relação inversa desta.

Note que o mecanismo de herança torna as classes em entidades extensíveis: uma classe

passa a ser uma implementação parcial para um número potencialmente infinito de subclasses.

Para potenciar ao máximo as vantagens e oportunidades de reutilização de código, os méto-

dos herdados devem ser reinterpretados no contexto das subclasses que os acolhem. Para isso,

é preciso alterar o significado de self no interior dos métodos herdados: se na classe original

self representa um objecto dessa mesma classe, já na subclasse self deve passar a representar

52 OM – Uma linguagem de programação multiparadigma

um objecto da subclasse. Esta reinterpretação de self nos métodos herdados é típica da genera-

lidade das linguagem orientadas pelos objectos.

Em L4, o nome super encontra-se disponível no interior de toda a subclasse e permite o

acesso às versões originais das componentes presentes na superclasse imediata. Note que, tam-

bém, no caso em que acedemos a uma componente da superclasse usando super, a componente

acedida é interpretada no contexto da subclasse (por exemplo, qualquer método da superclasse

acedido através de super é executado no contexto da subclasse, usando as versões das com-

ponentes disponíveis na subclasse). O nome super é particularmente útil para estender na sub-

classe a funcionalidade dum método definido na superclasse: basta redefinir o método na sub-

classe tendo o cuidado de invocar a versão anterior do mesmo método no ponto apropriado da

nova versão e através de super.

4.1.6 “Reutilização sem reverificação”A reinterpretação dos métodos herdados nas subclasses levanta a questão prática da “reutiliza-

ção sem reverificação”: será que é possível verificar os métodos apenas uma vez, no momento

em que são originalmente introduzidos, evitando ter de os reverificar (e recompilar) num novo

contexto sempre que são herdados? Esta é uma questão importante pois uma eventual resposta

negativa inviabilizaria a possibilidade de compilação separada de classes e a consequente ca-

pacidade de distribuir classes extensíveis sob a forma de código compilado.

Claramente, pretendemos que a resposta àquela pergunta seja “sim”. Para isso começamos

por impor uma restrição geral de boa formação sobre todas as subclasses imediatas duma clas-

se genérica c: o tipo-objecto gerado por toda a subclasse imediata de c deve ser subtipo do

tipo-objecto gerado por c. Esta restrição vai de encontro ao nosso objectivo, pois faz com que

o tipo de self dentro de qualquer método seja mais geral do que o tipo-objecto gerado por todas

as subclasses que possam herdar o método directamente.

Mas temos de pensar também nas classes não-imediatas de c pois estas também herdam de

c. A questão que agora se coloca é a seguinte: será que a restrição imposta sobre as subclasses

imediatas é suficiente para garantir “reutilização sem reverificação”, também no caso das sub-

classes não-imediatas? Para ver isso tomemos três classes c, b e a, onde c é uma subclasse ime-

diata bem formada de b, e b é subclasse imediata bem formada de a. A pergunta reformulada

para o caso de c, b e a fica assim: será que, usando o conhecimento disponível sobre c, b e a, se

pode garantir que a classe c é também uma subclasse (não-imediata) bem formada de a? A res-

posta será positiva se a restrição subjacente à noção de boa formação for transitiva. Mas nós já

sabemos que essa restrição é transitiva pois trata-se da relação de subtipo em F+. Portanto, a

resposta a esta última questão é “sim”.

O facto da relação de subtipo ser, já por si, reflexiva, e não apenas transitiva, é uma vanta-

gem adicional. Desta forma, a relação de subclasse permanece reflexiva e transitiva, mesmo

depois de submetida às restrições de boa formação.

4 Objectos simples com herança 53

Note como foi possível codificar a restrição que preside à definição das subclasses (bem

formadas) duma dada classe usando uma relação binária sobre os tipos-objecto gerados por es-

sas classes: a relação de subtipo, neste caso. Na linguagem L5 introduziremos uma relação de

herança mais geral que a de L4, mas também usando esta técnica de recorrer a uma relação

binária sobre os tipos-objecto gerados pelas classes.

Tal como L4, as linguagens C++ e Java suportam “reutilização sem reverificação”. Pelo

contrário, as linguagens Eiffel e Sather exigem “reutilização com reverificação”: são lingua-

gens com mecanismos de herança muito liberais, onde chega mesmo a ser possível fazer he-

rança selectiva de componentes usando uma directiva undefine: note que a herança selectiva é a

forma mais óbvia de violar a nossa restrição de boa formação.

4.1.7 SubtiposA relação de subtipo em L4 é inferida considerando a codificação desses tipos em F+ e usando

as regras de F+ relativas a subtipos. Isto significa que, em L4, a relação de subtipo entre tipos-

-objecto assenta em informação estrutural, não dependendo de nenhuma forma de aspectos

relacionados com a implementação das classes. Assim, os conceitos de subtipo e subclasse es-

tão separados em L4: mais precisamente, dois tipos podem estar na relação de subtipo sem que

as classes que lhes deram origem estejam na relação de subclasse. Neste aspecto L4 difere das

linguagens C++, Java e Eiffel, nas quais um subtipo se identifica com uma subclasse.

Agora que as noções de herança e subtipificação já foram introduzidas é conveniente cha-

mar a atenção de que se tratam de noções distintas. A herança é um mecanismo de implemen-

tação que permite a reutilização de código – o que simplifica a escrita de programas e os torna

mais compactos, legíveis e modificáveis. Subtipificação é um mecanismo semântico que per-

mite organizar em níveis de abstracção alguns conceitos usados nos programas: mais concreta-

mente, permite tratar objectos dum tipo específico como se fossem objectos dum tipo mais

geral.

4.2 Semântica de L4Esta secção, é dedicada à apresentação e discussão da formalização dos aspectos semânticos

de L4. O essencial da formalização da sintaxe e da semântica de L4 encontra-se na tabela apre-

sentada no início do presente capítulo.

4.2.1 Semântica dos tiposEm L4, a forma geral duma classe sem superclasses é class Rc, onde Rc representa o registo das

componentes da classe. Já uma subclasse imediata duma classe s tem a forma class\s Rc. Supon-

do que Rc tem o tipo ϒc, apresentamos seguidamente a formalização de alguns tipos chave,

essenciais para o estabelecimento do modelo que desenvolvemos para L4. Todos estes tipos

54 OM – Uma linguagem de programação multiparadigma

acompanhar-nos-ão ao longo das próximas linguagens evoluindo em paralelo com as próprias

linguagens.

INTERFACE(ϒc): Representa a interface que captura toda a informação de tipo observável

na classe class Rc. Formaliza-se, simplesmente, usando o tipo-registo ϒc. Note que, em L4, é

proibida a ocorrência de SAMET numa interface.

OBJTYPE(ϒ): É um tipo-objecto recursivo constituído pelas componentes do tipo-registo ϒ.

No contexto de OBJTYPE(ϒ), o nome SAMET é interpretado como uma referência recursiva ao

próprio tipo-objecto. Formalizamos o tipo-objecto como µSAMET.ϒ, ou equivalentemente, co-

mo ϒ[OBJTYPE(ϒ)/SAMET] (usando unfolding). O tipo-objecto gerado por uma classe com inter-

face INTERFACE(ϒc) é OBJTYPE(ϒc). Como o nome SAMET não pode ocorrer nas classes de L4,

SAMET também não pode ocorrer espontaneamente no tipo-objecto gerado por uma classe de

L4.

CLASSTYPE(ϒc): Representa o tipo-classe das classes com interface INTERFACE(ϒc). Forma-

lizamos este tipo usando OBJTYPE(ϒc)→OBJTYPE(ϒc), um tipo-gerador de objectos extensível.

A formalização do tipo-classe CLASSTYPE(ϒc) requer uma explicação. Devido à reinterpre-

tação de self nas subclasses, um gerador não se pode comprometer prematuramente com um si-

gnificado para self. Por isso temos de considerar um gerador como uma entidade aberta, para-

metrizada em função do significado de self. Para self escolhemos o tipo OBJTYPE(ϒc), porque

self vai representar qualquer um dos objectos que a classe gera. Para tipo do resultado do gera-

dor escolhemos também OBJTYPE(ϒc), pois este é o tipo dos objectos a gerar. Assim o tipo-ge-

rador pretendido será OBJTYPE(ϒc)→OBJTYPE(ϒc).

A escolha de OBJTYPE(ϒc) para tipo de self pode parecer prematura pois, com a mudança de

significado de self nas subclasses, talvez devêssemos considerar também a mudança do tipo de

self nas subclasses. No entanto a escolha que efectuámos é a única possível se tivermos por ob-

jectivo encontrar um modelo monomórfico para a linguagem L4. Esse será realmente o nosso

objectivo por agora, pelo que viveremos com as consequências desta escolha, em L4. Em L5,

self já terá uma tipificação mais sofisticada.

4.2.2 Semântica dos termosAgora, discutimos a semântica das classes e subclasses de L4, e ainda a semântica das opera-

ções de criação de objectos e de acesso a componentes desses objectos. As equações semân-

ticas encontram-se detalhadas na tabela que abre o capítulo corrente.

4.2.2.1 Semântica das classes

Na equação semântica correspondente à classe de L4 class Rc, o gerador que aí é introduzido é

realmente simples: ele limita-se a gerar um objecto com as componentes da classe e parametri-

zado relativamente a self. Não fora o problema da reinterpretação de self nas subclasses e seria

4 Objectos simples com herança 55

ainda possível aplicar o operador de ponto fixo ao gerador nessa mesma equação. Mas como

tal não é possível, a aplicação de fix fica adiada para o momento em que se criam os objectos,

por aplicação do operador new a uma classe. Note que esta definição faz implicitamente a vali-

dação do código dos métodos da classe: se o registo Rc estiver mal tipificado então o termo

class Rc está mal tipificado.

O tratamento da herança está formalizado na equação do termo subclasse imediata

class\s Rc. Logo à partida, tanto a interface da subclasse como o tipo-objecto gerado pela sub-

classe são determinados através da combinação, usando o operador ⊕, do tipo-registo das com-

ponentes herdadas, ϒs, com os tipo-registo das componentes definidas na subclasse, ϒc. A

interface da subclasse é INTERFACE(ϒs⊕ϒc) e o respectivo tipo-objecto é OBJTYPE(ϒs⊕ϒc). A

reinterpretação, e consequente mudança de tipo de self na subclasse, é efectuada da seguinte

forma. Primeiro, introduzimos um novo self do tipo dos objectos da subclasse. Seguidamente,

aplicamos o gerador S correspondente à superclasse ao esse novo self: desta forma ajustamos

os métodos da superclasse ao contexto da subclasse, o que é necessário para se ter efectiva

reutilização de código. Finalmente, modificamos o conjunto das componentes herdadas e já

adaptadas ao contexto da subclasse por aplicação do operador +. Assim criamos um novo gera-

dor por extensão dum gerador existente.

Relativamente a super, este nome fica ligado a (S self), o que está conforme a descrição infor-

mal de super efectuada um pouco atrás. Repare no seguinte pormenor: um gerador não se com-

promete com um significado para self (self é um parâmetro dum gerador) mas, pelo contrário,

compromete-se com um significado para super (super=(S self)). É por esta razão que a tentativa

de explicar a herança como cópia textual de componentes falha no caso dos métodos herdados

que usam super.

4.2.2.2 Boa formação das subclasses

Para que o termo class\s Rc esteja bem tipificado é necessário que o registo super+Rc esteja, tam-

bém ele, bem tipificado. Mas existe outra questão de tipificação que é prévia a esta. Note que o

gerador S correspondente à superclasse está preparado para receber um objecto da superclasse,

portanto do tipo OBJTYPE(ϒs). No entanto é aplicado, no termo

super = S self,

a um objecto da subclasse, portanto do tipo OBJTYPE(ϒs⊕ϒc). Assim, outra condição necessária

para a boa tipificação das subclasses é que os tipos-registo ϒs, ϒc obedeçam à restrição:

OBJTYPE(ϒs⊕ϒc)≤OBJTYPE(ϒc)

Esta condição indica que o tipo-objecto correspondente à subclasse deve ser subtipo do tipo-

-objecto correspondente à superclasse. Assim, podemos afirmar que em L4 “herança implica

subtipificação”, pois sem a verificação da restrição de subtipo não é possível ter herança neste

modelo. A implicação contrária não se verifica em L4, pois dois tipos-objecto podem estar na

relação de subtipo sem que as classes que os originaram estejam na relação de subclasse.

56 OM – Uma linguagem de programação multiparadigma

Ainda sobre a definição do termo class\s Rc, note que foi possível escrever a respectiva equa-

ção sem que fossem explicitadas as componentes da superclasse: foi apenas necessário expli-

citar o tipo das componentes da superclasse. É por esta razão que o mecanismo de herança de

L4 é compatível com a compilação separada de classes. Um mecanismo de herança irrestrito,

semelhante ao da linguagem Sather, teria de ser descrito usando uma definição semelhante à

que se segue:

class\(class Rs) Rc :CLASSTYPE(ϒs⊕ϒc) ˆ = λself:OBJTYPE(ϒs⊕ϒc).

let super:OBJTYPE(ϒs) = Rs in

super+Rc

4.2.2.3 Semântica dos outros termos

Vamos apresentar as definições das formas derivadas que restam por explicar.

O termo new c serve para criar objectos da classe c. De acordo com a equação semântica

respectiva, cada objecto é criado aplicando o operador de ponto fixo ao gerador de objectos

correspondente a c. O nome self fica ligado no contexto do próprio objecto.

O termo que permite o acesso às componentes dos objectos – o.l – é codificado num sim-

ples acesso a uma componente dum registo. Como já vimos, modelizamos os objectos usando

registos.

4.3 Discussão sobre L4Para ilustrar as potencialidades e as limitações de L4 vamos usar ao longo desta secção, e res-

pectivas subsecções, um exemplo que envolve a representação de pontos a duas e três dimen-

sões, com estruturas definidas pelos tipo-objecto Point2T e Point3T, respectivamente. Apresenta-

mos duas classes parametrizadas e recursivas, point2PC e point3PC, que implementam objectos

destes tipos, e indicamos quais as suas interfaces, Point2I e Point3I.

Point2T ˆ = OBJTYPE(x:Nat,y:Nat, sum:Nat, id:SAMET, inc:SAMET,eq:SAMET→Bool)

Point2I ˆ = INTERFACE(x:Nat,y:Nat, sum:Nat, id:Point2T, inc:Point2T,eq:Point2T→Bool)

point2PC ˆ = rec p2. λa:Nat.λb:Nat.class

x=a, y=b,

sum=self.x+self.y,

id=self,

inc=new (p2 (succ self.x) (succ self.y)),

eq=λa:Point2T.(self.x=a.x & self.y=a.y)

Point3T ˆ = OBJTYPE(x:Nat, y:Nat, z:Nat, sum:Nat, id:Point2T, inc:SAMET,eq:???→Bool)

Point3I ˆ = INTERFACE(x:Nat, y:Nat, z:Nat, sum:Nat, id:Point2T, inc:Point3T,eq:???→Bool)

point3PC ˆ = rec p3. λa:Nat.λb:Nat.λc:Nat. class\(point2PC a b)

z=c,

sum=super.sum + self.z,

4 Objectos simples com herança 57

inc=new (p3 (succ self.x) (succ self.y) (succ self.z)),

eq=λa:???.(super.eq a & self.z=a.z)

Nestas definições, usamos o símbolo ‘???’ para assinalar certas posições onde deveriam deve-

riam ocorrer tipos-objectos concretos. Atendendo às propriedades pretendidas para eq, não

existe substituição satisfatória para ‘???’ (cf. secção 4.3.4).

4.3.1 Inicialização dos objectos e criação de cópiasmodificadasConsideremos, em primeiro lugar, as duas seguintes restrições de L4: (1) todos os objectos

gerados por um classe são idênticos; (2) não há variáveis de instância mutáveis. Será possível,

com estas limitações, escrever programas não triviais que tenham utilidade prática? No caso de

L4 a resposta é positiva o que se deve à possibilidade de inicializar objectos e à possibilidade

de criar cópias modificadas de objectos usando classes parametrizadas definidas recursiva-

mente.

Para exemplificar a inicialização de objectos, considere a expressão que cria o ponto zero,

new (point2PC 0 0): a classe parametrizada point2PC é instanciada com os argumentos 0, 0 e,

depois, a classe não-parametrizada resultante é usada para gerar um objecto do tipo Point2T.

Quanto à possibilidade de criação de cópias modificadas, esta é uma consequência imediata da

possibilidade de inicializar objectos: observe como o método inc de point2PC, que tem a tarefa

de incrementar ambas as coordenadas dum ponto a duas dimensões, se limita a criar um novo

ponto inicializado com (succ self.x) e (succ self.y).

4.3.2 Problema da perda de informaçãoA linguagem L4 sofre do problema da perda de informação. Este problema, analisado em

[Car88], é típico de todas as linguagens que suportam a noção de subtipo, como é o caso das

linguagens Amber [Car86], C++, Java, Modula-3, etc. O problema pode ocorrer sempre que

um termo do tipo τ é promovido, por alguma razão, a um seu supertipo τ′. Como o conjunto de

operações suportadas pelo supertipo τ′ é frequentemente um subconjunto das operações supor-

tadas por τ, o sistema de tipos, no seu conservadorismo, impede por vezes a aplicação ao

termo do tipo τ de operações para as quais ele estaria, afinal, preparado para responder.

Para exemplificar uma situação de perda de informação, considere o seguinte objecto do

tipo Point3T

ob ˆ = new (Point3PC 1 2 3)

e ainda a função

ident ˆ = λx:Point2T.x

e a expressão

58 OM – Uma linguagem de programação multiparadigma

(ident ob).z

A função ident pode ser aplicada ao objecto ob, pois o tipo deste é Point3T, um subtipo de

Point2T. Avaliando a expressão ident ob, obtemos o próprio objecto ob. Mas os resultados da

função ident têm um tipo fixo – Point2T –, pelo que o objecto ob produzido é estaticamente con-

siderado como sendo um elemento do tipo Point2T. Como este tipo não prevê o método z, a

expressão (ident ob).z está mal tipificada.

A origem deste caso de perda de informação reside no facto do tipo do resultado da função

ident ser fixo, não dependendo do tipo do argumento da função. Usando uma construção de se-

gunda ordem já seria possível resolver o problema. Bastaria reescrever a função ident da se-

guinte forma

ident ˆ = λT≤Point2T.λx:T.x

e, depois, na invocação, indicar explicitamente qual o tipo pretendido para o resultado. É o

fazemos na expressão seguinte, a qual já está bem tipificada:

(ident[Point3T] ob).z

Assim, o problema da perda de informação desapareceria se eliminássemos da linguagem a

relação de subtipo e introduzíssemos polimorfismo de segunda ordem em sua substituição. No

entanto esta via não no satisfaz, pois há problemas que a relação de subtipo ajuda a resolver e

perante os quais o polimorfismo de segunda ordem se revela ineficaz (cf. secção 5.5.2).

Na realidade, o problema de perda de informação é insolúvel numa linguagem que suporte

a relação de subtipo e tenha um sistema de tipos puramente estático. O problema contorna-se

estendendo a linguagem com operações dinâmicas de teste e de despromoção de tipo (cf.

secção 4.3.5). Existem operações desta natureza – typecases, casts e downcasts – definidas na

maioria das linguagens que suportam a noção de subtipo. É o caso ds linguagens: Amber,

C++, Java e Modula-3.

4.3.3 Inflexibilidade na herança do tipo de self

Exemplificamos o problema da inflexibilidade na herança do tipo de self usando o método id,

introduzido na classe point2PC e herdado, mas não redefinido, em point3PC. Observe que este

método tem o tipo de self, ou seja Point2T, na classe em que é introduzido. O problema reside

no facto do método ser herdado sem que o tipo do seu resultado seja ajustado, apesar do tipo

de self ter mudado na subclasse.

Em resultado deste problema, pode surgir uma situação de perda de informação que é uma

variante da situação exemplificada no ponto anterior. Considere o seguinte objecto do tipo

Point3T:

ob ˆ = new (point3PC 1 2 3)

e a expressão:

4 Objectos simples com herança 59

ob.id.z

Em resposta à mensagem id, o método id de point3PC devolve o mesmo objecto ob, mas ago-

ra visto como um termo do tipo Point2T. Mas como o tipo Point2T não prevê o método z, a

expressão ob.id.z fica mal tipificada.

Em L4, e também C++, Java, Modula-3, etc., a única forma de solucionar este problema é

insatisfatória: consiste em copiar manualmente o método da superclasse para a subclasse, sem

tirar partido do mecanismo de herança. Em L5, a introdução do tipo SAMET, representativo do

tipo de self dentro de cada classe, permitirá uma tipificação mais precisa das componentes e

resolverá definitivamente o problema da inflexibilidade na herança do tipo de self. A for-

malização de SAMET irá requerer a utilização de construções de segunda ordem.

4.3.4 Métodos bináriosConsideramos agora o problema da definição de métodos binários [BCC+96]. Um método

binário é um método que tem pelo menos um parâmetro com o tipo de self. O método eq, intro-

duzido na classe point2PC, é um exemplo de método binário.

A tentativa de redefinir eq na subclasse ilustra a dificuldade do tratamento de métodos biná-

rios em L4. O problema tem a ver com a tipificação do argumento de eq na subclasse. A solu-

ção intuitiva seria fazer ???≡Point3T, mas isso levaria a que Point3T deixasse de ser subtipo de

Point2T (cf. regra [Sub →] da secção 2.3.4.1), o que violaria a restrição fundamental que preside

à definição das subclasses: desta forma a subclasse ficaria mal tipificada. Podemos também

tentar ???≡Point2T, mas o método eq em point3PC fica agora com um argumento demasiado gené-

rico, pelo que agora é o corpo do método que fica mal tipificado (o problema localiza-se exac-

tamente na expressão a.z). Efectuadas estas duas tentativas, concluímos que não é possível

encontrar qualquer substituição para ??? que nos permita evitar ter de reconfigurar o código

circundante. Esta é uma grave lacuna de expressividade de L4.

Em C++, Java, etc., a solução para este problema envolve a escolha de ???≡Point2T e o uso

de operações dinâmicas de teste e de despromoção de tipo, as quais permitem reescrever o cor-

po do método eq duma forma análoga à seguinte:

eq=λa:Point2T. super.eq a &

(if checkType[Point3T] a then self.z=(downcastPoint2T[Point3T] a).z else false)

No trabalho [BCC+96] são apresentadas outras técnicas de definição de métodos binários,

mas as técnicas que são aplicáveis em L4 não são propícias ao uso mecanismo de herança:

uma técnica envolve a troca dos métodos binários por funções exteriores às classes; outra a

substituição dos métodos binários por colecções de métodos não binários. Exemplificando

com o caso do método eq, a primeira técnica substituiria eq pelas duas funções seguintes:

eqPoint2 ˆ = λs:Point2T.λb:Point2T.(s.x=a.x & s.y=a.y)

eqPoint3 ˆ = λs:Point3T.λb:Point3T.(eqPoint2 s b & s.z=a.z)

60 OM – Uma linguagem de programação multiparadigma

Em L4, não há realmente solução completamente satisfatória para o problema da definição

de métodos binários. Em L5, a introdução do nome de tipo SAMET nas classes permitirá resol-

ver o problema de forma definitiva.

4.3.5 Tipos dinâmicos em L4Já o referimos anteriormente, uma solução de recurso, que permite minorar as deficiências

dum sistema de tipos estático, consiste na introdução de operações dinâmicas de teste e de des-

promoção de tipo na linguagem. As linguagens Amber, C++, Java, Módula-3, Eiffel e Sather

são exemplos de linguagens que suportam tipos dinâmicos, em parte por esse motivo.

As deficiências do sistema de tipos de L4 justificam a introdução de tipos dinâmicos, tam-

bém nesta linguagem. Na secção que agora se inicia, apresentamos uma técnica de introdução

de tipos dinâmicos em L4.

Repare que os tipos dinâmicos serão úteis, não só na linguagem L4, mas também nas futu-

ras linguagens L5, L6, etc., incluindo a linguagem OM final. É certo que a maioria das defi-

ciências de L4 se desvanecerá em breve, no contexto do sofisticado sistema de tipos de L5.

Contudo, um dos problemas de L4 – o problema da perda de informação (cf. secção 4.3.2) –

manter-se-á, sendo razão suficiente para a inclusão de tipos dinâmicos nas linguagens.

4.3.5.1 Introdução dos tipos dinâmicos

A introdução de tipos dinâmicos numa linguagem formalizada usando o sistema F+ é uma tare-

fa que não é imediata. O sistema F+ é paramétrico, pelo que está na sua essência a deliberada

abstracção de toda a informação de tipo em tempo de execução (cf. secção 2.1.1). Mas isso é

exactamente o oposto do que se pretende na nova versão de L4, uma versão que claramente

será não-paramétrica.

Assim, está fora de questão a codificação directa dos novos termos, e das novas operações

de teste e despromoção de tipo, como açúcar sintáctico sobre o sistema F+. Contudo, nada nos

impede de usar o sistema F+ como ferramenta para criar uma implementação duma nova ver-

são dinâmica de L4. Vamos explorar esta ideia tentando identificar os aspectos chave da sua

realização.

Os únicos termos de L4 a que desejamos associar tipos dinâmicos são os objectos da lin-

guagem. Por isso vamos construir, só para os objectos de L4, um sistema paralelo de represen-

tações explícitas de tipos-objecto dinâmicos.

Há dois aspectos a considerar.

Em primeiro lugar, há a questão do estabelecimento da associação física entre cada objecto

de L4 e o respectivo tipo dinâmico. Isso consegue-se através da inclusão automática, em todas

as classes de L4, duma componente específica chamada mytype, convenientemente inicializada.

4 Objectos simples com herança 61

Assim, cada objecto de L4 passa a contar com uma componente mytype que ficará ligada a uma

representação explícita do seu próprio tipo.

Em segundo lugar, há a questão da implementação da representação explícita dos tipos-

-objecto. Essa implementação pode, perfeitamente, ser realizada usando uma classe de L4,

digamos uma classe chamada $DYNAMIC_TYPE, geradora dum tipo-objecto $dyntype, e conten-

do uma implementação da relação de subtipo entre tipos-objecto num método chamado subtype.

Escrita a nova versão de L4, note que o sistema de tipos de F+ não nos dá quaisquer garan-

tias de segurança e correcção. Efectivamente estes aspectos de L4 dependem agora de diver-

sas questões exteriores a F+: Será que, nas classes de L4, a componente mytype é inicializada

com o tipo dinâmico certo, por referência ao tipo-objecto estático gerado? Será que a represen-

tação dos tipos dinâmicos captura fielmente as particularidades dos tipos estáticos de F+? Será

que o método subtype da classe $DYNAMIC_TYPE implementa correctamente a relação de sub-

tipo de F+? Todas estas questões têm de ser resolvidas sem a ajuda do sistema F+. (Tais proble-

mas não se colocaram na definição original de L4 porque o que estava aí em causa era uma

codificação, não uma implementação).

4.3.5.2 Operação de teste de tipo

Introduzida a representação explícita para tipos-objecto e assumindo os detalhes de implemen-

tação descritos na secção anterior, para cada tipo-objecto τ concreto escrevemos em F+ a ope-

ração dinâmica de teste de tipo checkType[τ], como se segue:

checkType[τ] ˆ = λx:mytype:$dyntype. (x.mytype).subtype(explrep[τ]) :mytype:$dyntype→Bool

O termo explrep[τ] representa a instância da classe $DYNAMIC_TYPE que corresponde ao tipo-

-objecto τ. O tipo mytype:$dyntype é o tipo-objecto mais geral de L4.

A função checkType[τ] extrai o tipo dinâmico do argumento x, e verifica se ele é subtipo de τ,

usando para isso o método subtype da classe $DYNAMIC_TYPE.

4.3.5.3 Operação de despromoção de tipo

Relativamente à operação de despromoção, o objectivo é definir uma operação downcastσ[τ] que

ao ser aplicada a uma expressão e:σ, com τ≤σ, despromova o tipo estático dos seus resultados

de σ para τ sem afectar os valores produzidos. Como os resultados são para respeitar, a opera-

ção de despromoção só faz sentido nos casos em que os resultados são já elementos de τ – se

não o fossem, mudar o seu tipo de forma arbitrária não faria sentido.

Eis uma definição segura para downcastσ[τ], escrita em F+:

downcastσ[τ] ˆ = λx:σ. if checkType[τ] x then e else divergeτ :<<σ→τ>>

Nesta definição divergeτ representa a computação divergente de tipo τ (cf. secção 2.3.3.1).

Quanto a if-then-else esta é uma primitiva booleana introduzida em L3.

62 OM – Uma linguagem de programação multiparadigma

A função produz simplesmente o seu próprio argumento, caso ele pertença ao tipo τ; se não

pertencer, a função aborta por efeito do termo divergeτ (nesta situação, a linguagem OM geraria

uma excepção).

O sistema F+ atribui à função downcastσ[τ] o tipo σ→σ e não o tipo σ→τ como gostaríamos.

Realmente, em F+ não há nenhuma forma de tipificar a função da forma que pretendíamos: a

única regra que permite mudar o tipo dum termo é a regra de promoção [Termo inclusão] e esta

regra não nos ajuda nesta situação. Por isso temos de definir uma regra especial de introdução

do termo downcastσ[τ] em L4:

[Termo downcast]

Γ τ≤σ

Γ downcastσ[τ]:σ→τ

Esta regra é segura. Realmente, analisando os dois ramos da definição de downcastσ[τ],

verificamos que o primeiro ramo produz sempre valores do tipo τ (assumindo a correcta reali-

zação de checkType[τ]) e o segundo também produz sempre valores do tipo τ (pois trata-se do

termo divergeτ)

O facto de termos introduzido esta nova regra de tipo é mais um sinal de que realmente

estamos a ultrapassar as fronteiras de F+. Repare que L4 passou a ter um sistema de tipos inde-

pendente, o qual requer argumentação independente relativamente à sua segurança.

4.3.5.4 Discussão

A introdução de tipos dinâmicos na linguagem L4, obrigou-nos a mudar a fundação da lingua-

gem: F+ não permite a codificação directa dos novos mecanismos de L4.

O plano que propusemos para a definição da nova L4 foi o mais prático que conseguimos

imaginar, tendo em consideração o facto da linguagem L4 já estar codificada em F+. Eis um

resumo comentado do plano proposto:

• A generalidade das construções de L4 continuam a ser codificadas em F+ da mesma

forma, ou quase da mesma forma. É verdade que adicionámos a componente mytype a

todas as classes, mas essa é uma componente como outra qualquer, já suportada pela

semântica original, e além disso, ignorada pelas construções originais.

• As novas construções, checkType[τ] e downcastσ[τ], também são codificadas em F+ mas

passam a depender da funcionalidade da classe $DYNAMIC_TYPE. Digamos que a classe

$DYNAMIC_TYPE realiza a semântica da faceta dinâmica de L4.

• As construções class e class\c passam a ter a responsabilidade de introduzir a componen-

te mytype correctamente inicializada.

• A boa definição semântica das construções checkType[τ] e downcastσ[τ] depende da cor-

recção da classe $DYNAMIC_TYPE e da correcta inicialização da componente mytype em

cada classe.

4 Objectos simples com herança 63

• O sistema de tipos da nova versão de L4 seria automaticamente seguro se deixássemos

as restrições de F+ propagarem-se naturalmente a L4 pela via das codificações. Mas co-

mo o termo downcastσ[τ] é tipificado através duma regra especial, torna-se necessário

garantir separadamente a segurança desta regra.

A nossa linguagem final, OM, usa este método para introduzir tipos dinâmicos (cf. classes

$CoreObject e $DYNAMIC_TYPE, da secção 11.7).

[ACPP91, ACPR92] são dois importantes elementos da literatura sobre mecanismos de

tipificação dinâmica. Ambos os estudos trabalham o problema usando os métodos operacional

e denotacional directo. O segundo distingue-se por discutir tipos dinâmicos polimórficos.

4.3.5.5 Utilidade dos tipos dinâmicos

Terminamos com um breve comentário sobre a utilidade dos tipos dinâmicos. É importante

compreender que a utilidade dos tipos dinâmicos ultrapassa a aplicação que nos levou a discu-

ti-los nesta secção, ou seja, o superar das deficiências do sistema de tipos.

Os tipos dinâmicos são essenciais quando um programa tem de lidar com tipos que não

podem ser determinados em tempo de compilação [ACPP91, ACPR92, Goo81, Hen92]. Em

particular, quando um programa recebe dados não homogéneos do exterior – de outro progra-

ma ou dum ficheiro – é conveniente que cada valor venha acompanhado por uma representa-

ção do seu tipo.

Os tipos dinâmicos são também úteis na escrita de funções polimórficas não-paramétricas.

Imagine, por exemplo, um procedimento print que tenha de lidar de forma não uniforme com

valores de vários tipos: esse procedimento deve poder fazer uma análise de casos sobre o tipo

do seu argumento.

4.3.6 Simulação em L4 do sistema de tipos do C++A diferença mais notória entre os sistemas de tipos da linguagem L4 e da linguagem C++ resi-

de na questão da separação dos conceitos de classe e de tipo-objecto: enquanto L4 separa estes

dois conceitos, o C++ unifica-os.

É instrutivo tentar simular em L4 a unificação dos conceitos de classe e de tipo-objecto.

Para isso temos de estabelecer uma bijecção entre classes e tipos-objecto que garanta que to-

dos os objectos com um dado tipo são gerados por uma mesma classe e vice-versa. Consegui-

mos isso de forma simples, adicionando a cada classe uma componente auxiliar pública com

um nome único, não usado para nomear qualquer componente de qualquer outra classe. Se ant-

es da inclusão destas componentes auxiliares, duas classes distintas de L4 podiam gerar tipos-

-objecto idênticos, depois daquela inclusão as duas classes passam a gerar tipos-objecto distin-

tos. Repare que as componentes auxiliares se transferem das classes para os tipos-objecto por

elas gerados, onde actuam como elementos discriminantes.

64 OM – Uma linguagem de programação multiparadigma

Em C++, não são só as noções de tipo e classe que se misturam: as noções de subtipo e sub-

classe também se misturam. Nesta linguagem, não só “herança implica subtipificação” mas

também “subtipificação implica herança”. De facto, em C++, se A é subtipo de B então é por-

que estes tipos foram gerados a partir de classes que estão na relação de subclasse.

A nossa simulação, baseada em nomes únicos, verifica automaticamente esta última pro-

priedade se, num programa, nos restrinjamos ao domínio dos tipos-objecto gerados pelas clas-

ses existentes. Note como as componentes artificiais são herdadas e se vão acumulando nas

subclasses à medida que se desce numa hierarquia de classes.

4.4 ConclusõesO sistema de tipos de L4 é bastante simples. Apesar disso, consegue capturar todos os aspectos

essenciais dos sistemas de tipos das linguagens C++ e Java, entre outras. Chega mesmo a ser

um pouco mais flexível na medida em que admite a mudança do tipo dos métodos redefinidos

nas subclasses, ao contrário do C++ e Java. Mas não esqueçamos que a simplicidade de L4

está associada a algumas fragilidades, partilhadas com as linguagens C++ e Java. Algumas

dessas fragilidades só encontrarão solução na próxima linguagem, L5.

No tratamento semântico dos aspectos dinâmicos de L4, baseámo-nos nos modelos denota-

cionais de herança de Kamin e Cook em [Red88, Kam88, CP89, KR93], os quais foram, por

sua vez, desenvolvidos a partir do modelo-dos-registos (record model) de Cardelli: historica-

mente, os modelos de Kamin e Cook foram os primeiros modelos definidos para linguagens do

estilo do Smalltalk. De qualquer forma, estes são modelos não-tipificados e nós apresentámos

um modelo tipificado para L4. Na tipificação do modelo seguimos a via, mais simples, de usar

apenas as construções de primeira ordem de F+. Obtivemos assim um sistema próximo do

C++, Java, etc., um sistema simples, mas que exibe deficiências que provocariam importantes

restrições de expressividade, não fora a existência de operações dinâmicas de teste e de des-

promoção de tipo.

Capítulo 5

Tipo SAMET, relações decompatibilidade e de extensão

Sintaxe dos géneros,tipos e termos de L5

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | o.l |

checkType[τ] | downcastσ[τ]

Semântica dos tipos

INTERFACE(ϒc) :∗⇒∗ ˆ = interface

ΛSAMET.ϒcOBJTYPE(ϒc) :∗ ˆ = tipo-objecto

µSAMET.INTERFACE(ϒc)[SAMET] (= ϒc[OBJTYPE(ϒc)/SAMET])CLASSTYPE(ϒc) :∗ ˆ = tipo-classe

∀SAMET≤*INTERFACE(ϒc).SAMET→INTERFACE(ϒc)[SAMET]

Semântica das relaçõesRelação binária de compatibilidade

X≤*INTERFACE(ϒc) ˆ = X≤INTERFACE(ϒc)[X]

Relação binária de extensão entre interfaces

INTERFACE(ϒc) ext INTERFACE(ϒs) ˆ = T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs)

Semântica dos termos

class Rc :CLASSTYPE(ϒc) ˆ = λSAMET≤*INTERFACE(ϒc).

λself:SAMET.Rc

class\s Rc :CLASSTYPE(ϒs⊕ϒc) ˆ = let S:CLASSTYPE(ϒs) = s in

λSAMET≤*INTERFACE(ϒs⊕ϒc).λself:SAMET.

let super:INTERFACE(ϒs)[SAMET] = (S[SAMET] self) insuper+Rc

*Restrição implícita: ϒs,ϒc devem ser tais que:INTERFACE(ϒs⊕ϒc) ext INTERFACE(ϒs)

66 OM – Uma linguagem de programação multiparadigma

new c :OBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let gen:OBJTYPE(ϒc)→OBJTYPE(ϒc) = C[OBJTYPE(ϒc)] inlet o:OBJTYPE(ϒc) = fix gen in

o

o.l :τ ˆ = let R:OBJTYPE(ϒc) = o in

R.l

Na secção 5.1, começamos por introduzir informalmente os conceitos e mecanismos caracte-

rísticos da linguagem L5. Seguidamente, na secção 5.2 formalizamos a semântica de L5 e

introduzimos as importantes relação de compatibilidade e relação de extensão. Na secção 5.3,

enunciamos e demonstramos diversos teoremas relativos a estas relações. Na secção 5.4, intro-

duzimos o operador “+”, um operador aplicável a classes que ajuda a conciliar o mecanismo

de herança de L5 com a relação de subtipo. Na secção 5.5, revisitamos a relação de extensão e

analisamos as técnicas de programação genérica aplicáveis em L5. Finalmente, na secção 5.6

tiramos algumas conclusões e relacionamos o nosso material com trabalhos de outros autores.

5.1 Conceitos e mecanismos de L5Relativamente a L4, a linguagem L5 tem apenas uma modesta novidade sintáctica: o nome de

tipo SAMET passa a estar disponível dentro de todas as classes, onde representa o tipo de self.

Mas esta alteração não tem nada de insignificante a nível semântico, já que nos obriga a rever

os conceitos introduzidos em L4 e mesmo a mudar a forma prática de usar a linguagem.

Entre as linguagens com suporte para uma construção análoga a SAMET, contam-se as lin-

guagens Trellis/Owl [SCB+86], Emerald [BHJL86], Eiffel [Mey88, Mey92], Sather [Omo92,

SO91], PolyTOIL [BSG95, BFSG98], LOOP [ESTZ94] e LOOM [BPF97]. Entre os estudos

teóricos que incluem construções semelhantes destacam-se [CHC90, Bru94, ESTZ94, BSG95,

BFSG98].

Tal como L4, a linguagem L5 é puramente funcional e os seus objectos só possuem parte

pública. Na linguagem L7, serão introduzidas variáveis de instância mutáveis e componentes

privadas nos objectos, mas isso não afectará as conclusões e teoremas no presente capítulo

relativamente à parte pública dos objectos de L7.

5.1.1 O tipo SAMETNa linguagem L4, o tratamento do tipo de self é um incipiente, pois apesar de self estar sujeito a

reinterpretação nos métodos herdados, esses métodos são verificados sujeitos à condição de

que self tem um tipo fixo: exactamente o tipo-objecto correspondente à classe onde os métodos

são introduzidos.

5 Tipo SAMET, relações de compatibilidade e de extensão 67

A introdução do tipo SAMET nas classes representa uma nova atitude relativamente à tipifi-

cação de self em L5. Convencionalmente, o nome SAMET representa o tipo de self dentro de

cada classe individual.

Em L5, a reinterpretação de self nos métodos herdados continua a ser efectuada, mas agora

sendo acompanhada em paralelo pela reinterpretação de SAMET. A reinterpretação de SAMET

nas subclasses impõe novas regras de validação das classes. A partir de agora, na validação

das componentes duma classe c, será preciso considerar simultaneamente todos os significados

que SAMET possa assumir nas subclasses de c.

O tipo SAMET será, pois, tratado como um tipo aberto, i.e. parcialmente indeterminado, re-

presentando a família (infinita) dos tipos-objecto gerados por todas as subclasses potenciais de

c. Esta é uma opção de tipificação conservadora, mas segura, que reflecte a nossa continuada

adesão ao princípio da “reutilização sem reverificação”.

Note que o nome SAMET, já usado nos tipos-objecto de L4 com significado fixo (represen-

tava o próprio tipo-objecto), passa agora, também, a ser usado nas classes de L5 com significa-

do aberto (a família dos tipos gerados por todas as extensões possíveis duma classe). Este uso

dual de SAMET é consistente, e também de grande conveniência do ponto de vista prático. É

consistente pois, no momento em que uma classe é usada para gerar um tipo-objecto, o carác-

ter extensível da classe torna-se irrelevante, devendo então SAMET ser interpretado como o

tipo-objecto gerado por essa mesma classe: nesse momento preciso, os significados de SAMET

dentro da classe e dentro do respectivo tipo-objecto tornam-se equivalentes. É também conve-

niente pois, desta forma, todas as ocorrências de SAMET na interface duma classe transferem-

-se directamente para o tipo-objecto recursivo gerado a partir dessa classe: com efeito, obser-

vando uma classe conseguimos imaginar imediatamente qual o tipo-objecto gerado por essa

classe.

A introdução de SAMET em L5 permite uma tipificação mais precisa de self, o que flexibili-

za o mecanismo de herança, aumentando as oportunidades de reutilização do código e mino-

rando os problemas associados a essa reutilização: em particular os problemas da inflexibilida-

de na herança do tipo de self (cf. secção 4.3.3) e da definição de métodos binários (ver 4.3.4)

resolvem-se em L5 através dum uso criterioso de SAMET.

Para ilustrar a utilização de SAMET em L5, reformulamos agora o exemplo da secção 4.3:

Point2T ˆ = OBJTYPE(x:Nat,y:Nat, sum:Nat,id:SAMET,inc:SAMET,eq:SAMET→Bool)

Point2I ˆ = INTERFACE(x:Nat,y:Nat, sum:Nat,id:SAMET,inc:Point2T,eq:SAMET→Bool)

point2PC ˆ = rec p2. λa:Nat.λb:Nat. class

x=a, y=b,

sum=self.x+self.y,

id=self,

inc=new (p2 (succ self.x) (succ self.y)),

eq=λa:SAMET.(self.x=a.x & self.y=a.y)

68 OM – Uma linguagem de programação multiparadigma

Point3T ˆ = OBJTYPE(x:Nat,y:Nat,z:Nat,sum:Nat,id:SAMET,inc:SAMET,eq:SAMET→Bool)

Point3I ˆ = INTERFACE(x:Nat,y:Nat,z:Nat, sum:Nat,id:SAMET,inc:Point3T,eq:SAMET→Bool)

point3PC ˆ = rec p3. λa:Nat.λb:Nat.λc:Nat. class\(point2PC a b)

z=c,

sum=super.sum + self.z,

inc=new (p3 (succ self.x) (succ self.y) (succ self.z)),

eq=λa:SAMET.(super.eq a & self.z=a.z)

As alterações desta nova versão localizam-se no tipo atribuído, nas interfaces, aos métodos

id e eq e ainda ao método binário eq. No caso dos métodos id e eq, anteriormente o seu tipo era

fixo, agora passou a ser SAMET. No caso do método binário eq, anteriormente não era possível

tipificar convenientemente este método, agora tal já é possível usando SAMET como o tipo do

argumento.

Note ainda que, na interface Point2I, não é possível atribuir o tipo SAMET ao método inc,

pois o sistema de tipos tem de se precaver quanto à possibilidade de inc ser herdado: se essa

herança ocorrer, é notório que o método inc, incluído na classe point2PC, continuará a produzir

objectos do tipo Point2T na subclasse, e não do tipo-objecto correspondente à subclasse.

5.1.2 Relação de extensão entre interfacesNa linguagem L4, todas as subclasses duma classe geradora dum tipo-objecto τ estão submeti-

das à restrição de terem de gerar subtipos de τ. Este característica de L4 resulta da conjugação

de dois factores: da adesão da linguagem ao princípio da “reutilização sem reverificação”, e do

facto de só estarem disponíveis tipos fixos para serem usados na atribuição de tipos às compo-

nentes.

A linguagem L5 também adere ao princípio da “reutilização sem reverificação”. Contudo,

em L5 as componentes são verificados levando agora em conta a existência do tipo aberto

SAMET. Assim a relação binária de subclasse de L5 resulta mais rica do que a de L4, existindo

em L5 muitos casos de subclasses que não geram subtipos. Para dar um exemplo de subclasse

que não gera um subtipo consideremos, no contexto do exemplo anterior, as classes

(point3PC 0 0 0) e (point2PC 0 0): a primeira é subclasse da segunda mas o tipo-objecto gerado pe-

la primeira – Point3T – não é subtipo do tipo-objecto gerado pela segunda – Point2T. Neste caso,

uma razão para a relação de subtipo falhar é o facto do método binário eq ter um argumento do

tipo SAMET: se Point3T fosse subtipo de Point2T então o tipo do argumento de eq evoluiria do

supertipo para o subtipo de forma covariante, o que entraria em conflito com a regra [Sub →] de

F+. (Note que SAMET representa Point2T dentro de Point2T, e que SAMET representa Point3T

dentro de Point3T).

Assim, em L5, “herança não implica subtipificação”. No entanto, como veremos na secção

5.2.2.2, o objectivo da “reutilização sem reverificação” vai também impor uma restrição sobre

todas as subclasses duma classe. Em L4, essa restrição expressa-se através duma relação biná-

ria, reflexiva e transitiva, definida sobre os tipos-objecto gerados pelas classes: a relação de

5 Tipo SAMET, relações de compatibilidade e de extensão 69

subtipo “≤”. No caso de L5, essa restrição vai expressar-se através duma relação binária, refle-

xiva e transitiva, definida agora sobre as interfaces das classes. Chamaremos relação de exten-

são a esta relação binária e usaremos o operador binário infixo “ext” para a representar. Assim,

em L5, é valida a seguinte regra: se duas classes estão na relação de subclasse então as respec-

tivas interfaces estão na relação de extensão. Abreviadamente: em L5 “herança implica exten-

sibilidade”.

Mas, apesar de herança não implicar subtipificação em L5, muitas subclasses em L5 conti-

nuarão a gerar subtipos. Isto poderá ser verificado, caso a caso, usando as regras de subtipos

de F+. Na prática, a ocorrência de métodos binários nas classes é a razão que ocorre mais fre-

quente para a não geração de subtipos (cf. teoremas 5.3-11 e 5.3-12).

5.1.3 Tipificação aberta e tipificação fixaEm L5, o tipo de self é SAMET. Esta tipificação está predefinida na linguagem, não estando ao

alcance do programador alterá-la. No entanto, relativamente a uma entidade qualquer, por

exemplo, um argumento dum método, que dentro duma classe deva ser declarada com o tipo

dos objectos gerados por essa mesma classe, o programador tem por vezes duas alternativas

razoáveis de tipificação: ou usa o tipo aberto SAMET, ou usa o tipo fixo associado aos objectos

gerados pela classe. No primeiro caso dizemos que o programador optou por uma tipificação

aberta da entidade; no segundo caso que optou por uma tipificação fixa da entidade.

Quando o programador usa sistematicamente tipificação aberta numa classe, ele manifesta-

mente está a pensar um pouco para além da classe concreta que está a escrever, pois sabemos

que SAMET, na sua semi-indeterminação, representa não só o tipo-objecto associado à classe

corrente, mas também o tipo-objecto associado a qualquer uma das suas potenciais subclasses.

Esta escolha favorece o uso de herança e permite formas de reutilização de código que em L4

não eram possíveis. Mas surge agora um problema: dependendo das localizações exactas de

SAMET na interface da classe, as suas subclasses podem deixar de gerar subtipos úteis (de

acordo com a definição de subtipo inútil do teorema 5.3-12). Este aspecto é restritivo da liber-

dade do programador pois compromete a capacidade de programar genericamente: note que,

tal como em L4, a única forma de polimorfismo existente em L5 é o polimorfismo de inclusão,

uma forma de polimorfismo que depende da relação de subtipo. (Mais sobre este assunto na

secção 5.5.2).

Tudo se passa de forma inversa se o programador usar sistematicamente tipificação fixa

numa classe. Neste caso, o programador opta pela tipificação menos precisa e mais problemá-

tica de L4, aceitando ter de lidar com os problemas que ela acarreta, incluindo certas limita-

ções de expressividade. Mas tem a vantagem de dispor duma classe cujas subclasses geram

subtipos, o que favorece a capacidade de escrever funções polimórficas.

Resumindo, a tipificação aberta favorece a generalidade e a eficácia do mecanismo de he-

rança mas, em muitos casos, não propicia a geração de subtipos o que prejudica, ou impossibi-

70 OM – Uma linguagem de programação multiparadigma

lita mesmo, a capacidade de programação genérica. Por outro lado, a tipificação fixa prejudica

a herança mas favorece a criação de subtipos. Esta contradição mostra que, se queremos inte-

grar estas duas formas de tipificação na linguagem L5, sem colocar o programador face a dile-

mas desmesurados, temos algum trabalho a efectuar.

Na discussão precedente considerámos apenas as duas situações extremas, em que o pro-

gramador usa em cada classe apenas tipificação aberta, ou apenas tipificação fixa. No entanto

é possível misturar as duas formas de tipificação na mesma classe. É o que se passa no exem-

plo da secção 5.1.1: as classes Point2PC e Point3PC enfatizam o uso de tipificação aberta, mas

usam tipificação fixa no caso particular do método inc.

A introdução de SAMET em L5, ou seja a introdução de tipificação aberta, é um importante

avanço, mas prejudica, a capacidade de programar genericamente, como vimos. Um impor-

tante passo que permitiria proporcionar ao programador meios para minorar este problema,

consistiria em dotar a linguagem dum sistema de tipos que, sem prejudicar o princípio da

“reutilização sem reverificação”, permitisse rever nas subclasses algumas decisões de tipi-

ficação tomadas ao nível das superclasses. Especialmente importante seria a possibilidade de

definir, a partir duma classe a cujas subclasses não gerassem subtipos úteis, uma sua subclasse

imediata b, geradora do mesmo tipo-objecto que a, mas já garantindo a geração de subtipos

úteis por todas as suas subclasses. Isso seria feito fixando na subclasse b certas instâncias de

uso de tipificação aberta.

Apresentamos seguidamente um exemplo que concretiza este ideal para uma classe a cujas

subclasses não geram subtipos úteis, e uma sua subclasse b cujas subclasses já geram subtipos.

AT ˆ = OBJTYPE(x:Nat,y:Nat,sum:Nat,eq:SAMET→Bool)

a ˆ = class x=a, y=b,

sum=self.x+self.y,

eq=λa:SAMET.(self.x=a.x & self.y=a.y)

b ˆ = class\a eq=λa:AT.(super.eq a)

Note que o método eq foi redefinido na subclasse b, sendo agora o seu argumento alvo de tipi-

ficação fixa.

Um dos objectivos do sistema de tipos de L5 será a possibilidade de definir subclasses se-

melhantes à classe b deste exemplo. A relação de extensão será definida com suficiente gene-

ralidade para que a classe b seja considerada uma subclasse admissível de a.

5.2 Semântica de L5Nesta secção, formalizamos e discutimos da semântica da linguagem L5, nomeadamente a

codificação dos tipos e dos termos da linguagem em F+. O essencial das formalizações aqui

descritas integra a tabela de equações que abre o presente capítulo.

5 Tipo SAMET, relações de compatibilidade e de extensão 71

5.2.1 Semântica dos tiposEm L5, a forma geral duma classe sem superclasses é class Rc; a forma geral duma subclasse

imediata duma classe s é class\s Rc. Em ambos os casos usamos Rc para representar o registo das

componentes da classe. No que se segue assumiremos que o tipo de Rc é ϒc.

Discutimos agora a codificação em F+ das interfaces de classe, dos tipos-objecto, e dos ti-

pos-classe.

INTERFACE(ϒc): Representa a interface duma classe com componentes ϒc. Sintacticamente,

o que há de novo nas classes de L5 relativamente a L4 é a introdução do nome SAMET no con-

texto das classes. Respeitando a característica essencial das interfaces, não fazemos qualquer

interpretação prematura de SAMET e formalizamos a interface INTERFACE(ϒc) usando o opera-

dor de tipo ΛSAMET.ϒc. Nesta formalização, a única assunção que efectivamente se faz relati-

vamente a SAMET é que denota um tipo.

OBJTYPE(ϒc): Representa o tipo-objecto gerado por uma classe com interface

INTERFACE(ϒc). É definido como µSAMET.INTERFACE(ϒc)[SAMET]. Note que o nome SAMET,

que no contexto duma interface estava por interpretar, no contexto dum tipo-objecto é interpre-

tado como sendo uma referência recursiva ao próprio tipo-objecto OBJTYPE(ϒc).

CLASSTYPE(ϒc): Representa o tipo-classe das classes com interface INTERFACE(ϒc). A parte

mais delicada da formalização dum tipo-classe reside na forma de tratar o nome SAMET dentro

duma classe. Em CLASSTYPE(ϒc), o nome SAMET é introduzido como um tipo-objecto genérico

sujeito à restrição SAMET≤INTERFACE(ϒc)[SAMET]. Como veremos adiante, sob esta condição

SAMET representa, genericamente, todos os tipos-objecto gerados pelas subclasses duma classe

c com tipo CLASSTYPE(ϒc). O tipo-classe CLASSTYPE(ϒc) é formalizado usando o tipo:

∀SAMET≤INTERFACE(ϒc)[SAMET].

SAMET→INTERFACE(ϒc)[SAMET].

O resto desta secção é dedicado a discutir os detalhes da formalização de CLASSTYPE(ϒc).

Vamos começar por motivar a condição que caracteriza todas as interpretações possíveis de

SAMET nas subclasses. Como primeiro passo, consideramos apenas subclasses x, duma classe

c, que não redefinam os métodos herdados de c nem introduzam novos métodos relativamente

a c. Neste caso, o tipo SAMET dos objectos gerados por x contém exactamente as componentes

que ocorrem na interface de c, pelo que simbolicamente podemos escrever: SAMET≡

INTERFACE(ϒc)[???]. Falta apenas ver qual é a instanciação correcta da interface: faremos

???≡SAMET pois, como foi convencionado, a interpretação do parâmetro da interface dentro da

classe x é o tipo dos objectos gerados por x, ou seja o próprio tipo que estamos correntemente a

tentar definir. Portanto, em qualquer subclasse x de c, que não altere nada relativamente a c,

verifica-se a equivalência SAMET≡INTERFACE(ϒc)[SAMET].

Temos agora de levar em conta os casos restantes, aqueles em que a subclasse x redefine

métodos herdados de c ou introduz novos métodos. Para isso enfraquecemos um pouco a equi-

72 OM – Uma linguagem de programação multiparadigma

valência anterior. A escolha da condição SAMET≤INTERFACE(ϒc)[SAMET] é razoável pois é

compatível com a adição dum número irrestrito de novos métodos e admite alguma liberdade

na mudança do tipo dos métodos herdados quando são redefinidos. Todos os tipos SAMET que

verificam esta condição têm a estrutura recursiva básica de µSAMET.INTERFACE(ϒc)[SAMET] e,

ainda, possivelmente, métodos adicionados e métodos redefinidos.

Encontrámos pois uma caracterização do significado de SAMET nas classes. Vamos agora

motivar o tipo-classe: CLASSTYPE(ϒc). Este tipo tem de ser parametrizado em função de todos

os significados admissíveis para SAMET nas subclasses. Tem, além disso, de depender do tipo

de self porque todas as classes serão parametrizadas em função do significado de self, pelas

mesmas razões de L4. Assim o esquema geral da codificação de CLASSTYPE(ϒc) será:

∀SAMET≤INTERFACE(ϒc)[SAMET].

SAMET→(tipo dos resultados)

Por causa da herança, é prematuro usar o tipo OBJTYPE(ϒc) como tipo dos resultados, não

obstante ser exactamente este o tipo dos objectos gerados por uma classe do tipo

CLASSTYPE(ϒc). O tipo dos resultados terá de depender do tipo aberto SAMET para que a rein-

terpretação de SAMET possa ter lugar no tipo dos métodos herdados, nos objectos gerados

pelas subclasses; i.e. temos de nos preocupar não só com o tipo dos objectos gerados pela

classe corrente, mas também com o tipo dos objectos gerados pelas suas subclasses. Assim o

tipo escolhido para os resultados é INTERFACE(ϒc)[SAMET], com SAMET quantificado da forma

anteriormente apresentada. Pensando no caso particular dos objectos gerados pela classe cor-

rente, i.e. tomando SAMET≡OBJTYPE(ϒc), obtemos

INTERFACE(ϒc)[SAMET] = INTERFACE(ϒc)[OBJTYPE(ϒc)] = OBJTYPE(ϒc)

o que ajuda a confirmar a justeza da escolha efectuada.

Juntando os vários elementos, concluímos que o tipo-classe CLASSTYPE(ϒc) deve ser codifi-

cado usando o tipo universal F-restringido:

∀SAMET≤INTERFACE(ϒc)[SAMET].

SAMET→INTERFACE(ϒc)[SAMET]

Definição 5.2.1-1 (Relação de compatibilidade) Chamamos relação de compatibili-

dade, ≤*, à relação binária entre um tipo-objecto X e uma interface INTERFACE(ϒc) que se defi-

ne, por tradução para F+, da seguinte forma:

X≤*INTERFACE(ϒc) ˆ = X≤INTERFACE(ϒc)[X]

Nota: A asserção X≤*INTERFACE(ϒc) lê-se assim: “O tipo-objecto X é compatível com a inter-

face INTERFACE(ϒc)”.

Usando esta relação de compatibilidade, a formalização de CLASSTYPE(ϒc) pode ser rees-

crita da seguinte forma mais compacta:

5 Tipo SAMET, relações de compatibilidade e de extensão 73

∀SAMET≤*INTERFACE(ϒc).

SAMET→INTERFACE(ϒc)[SAMET]

Podemos agora dizer que as reinterpretações admissíveis de SAMET nas subclasses duma clas-

se c são os tipos compatíveis com a interface de c.

5.2.2 Semântica dos termosNesta secção, analisamos a semântica das classes e subclasses de L5, assim como a semântica

das operações de criação de objectos e de acesso a componentes desses objectos. As respec-

tivas equações semânticas encontram-se na tabela que abre o presente capítulo.

5.2.2.1 Semântica das classes

Na equação que define a classe class Rc, o gerador polimórfico que aí é introduzido está para-

metrizado relativamente a SAMET e a self. Além disso, ele gera objectos constituídos por todas

as componentes declaradas na classe. Note que a definição faz implicitamente a validação do

código da classe: se o registo Rc estiver mal tipificado então a classe class Rc também está mal

tipificada.

Formalizamos o tratamento da herança na equação do termo subclasse imediata class\s Rc.

Para chegarmos a essa formalização, o primeiro passo consiste na determinação da interface da

nova subclasse. Essa interface é INTERFACE(ϒs⊕ϒc): resulta portanto da combinação, usando o

operador ⊕, dos tipo-registo das componentes herdadas, ϒs, com o tipo-registo das componen-

tes definidas na subclasse ϒc. O tipo-objecto gerado pela subclasse é OBJTYPE(ϒs⊕ϒc). Fazemos

as reinterpretações de SAMET e self na subclasse da seguinte forma. Primeiro introduzimos uma

nova variável de tipo SAMET compatível com a interface da subclasse e um novo self do tipo

SAMET. Seguidamente, aplicamos o gerador S correspondente à superclasse a estes novos

SAMET e self (e chamamos super ao resultado): desta forma ajustamos as componentes da super-

classe ao contexto da subclasse. Finalmente, enriquecemos o conjunto das componentes herda-

das usando o operador +. Assim criámos um novo gerador por extensão dum gerador existente.

Em L5, tal como em L4, o nome super encontra-se definido no contexto de toda a subclasse.

A definição original de super em L4 é generalizada, ficando agora super ligado a (S[SAMET] self).

Note que, nos geradores de L5, enquanto self constitui um nome não ligado (por ser um parâ-

metro), super já um nome ligado (ligado a (S[SAMET] self)).

5.2.2.2 Boa formação das subclasses

Sem abandonar ainda o tratamento da herança, consideremos agora a questão da boa tipifica-

ção do termo de F+ que codifica a subclasse class\s Rc. Para que este termo esteja bem tipificado

é necessário, em primeiro lugar, que o registo super+Rc esteja bem tipificado. Uma outra ques-

tão, mais subtil, prende-se com a boa tipificação do termo que adapta as componentes da

superclasse ao contexto da subclasse:

74 OM – Uma linguagem de programação multiparadigma

super = S[SAMET] self

Neste termo, o gerador polimórfico S, correspondente à superclasse, espera um tipo-objecto

SAMET que obedeça à condição SAMET≤*INTERFACE(ϒs) mas é aplicado, em (S[SAMET] self), a

um tipo-objecto SAMET relativamente ao qual apenas se sabe que SAMET≤*INTERFACE(ϒs⊕ϒc).

Para que o termo esteja bem tipificado é pois necessário que os tipos-registo ϒs, ϒc sejam tais,

que a seguinte condição se verifique:

SAMET≤*INTERFACE(ϒs⊕ϒc) ⇒ SAMET≤*INTERFACE(ϒs)

Esta é a condição mais geral que se consegue encontrar e que garante a boa tipificação do ter-

mo (S[SAMET] self). Ela estabelece que qualquer tipo-objecto compatível com a interface da

subclasse será também compatível com a interface da superclasse.

Neste ponto, é tentador investigar a possibilidade de se usar uma relação mais simples mas

que, apesar de tudo, seja suficientemente forte para garantir a boa tipificação do termo

(S[SAMET] self). Uma relação mais simples seria menos complicada de verificar automatica-

mente pelo compilador, e, acima de tudo, menos confusa de usar na prática pelo programador.

A hipótese mais óbvia, neste sentido, é a seguinte condição, baseada na relação de subtipo

entre operadores de tipo:

INTERFACE(ϒs⊕ϒc)≤INTERFACE(ϒs)

Contudo esta condição tem o grave inconveniente de bloquear nas subclasses todas as decisões

de tipificação, aberta ou fixa, tomadas numa classe relativamente a SAMET (cf. 5.1.3); este blo-

queio é inconveniente porque empobrece a relação de subtipo em muitos programas, e esta

relação faz-nos falta (cf. secção 5.5.2 e [CHC90]).

Assim, por agora, manteremos a condição geral que deduzimos inicialmente. Voltaremos à

questão da sua simplificação na secção 5.5.1.

A condição geral que garante a boa tipificação do termo (S[SAMET] self) induz uma relação

binária no conjunto das interfaces das classes. Chamaremos essa relação de relação de exten-

são, pois ela estabelece quais são as extensões de interface válidas que dão origem a subclas-

ses bem formadas. É uma relação reflexiva e transitiva (cf. teorema 5.3-7), o que é essencial

para que a relação de subclasse continue a ser reflexiva e transitiva.

Definição 5.2.2.2-1 (Relação de extensão) Chamamos relação de extensão, ext, à rela-

ção binária entre interfaces que se define, por tradução para F+, da seguinte forma:

INTERFACE(ϒc) ext INTERFACE(ϒs) ˆ = T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs)

Sinteticamente, podemos dizer que “herança implica extensibilidade” em L5 pois sem a

verificação da restrição de extensibilidade entre interfaces não seria possível ter herança no

modelo. A implicação contrária não se verifica, em geral, pois duas interfaces podem estar na

5 Tipo SAMET, relações de compatibilidade e de extensão 75

relação de extensão sem que as classes donde elas foram extraídos estejam na relação de sub-

classe: o facto das classes terem interfaces relacionadas poderá ser simples coincidência.

Note que, tal como em L4, conseguimos escrever a equação relativa ao termo class\s Rc sem

explicitar as componentes da superclasse; foi apenas necessário explicitar o tipo das compo-

nentes da superclasse. Por isso o mecanismo de herança de L5 é também compatível com a

compilação separada de classes, isto é, com o principio da “reutilização sem reverificação“.

5.2.2.3 Semântica dos outros termos

Há ainda duas construções de L5 por explicar.

Na equação semântica referente à criação de objectos – termo new c – começamos por

instanciar o gerador polimórfico correspondente à classe c com o tipo dos objectos a criar:

vemos assim que só no momento da criação dum objecto o nome SAMET é realmente ligado.

Desta instanciação obtemos um gerador monomórfico C do tipo OBJTYPE(ϒc)→OBJTYPE(ϒc).

Este gerador é semelhante aos geradores de L4, e a ele aplicamos o operador de ponto fixo

para estabelecermos a ligação do nome self no contexto do novo objecto.

Quando à definição do termo que descreve o acesso às componentes dos objectos – termo

o.l – nada muda relativamente a L4. Codificamos este termo por meio dum simples acesso a

uma componente dum registo.

5.2.3 Relação de subtipo vs. relação de compatibilidadeConsideremos uma classe a com interface AI e geradora do tipo-objecto AT. Consideremos

também uma subclasse qualquer b de a, com interface BI e geradora do tipo-objecto BT: b pode

ser subclasse imediata ou subclasse não-imediata de a.

Sabemos que na linguagem L4 “herança implica subtipificação”. Por isso, sendo b sub-

classe de a, em L4 podemos garantir que BT≤AT. Já na linguagem L5 não se pode garantir esta

propriedade, não obstante ela poder ser verdadeira muitas vezes (ver, por exemplo, o teorema

5.3-11).

Saber que BT≤AT é útil de duas formas, tanto em L4 como em L5. Em primeiro lugar, ga-

nhamos a liberdade de usar expressões do tipo BT onde se esperam expressões do tipo AT: esta

propriedade designa-se por substitutividade. Em segundo lugar ficamos a saber que os objec-

tos de BT sabem responder a todas as mensagens a que os objectos de AT sabem responder (cf.

regra [Sub …]). Esta segunda propriedade é uma consequência da primeira, mas interessa-nos

considerá-la separadamente aqui.

Agora, apenas no contexto da linguagem L5, e continuando a considerar as classes a e b, as-

sumamos que BT/≤AT. Neste caso não podemos relacionar os tipos-objecto BT e AT directamen-

te. O máximo que podemos dizer sobre BT é que este tipo é compatível com a interface AI da

classe a, ou seja BT≤*AI, ou ainda BT≤AI[BT]. Mas saber que BT≤AI[BT] não é tão útil como sa-

76 OM – Uma linguagem de programação multiparadigma

ber que BT≤AT. Efectivamente, com base apenas na condição BT≤AI[BT], não se pode garantir

que AT é substituível por BT. Apesar de tudo, com base na condição BT≤AI[BT] já se pode ga-

rantir que o tipo BT suporta todas as mensagens registadas no tipo AI[BT].

Dentro duma classe a com interface AI e geradora do tipo-objecto AT, a variável de tipo

SAMET é introduzida subordinada à restrição SAMET≤AI[SAMET]. Da discussão anterior resulta

que, no contexto da classe a, tudo o que se pode garantir relativamente aos objectos do tipo

SAMET, é que eles sabem responder às mensagens previstas em AI[SAMET].

5.3 Propriedades das relações decompatibilidade e extensãoAo longo desta extensa secção, apresentamos uma sequência de lemas e teoremas que nos aju-

dam a compreender melhor as duas relações binárias introduzidas em L5. Quase todos os

resultados que mostramos têm interesse prático. Mas são particularmente importantes os teore-

mas 5.3-11 e 5.3-13 que mostram como conciliar a utilização de SAMET nas classes com a

geração de subtipos pelas subclasses.

Começamos por sumariar os vários lemas e teoremas fazendo o seu enquadramento.

Os lemas que apresentamos no início ensinam-nos a tirar partido do uso de nomes simbóli-

cos para simplificar o tratamento da recursão. De entre estes, os lemas 4 e 5 são particular-

mente úteis pois ensinam-nos a visualizar rápida se um tipo-objecto é subtipo de outro tipo-

-objecto, ou se um tipo-objecto é compatível com uma interface.

O primeiro teorema da lista, 5.3-7, mostra que a relação ext é reflexiva e transitiva.

As relações de subtipo e compatibilidade não são directamente comparáveis por terem do-

mínios diferentes. No entanto, uma interface determina univocamente um tipo-objecto pelo

que faz sentido comparar as duas relações de forma indirecta. Neste sentido, o teorema 5.3-8

mostra que a relação de compatibilidade não é nem mais forte nem mais fraca do que a relação

de subtipo.

A relação de extensão é complexa, sendo por vezes difícil verificar se um par de interfaces

concretas pertencem à relação. Os teoremas 5.3-9 e 5.3-10 delimitam relação de extensão entre

duas relações mais simples e mais fáceis de verificar. Concretamente, o teorema 5.3-9 mostra

que, comparando indirectamente as relações, a relação de compatibilidade é mais fraca do que

a relação de extensão. Por seu turno, o teorema 5.3-10 mostra que, comparando directamente

as relações, a relação de extensão é mais fraca do que a relação de subtipo entre operadores de

tipo.

O teorema 5.3-11 estabelece que se na interface duma classe todas as ocorrências de SAMET

forem positivas, então todas as suas subclasses geram subtipos. É complementado pelo teore-

ma 5.3-12 que mostra que se a interface da classe contiver ocorrências negativas de SAMET

5 Tipo SAMET, relações de compatibilidade e de extensão 77

(basta uma) então as suas subclasses não geram subtipos, exceptuando alguns casos marginais,

de pouco interesse prático em que os subtipos gerados serão por nós designados por subtipos

inúteis.

Repare que afirmar que na interface duma classe todas as ocorrências de SAMET são positi-

vas, é equivalente a afirmar que essa classe não contém métodos binários. Por sua vez, dizer

que a interface da classe contém ocorrências negativas de SAMET, equivale a dizer que a classe

contém métodos binários.

O importante teorema 5.3-13 ensina-nos como, a partir duma classe a com ocorrências ne-

gativas de SAMET na sua interface, se consegue criar uma classe b quase idêntica, e gerando o

mesmo tipo-objecto, mas cujas subclasses já geram subtipos.

O teorema 5.3-14 mostra que o teorema 5.3-13 não se estende às ocorrências positivas de

SAMET. Aliás, basta aplicar a substituição descrita no teorema 5.3-13 a uma única ocorrência

positiva de SAMET para que a classe resultante não não seja subclasse da primeira classe. De

qualquer forma este problema é benigno pois, de acordo com o teorema 5.3-11, as ocorrências

positivas de SAMET não impedem a geração de subtipos pelas subclasses.

Os teoremas 5.3-15 e 5.3-16 são menos importantes do ponto de vista prático, mas ajudam-

-nos a completar o conhecimento da relação de extensão. O primeiro ensina-nos que partindo

duma classe a onde todas as ocorrências de SAMET são positivas, é possível criar uma nova

subclasse b de a, obtida a partir de a substituindo no tipo dos métodos herdados uma ou mais

ocorrências positivas do tipo gerado por a por SAMET; dizemos, neste caso, que se abre uma

ocorrência positiva do tipo (fixo) gerado por a. O segundo mostra que o teorema 5.3-15 não é

extensível às ocorrências positivas de SAMET. Aliás, basta aplicar a substituição descrita no

teorema 5.3-15 a uma única ocorrência negativa de SAMET para que a classe resultante deixe

de ser subclasse da primeira.

Lema 5.3-1 Se AT=OBJTYPE(ϒA) então:

AT=ϒA[AT/SAMET]

Prova:AT

= OBJTYPE(ϒA) por hipótese

= µSAMET.INTERFACE(ϒA)[SAMET] por definição de OBJTYPE

= µSAMET.(ΛSAMET.ϒA)[SAMET] por definição de INTERFACE

= µSAMET.ϒA[SAMET] pela regra [Tipo= β Λ] de F+

= ϒA[AT/SAMET] pela regra [Tipo= fold/unfold µ]

Lema 5.3-2 Se AI=INTERFACE(ϒA) e BT=OBJTYPE(ϒB) então:

AI[BT]=ϒA[BT/SAMET]

78 OM – Uma linguagem de programação multiparadigma

Prova:AI[BT]

= (ΛSAMET.ϒA)[BT] por definição de interface

= ϒA[BT/SAMET] pela regra [Tipo= β Λ]

Lema 5.3-3 O tipo-objecto gerado por uma interface é compatível com essa interface. For-

malmente, se AI=INTERFACE(ϒA) e AT=OBJTYPE(ϒA) então:

AT≤*AI

Prova:AT

= ϒA[AT/SAMET] pelo lema 5.3-1

= AI[AT] pelo lema 5.3-2

Usando a regra [Sub =] temos:

AT≤AI[AT]

donde por definição de ≤*:

AT≤*AI[AT]

Lema 5.3-4 Se AT=OBJTYPE(ϒA) e BT=OBJTYPE(ϒB) então:

BT≤AT ⇔ ϒB[BT/SAMET]≤ϒA[AT/SAMET]

Nota: Este lema fornece uma regra prática para verificar se o tipo-objecto BT é subtipo do

tipo-objecto AT: basta ver se o tipo-registo que integra BT é subtipo do tipo-registo que integra

AT, depois de se ter substituído SAMET por BT em BT e SAMET por AT em AT.

Prova: Imediato, aplicando o lema 5.3-1 a ambos os lados do operador ≤.

Lema 5.3-5 Se AI=INTERFACE(ϒA) e BT=OBJTYPE(ϒB) então

BT≤*AI ⇔ ϒB[BT/SAMET]≤ϒA[BT/SAMET]

Nota: Este lema fornece uma regra prática para verificar se o tipo-objecto BT é compatível

com a interface AI: basta ver se o tipo-registo que integra BT é subtipo do tipo-registo que inte-

gra AI, depois de se ter substituído SAMET por BT nos dois tipos-registo.

Prova: Pelo lema 5.3-1 temos:

BT=ϒB[BT/SAMET]

Pelo lema 5.3-2 temos:

AI[BT]=ϒA[BT/SAMET]

5 Tipo SAMET, relações de compatibilidade e de extensão 79

Pela definição da relação de compatibilidade temos:

BT≤*AI ⇔ BT≤AI[BT]

Por substituição, o resultado sai imediatamente

Lema 5.3-6 Sejam AI ˆ = INTERFACE(ϒA), BI ˆ = INTERFACE(ϒB). Então a relação ext cuja defini-

ção original é:

AI ext BI ˆ = (T≤AI[T] ⇒ T≤BI[T])

tem a seguinte definição alternativa:

AI ext BI ˆ = (T≤AI[T] ⇒ AI[T]≤BI[T])

Nota: Este resultado é o elemento chave da demonstração dos teoremas 7.2.2.2-3 e 9.2.2.3-1.

Prova: Vamos desenvolver a prova usando uma versão equivalente das regras de subtipo de F+

(cf. secção 2.3.4.2) na qual a regra [Sub var-trans2] é usada em lugar da regra original [Sub trans]

(cf. secção 2.3.4.5). Considerando as condições T≤AI[T]⇒T≤BI[T] e T≤AI[T]⇒AI[T]≤BI[T], o nos-

so objectivo é mostrar que elas são equivalentes.

Se AI=BI, então as duas condições degeneram em tautologias, e duas tautologias são sempre

equivalentes.

Assumindo agora AI≠BI, vamos estudar a estrutura das provas que permitem provar a impli-

cação T≤AI[T]⇒T≤BI[T]. Para isso escrevemos esta condição sob a forma dum juízo:

∅‚T≤AI[T] T≤BI[T]

Qualquer prova deste juízo, a existir, terá naturalmente a forma:

…∅‚T≤AI[T] T≤BI[T]

Examinando as regras que definem a relação de subtipo, verificamos que as regras [Sub κµ],

[Sub X], [Sub refl] e [Sub var-trans2] são as únicas que permitem deduzir uma conclusão da forma

(… T≤…), onde T é uma variável de tipo. Mas a primeira regra não é aplicável porque BI[T] não

é um tipo recursivo; a segunda também não porque T≤BI não ocorre no contexto ∅‚T≤AI[T]; a

terceira também não porque o nosso juízo não tem a forma … T≤T. Assim resta a regra

[Sub var-trans2], e logo a nossa prova terá necessariamente a forma:

∅‚T≤AI[T] AI[T]≤BI[T] ∅‚T≤AI[T] AI[T]:K∅‚T≤AI[T] T≤BI[T]

Mas sendo esta a única prova para o juízo ∅‚T≤AI[T] T≤BI[T], concluímos que a validade

deste juízo é equivalente à validade do juízo ∅‚T≤AI[T] AI[T]≤BI[T] (assumindo que os tipos

envolvidos estão bem formados). Era isto o que queríamos provar.

Teorema 5.3-7 A relação de extensão ext é reflexiva e transitiva.

80 OM – Uma linguagem de programação multiparadigma

Prova: Sejam AI ˆ = INTERFACE(ϒA), BI ˆ = INTERFACE(ϒB), CI ˆ = INTERFACE(ϒC).

A relação ext é reflexiva pois a asserção:

AI ext AI

é equivalente à tautologia:

T≤AI[T] ⇒ T≤AI[T]

Para verificarmos a transitividade vamos assumir:

AI ext BI

BI ext CI

ou seja:

T≤AI[T] ⇒ T≤BI[T]

T≤BI[T] ⇒ T≤CI[T]

Por transitividade de ⇒ obtemos

T≤AI[T] ⇒ T≤CI[T]

ou seja:

AI ext CI

Teorema 5.3-8 Sejam AI ˆ = INTERFACE(ϒA), AT ˆ = OBJTYPE(ϒA), e T um tipo-objecto qual-

quer. Então:

T≤*AI /⇒ T≤AT

T≤AT /⇒ T≤*AI

Prova: Basta apresentar um contra-exemplo adequado a cada uma das asserções.

Contra-exemplo para a primeira asserção:

AT ˆ = OBJTYPE(x:Nat, eq:SAMET→Bool)

AI ˆ = INTERFACE(x:Nat, eq:SAMET→Bool)

T ˆ = OBJTYPE(x:Nat, y:Nat, eq:SAMET→Bool)

Usando o lema 5.3-5 é imediato que T≤*AI. No entanto não é verdade que T≤AT pois, pelo

lema 5.3-4, a asserção T≤AT é equivalente a

x:Nat,y:Nat,eq:T→Bool≤x:Nat,eq:AT→Bool

a qual implica T→Bool≤AT→Bool e seguidamente AT≤T, o que não é possível pois AT tem me-

nos componentes que CT.

Contra-exemplo para a segunda asserção:

5 Tipo SAMET, relações de compatibilidade e de extensão 81

AT ˆ = OBJTYPE(x:Nat, f:Nat→SAMET)

AI ˆ = INTERFACE(x:Nat, f:Nat→SAMET)

T ˆ = OBJTYPE(x:Nat, y:Nat, f:Nat→AT)

É verdade que T≤AT, pois pelo lema 5.3-4 esta asserção é equivalente à condição verda-

deira:

x:Nat,y:Nat,f:Nat→AT≤x:Nat,f:Nat→AT

Mas não temos T≤*AT, pois pelo lema 5.3-5 esta asserção é equivalente à condição:

x:Nat,y:Nat,f:Nat→AT≤x:Nat,f:Nat→T

a qual implica Nat→AT≤Nat→T e seguidamente AT≤T (cf. regra [Sub →]), o que não é possível

pois AT tem menos componentes que CT (cf. regra [Sub …]).

Teorema 5.3-9 Se AI=INTERFACE(ϒA), BI=INTERFACE(ϒB) e BT=OBJTYPE(ϒB) então:

BI ext AI ⇒ BT≤*AI

BT≤*AI /⇒ BI ext AI

ou seja, se a interface BI estende a interface AI, então o tipo BT, gerado pela primeira interface,

é compatível com a segunda interface. A implicação recíproca não é verdadeira.

Nota: A primeira asserção deste teorema fornece uma regra prática para verificar de forma ex-

pedita se uma interface pode ser uma extensão de outra interface (ou seja, se uma classe pode

ser subclasse de outra classe). Recordamos que o lema 5.3-5 permite verificar BT≤*AI de forma

simples.

Prova: Para provar a primeira asserção, assumamos a hipótese:

BI ext AI

ou seja:

T≤*BI ⇒ T≤*AI

Pelo lema 5.3-3 sabemos que:

BT≤*BI

donde, usando a hipótese, se obtém imediatamente:

BT≤*AI

o que prova a primeira asserção.

Para provar a segunda asserção, consideremos o seguinte contra-exemplo:

82 OM – Uma linguagem de programação multiparadigma

AT ˆ = OBJTYPE(x:Nat, eq:SAMET→Bool)

AI ˆ = INTERFACE(x:Nat, eq:AT→Bool)

BT ˆ = OBJTYPE(x:Nat, eq:SAMET→Bool)

BI ˆ = INTERFACE(x:Nat, eq:SAMET→Bool)

CT ˆ = OBJTYPE(x:Nat, y:Nat, eq:SAMET→Bool)

Pelo lema 5.3-3, e porque AT=BT, temos

BT≤*AI

No entanto não é verdade que BI ext AI pois existe um tipo CT, tal que CT≤*BI (fácil de veri-

ficar usando o lema 5.3-5) mas não CT≤*AI. Note que esta última asserção é, pelo lema 5.3-5,

equivalente a

x:Nat,y:Nat,eq:CT→Bool≤x:Nat,eq:AT→Bool

a qual nunca poderia ser verdadeira, pois para isso teríamos de ter CT→Bool≤AT→Bool e logo

AT≤CT. Mas isso não é possível pois AT tem menos componentes que CT.

Teorema 5.3-10 A relação de subtipo entre interfaces (cf. regra [Sub Λ]) é mais forte do que

a relação de extensão. Formalmente, tomando AI ˆ = INTERFACE(ϒA) e BI ˆ = INTERFACE(ϒB) te-

mos:

BI≤AI ⇒ BI ext AI

Nota: Este teorema fornece uma regra prática que, em muitos casos, permite verificar de forma

expedita que uma interface é uma extensão de outra interface. Note que de acordo com a regra

[Sub Λ], para determinarmos se o operador de tipo ϕ ˆ = ΛSAMET.τ é subtipo do operador de tipo

ϕ′ ˆ = ΛSAMET.τ′, basta verificar τ≤τ′ assumindo que SAMET é apenas subtipo de si próprio (ou

ainda, do tipo-objecto vazio OBJTYPE()). Esta relação é simples, mas se fosse usada para fun-

dar o mecanismo de herança, as decisões de tipificação, aberta ou fechada, tomadas ao nível

duma classe, nunca poderiam ser revistas ao nível das subclasses.

Prova: Com o objectivo de concluirmos no final que T≤*AI, consideremos um tipo-objecto T

tal que T≤*BI, ou seja T≤BI[T]. Tomando o antecedente, BI≤AI, da implicação que pretendemos

demonstrar e aplicando os operadores de tipo BI e AI a T obtemos BI[T]≤AI[T] (cf. regra

[Sub aplic Λ]). Usando agora T≤BI[T] e a transitividade de ≤ obtemos T≤AI[T], ou seja T≤*AI

como pretendíamos.

Teorema 5.3-11 Seja AI+ ˆ = INTERFACE(ϒA), uma interface onde todas as ocorrências de

SAMET são positivas. Seja AT ˆ = OBJTYPE(ϒA) o tipo-objecto correspondente. Então, dada uma

interface qualquer BI ˆ = INTERFACE(ϒB) e correspondente tipo-objecto BT ˆ = OBJTYPE(ϒB) veri-

fica-se:

BT≤*AI+ ⇒ BT≤AT

BI ext AI+ ⇒ BT≤AT

5 Tipo SAMET, relações de compatibilidade e de extensão 83

Nota: A segunda asserção tem um grande interesse prático pois indica que “herança implica

subtipificação” nos casos em que todas as ocorrências de SAMET na interface da superclasse

são positivas.

Prova: Dizer que todas as ocorrências de SAMET em AI+ são positivas é equivalente a dizer

que a interface AI+ é um operador de tipo crescente. Desta forma:

BT≤*AI+

⇔ BT≤AI+[BT] por definição de ≤*

⇒ BT≤AT pela regra [Sub κµ] de F+

A segunda asserção resulta da combinação da primeira asserção com a primeira parte do

teorema 5.3-9.

Teorema 5.3-12 Seja AI uma interface contendo ocorrências negativas de SAMET, e seja AT

o tipo-objecto correspondente. Seja T um tipo-objecto qualquer. Então:

T≤AT ⇒ Todas as ocorrências negativas de SAMET em AI foram substituídas em T por supertipos de AT

Nota: Este resultado mostra que, dada uma classe com ocorrências negativas de SAMET, é difí-

cil encontrar, entre as suas subclasses que têm a particularidade de gerar subtipos, alguma com

utilidade prática (note que a substituição, em AI, de SAMET por um supertipo de AT, correspon-

de a perda de informação). Por esse motivo chamamos, genericamente, a essas subclasses,

subclasses inúteis, e aos subtipos-objecto respectivos, subtipos inúteis. Provavelmente, de

entre as subclasses consideradas, a única com utilidade prática é aquela em que todas as ocor-

rências negativas de SAMET são substituídas simplesmente por AT (cf. teorema 5.3-13).

Prova: Começamos por analisar um caso particular, que corresponde à situação mais fre-

quente. Consideremos a interface, e respectivo tipo-objecto:

AI ˆ = ΛSAMET.f:SAMET→Z

AT ˆ = f:AT→Z

Sendo T≤AT, então T tem necessariamente a forma:

T ≡ f:X→Y,…≤f:AT→Z

o que só é possível se X→Y≤AT→Z, ou seja Y≤Z e AT≤X. Esta última asserção é já a conclusão

pretendida.

Esboçamos apenas a demonstração do caso geral. O resultado sai da definição de pola-

ridade (definição 2.3.4.3-1) e do facto da asserção α≤β implicar que α tem uma estrutura tão

ou mais rica que β (i.e. a estrutura arbórea de β pode ser mergulhada na estrutura arbórea de α,

podendo esta última ter ramificações suplementares).

Seja S o conjunto de ocorrências negativas de SAMET em AI. S dá origem, por substituição

de SAMET por AT em AI, a S′, um conjunto de ocorrências negativas de AT em AT≡AI[AT].

Como T≤AT, logo T tem uma estrutura mais rica do que AT e portanto existe uma ocorrência

84 OM – Uma linguagem de programação multiparadigma

negativa duma subexpressão de T por cada ocorrência de S′. Essas subexpressões de T têm de

ser supertipos de AT pois tratam-se de ocorrências negativas (se fossem subtipos então, por

definição, seriam ocorrências positivas, o que é falso; se não estivessem relacionadas pela rela-

ção de subtipo então nunca teríamos T≤AT.

Teorema 5.3-13 Sejam AI ˆ = INTERFACE(ϒA), BI+ ˆ = INTERFACE(ϒB), AT ˆ = OBJTYPE(ϒA) e

BT ˆ = OBJTYPE(ϒB) e assumamos que todas as ocorrências de SAMET em BI+ são positivas. Caso

BI+ tenha sido obtido a partir de AI substituindo todas as ocorrências negativas de SAMET por

AT (ou BT, pois são iguais), e não alterando mais nada, então:

1. Os tipos AT e BT são iguais:

AT = BT

2. BI+ é uma extensão válida da interface AI:

BI+ ext AI

3. Tomando uma classe a com a interface AI e uma sua subclasse imediata b com interface

BI+, então todo o método binário f definido em a com o tipo SAMET→τ, tem de ser rede-

finido na subclasse b com o tipo BT→τ. Existem infinitas formas seguras de redefinir f.

Vamos sugerir uma forma por defeito que tem três vantagens: (1) pode ser gerada auto-

maticamente; (2) invoca a versão original de f; (3) trata apenas os casos previstos na

versão original de f (i.e. não toma decisões prematuras relativamente aos outros casos).

Eis a sugestão:

f=λx:BT. if checkType[SAMET] x then super.f (downcastBT[SAMET] x) else divergeτ :BT→τ

No lado direito da função, divergeτ representa a computação divergente de tipo τ (cf.

secção 2.3.3.1).

Nota: Este teorema proporciona uma técnica que permite transformar uma classe a, cujas sub-

classes não geram subtipos, numa outra classe b, subclasse imediata de a, que produz objectos

do mesmo tipo da primeira e cuja subclasses geram sempre subtipos (pelo teorema 5.3-11).

Prova: Como temos de tratar de forma distinta as ocorrências negativas e positivas de SAMET

em AI, vamos alterar a formulação original de AI e usar agora dois parâmetros: um chamado

POS, representando as ocorrências positivas de SAMET, e outro chamado NEG, representando as

ocorrências negativas de SAMET. Seja AI* a nova formulação da interface AI:

AI* ˆ = ΛPOS.ΛNEG.ϒA*

Comecemos por ver como AT, AI, BT, BI podem ser expressos à custa de AI*:

AT = OBJTYPE(ϒA) = µSAMET.AI*[SAMET][SAMET]

AI = INTERFACE(ϒA) = ΛSAMET.AI*[SAMET][SAMET]

BT = OBJTYPE(ϒB) = µSAMET.AI*[SAMET][SAMET]

BI+ = INTERFACE(ϒB) = ΛSAMET.AI*[SAMET][BT]

5 Tipo SAMET, relações de compatibilidade e de extensão 85

Note que o segundo argumento de AI* na interface BI+ não é SAMET mas sim BT, conforme

o enunciado do teorema.

1ª Parte: Como se pode observar AT e BT são sintacticamente idênticos. Assim podemos escre-

ver:

AT = BT

2ª Parte: Partamos da hipótese T≤BI+[T] para provar T≤AI[T] e concluir BI+ ext AI, como se

pretende.

Para começar, como BI+ não tem ocorrências negativas de SAMET, BI+ é um operador de

tipo crescente. Assim, aplicando a regra [Sub κµ] de F+ à hipótese T≤BI+[T] obtemos a seguinte

asserção que utilizaremos adiante:

T≤BT

Usando outra vez a hipótese T≤BI+[T], obtemos sucessivamente:

T≤BI+[T]

= AI*[T][BT] por definição de BI+

≤ AI*[T][T] porque T≤BT e BT ocorre negativamente

= AI[T] por definição de AI

Desta forma T≤AI[T], e portanto BI+ ext AI, como queríamos provar.

3ª Parte: O método f:SAMET→τ da classe a tem de ser redefinido com tipo BT→τ na classe b

para que a interface desta classe cumpra as condições do teorema. A sugestão do enunciado é

uma hipótese de redefinição pois tem o tipo BT→τ, como é fácil de confirmar.

A redefinição sugerida assume que a linguagem suporta operações dinâmicas de teste e des-

promoção de tipo, o que em última análise deve acontecer em toda a linguagem que suporte a

noção de subtipo (cf. secção 4.3.2). Mas é possível redefinir o método f de outras formas. No

entanto, se o programador quiser invocar a versão de f da superclasse com o argumento x:BT,

então terá mesmo de usar tipificação dinâmica (a invocação simples (super.f x) estaria mal tipifi-

cada). Nestes casos provavelmente o programador usará um padrão semelhante ao exemplifi-

cado, substituindo apenas o termo divergeτ por um outro termo à sua escolha: ver o exemplo do

método eq introduzido na classe point2PC da secção 4.3 e redefinido na secção 4.3.4.

A redefinição de função f, sugerida no enunciado do teorema, tem a seguinte forma equiva-

lente:

f=λx:BT.(super.f (downcastBT[SAMET] x)) :BT→τ

Demos mais relevo à outra versão complicada pois ela tem a virtualidade de explicitar o caso

em que a função aborta.

1ª Parte revisitada: Retomando a primeira parte do teorema, note que se não forem substituí-

das todas as ocorrências negativas de SAMET por BT em BI+, então não se poderia garantir que

86 OM – Uma linguagem de programação multiparadigma

BI+ estendia AI. Considere o seguinte contra-exemplo, onde apenas a primeira de duas ocor-

rências negativas de SAMET é substituída por BT:

AI ˆ = INTERFACE(f:SAMET→Bool, g:SAMET→Bool)

BT ˆ = OBJTYPE(f:SAMET→Bool, g:SAMET→Bool)

BI ˆ = INTERFACE(f:BT→Bool, g:SAMET→Bool)

CT ˆ = OBJTYPE(x:Nat, f:BT→Bool, g:SAMET→Bool)

Verifiquemos que CT≤BI[CT] /⇒CT≤AI[CT]:

CT≤BI[CT]

⇔ x:Nat,f:BT→Bool,g:CT→Bool≤f:BT→Bool,g:CT→Bool

⇔ verdade

CT≤AI[CT]

⇔ x:Nat,f:BT→Bool,g:CT→Bool≤f:CT→Bool,g:CT→Bool

⇔ CT≤BT

⇔ x:Nat,f:BT→Bool,g:CT→Bool≤f:BT→Bool,g:BT→Bool

⇔ BT≤CT

⇔ falso (pois CT tem mais componentes que BT)

Teorema 5.3-14 Sejam AI ˆ = INTERFACE(ϒA), BI ˆ = INTERFACE(ϒB), AT ˆ = OBJTYPE(ϒA) e

BT ˆ = OBJTYPE(ϒB). Caso BI tenha sido obtido a partir de AI, substituindo apenas algumas ocor-

rências positivas de SAMET por BT, então BI nunca poderá ser uma extensão válida de AI, não

obstante ser verdade que AT=BT.

Prova: Como temos de tratar de forma diferente um grupo de ocorrências positivas de SAMET

em AI (não necessariamente todas), vamos alterar a formulação original da interface AI por

forma que esta fique parametrizada em função dessas ocorrências. Para isso, introduzimos um

novo parâmetro POS, representando esse grupo ocorrências positivas de SAMET. Seja AI* a

nova formulação da interface AI:

AI* ˆ = ΛPOS.ΛSAMET.ϒA*

Verifiquemos em primeiro lugar como AT, AI, BT, BI podem ser expressos à custa de AI*:

AT = OBJTYPE(ϒA) = µSAMET.AI*[SAMET][SAMET]

AI = INTERFACE(ϒA) = ΛSAMET.AI*[SAMET][SAMET]

BT = OBJTYPE(ϒB) = µSAMET.AI*[SAMET][SAMET]

BI = INTERFACE(ϒB) = ΛSAMET.AI*[BT][SAMET]

Note que o primeiro argumento de AI* na interface BI não é SAMET mas sim BT, conforme o

enunciado do teorema.

Pretendemos mostrar que não é verdade que BI ext AI. Para isso vamos construir um tipo CT

tal que CT≤BI[CT] mas CT/≤AI[CT].

Considere a interface CI obtida a partir de BI acrescentando-lhe uma nova componente qual-

quer:

5 Tipo SAMET, relações de compatibilidade e de extensão 87

CI ˆ = ΛSAMET.AI**[BT][SAMET]

Seja CT o tipo gerado pela interface CI:

CT ˆ = µSAMET.AI**[BT][SAMET]

Pelo teorema 5.3-10 sabemos que CI ext BI. Além disso, como CT≤CI[CT] (pelo lema 5.3-3)

podemos concluir CT≤BI[CT]. Falta só mostrar que CT/≤AI[CT].

Se fosse verdade que CT≤AI[CT], então teríamos de ter, pelo lema 5.3-5,

AI**[BT][CT]≤AI*[CT][CT]

o que nos obrigaria a ter BT≤CT, porque estão em causa ocorrências positivas de BT e CT. Mas

isso é impossível pois CT tem mais componentes que BT.

Teorema 5.3-15 Sejam AI+ ˆ = INTERFACE(ϒA), BI+ ˆ = INTERFACE(ϒB), AT ˆ = OBJTYPE(ϒA) e

BT ˆ = OBJTYPE(ϒB), onde todas as ocorrências de SAMET em AI+ e BI+ são positivas. Caso BI+

tenha sido obtido a partir de AI+ substituindo apenas algumas ocorrências positivas de AT por

SAMET, então BI+ é uma extensão válida da interface AI, ou seja:

BI+ ext AI+

Nota: Este teorema terá pouca importância prática se for seguida a prática, metodologicamente

recomendada, de introduzir os métodos usando tipificação aberta no tipo do resultados: não é

por este facto que é posta em causa a geração de subtipos pelas subclasses, e assim potencia-se

a máxima reutilização do código.

Prova: Como temos de tratar de forma diferente um grupo de ocorrências positivas de AT em

AI+ (não necessariamente todas), vamos alterar a formulação original de AI+ por forma a que

esta fique parametrizada em função dessas ocorrências. Introduzimos, para isso, um novo

parâmetro POS, representando esse grupo de ocorrências positivas de AT. Seja AI* a nova for-

mulação da interface AI+:

AI* ˆ = ΛPOS.ΛSAMET.ϒA*

Vejamos primeiro como AT, AI+, BT, BI+ podem ser expressos à custa de AI*:

AT = OBJTYPE(ϒA) = µSAMET.AI*[SAMET][SAMET]

AI+ = INTERFACE(ϒA) = ΛSAMET.AI*[AT][SAMET]

BT = OBJTYPE(ϒB) = µSAMET.AI*[SAMET][SAMET]

BI+ = INTERFACE(ϒB) = ΛSAMET.AI*[SAMET][SAMET]

Note que o primeiro argumento de AI* na interface AI+ não é SAMET mas sim AT, conforme

o enunciado do teorema.

Partamos da hipótese T≤BI+[T] para provar T≤AI+[T] e concluir BI+ ext AI+, como se pretende.

88 OM – Uma linguagem de programação multiparadigma

Para começar, como BI+ não tem ocorrências negativas de SAMET, BI+ é um operador de

tipo crescente. Assim, aplicando a regra [Sub κµ] de F+ à hipótese T≤BI+[T] obtemos T≤BT. Mas

como além disso AT=BT, obtemos a seguinte asserção que será utilizada adiante:

T≤AT

Retomando novamente a hipótese T≤BI+[T], obtemos sucessivamente:

T≤BI+[T]

= AI*[T][T] por definição de BI+

≤ AI*[AT][T] porque T≤AT e AT ocorre positivamente

= AI[T] por definição de AI

Desta forma T≤AI+[T] e, portanto, BI+ ext AI+, como queríamos provar.

Teorema 5.3-16 Sejam as interfaces AI ˆ = INTERFACE(ϒA), BI ˆ = INTERFACE(ϒB), e os tipos-

-objecto AT ˆ = OBJTYPE(ϒA) e BT ˆ = OBJTYPE(ϒB). Caso BI tenha sido obtido a partir de AI, substi-

tuindo algumas ocorrências negativas de AT por SAMET, então em caso algum BI pode ser uma

extensão válida de AI, não obstante ser verdade que AT=BT.

Prova: Esta demonstração é quase idêntica à demonstração do teorema 5.3-14.

Como temos de tratar de forma diferente um grupo de ocorrências negativas de AT em AI

(não necessariamente todas), vamos alterar a formulação original de AI por forma a que esta

fique parametrizada em função dessas ocorrências. Para isso, introduzimos um novo parâmetro

NEG, representando esse grupo de ocorrências negativas de AT. Seja AI* a nova formulação da

interface AI:

AI* ˆ = ΛSAMET.ΛNEG.ϒA*

Vejamos em primeiro lugar como AT, AI, BT, BI podem ser expressos à custa de AI*:

AT = OBJTYPE(ϒA) = µSAMET.AI*[SAMET][SAMET]

AI = INTERFACE(ϒA) = ΛSAMET.AI*[SAMET][AT]

BT = OBJTYPE(ϒB) = µSAMET.AI*[SAMET][SAMET]

BI = INTERFACE(ϒB) = ΛSAMET.AI*[SAMET][SAMET]

Note que o segundo argumento de AI* na interface AI não é SAMET mas sim AT, conforme o

enunciado do teorema.

Pretendemos mostrar que não é verdade que BI ext AI. Para isso vamos construir um tipo CT

tal que CT≤BI[CT] mas CT/≤AI[CT].

Considere a interface CI obtido a partir de BI acrescentando-lhe uma nova componente

qualquer:

CI ˆ = ΛSAMET.AI**[SAMET][SAMET]

Seja CT o tipo gerado pela interface CI:

5 Tipo SAMET, relações de compatibilidade e de extensão 89

CT ˆ = µSAMET.AI**[SAMET][SAMET]

Pelo teorema 5.3-10 sabemos que CI ext BI. Além disso, como CT≤CI[CT] (pelo lema 5.3-3)

podemos concluir CT≤BI[CT]. Falta só mostrar que CT/≤AI[CT].

Se fosse verdade que CT≤AI[CT], então teríamos de ter, pelo lema 5.3-5,

AI**[CT][CT]≤AI*[CT][AT]

o que nos obrigaria a ter AT≤CT, porque estão em causa ocorrências negativas de AT e CT. Mas

isso é impossível pois CT tem mais componentes que AT.

5.4 O operador “+”O teorema 5.3-13 mostra que, tomando uma classe qualquer, é possível gerar automaticamente

uma sua subclasse imediata que fixa todas as ocorrências negativas de SAMET: Transformam-

-se assim todos os métodos binários herdados em métodos não binários. Agora, todas as sub-

classes da nova classe já geram subtipos, de acordo com o teorema 5.3-11.

Nesta secção introduzimos um novo operador unário prefixo “+”, aplicável a classes, que

gera automaticamente a subclasse prevista no teorema 5.3-11. Nesta secção introduzimos tam-

bém a noção de método binário transformado.

Definição 5.4-1 (Classe +c) Seja uma classe c com interface CI e geradora do tipo objecto

CT:

CI ˆ = ΛPOS.ΛNEG.ϒcCT ˆ = µSAMET.CI[SAMET][SAMET]

Definimos +c como sendo a nova classe com interface +CI e geradora do tipo objecto +CT que a

seguir se descreve:

+CI ˆ = ΛSAMET.CI[SAMET][CT]

+CT ˆ = CT

+c ˆ = class\c f–= λx:CT.(super.f (downcastCT[SAMET] x))

–––––––––––––––––––––––––––––––––––––––––––

Nesta última linha, os nomes f– representam os nomes de todos os métodos binários de c.

Os factos mais importantes sobre a classe +c são os seguintes:

• A classe +c pode ser gerada automaticamente, bastando agora escrever “+c” para temos

acesso a ela;

• As classes c e +c geram rigorosamente o mesmo tipo-objecto (cf. teorema 5.3-13);

• Todas as subclasses de +c geram subtipos (cf. teorema 5.3-11).

Definição 5.4-2 (Métodos binários transformados) Quando se define a classe +c a

partir duma classe c, sabemos que todos os métodos binários de c são automaticamente substi-

90 OM – Uma linguagem de programação multiparadigma

tuídos em +c por novos métodos gerados a partir dos primeiros. A estes novos métodos damos

o nome de métodos binários transformados.

Note que quando um método binário transformado é invocado com um argumento que não

tem o tipo do receptor, então o programa aborta por efeito do termo divergeτ (cf. teorema

5.3-13).

Usando o operador “+”, a classe b do exemplo do final da secção 5.1.3 pode agora ser defi-

nida simplesmente como b ˆ = +a.

5.4.1 Problemas que o operador “+”resolveO operador “+” resolve o problema da reutilização de classes contendo métodos binários,

em contextos que requerem que as suas subclasses gerem subtipos. Note bem: Este operador

pode mesmo ser aplicado a classes distribuídas sob a forma de código objecto, portanto a clas-

ses cujo código fonte não está disponível.

O operador “+” vem também libertar o programador da obrigação de ter de antecipar com

demasiado rigor a futura utilização das suas classes. Assim, será sempre possível, mesmo reco-

mendado, começar por usar liberalmente tipificação aberta nas classes, por forma a maximizar

as possibilidades de reutilização do código dessas classes. Mais tarde, se for necessário criar

uma versão de alguma dessas classes sem métodos binários, basta aplicar a essa classe o ope-

rador “+” para obter a classe pretendida.

5.4.2 Ilustração duma aplicação de “+”Nesta secção, vamos apresentar um exemplo simples que ilustra a utilidade do operador “+”.

Suponhamos que pretendemos definir um conjunto de classes que representem, e permitam

manipular, formas geométricas a duas dimensões. Vamos introduzir uma classe abstracta shape,

que captura a ideia abstracta de forma geométrica, e duas suas subclasses concretas, rectangle e

oval, que capturam a funcionalidade de duas formas geométricas específicas. No que se segue,

assumimos que as três classes, shape, rectangle e oval, geram respectivamente os tipos-objecto

ShapeT, RectangleT, e OvalT.

Na biblioteca de classes da linguagem OM final, existe uma classe de biblioteca chamada

equality que implementa o método de igualdade genérico eq:SAMET→Bool. Vamos definir as

nossas classes, shape, rectangle e oval como subclasses de equality para tirar partido desse método.

Considerando o que foi dito, o nosso sistema de classes terá a seguinte organização:

equality ˆ = class\object eq=λx:SAMET.(…), neq=λx:SAMET.(not (self.eq x))

shape ˆ = class\equality …

rectangle ˆ = class\shape …

oval ˆ = class\shape …

5 Tipo SAMET, relações de compatibilidade e de extensão 91

Como se pode observar, as classes shape, rectangle, oval herdam efectivamente o método eq de

equality. No entanto levanta-se a questão esperada: a natureza do nosso problema faz com que

seja natural, mesmo desejável, que os tipos RectangleT e OvalT sejam subtipos de ShapeT: só

desta forma, uma expressão do tipo RectangleT, por exemplo, poderá ser usada num contexto

onde se espera uma expressão do tipo ShapeT. Contudo, o facto de shape herdar métodos biná-

rios de equality impede as subclasses de shape de gerarem subtipos.

Uma solução para este problema consistiria em não herdar de equality. Mas podemos fazer

melhor do que isso usando o operador “+”. Assim, vamos substituir a definição original de

shape por esta outra:

shape ˆ = class\+equality …

eq=λx:ShapeT.if checkType[SAMET] x

then super.f (downcastBT[SAMET] x)

else false

A nova classe shape tem duas virtualidades. Em primeiro lugar herda directamente de

+equality, o que resolve o anterior problema da geração de subtipos pelas suas subclasses. Em

segundo lugar redefine o método de igualdade duma forma que serve as necessidades das suas

subclasses e de tal forma que estas não têm necessidade de redefinir o método. Note como o

tipo SAMET ocorre na condição (checkType[SAMET] x), um tipo cujo significado é automatica-

mente ajustado nas subclasses.

Analisemos a funcionalidade da nova versão do método eq. Ele começa por testar se o seu

argumento é do mesmo tipo que o receptor. Se for do mesmo tipo (imagine que estamos a

comparar dois elementos do tipo RectangleT) então é invocado o método de igualdade original-

mente definido em equality (através do método binário transformado eq de +equality). Se não for

do mesmo tipo (imagine que estamos a comparar um elemento do tipo RectangleT com um do

tipo OvalT), a função retorna imediatamente o valor false.

Se redefiníssemos o método eq como eq=λx:ShapeT.(super.f x) na classe em shape, a nova

versão estaria estaticamente bem tipificada, mas existiria o perigo do método binário transfor-

mado eq de +equality ser invocado com um argumento de tipo distinto do tipo do receptor: por

exemplo, numa mensagem (re.eq ov) com re:RectangleT e ov:OvalT. O teste de tipo introduzido na

nossa redefinição de eq, (checkType[SAMET] x), resolve o problema.

5.4.3 Eficácia da classe +c na práticaÉ necessário verificar se a transformação dos métodos binários de c em métodos não binários

de +c prejudica em alguma medida a reutilização do código de c nas subclasses de +c. Para

simplificar a discussão, vamos começar por admitir que os métodos binários transformados de

+c não são redefinidos em nenhuma das futuras subclasses de +c.

92 OM – Uma linguagem de programação multiparadigma

5.4.3.1 Métodos binários transformados não redefinidos

Para começar, todos os métodos da superclasse c que nela dependiam dos métodos binários de

c, ao serem herdados pelas subclasses de +c, passam a depender dos métodos binários transfor-

mados de +c. Neste caso tudo funciona bem, pois as restrições estáticas associadas ao uso dos

métodos binários originais mantêm-se relativamente aos métodos binários transformados (i.e.

o teste de tipo dinâmico efectuado nos métodos transformados produz sempre true). Além dis-

so, como através de super, cada método binário transformado invoca o correspondente método

binário original, não será por culpa dos métodos binários transformados que a semântica do

código herdado de c muda.

Resta-nos verificar se, nas subclasses de +c, existe algum problema associado a novos usos

dos métodos herdados.

Primeiro, relativamente a todos os métodos não binários de c, eles continuam disponíveis

nas subclasses de +c (pelo menos enquanto não forem redefinidos); não há qualquer problema

especial a apontar à sua utilização.

Segundo, relativamente aos métodos binários de c, todos eles foram alvo de transformação

em +c. É preciso cautela com estes métodos. Se for necessário invocar algum deles a partir de

código novo numa subclasse de +c, então o programador terá de garantir dinamicamente que o

tipo do argumento x tem o tipo do receptor da mensagem (ou seja (checkType[SAMET] x)). Se

esta condição não se verificasse então seria activada a expressão diverge e o programa aborta-

ria. É portanto natural que o programador queira redefinir os métodos binários transformados

nas subclasses directas de +c.

Relativamente a este “problema da invocação, a partir de código novo, de métodos binários

transformados não redefinidos”, convém que o compilador considere como erro todas as invo-

cações de métodos binários transformados não redefinidos que não estejam protegidas por um

teste dinâmico de tipo da forma (checkType[τ] x), onde τ é o tipo do receptor da mensagem

(geralmente SAMET).

5.4.3.2 Métodos binários transformados redefinidos

Nas subclasses de +c, não há qualquer problema em redefinir os métodos binários transforma-

dos. Inclusivamente, essa redefinição é recomendada pois elimina o “problema da invocação

de métodos binários transformados não redefinidos a partir de código novo”.

5.4.3.3 Conclusão

As subclasses de +c reutilizam de forma efectiva todo o código da classe original c. É apenas

necessário tomar algumas precauções relativamente à invocação dos métodos binários trans-

formados que surgem na classe +c.

5 Tipo SAMET, relações de compatibilidade e de extensão 93

Há três atitudes correctas perante os métodos binários transformados: (1) não se usam; (2)

redefinem-se; (3) usam-se, mas só sob uma garantia, dada por checkType, de que o argumento

tem o tipo do receptor.

A atitude preferível é a da redefinição.Mas repare que se o método redefinido quiser aceder

à funcionalidade do método original, como a versão do método eq na classe shape da secção

5.4.2, tem de usar a técnica (3).

5.5 Discussão sobre L5A introdução de suporte para o nome SAMET em L5 veio corrigir algumas deficiências do me-

canismo de herança de L4. O mecanismo de herança ficou mais flexível, passando a suportar a

definição de subclasses que em L4 não era possível definir. Na linguagem L5, o programador

tem a possibilidade de exprimir com mais precisão as suas intenções ao definir as suas classes.

No entanto dois novos problemas surgem em L5.

O primeiro problema consiste na complexidade da relação de extensão entre interfaces: é

fácil criar situações em que é difícil para o programador, ou para os leitores dos programas,

verificar se uma interface estende, ou não estende, uma outra interface. Não parece boa ideia

obrigar os utilizadores da linguagem a compreender todas as implicações duma relação de ex-

tensão intrincada. Propomos uma solução para este problema no ponto 5.5.1, já a seguir.

O segundo problema, já aflorado na secção 5.1.3, tem a ver com o facto das possibilidades

de programação genérica ficarem prejudicadas pela ausência de garantias quanto à geração de

subtipos pelas subclasses duma classe. A introdução do operador “+”, na secção anterior, aju-

da a resolver este problema, mas convém aprofundar um pouco mais o tópico da “programa-

ção genérica em L5”. É isso o que faremos no ponto 5.5.2.

5.5.1 Complicação da relação de extensãoO seguinte exemplo, apesar de relativamente simples, já obriga a uma verificação não trivial

da condição BI ext AI.

XT ˆ = OBJTYPE(x:Nat) = x:Nat

AI ˆ = INTERFACE(x:Nat, f:Nat→XT)

BI ˆ = INTERFACE(x:Nat, y:Nat, f:Nat→SAMET)

A prova de que BI ext AI faz-se da seguinte forma: tomando uma variável de tipo T tal que

T≤x:Nat, y:Nat, f:Nat→T, como x:Nat, y:Nat, f:Nat→T≤x:Nat, logo por transitividade obtemos

T≤x:Nat. Agora, pela regra [Sub →] obtemos x:Nat, y:Nat, f:Nat→T≤x:Nat, f:Nat→x:Nat

donde, por transitividade, sai T≤x:Nat, y:Nat, f:Nat→x:Nat, como pretendíamos.

Este exemplo mostra que a relação de extensão ext, por ser muito geral, permite a criação

de programas obscuros em que a relação de extensão entre uma classe e as suas subclasses

94 OM – Uma linguagem de programação multiparadigma

pode ser difícil de entender. Naturalmente, programas que usem a relação desta forma serão

difíceis de manter.

Na prática, convém restringir as possibilidades de definição de subclasses directas em L5:

Restrição 5.5.1-1 Dada uma classe c, apenas as seguintes subclasses directas de c são consi-

deradas bem formadas:

1 - As subclasses directas de c que mantêm as decisões de tipificação relativas a SAMET;

2 - A classe +c.

Esta restrição sobre subclasses imediatas pode ser capturada pela seguinte relação de exten-

são ext2:

[ext2 ≤]

Γ ΛX.κ≤ΛX.κ′

Γ ΛX.κ ext2 ΛX.κ′

[ext2 +]

Γ ΛX+.ΛX-.κ :∗⇒∗⇒∗

Γ ΛX.κ[X][(µX.κ[X][X])] ext2 ΛX.κ[X][X]

A primeira regra corresponde à relação de subtipo entre interfaces ≤, referida no teorema

5.3-10. Esta regra obriga a respeitar as decisões de tipificação, aberta ou fechada, tomadas na

superclasse imediata.

A segunda regra permite substituir todas as ocorrências negativas de SAMET pelo tipo-ob-

jecto fixo gerado pela subclasse. A subclasse está de acordo com a primeira parte do enuncia-

do do teorema 5.3-13 e caracteriza-se pelo facto de todas as suas subclasses serem geradoras

de subtipos (cf. teorema 5.3-11). Esta regra permite o uso do operador “+”.

A relação ext2 está bem fundada sobre a relação ext, como indicam os teoremas 5.3-10 e

5.3-13. Adoptamos a relação ext2 ao nível do sistema de tipos da linguagem. De qualquer

forma, ao nível do modelo semântico, não há vantagem em substituir a relação ext pela relação

mais estrita ext2. Aliás, a relação ext2 seria insuficiente descrever para uma relação de herança

satisfatória pelo facto de não ser transitiva.

5.5.2 Programação genéricaA linguagem L4 suporta polimorfismo de inclusão, uma forma de polimorfismo que depende

da relação de subtipo. Aliás, em L4, todas as funções beneficiam desta forma de polimorfismo,

na medida em que uma função que aceite argumentos dum tipo τ também aceita argumentos

dum subtipo τ′≤τ.

O polimorfismo de inclusão é um mecanismo da maior importância em L4, devido ao facto

da relação de herança estar ligada à relação de subtipo em L4. É usando polimorfismo de

inclusão que, em L4, se podem escrever funções aplicáveis a todos os objectos gerados pelas

subclasses duma dada classe.

5 Tipo SAMET, relações de compatibilidade e de extensão 95

Também está disponível polimorfismo de inclusão em L5, mas o facto de nesta linguagem

as subclasses nem sempre gerarem subtipos reduz o âmbito de aplicação deste mecanismo.

Efectivamente, existem muitas classes para as quais não é possível escrever funções aplicáveis

a todos os objectos gerados pelas suas subclasses. Este é um problema essencial que importa

resolver e que trataremos na linguagem L6. Em L6, introduziremos uma forma de polimorfis-

mo paramétrico restringido, baseado na relação de compatibilidade entre um tipo e uma inter-

face de L5 que permite a definição de funções paramétricas aplicáveis a todos os objectos ge-

rados pelas subclasses duma dada classe.

A forma de polimorfismo paramétrico, baseado na relação de compatibilidade, a introduzir

em L6, resolverá certamente muitas das nossas necessidades de programação genérica. Mas

será que resolve todas as necessidades?

A resposta é negativa. Na secção 5.2.3 comparámos a relação de subtipo com a relação de

compatibilidade e verificámos que a segunda relação fica a perder relativamente à primeira no

aspecto da substitutividade. Ora acontece que existem formas recorrentes de programação ge-

nérica que dependem da possibilidade de usar valores dum tipo onde se esperam valores de

outro tipo. Como uma das nossas preocupações, relativamente à linguagem L5, consiste em

estudar e minorar as tensões existentes entre a relação de subtipo e as relações de compatibili-

dade e de extensão, este é o momento certo para analisar até que ponto a linguagem L5 conse-

gue lidar com essas situações.

Os casos práticos de programação genérica que dependem da propriedade da substitutivida-

de envolvem tipos heterogéneos ou colecções heterogéneas. Analisamos agora cada um destes

dois casos, para ver como eles se tratam em L5.

5.5.2.1 Tipo heterogéneo

Consideremos o conhecido problema da representação e manipulação de expressões algébri-

cas usando a linguagem L5. A estrutura duma expressão algébrica pode ser adequadamente

representada usando uma árvore de nós heterogéneos, onde esses nós representam diferentes

categorias de entidades: operações algébricas de diferentes aridades, constantes inteiras, cons-

tantes reais, variáveis, etc. Para exemplificar, a expressão simples “1+x” pode ser representada

usando uma árvore constituída por três nós: a raiz desta árvore é um nó binário aditivo, que

tem como subárvore esquerda um nó constante, representando o número 1, e como subárvore

direita um nó variável, representando a variável x.

Sendo necessário definir vários tipos de nós, todos variantes duma ideia geral de nó de

árvore de expressão, a sua descrição pode ser convenientemente organizada numa hierarquia

de classes com três níveis: na raiz encontra-se uma classe abstracta node que captura a noção

geral de nó de árvore de expressão; no segundo nível encontram-se três classes abstractas,

binNode, unNode, zeroNode, que, respectivamente, capturam as ideias, um pouco menos gerais, de

nó binário (nó contendo duas subárvores), nó unário (nó contendo uma única subárvore) e nó

96 OM – Uma linguagem de programação multiparadigma

sem filhos; finalmente, no terceiro nível encontram-se várias classes ditas concretas, cada uma

delas definindo completamente um tipo específico de nó: addNode e subNode (subclasses de

binNode), simNode (subclasse de unNode), iconstNode, rconstNode e varNode (subclasses de zeroNode).

Vamos designar por NodeT, BinNodeT, AddNoteT, etc, os tipos-objectos gerados respectivamente

pelas classes node, binNode, addNode, etc.

Chamamos tipo heterogéneo a um tipo cujos valores podem assumir formas diversas. Cada

elemento com uma forma particular é um elemento do tipo heterogéneo, o que significa que,

do ponto de vista lógico, os vários tipos que descrevem as várias formas do tipo heterogéneo

são subtipos deste. Por exemplo, o tipo-objecto NodeT, gerado pela classe node, é um tipo hete-

rogéneo, por decisão de construção, sendo os tipos BinNodeT, AddNoteT, etc. gerados pelas sub-

classes de node subtipos de NodeT.

É fácil ver que sem este requisito de subtipo, ou requisito de substitutividade, não seria pos-

sível construir certos elementos de NodeT. Consideremos o caso da construção dum nó aditivo,

do tipo AddNoteT: a construção é efectuada a partir de dois nós do tipo NodeT, devendo ser pos-

sível a utilização de nós de tipos concretos, e.g. AddNodeT, VarNodeT, ConstNodeT, onde se espe-

ram nós do tipo heterogéneo NodeT.

Mas o requisito da substitutividade não permite que a classe node contenha qualquer método

binário. Felizmente esse aspecto não é problema pois, por imperativo lógico, espera-se que

todo o método que receba outra árvore como argumento tenha o seu argumento declarado com

o tipo fixo NodeT, e não com o tipo aberto SAMET. Pensando, por exemplo, numa operação de

comparação de expressões (i.e. no método de igualdade), a versão desta operação definida ao

nível da classe addNode deve estar preparada para estabelecer comparação com outras árvores

quaisquer, portanto com raiz do tipo mais geral NodeT, e não apenas com árvores cuja raiz seja

apenas do tipo AddNodeT.

A necessidade de dispor duma noção de subtipo surge naturalmente no problema da repre-

sentação e manipulação de expressões algébricas que acabámos de apresentar e discutir.

Assim, este exemplo serve para argumentarmos que a nossa linguagem deve continuar a

suportar a noção de subtipo. É certo que a possibilidade de definir métodos binários vem intro-

duzir complicações, mas o operador “+” foi criado para nos permitir lidar com estas complica-

ções.

No contexto do problema que temos vindo a discutir vamos considerar uma complicação

induzida pelos métodos binários que o operador “+” resolve. Suponhamos que a nossa classe

node precisa de herdar duma classe x:

node ˆ = class\x …

mas que, por mero acaso, entre os métodos de x se encontram alguns métodos binários.

5 Tipo SAMET, relações de compatibilidade e de extensão 97

Imediatamente, cai pela base toda a solução apresentada anteriormente, pois as subclasses

de node deixam de gerar subtipos de NodeT. É aqui que o operador “+” vem em nosso socorro:

se node herdar de +x e não de x, tudo se resolve:

node ˆ = class\+x …

5.5.2.2 Colecções heterogéneas

Uma colecção heterogénea é constituída por objectos de tipos diferentes, definidos indepen-

dentemente uns dos outros, mas possuindo algumas características comuns. Eis dois exemplos

de colecções heterogéneas: (1) uma lista de objectos heterogéneos que, apesar disso, suportam

um método de escrita chamado print; (2) um array onde são guardadas as janelas duma inter-

face gráfica: as janelas têm funcionalidade variável mas suportam um núcleo de operações

comuns.

Nas colecções heterogéneas também se requer o uso de substitutividade, pois objectos de

diferentes tipos têm de ser guardados numa estrutura de dados, a qual tem de se comprometer

com algum tipo para os objectos a guardar.

O tratamento duma colecção heterogénea é simples se não ocorrerem métodos binários no

núcleo de operações comuns dos objectos a guardar na colecção. Nesta circunstância o tipo

mais geral que captura essas operações comuns é supertipo de todos os tipos considerados na

colecção.

Relativamente ao caso em que ocorrem métodos binários no núcleo de operações comuns,

nem a linguagem L5, nem mesmo a linguagem OM, final, oferecem uma solução específica

que não passe pela utilização de tipificação dinâmica. Se for mesmo necessário manter os mé-

todos binários, podemos proceder da seguinte forma: numa primeira fase deixamos de consi-

derar os métodos binários como pertencentes ao núcleo de operações comuns e aplicamos a re-

ceita do caso anterior. Depois, perante a necessidade de aplicar um método binário a um objec-

to a usando um objecto b como argumento, usamos a operação dinâmica de teste de tipo para

validar dinamicamente a operação e a operação dinâmica de despromoção de tipo para prepa-

rar a sua realização.

O problema da definição duma colecção heterogénea em que o núcleo comum de operações

contém métodos binários não é solúvel usando um sistema de tipos estático convencional.

Quando se aplica um método binário a um objecto a duma colecção usando outro objecto b da

mesma colecção como argumento, o conhecimento dos tipos de a e b é sempre parcial. Assim

não é possível decidir estaticamente se o tipo de a é igual ao tipo de b, uma condição necessá-

ria para que o método binário seja aplicável ao par. Uma solução envolveria uma análise glo-

bal do fluxo da execução, mas isso ultrapassa os limites dum sistema de tipos convencional.

98 OM – Uma linguagem de programação multiparadigma

5.6 ConclusõesÉ notável como a simples introdução do nome SAMET nas classes de L5 conseguiu marcar tão

significativamente a linguagem: as possibilidades de reutilização de código aumentaram; a

capacidades de expressão das intenções do programador melhorou; foi necessário recorrer a

novas noções para explicar a linguagem. Um aspecto negativo é o facto da linguagem L5 ser

ficado um pouco mais complicada do que a linguagem L4, mas esse é o preço normal a pagar

pelas capacidades de expressão acrescidas.

Considerando a literatura sobre sistemas de tipos estáticos para linguagens orientadas pelos

objectos, o trabalho ao qual a linguagem L5 mais deve é o trabalho de Cook, Hill e Canning

[CHC90]. Neste trabalho estuda-se um modelo geral de herança e mostra como um mecanismo

de herança flexível pode comprometer a geração de subtipos pelas subclasses. Outros traba-

lhos próximos do nosso, por também envolverem o estudo de mecanismos de herança, são

[Bru94, ESTZ94, PT94, BPF97, BSG95, BFSG98,AC96]. Estes trabalhos diferem muito nas

técnicas de formalização adoptadas: semântica denotacional, semântica operacional, codifi-

cação em cálculo-lambda polimórfico.

Tipicamente, nos trabalhos referidos, a relação de subclasse baseia-se directamente na rela-

ção de subtipo entre operadores de tipo (cf. regra [Sub Λ]). Isso significa que nesses trabalhos

se coloca todo o ênfase no mecanismo de herança, sendo deixada para segundo plano a relação

de subtipo. Os aspectos práticos da conciliação dos dois mecanismos são assim ignorados.

O único trabalho que se preocupa com este aspecto é [BPF97]. Relativamente à linguagem

LOOM, introduzida nesse trabalho, os autores adoptam uma solução interessante: prescindem

da relação de subtipo, e introduzem um mecanismo de substitutividade alternativo, baseado na

relação de subclasse – o mecanismo dos hash types. Um hash type é um tipo polimórfico com

a forma #τ e com a seguinte característica essencial: dado um tipo-objecto τ gerado por uma

classe c, num contexto onde se espera uma expressão do tipo #τ, pode escrever-se uma expres-

são de tipo tipo-objecto σ que seja gerado por uma subclasse de c. Assim, na linguagem

LOOM, “herança implica substitutividade” (NB: não implica subtipificação). Contudo os hash

types têm uma limitação importante: relativamente a expressões com tipo polimórfico #τ, não

se permite a invocação de métodos binários; só é possível a invocação de métodos binários de

expressões com tipo monomórfico.

O trabalho [ESTZ94] apresenta a linguagem LOOP. Este trabalho merece-nos uma menção

especial pelo facto de usar uma relação de herança mais geral do que os outros, apesar dela

nunca chegar a ser explicitada, ficando implícita e difusa nas regras do sistema de tipos. É

também o único trabalho que discute a possibilidade de se usar tipificação aberta ou tipificação

fixa nas classes, alegando-se, inclusivamente, que as decisões de tipificação fixa/aberta toma-

das ao nível das superclasses poderiam ser arbitrariamente alteradas nas subclasses. No entan-

to o sistema de tipos apresentado, e cuja correcção se prova, não está acordo com esta alega-

5 Tipo SAMET, relações de compatibilidade e de extensão 99

ção. Aliás, nunca poderia estar, pois só pondo em causa o princípio da “reutilização sem reve-

rificação” é que se poderia alcançar a total liberdade de revisão do código.

A principal contribuição deste capítulo, referente à linguagem L5, tem a ver com o proble-

ma da minimização das tensões entre o mecanismo de herança e a relação de subtipo. Para re-

solver o problema, introduzimos uma relação de extensão entre interfaces de classes ext, o

mais geral possível, estudámos as suas propriedades, e descobrimos e provámos a possibilida-

de de introduzir o operador “+” descrito na secção 5.4. Finalmente, introduzimos algumas res-

trições à utilização da relação ext, apenas por uma questão pragmática de simplificação da lin-

guagem: na verificação de qualquer subclasse imediata usamos a regra [Sub Λ], tal como na

maioria dos trabalhos que acabámos de referir, com a diferença que introduzimos uma regra

adicional que fornece suporte específico para o operador “+”.

Capítulo 6

Polimorfismo paramétrico

Sintaxe dos géneros,tipos e termos de L6

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ) |

∀X≤*INTERFACE(ϒB).τ

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | o.l |

checkType[τ] | downcastσ[τ] |

λX≤*INTERFACE(ϒB).e | P[τ]

Semântica dos tipos

∀X≤*INTERFACE(ϒB).τ ˆ = tipo universal ≤*-restringido

∀X≤INTERFACE(ϒB)[X].τϕ[τ] ˆ = ϕ[τ] instanciação de tipo universal ≤*-restringido

Semântica dos termos

λX≤*INTERFACE(ϒB).e ˆ = abstracção paramétrica ≤*-restringidaλX≤INTERFACE(ϒB)[X].e

P[τ] ˆ = P[τ] instanciação de abstracção paramétrica ≤*-restringida

Instanciação duma entidade paramétrica S com uma variável de tipo X

S ˆ = λX≤*Is.eC ˆ = λX≤*Ic.S[X]*Restrição implícita: Ic ext Is (cf. secção 6.2.2)

Classes paramétricas (caso particular)

∀X≤*INTERFACE(ϒB).CLASSTYPE(ϒc) tipo das classes paramétricas

ΛX.OBJTYPE(ϒc) tipo-objecto paramétrico (gerado por classe paramétrica)

∀X≤*INTERFACE(ϒB).class Rc classe paramétrica

Na secção 6.1, introduzimos o conceito polimorfismo paramétrico ≤*-restringido e ilustra-

mos as suas aplicações mais importantes. Na secção 6.2, discutimos a formalização do poli-

morfismo paramétrico ≤*-restringido e quais requisitos de boa tipificação das respectivas equa-

ções semânticas. Na secção 6.3, discutimos e solucionamos certos problemas práticos ligados

à utilização de polimorfismo paramétrico em L6. Na secção final, 6.4, tiramos algumas con-

clusões e relacionamos a nossa variedade de polimorfismo paramétrico com trabalhos de

outros autores.

102 OM – Uma linguagem de programação multiparadigma

6.1 Conceitos e mecanismos de L6Na linguagem funcional L6, introduzimos uma forma de polimorfismo paramétrico restringido

em que a restrição sobre a variável de tipo se baseia na relação de compatibilidade ≤* entre um

tipo-objecto e uma interface (cf. definição 5.2-11). Chamaremos a esta forma de polimorfismo,

“polimorfismo paramétrico ≤*-restringido”. Uma entidade paramétrica ≤*-restringida tem a

forma P ˆ = λX≤*INTERFACE(ϒB).e, com tipo ∀X≤*INTERFACE(ϒB).τ. Pode ser instanciada com

qualquer tipo-objecto σ que inclua todas as operações indicadas na interface-limite, ou baliza,

INTERFACE(ϒB). A operação de instanciação da entidade paramétrica P com o tipo-objecto σ é

denotada por P[τ].

No caso duma entidade paramétrica ≤*-restringida P ter como interface-limite a interface

duma classe c, então P pode ser instanciada com qualquer tipo-objecto gerado por uma sub-

classe de c. Assim, P consegue operar de forma genérica sobre todos os objectos gerados pelas

subclasses de c.

O polimorfismo paramétrico ≤*-restringido de L6 tem três aplicações importantes: (1) per-

mite definir classes paramétricas; (2) permite definir funções paramétricas; (3) permite resol-

ver o problema da definição de métodos em que o tipo dos parâmetros é alvo de especialização

(tratam-se dos chamados parâmetros covariantes). Vamos discutir estas três aplicações nas

três subsecções seguintes.

6.1.1 Classes paramétricasUma classe paramétrica considera-se um construtor de classes por servir para a construir clas-

ses simples (não-paramétricas) através da operação instanciação. Na linguagem L9, introduzi-

remos uma segunda forma de construtor de classes, o modo (cf. 9.1.3).

Em função do tipo usado para instanciar o seu tipo-parâmetro, uma classe paramétrica ori-

gina classes não-paramétricas distintas, as quais, por sua vez, geram tipos-objecto simples dis-

tintos. Ora os tipos-objecto gerados dependem dos argumentos de instanciação da classe para-

métrica, pelo que é razoável dizer que uma classe paramétrica gera um tipo-objecto paramé-

trico. Esse tipo-objecto paramétrico é uma função de tipos-objecto para tipos-objecto, ou seja,

é um operador de tipo com uma forma particular.

A título de ilustração, vamos analisar a seguinte classe paramétrica bag e a sua subclasse pa-

ramétrica set:

EqI ˆ = INTERFACE(eq:SAMET→Bool) :∗⇒∗bag ˆ = λX≤*EqI.classRbag :∀X≤*EqI.CLASSTYPE(ϒbag)

set ˆ = λX≤*EqI.class\bag[X]Rset :∀X≤*EqI.CLASSTYPE(ϒbag⊕ϒset)

Nesta equações, EqI:∗⇒∗ representa a interface-limite de ambas as classes; Rbag:ϒbag é o registo

das componentes da classe bag; Rset:ϒset é o registo das componentes novas ou modificadas da

subclasse set.

6 Polimorfismo paramétrico 103

Note que as interfaces de bag e set, respectivamente BagI e SetI, são tipos paramétricos pois

dependem da variável de tipo X:

BagI ˆ = ΛX.INTERFACEϒbag :∗⇒∗⇒∗SetI ˆ = ΛX.INTERFACEϒbag⊕ϒset :∗⇒∗⇒∗

Os tipos-objecto gerados por bag e set, respectivamente BagT e SetT, são também tipos para-

métricos dependentes da mesma variável X:

BagT ˆ = ΛX.OBJTYPEϒbag :∗⇒∗SetT ˆ = ΛX.OBJTYPEϒbag⊕ϒset :∗⇒∗

Como indica a interface-limite EqI, as classes paramétricas bag e set podem ser aplicadas a

tipos-objecto que definam publicamente uma igualdade eq. Por exemplo:

bagPoint3 ˆ = bag[Point3T] :OBJTYPE(ϒbag[Point3T/X])

setPoint2 ˆ = set[Point2T] :OBJTYPE((ϒbag⊕ϒset)[Point3T/X])

Estas duas classes implementam colecções homogéneas que armazenam, respectivamente, ob-

jectos de tipo Point3T e objectos de tipo Point2T.

Diz-se que set é uma extensão paramétrica bem formada da classe bag sse para todas as pos-

síveis instanciações de set, se verificar que a interface de set[X] estende a interface de bag[X].

Esta condição escreve-se:

X≤*EqI ⇒ SetI[X] ext BagI[X]

ou equivalentemente:

X≤EqI[X] ⇒ (T≤SetI[X][T] ⇒ T≤BagI[X][T])

6.1.2 Funções paramétricasApresentamos seguidamente uma função paramétrica search que sabe procurar objectos do tipo

X em objectos do tipo BagT[X], onde X representa um tipo qualquer que defina uma igualdade

eq. BagT é o tipo-objecto paramétrico gerado pela classe paramétrica bag da secção anterior.

EqI ˆ = INTERFACE(eq:SAMET→Bool)

BagT ˆ = ΛX.OBJTYPE(ϒbag)

search ˆ = λX≤*EqI.λx:X.λb:BagT[X].(esearch) :∀X≤*EqI.X→BagT[X]→τsearch

A função search tem três parâmetros. O primeiro parâmetro representa o tipo X≤*EqI dos ele-

mentos a pesquisar. O segundo parâmetro representa o elemento x:X a pesquisar. O terceiro

parâmetro é o objecto b:BagT[X] no qual a procura é efectuada. Eis um exemplo de invocação

de search:

search[Point2T] (new (point2PC 4 5)) (new bag[Point2T])

A definição de search pode ser adaptada por forma a constituir um método da classe bag. Ve-

jamos como é que a definição da função search seria adaptada ao contexto da classe bag:

104 OM – Uma linguagem de programação multiparadigma

bag ˆ = λX≤*EqI.class…, search=λx:X.esearch, …

Note que no novo contexto, a função search não precisa de declarar os parâmetros X e b. Isto re-

sulta do facto de, dentro da classe bag, todos os métodos já se encontram implicitamente para-

metrizados por um tipo X e por um valor chamado self, do tipo SAMET.

6.1.3 Parâmetros covariantesNas linguagens L5 e L6, o tipo SAMET tem um tratamento privilegiado, no sentido em que é o

único tipo que, ocorrendo negativamente no tipo dum método (neste caso binário), pode variar

da superclasse para uma subclasse de forma covariante.

Existem exemplos que mostram que seria útil poder alargar esta flexibilidade de SAMET aos

outros tipos da linguagem. Infelizmente não existe uma solução directa para este problema que

respeite a relação de extensão ext entre interfaces. Contudo, existe uma técnica indirecta que

permite obter grande parte do efeito desejado através do uso de classes paramétricas. Trata-se

duma técnica conhecida na literatura [Coo89, Sha94, Sha95], que todo o programador de OM

teria vantagem em conhecer. Vamos ilustrá-la, adaptando um exemplo de Shang [Sha94].

Considere a duas classes food e animal com interfaces FoodI e AnimalI e geradoras dos tipos-

-objecto FoodT e AnimalT:

FoodT ˆ = OBJTYPE(ϒfood)

FoodI ˆ = INTERFACE(ϒfood)

food ˆ = classRfood

AnimalT ˆ = OBJTYPE(eat:FoodT→Nat, …)

AnimalI ˆ = INTERFACE(eat:FoodT→Nat, …)

animal ˆ = classeat=λx:FoodT.eeat, …

Vamos assumir que a classe animal contém um método eat que aceita um objecto do tipo FoodT

(um alimento a ingerir) e retorna um número natural (por exemplo, o número de mooooos de

satisfação emitidos pelo animal, se se tratar duma vaca).

Suponha agora que necessitamos de introduzir uma nova categoria de animais, um pouco

mais particular do que a definida pela classe animal: por exemplo, uma categoria de carnívoros.

Para isso criamos uma classe carnivoreAnimal na qual assumimos que os carnívoros comem ali-

mentos duma nova categoria FleshFoodT, sendo FleshFoodT≠FoodT.

FleshFoodT ˆ = OBJTYPE(ϒfood⊕ϒfleshfood)

FleshFoodI ˆ = INTERFACE(ϒfood⊕ϒfleshFood)

fleshFood ˆ = class\foodRfleshFood

CarnivoreAnimalT ˆ = OBJTYPE(eat:FleshFoodT→Nat, …)

CarnivoreAnimalI ˆ = INTERFACE(eat:FleshFoodT→Nat, …)

carnivoreAnimal ˆ = classeat=λx:FleshFoodT.eeat2, …

6 Polimorfismo paramétrico 105

Tecnicamente, não há qualquer problema com estas definições. Mas repare que evitámos

definir a classe carnivoreAnimal como subclasse de animal. Por uma boa razão: tal definição não

seria válida. Se a tentássemos tal definição, o tipo do argumento do método eat evoluiria da

classe animal para a classe carnivoreAnimal de forma covariante, o que iria contra a relação de

extensão ext: a relação ext exigiria que FleshFoodT fosse um supertipo de FoodT, o que é false.

Usando classes paramétricas, vejamos então como se consegue fazer com que a classe

carnivoreAnimal seja subclasse de animal e, ao mesmo tempo, tornar o parâmetro do método eat

covariante. Para isso temos de parametrizar as duas classes de animais em função dos tipos dos

alimentos que os animais de cada classe podem ingerir:

animal ˆ = λF≤*FoodI.classeat=λx:F.eeat, …

carnivoreAnimal ˆ = λF≤*FleshFoodI.class\animal[F] eat=λx:T.eeat2, …

Note que para que estas definições estejam bem tipificadas é necessário que a interface que

restringe o tipo dos alimentos dos carnívoros – FleshFoodI – seja mais estrita do que a interface

que restringe o tipo dos alimentos dos animais – FoodI – ou seja, FleshFoodI ext FoodI. Só assim

carnivoreAnimal[F], com F≤*FleshFoodI, pode ser subclasse de animal[F].

O nosso objectivo está cumprido. No entanto será instrutivo expandir um pouco mais o

exemplo anterior, considerando agora o problema da definição de uma classe conjunto de ani-

mais, animalSet, e duma sua subclasse conjunto de carnívoros: carnivoureAnimalSet. Para isso pre-

cisamos de usar classes duplamente paramétricas: a classe animalSet deve poder ser instanciada

com qualquer tipo de alimento F e com qualquer tipo de animal A que coma esse alimento; a

classe carnivoureAnimalSet deve poder ser instanciada com qualquer tipo de alimento F adequado

a carnívoros e com qualquer tipo de carnívoro A que coma esse alimento. Eis uma solução:

AnimalI ˆ = ∀F≤*FoodI.INTERFACE(eat:F→Nat, …)

CarnivoreAnimal ˆ = ∀F≤*FleshFoodI.INTERFACE(eat:F→Nat, …)

animalSet ˆ = λF≤*FoodI.λA≤*AnimalI[F].class…

carnivoreAnimalSet ˆ = λF≤*FleshFoodI.λA≤*CarnivoreAnimalI[F].class\animalSet[F][A]…

O problema da covariância a que dedicámos esta subsecção sugere-nos um comentário final

sobre a leitura lógica do tipo dos parâmetros dos métodos das classes animal. Na classe animal

original, não-paramétrica, podemos dizer que o tipo do argumento do método eat=λx:FoodT.eeat

está quantificado universalmente: realmente, todo o objecto da classe animal é obrigado a acei-

tar qualquer alimento do tipo FoodT, independentemente da categoria específica a que o animal

pertença. Já na versão final, paramétrica, da classe animal, o tipo do argumento do método

eat=λx:F.eeat pode considerar-se quantificado existencialmente: para cada categoria de animais,

é já possível especificar uma categoria de alimentos F adequada a esses animais.

106 OM – Uma linguagem de programação multiparadigma

6.2 Semântica de L6Nesta secção, vamos considerar a formalização dos tipos e dos termos de L6 através das cons-

truções de F+.

6.2.1 Semântica dos tipos e termosÉ particularmente simples a introdução em L6 da sua variedade de polimorfismo paramétrico.

A relação ≤*, usada para restringir os tipos-argumento das abstracções paramétricas de L6, tem

tradução imediata para F+ (cf. definição 5.2-11), o que significa que o polimorfismo paramétri-

co ≤*-restringido de L6 se pode reduzir de forma imediata ao polimorfismo paramétrico F-res-

tringido de F+.

Como ilustração, o significado do termo P de L6:

P ˆ = λT≤*EqI.λx:T.λs:set[T].e :∀T≤*EqI.T→et[T]→τ

é dado pelo seguinte termo, P′, de F+:

P′ = λT≤EqI[T].λx:T.λs:set[T].e :∀T≤EqI[T].T→et[T]→τ

6.2.2 Boa tipificação da instanciação com variáveis detipoCuriosamente, no contexto da linguagem L6, a relação de extensão entre interfaces ext (cf. de-

finição 5.2.2.2-1) reaparece no contexto da verificação da boa tipificação da instanciação de

abstracções paramétricas P com variáveis de tipo X: P[X].

Consideremos as duas abstracções paramétricas S e C:

S ˆ = λY≤*Is.e

C ˆ = λX≤*Ic.S[X]

Sob que condições é que a instanciação S[X], efectuada dentro da abstracção C, está bem tipifi-

cada? A condição necessária e suficiente é a seguinte, X≤*Ic⇒X≤*Is, ou seja as duas interfaces-

-limite, Ic e Is, envolvidas na instanciação devem estar relacionadas na propriedade:

Ic ext Is

Em L6, para além de variáveis de tipo introduzidas nas abstracções paramétricas existe ain-

da a variável de tipo predefinida SAMET. Este é o caso que analisamos seguidamente.

Consideremos novamente a abstracção paramétricas S:

S ˆ = λY≤*Is.e

Sob que condições é que a instanciação S[SAMET], efectuada dentro duma classe c, está bem

tipificada? As equações semânticas de L5 mostram que uma classe de L5 (e L6) se formaliza

6 Polimorfismo paramétrico 107

usando uma entidade paramétrica ≤*-restringida que está dependente dum tipo-parâmetro cha-

mado SAMET. Ora, na classe c, o tipo-parâmetro SAMET é introduzido em sujeito à, já familiar,

restrição SAMET≤*INTERFACE(ϒc), onde INTERFACE(ϒc) representa a interface da classe c. As-

sim, podemos concluir que SAMET poderá ser usado na instanciação de S sse for possível de-

duzir SAMET≤*Is de SAMET≤*INTERFACE(ϒc), ou seja sse a interface-limite Is verificar a con-

dição:

INTERFACE(ϒc) ext Is

6.3 Discussão sobre L6O polimorfismo paramétrico de L6 constitui um poderoso mecanismo cujas aplicações mais

importantes já foram ilustradas nas secções 6.1.1, 6.1.2, e 6.1.3. Uma outra possível aplicação

seria a parametrização duma classe em função dum tipo-objecto genérico, representativo de to-

das as possíveis extensões dessa mesma classe. Mas esta ideia já foi implicitamente incorpora-

da nas classes de L5: recordamos que a variável de tipo SAMET foi introduzida para represen-

tar esse mesmo tipo-objecto genérico.

Se na linguagem L5, a relação de compatibilidade ≤* era apenas usada nas equações semân-

ticas da linguagem, já na linguagem L6 ela passa a poder ocorrer explicitamente nos progra-

mas, mais exactamente na definição de entidades polimórficas. Relativamente a este uso explí-

cito de ≤*, lembramos que X≤*I é simples açúcar sintáctico para a asserção X≤I[X]. A ideia é

que quando se escreve X≤*I, fica subentendido que o significado de SAMET dentro de I é X.

Justifica-se a introdução desta notação pelo facto da expressão X≤*I ser menos complexa do

que a expressão X≤I[X]. Por exemplo, a primeira forma será certamente menos confusa e intri-

gante do que a segunda para o programador iniciado.

A utilização prática do polimorfismo paramétrico de L6, cria algumas necessidades novas a

que dedicaremos as duas subsecções seguintes. Felizmente essas necessidades conseguem ser

completamente satisfeitas no âmbito de L6, sem que seja necessário modificar a linguagem.

6.3.1 Operações dependentes do tipo-parâmetroNo contexto duma entidade paramétrica P ˆ = λX≤*INTERFACE(ϒB).e, por vezes sente-se a falta

duma constante do tipo X que possa ser usada na inicialização de variáveis locais do tipo X,

ou, então, sente-se a falta duma função de criação de objectos do tipo X que permita organizar

uma estrutura de dados complexa constituída por objectos do tipo X. Em geral, seria conve-

niente que no contexto de P, estivesse à disposição do programador um registo de constantes e

funções ops:OpsT[X] de tipo dependente de X, para serem usados consoante as necessidades.

Felizmente consegue-se resolver este problema usando apenas as construções de L6. Basta

adicionar à entidade paramétrica um segundo parâmetro ops, de tipo OpsT[X], ficando a entida-

de paramétrica com a seguinte nova configuração:

108 OM – Uma linguagem de programação multiparadigma

P ˆ = λX≤*I.λops:OpsT[X].e :∀X≤*I.OpsT[X]→σ

Se τ for um tipo-objecto tal que τ≤*I, e se r for um objecto do tipo OpsT[τ], então a aplicação

de P a τ e a r processa-se assim:

P[τ]r = e[τ/X,r/ops] :σ[τ/X]

Este problema tem interessantes ligações com os meta-objectos da linguagem L8 (cf. sec-

ção 8.3.1).

6.3.2 Polimorfismo paramétrico e coerçõesO mecanismo dos modos, a introduzir em L9 para ser usado na linguagem OM final, requer

que a linguagem OM disponha dum sistema de conversões implícitas de tipo (coerções) que,

em particular, permita converter o modo das expressões em função do contexto onde elas

ocorrem (cf. secções 9.1.2.2 e 10.1.3). A praticabilidade do mecanismo dos modos depende da

existência dum tal sistema de coerções, sendo o capítulo 10 dedicado ao desenvolvimento

deste sistema.

Para além destas conversões de tipo ligadas ao uso de expressões, precisamos ainda de per-

mitir que certas coerções possam ser aplicadas durante a instanciação de entidades paramétri-

cas, com o fim de legitimar algumas instanciações, à partida proibidas. Veremos um pouco

mais adiante, no exemplo 2 da secção 6.3.2.2, que a efectividade do mecanismo dos modos

requer a presença dum mecanismo de instanciação generalizada de entidades paramétricas,

devendo esse mecanismo usar coerções.

As questões ligadas à introdução e uso deste mecanismo de instanciação generalizada são

tópico da presente secção e respectivas subsecções. Na primeira subsecção, vamos discutir

abstractamente o problema e propor uma solução. Na segunda subsecção, ilustramos o nosso

método por meio de dois exemplos.

6.3.2.1 Problema e solução

Considere a entidade paramétrica genérica P:

P ˆ = λX≤*I.e :∀X≤*I.τ

Imagine que pretendemos instanciar P com um tipo υ que, não sendo imediatamente com-

patível com a interface-limite INTERFACE(ϒB), possa ser tornado compatível com ela por acção

duma coerção prevista na linguagem.

Vamos formalizar as condições desta hipotética instanciação, assumindo que no contexto Γ

da instanciação de P estão definidas as seguintes entidades:

- um tipo auxiliar σ, tal que Γ σ≤*I;

- a coerção Γ υ≤cσ;

- a função de conversão associada da coerção anterior: (Γ υ≤cσ) :υ→σ.

6 Polimorfismo paramétrico 109

A técnica que usamos para permitir a instanciação de P com υ, atribuindo assim um signifi-

cado à expressão, à partida errada, P[υ], envolve a incorporação de parametrização extra em P:

adicionamos-lhe um novo parâmetro de tipo A para representar o tipo auxiliar σ, e um novo

parâmetro c:C→A para representar a função de conversão (Γ υ≤cσ). Representemos por P′ esta

reescrita de P. Eis como fica P′:

P′ ˆ = λX≤*.λA≤*I.λc:X→A.e′ :∀X≤*.∀A≤*I.X→A.τ′

No corpo da nova abstracção, é assumido que fica disponível a coerção ∅,X≤*,A≤*I X≤cA

(com função de conversão associada c) para ser aplicada a qualquer expressão do tipo X, sem-

pre que necessário. Em P′ representámos por e′ a reescrita de e que resulta da acção desta coer-

ção.

A instanciação de P com υ, originalmente impossível, define-se agora da seguinte forma:

P[υ] ˆ = P′[υ][σ] (Γ υ≤cσ)

Chamaremos a esta forma de instanciação, instanciação generalizada com coerção.

Note que esta forma de alargar o âmbito de aplicação das entidades paramétricas de L6 por

meio da introdução de parametrização extra, pode ser automaticamente aplicada pelo compila-

dor da linguagem a todas as entidades paramétricas que ocorrem nos programas. Não existe o

perigo de pecar por excesso: a instanciação normal não é mais do que um caso particular da

instanciação generalizada, e pode sempre ser obtida usando a coerção identidade (Γ υ≤cυ),

como se mostra:

P[υ] ˆ = P′[υ][υ] (Γ υ≤cυ)

6.3.2.2 Exemplos

Descritos o problema e respectiva solução, vamos ilustrar o nosso método por meio de dois

exemplos: o primeiro, muito simples e um pouco artificial; o segundo mais realista e um

pouco mais complicado.

Exemplo1: Considere a interface-limite I, a função paramétrica P que depende dessa interface-

-limite, e o tipo υ:

I ˆ = INTERFACE(a:Int→Bool)

P ˆ = λX≤*I.b=λx:X.(x.a 1) :∀X≤*I.b:X→Bool

υ ˆ = a:Float→Bool

É fácil ver que neste caso a instanciação directa P[υ] não é possível. Vamos por isso aplicar

o método estudado na secção anterior, começando por introduzir o seguinte tipo auxiliar:

σ ˆ = a:Int→Bool

110 OM – Uma linguagem de programação multiparadigma

Este tipo é compatível com a interface-limite I e verifica a condição Γ υ≤cσ (o que é fácil de

verificar usando as regras da secção 10.3.2). Assim reescrevemos P como P′ e P[υ] como Pσ[υ]

da seguinte forma:

P′ ˆ = λX≤*.λA≤*I.λc:X→A.b=λx:X.((c x).a 1)

Pσ[υ] ˆ = P′[υ][σ] (Γ υ≤cσ)

E pronto! O problema ficou resolvido. Note como no corpo de P′ foi necessário aplicar a

coerção c:X→A ao argumento x:X.

Exemplo2: Neste segundo exemplo, considere a interface-limite EqI, um tipo υ compatível com

esta interface, e o tipo LazyT υ gerado pela classe lazy υ (cf. secção 9.2.2.2):

EqI ˆ = INTERFACE(eq:SAMET→Bool)

υ ˆ = eq:SAMET→Bool, …

LazyT υ = eq:υ→Bool, …

Suponhamos, que pretendíamos aplicar, o modo log ao tipo LazyT υ, num contexto Γ, com o

fim de obter a classe log LazyT υ Este é um desejo razoável pois o tipo LazyT υ tem uma

igualdade definida (originária de υ), e o requisito básico que o modo log impõe aos seus tipos-

-argumento é que definam uma igualdade. Além disso a igualdade do tipo LazyT υ pode ser

usada na maioria dos contextos sem que surja qualquer problema. É apenas no contexto da ins-

tanciação duma entidade paramétrica particular – o modo log – que surge um obstáculo: a in-

terface-limite deste modo é EqI, e o tipo LazyT υ não é tecnicamente compatível com ela. Por

esta razão, o (pré-)tipo log LazyT υ tem de ser considerado mal formado, à partida.

Problemas desta natureza têm de ser resolvidos, sob pena do mecanismo dos modos ficar

prejudicado por restrições de utilização que comprometem muito a sua utilidade prática. A so-

lução que propomos passa, mais uma vez, pelo recurso ao método da subsecção 6.3.2.1, embo-

ra, no caso presente tenhamos de o aplicar ao nível da equação semântica dos modos. Sendo a

equação original que define um modo na linguagem L9 a seguinte:

mode X≤*INTERFACE(ϒB).RM ˆ = λX≤*INTERFACE(ϒB).

classaccess(pub(ϒB)[X/SAMET]) + RM

vamos reescrevê-la da forma:

mode′ X≤*INTERFACE(ϒB).RM ˆ = λX≤*.λA≤*INTERFACE(ϒB).λc:X→A.

class′access(pub(ϒB)[X/SAMET]) + RM

No corpo da nova definição, a função de conversão c:X→A é implicitamente aplicada, sempre

que necessário, às expressões do tipo X que aí ocorram.

Para concluirmos a resolução do nosso problema inicial, temos de tirar ainda partido do

facto da asserção de coerção LazyT υ≤cυ ser válida para qualquer tipo υ. Definimos então o tipo

log LazyT υ da seguinte forma elegante:

log lazy υ ˆ = log′[LazyT υ][υ] (Γ LazyT υ≤cυ)

6 Polimorfismo paramétrico 111

Resolvido o problema, justificam-se alguns comentários finais sobre a essência do proble-

ma e da solução encontrada.

Para começar, note que o modo lazy não define qualquer igualdade da sua responsabilidade:

este modo limita-se a proporcionar um acesso indirecto à igualdade definida no seu objecto

conexo. Esse acesso indirecto revela-se suficiente na maioria das situações em que a igualdade

é usada. Contudo não foi suficiente para que o tipo log LazyT υ pudesse ser definido.

Solucionámos o problema através da criação dum acesso directo à igualdade definida no

objecto conexo. Efectivamente a função de conversão c:(LazyT υ)→υ, quando usada dentro do

modo log, não faz mais do que proporcionar este acesso: por exemplo, por sua acção, a expres-

são a=b, com a:LazyT υ e b:LazyT υ, é convertida na expressão (c a)=(c b), a qual já compara direc-

tamente dois elementos de υ, sendo υ≤*EqI.

6.4 ConclusõesNa literatura, a referência mais antiga a polimorfismo paramétrico restringido encontra-se no

trabalho de Cardelli e Wegner [CW85], tendo surgido no contexto duma tentativa de modeli-

zação de mecanismos de linguagens orientadas pelos objectos usando a relação de subtipo. A

restrição desta forma de polimorfismo baseava-se exactamente na relação de subtipo. As limi-

tações deste sistema (cf. secção 2.1.4) levaram à criação do polimorfismo paramétrico

F-restringido [CCH+89], o qual foi inicialmente usado implicitamente na modelização de he-

rança, mas rapidamente surgiu como mecanismo explicito em algumas linguagens: Emerald

[BHJL86], Sather [Omo92], k-bench [San93].

Nas linguagens, POLYToil [BSG95, BFSG98] e LOOM [BPF97], Bruce introduziu uma

relação de matching entre tipos-objecto e usou-a na definição semântica do mecanismo de

herança. Definiu depois uma forma de polimorfismo paramétrico baseada nessa relação. A re-

lação de matching trata os tipos-objecto como operadores de tipo parametrizados numa variá-

vel de tipo MyType (semelhante à nossa variável SAMET). Sendo essa relação de matching vir-

tualmente idêntica à relação de subtipo entre operadores de tipo (cf. regra [Sub Λ]), o poli-

morfismo baseado naquela relação é, na sua essência, polimorfismo restringido de ordem

superior (em que o limite superior é definido por um operador de tipo).

A nossa relação de extensão ext é diferente (mais geral) da relação de matching de Bruce.

Naturalmente, a nossa versão de polimorfismo paramétrico baseia-se na relação ext, ou mais

exactamente na relação prévia de compatibilidade ≤*, pois um dos nossos objectivos foi usar o

polimorfismo paramétrico como mecanismo de programação genérica para operar sobre todos

os objectos gerados pelas subclasses duma dada classe.

Neste capítulo julgamos ter ilustrado bem as aplicações mais importantes do polimorfismo

paramétrico ≤*-restringido da linguagem L6. De qualquer forma muitas destas aplicações não

diferem de forma essencial de aplicações semelhantes que ocorrem noutras formas de polimor-

112 OM – Uma linguagem de programação multiparadigma

fismo paramétrico. Isso é verdade nas situações em que o que importa é a capacidade genérica

de definir abstracções sobre variáveis de tipo, e não a forma particular das restrições de instan-

ciação a que essas abstracções estão submetidas.

No final deste capítulo resolvemos algumas questões complexas ligadas à compatibilização

mútua dos seguintes três mecanismos da linguagem OM: modos, coerções, polimorfismo para-

métrico ≤*-restringido.

Capítulo 7

Componentes privadas e variáveis deinstância

Sintaxe dos géneros,tipos e termos de L7

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ) |SELFT | GINTERFACE(ϒ) | IINTERFACE(ϒ) | SINTERFACE(ϒ) | IOBJTYPE(ϒ) |

∀X≤*INTERFACE(ϒB).τ |priv(ϒ) | pub(ϒ) | all(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | o.l | priv_new c |

checkType[τ] | downcastσ[τ] |

λX≤*INTERFACE(ϒB).e | P[τ] |priv(R) | pub(R) | all(R)

Tipos-registo parciais

priv(ϒc) ˆ = tipo-registo das componentes privadaspub(ϒc) ˆ = tipo-registo das componentes públicasall(ϒc) ˆ = ϒc = tipo-registo das componentes privadas e públicas

Semântica dos tipos

GINTERFACE(ϒc) :∗⇒∗ ˆ = interface global

ΛSAMET.ΛSELFT.ϒcIINTERFACE(ϒc) :∗⇒∗ ˆ = interface interna

ΛSAMET.ΛSELFT.all(ϒc) (= GINTERFACE(ϒc))SINTERFACE(ϒc) :∗⇒∗ ˆ = interface secreta

ΛSAMET.ΛSELFT.priv(ϒc)INTERFACE(ϒc) :∗⇒∗ ˆ = interface externa

ΛSAMET.pub(ϒc)IOBJTYPE(ϒc) :∗ ˆ = tipo-objecto interno

µSELFT.IINTERFACE(ϒc)[OBJTYPE(ϒc)][SELFT](= all(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT])

OBJTYPE(ϒc) :∗ ˆ = tipo-objecto externo

µSAMET.INTERFACE(ϒc)[SAMET] (= pub(ϒc)[OBJTYPE(ϒc)/SAMET])*Verifica-se: IOBJTYPE(ϒc)≤OBJTYPE(ϒc) (teorema 7.2.1-1)

114 OM – Uma linguagem de programação multiparadigma

CLASSTYPE(ϒc) :∗ ˆ = tipo-classe

∀SAMET≤*INTERFACE(ϒc).

∀SELFT≤*IINTERFACE(ϒc)[SAMET].(SELFT→SAMET)→

SELFT→IINTERFACE(ϒc)[SAMET][SELFT]

Semântica das relaçõesRelação binária de extensão geral de L7 entre interfaces globais

GINTERFACE(ϒc) gen_ext GINTERFACE(ϒs) ˆ = (T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs)) & SINTERFACE(ϒc)≤SINTERFACE(ϒs)

Registos parciais

priv(Rc) ˆ = registo das componentes privadaspub(Rc) ˆ = registo das componentes privadasall(Rc) ˆ = Rc = registo das componentes privadas e públicas

Semântica dos termos

class Rc :CLASSTYPE(ϒc) ˆ = λSAMET≤*INTERFACE(ϒc).

λSELFT≤*IINTERFACE(ϒc)[SAMET].λhide:SELFT→SAMET.

λself:SELFT.all(Rc)

class\s Rc :CLASSTYPE(ϒs⊕ϒc) ˆ = let S:CLASSTYPE(ϒs) = s in

λSAMET≤*INTERFACE(ϒs⊕ϒc).λSELFT≤*IINTERFACE(ϒs⊕ϒc)[SAMET].

λhide:SELFT→SAMET.

λself:SELFT.let super:IINTERFACE(ϒs)[SAMET][SELFT] =

(S[SAMET][SELFT] hide self) insuper+all(Rc)

*Restrição implícita: ϒs,ϒc devem ser tais que:GINTERFACE(ϒs⊕ϒc) gen_ext GINTERFACE(ϒs)

priv_new c :IOBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x) inlet gen:IOBJTYPE(ϒc)→IOBJTYPE(ϒc) = (C[OBJTYPE(ϒc)][IOBJTYPE(ϒc)] hide) in

let priv_o:IOBJTYPE(ϒc) = fix gen inpriv_o

new c :OBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let priv_o:IOBJTYPE(ϒc) = priv_new C inlet hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x) in

let o:OBJTYPE(ϒc) = hide priv_o ino

o.l :τ ˆ = let R:OBJTYPE(ϒc) = o in

R.l

o.l :τ ˆ = let R:IOBJTYPE(ϒc) = o in

R.l

7 Componentes privadas e variáveis de instância 115

Na primeira secção do presente capítulo, apresentamos e discutimos as principais ideias da

linguagem L7 a nível intuitivo. Depois, na secção 7.2, desenvolvemos as equações semânticas

de L7 e determinamos os requisitos para a sua boa tipificação. Na secção 7.3, introduzimos

uma versão de L7 com suporte para variáveis de instância mutáveis: esta versão imperativa de

L7 designa-se por L7& e são muitos os problemas técnicos que discutimos no seu âmbito. Fi-

nalmente, a secção 7.4 dá um pouco de perspectiva sobre a linguagem L7 e o seu modelo, rela-

tivamente a outras linguagens e modelos.

7.1 Conceitos e mecanismos de L7Na linguagem L7, introduzimos uma partição das componentes das classes e respectivos

objectos, passando a distinguir entre componentes privadas e componentes públicas. Por defi-

nição, as componentes privadas dum objecto são acessíveis apenas a partir do interior do pró-

prio objecto (usando o nome self), enquanto que as componentes públicas são acessíveis a par-

tir do interior e a partir do exterior do objecto. Os objectos passarão a ter dois tipos atribuídos

simultaneamente: um tipo interno constituído pelas assinaturas de todas as suas componentes,

privadas e públicas; e um tipo externo constituído pelas assinaturas das suas componentes

públicas.

Introduzimos também o novo nome de tipo SELFT no contexto de cada classe. O nome

SELFT será usado para representar o tipo interno de self. O nome SAMET continuará a ser

usado, tal como em L5 e L6, para representar o tipo externo de self. O significado de SELFT e

SAMET coincidirá nas classes que possuam exclusivamente componentes públicas.

Neste capítulo introduzimos ainda variáveis de instância mutáveis. Isso será feito numa

variante imperativa de L7, que designaremos por L7&. Tratamos desta questão separadamente,

porque a formalização das variáveis de instância requer o uso de artifícios técnicos que com-

plicam a semântica da linguagem, obscurecendo-a. A tabela das equações de L7& foi colocada

no final do capítulo, pelo facto das equações que a compõem não serem particularmente ilumi-

nantes. Formalizamos a linguagem L7& no sistema F+&, introduzido na secção 2.5.6.

Praticamente todas as linguagens de programação práticas orientadas pelos objectos inte-

gram alguma forma de encapsulamento. Isso não surpreende se pensarmos que alguns dos

mais importantes requisitos da Engenharia de Software – segurança, modificabilidade local,

abstracção – são alcançados por meio de encapsulamento. A linguagem CLOS [DG87] é um

raro exemplo que não suporta encapsulamento: esta linguagem inclui multimétodos, os quais

não são compatíveis com barreiras de acesso à funcionalidade dos objectos.

A maioria das linguagens práticas orientadas pelos objectos também suportam variáveis de

instância mutáveis: Smalltalk, C++, Modula-3, Java, Eiffel, Sather, Objective Caml [LRVD99,

RV98]. A maioria das linguagens teóricas associadas aos modelos teóricos referidos na secção

116 OM – Uma linguagem de programação multiparadigma

5.6 não tem estado mutável. Nessas linguagens, a dita actualização dos objectos realiza-se

produzindo cópias modificadas deles.

7.1.1 Formas de encapsulamentoRelativamente à ocultação da parte interna dos objectos, existem variações ao nível do local

onde a barreira de encapsulamento é colocada (cf. [FM96]). Nem todas as linguagens adoptam

a mesma solução. As linguagem Smalltalk e C++ são paradigmáticas relativamente às varian-

tes possíveis.

No caso da linguagem Smalltalk, uma barreira de encapsulamento envolve cada objecto,

logo desde o momento da sua criação. Assim, garante-se que as suas componentes privadas

nunca serão acedidas a partir doutros objectos.

Esta forma de encapsulamento é flexível no sentido em que permite que objectos do mesmo

tipo possam ser internamente diferentes. Podemos, por exemplo, introduzir um tipo-objecto

Matriz, único, mas implementar os seus valores usando duas classes distintas: uma especializa-

da na representação de matrizes densas e outra especializada na representação de matrizes

esparsas. No que diz respeito a informação de interface, não há qualquer problema que as duas

classes tenham interfaces internas distintas.

No caso da linguagem C++, a barreira de encapsulamento é colocada à volta de cada classe

e não dos objectos individuais, comportando-se cada classe como um tipo abstracto de dados.

Desta forma, no contexto da sua classe-mãe, os objectos têm conhecimento da estrutura priva-

da dos objectos irmãos. Inclusivamente, um objecto que tenha abandonado os limites da clas-

se-mãe e regresse ao seu contexto (como argumento dum método, por exemplo) continua a ser

reconhecido como um objecto dessa mesma classe.

Nesta forma de encapsulamento perde-se a vantagem da independência da parte privada

dos tipos-objecto. De facto, todas as classe que implementam um dado tipo-objecto são obri-

gadas a ter a mesma interface interna. Mas não há só desvantagens, já que o conhecimento da

interface interna traz algumas vantagens não desprezíveis: os métodos binários ficam com

acesso à parte privada dos seus argumentos; permite-se a inicialização de objectos a partir de

objectos irmãos (no contexto da classe-mãe); é possível aumentar a qualidade do código gera-

do pelo compilador (por existir mais informação de tipo disponível).

O C++ obriga ainda cada tipo-objecto a ser implementado numa única classe, mas esta é

outra questão.

A linguagem L7 adopta uma visão da encapsulamento próxima da visão do Smalltalk, mas

pontualmente influenciada pela visão do C++. Assim, em L7, tal como em Smalltalk, existe

uma barreira de encapsulamento intransponível protegendo a parte privada de cada objecto.

Abrimos, no entanto, uma excepção para objectos criados no contexto da sua própria classe-

-mãe. Um objecto criado no contexto da sua classe-mãe fica com a instalação da sua barreira

7 Componentes privadas e variáveis de instância 117

de encapsulamento adiada. Só a recebe mais tarde, se e quando for transferido para um con-

texto sintáctico exterior à classe-mãe: por exemplo, se for retornado por um método público.

Depois de recebida a barreira de encapsulamento a parte privada do objecto fica definitiva-

mente oculta: mesmo que o objecto regresse mais tarde ao seio da classe-mãe, a sua parte pri-

vada já não poderá ser acedida pelos irmãos.

Esta variante de encapsulamento facilita a inicialização de objectos, particularmente por

parte dos construtores da linguagem L8. Além disso não compromete o seguinte princípio fun-

damental do Smalltalk que perfilhamos: “objectos do mesmo tipo devem poder ter partes pri-

vadas distintas”.

Entrando agora em detalhes, fora do contexto da sua classe-mãe, um objecto é sempre

criado com barreira, ou seja com um tipo externo, usando o operador new. Dentro do contexto

da sua classe-mãe, um objecto é sempre criado sem barreira, ou seja com o tipo interno SELFT,

usando o operador priv_new. Relativamente à colocação da barreira nos objectos com tipo

SELFT, sempre que um deles é usado num contexto que requer uma expressão do tipo SAMET,

aplica-se ao objecto uma coerção hide:SELFT→SAMET, que faz com que o objecto passe a ter

associado um tipo externo.

O tipo interno SELFT está proibido de ocorrer na assinatura das componentes públicas das

classes (cf. secção 7.1.3). Basta esta regra para garantir que objectos com tipo SELFT não pos-

sam escapar do contexto da classe-mãe.

7.1.2 Nomeação das componentes das classesNa nomeação das componentes das classes adoptaremos as seguintes convenções: os nomes

das componentes privadas distinguem-se por serem prefixados com “priv_”; os nomes das

componentes públicas distinguem-se por não serem prefixados com “priv_”.

Considerando um tipo-registo ϒ, usaremos a notação priv(ϒ) para representar o tipo-registo

parcial das componentes privadas de ϒ. A notação pub(ϒ) será usada para representar o tipo-re-

gisto parcial das componentes públicas de ϒ. Usaremos ainda a notação all(ϒ) para representar

o tipo-registo de todas as componentes de ϒ, ou seja o próprio ϒ. Portanto:

ϒ = priv(ϒ) ⊕ pub(ϒ)

ϒ = all(ϒ)

Relativamente aos registos, adoptamos convenções análogas. Assim, dado um registo R,

introduzimos os registos priv(R), pub(R) e all(R) de forma idêntica. As seguintes equivalências de

termos são válidas para qualquer registo R:

R = all(ϒ) = priv(R) + pub(R)

Como ilustração, eis uma classe com duas componentes privadas, priv_x e priv_y:

118 OM – Uma linguagem de programação multiparadigma

pointC ˆ = class priv_x=0, priv_y=0,

sum=self.priv_x+self.priv_y,

priv_eq=λa:SELFT.(self.priv_x=a.priv_x & self.priv_y=a.priv_y)

7.1.3 Tipo externo e tipo internoEm L7, por razões técnicas, precisamos de associar dois tipos a cada objecto: um tipo-objecto

externo, que representa a visão externa do objecto e expõe apenas as suas componentes públi-

cas, e um tipo-objecto interno, que representa a visão interna do objecto e expõe as suas com-

ponentes públicas e privadas.

Se ϒc for o tipo-registo das componentes dum objecto, o tipo interno desse objecto é repre-

sentado por IOBJTYPE(ϒc) e o seu tipo externo é representado por OBJTYPE(ϒc). Para todo o

objecto, os seus tipos interno e externo verificam a condição: OBJTYPE(ϒc)≤IOBJTYPE(ϒc) (cf.

teorema 7.2.1-1):

No contexto dum tipo-objecto externo recursivo, OBJTYPE(ϒc), o nome SAMET tem um

significado fixo: ele representa uma referência recursiva ao próprio tipo-objecto externo. O

nome SELFT está proibido de ocorrer nos tipos-objecto externos, pelas razões que apresenta-

mos no final desta secção.

No contexto dum tipo-objecto interno recursivo, IOBJTYPE(ϒc), o nome SELFT tem um

significado fixo: ele representa uma referência recursiva ao próprio tipo-objecto interno. O

nome SAMET também tem a liberdade de ocorrer num tipo-objecto interno onde, convencio-

nalmente, representa o correspondente tipo-objecto externo.

No contexto duma classe, os nomes SAMET e SELFT têm um significado aberto: eles repre-

sentam o tipo externo e o tipo interno de self. Como sabemos a interpretação das ocorrências

de self no código herdado varia através das subclasses.

Os tipos internos da forma IOBJTYPE(ϒc) são tipos técnicos, introduzidos por razões técnicas

de definição da linguagem. Não se permite que sejam explicitamente usados nos programas. Já

os tipos externos da forma OBJTYPE(ϒc) não são tipos técnicos, e podem ser livremente usados

nos programas.

O tipo SELFT é considerado um tipo interno, e SAMET um tipo externo. Eles não são tipos

técnicos, e podem ser usados nos programas.

Como ilustração, o tipo interno e o tipo externo dos objectos da classe pointC, da secção

anterior, são respectivamente:

pointTi ˆ = IOBJTYPE(priv_x:Nat, priv_y:Nat, sum:Nat, priv_eq:SELFT→Bool)

pointT ˆ = OBJTYPE(priv_x:Nat, priv_y:Nat, sum:Nat, priv_eq:SELFT→Bool)

= OBJTYPE(sum:Nat)

É essencialmente por uma questão de consistência que se proíbe a ocorrência do nome

SELFT em qualquer tipo externo OBJTYPE(ϒc). Se SELFT ocorresse no tipo duma componente

7 Componentes privadas e variáveis de instância 119

pública dum objecto particular x desse tipo, digamos numa componente p:Nat→SELFT, então a

mensagem (x.p 3) só poderia ser tipificada num contexto em que o tipo interno de x fosse

conhecido: ou seja, apenas dentro da classe de x. Mas nesse caso, p ficaria com as limitações

de acesso duma componente privada, o que seria inconsistente com o facto da componente p

ter sido introduzida como componente pública e de estar identificada como tal.

7.1.4 Interfaces global, externa, interna e secretaEm L7, associamos quatro interfaces a cada classe: uma interface global que contém as assina-

turas de todas as componentes da classe, uma interface externa que contém apenas as assina-

turas das componentes públicas da classe, uma interface interna que contém as assinaturas das

componentes públicas e privadas da classe, e uma interface secreta que contém apenas as as-

sinaturas das componentes privadas da classe.

Em L7 as interfaces global e interna coincidem, mas em L8 já não será assim.

Se ϒc for o tipo-registo das componentes duma classe, então a respectiva interface global

representa-se por GINTERFACE(ϒc), a respectiva interface externa por INTERFACE(ϒc), a

respectiva interface interna por IINTERFACE(ϒc), e a respectiva interface secreta representa-se

por SINTERFACE(ϒc). Note que, como uma interface global GINTERFACE(ϒc) tem todas as com-

ponentes de ϒc, ela consegue determinar univocamente as restantes três interfaces: a externa, a

interna e a secreta.

Numa interface externa o nome SAMET pode ocorrer, não sendo à partida alvo de qualquer

interpretação particular (cf. 4.1.4). O nome SELFT está proibido de ocorrer numa interface ex-

terna.

Numa interface global, interna ou secreta, os nome SAMET e SELFT podem ocorrer, não sen-

do à partida alvo de qualquer interpretação particular.

Uma classe com interface global GINTERFACE(ϒc) é uma classe geradora de objectos com

tipo externo OBJTYPE(ϒc) e com tipo interno IOBJTYPE(ϒc).

É importante notar que, se o nome SELFT não pode ocorrer na interface externa duma clas-

se, ele já tem liberdade de ocorrer nas outras interfaces, e também no corpo dos métodos

privados e públicos. Aliás, nem que seja implicitamente, o tipo SELFT é usado para tipificar self

no contexto de todos os método, privados e públicos. Quanto ao nome SAMET, ele tem a liber-

dade de ocorrer nas quatro interfaces e no corpo de todos os métodos.

7.1.5 SELFT, SAMET e herançaNo contexto duma classe, os nomes SELFT e SAMET representam, respectivamente, o tipo

interno e o tipo externo de self. Os tipos SELFT e SAMET dizem-se tipos de significado aberto,

120 OM – Uma linguagem de programação multiparadigma

pois são ambos reinterpretados nas componentes herdadas, aliás, como já acontecia com

SAMET na linguagem L5.

Todas as questões que na linguagem L5 envolviam o nome SAMET e estavam ligadas às

tensões entre o mecanismo de herança e a relação de subtipo nessa linguagem não mudam em

L7: concretamente, continuam apenas a envolver componentes públicas e o nome SAMET. O

motivo é o seguinte: na linguagem L7, tal como pretendemos que ela seja vista pelo programa-

dor, a relação de subtipo entre tipos-objecto envolve apenas tipos externos.

Metodologicamente, em L7 deveremos tirar o máximo partido de tipificação aberta, ou seja

dos tipos SAMET e SELFT, na parte privada das classes. Assim se maximiza o potencial de reu-

tilização do código privado herdado sem que, com isso, se comprometa a geração de subtipos

pelas subclasses!

Na linguagem L7, continuará a ser garantida a validade do princípio da “reutilização sem

reverificação”, mas agora aplicado tanto ao código herdado privado como ao código herdado

público. O cumprimento desse princípio é garantido através duma relação de extensão geral,

gen_ext (cf. definição 7.2.2.2-1) que define a boa forma das classes que podem ser definidas

em L7 usando herança. A nova relação generaliza a relação de extensão ext de L5.

7.2 Semântica de L7Nesta secção, formalizamos e discutimos a semântica da linguagem L7. A tabela das equações

semânticas de L7 é apresentada no início do presente capítulo.

7.2.1 Semântica dos tiposNeste ponto, discutimos a codificação das interfaces externas e internas das classe, dos tipos-

-objecto externos e internos, e dos tipos-classe.

GINTERFACE(ϒc): Representa a interface global duma classe com componentes ϒc. Nesta

interface consideramos todas as componentes da classe. Os nomes SAMET e SELFT podem

ocorrer numa interface global. A formalização é ΛSAMET.ΛSELFT.ϒc. Em L7 a interface global

coincide com a interface interna.

INTERFACE(ϒc): Representa a interface externa duma classe com componentes ϒc. Nesta

interface consideramos apenas as componentes públicas da classe. O nome SAMET pode ocor-

rer numa interface externa. Formaliza-se por meio do operador de tipo ΛSAMET.pub(ϒc).

IINTERFACE(ϒc): Representa a interface interna duma classe com componentes ϒc. Nesta

interface consideramos as componentes privadas e públicas da classe, i.e. todas as componen-

tes. Os nomes SAMET e SELFT podem ocorrer numa interface interna. A formalização deste ti-

po de L7 é ΛSAMET.ΛSELFT.all(ϒc). Em L7 a interface interna é equivalente à interface global.

7 Componentes privadas e variáveis de instância 121

SINTERFACE(ϒc): Representa a interface secreta duma classe com componentes ϒc. Os

nomes SAMET e SELFT podem ocorrer numa interface secreta. A formalização é

ΛSAMET.ΛSELFT.priv(ϒc).

OBJTYPE(ϒc): Representa a faceta externa do tipo dos objectos gerados por uma classe com

interface externa INTERFACE(ϒc). Tal como em L5, trata-se do seguinte tipo-objecto recursivo

na variável SAMET: µSAMET.INTERFACE(ϒc)[SAMET]. Note que este tipo-objecto pode ser defi-

nido antes do tipo-objecto interno associado à mesma classe estar definido.

IOBJTYPE(ϒc): Representa a faceta interna do tipo dos objectos gerados por uma classe

com interface interna IINTERFACE(ϒc). É um tipo-objecto recursivo na variável SELFT. No seu

contexto, o nome SAMET está predefinido como OBJTYPE(ϒc). A formalização do tipo-objecto

interno é portanto: µSELFT.IINTERFACE(ϒc)[OBJTYPE(ϒc)][SELFT].

CLASSTYPE(ϒc): Representa o tipo-classe associado a uma classe com interface interna

IINTERFACE(ϒc) e interface externa INTERFACE(ϒc). Na sua formalização há que considerar a

introdução de várias entidades: o tipo aberto SAMET, o tipo aberto SELTF, uma função de coer-

ção chamada hide com o tipo SELFT→SAMET, e ainda o nome self. Como a herança envolve

tanto componentes públicas como componentes privadas, as classes são tratadas como entida-

des que revelam todas as suas componentes. A ocultação da parte privada dos objectos gerados

é efectuada só depois da criação dos objectos, por meio da coerção hide.

O resto desta secção é quase todo dedicado à pormenorização dos detalhes da formalização

do tipo-classe CLASSTYPE(ϒc).

Começamos por descrever como o nome SAMET é introduzido. Tal como em L5, este nome

é introduzido como um tipo-objecto externo genérico usando ∀SAMET≤*INTERFACE(ϒc). Quan-

tificado desta forma, SAMET representa todos os tipos-objecto externos gerados pelas subclas-

ses duma classe com tipo CLASSTYPE(ϒc).

O nome SELFT é introduzido no contexto da quantificação de SAMET, como um tipo-objec-

to interno genérico usando a quantificação ∀SELFT≤*IINTERFACE(ϒc)[SAMET]. Sob esta quanti-

ficação, SELFT representa todos os tipos-objecto internos gerados pelas subclasses duma classe

com tipo CLASSTYPE(ϒc).

Seguidamente, o tipo-classe CLASSTYPE(ϒc) está parametrizado por uma coerção do tipo

SELFT→SAMET que se destina a ser aplicada sempre que um termo do tipo SELFT, por exemplo

self, seja usado num contexto onde um termo do tipo SAMET seja esperado.

Finalmente, como sabemos, toda a classe tem de estar parametrizada em função do signifi-

cado de self. Assim, o último parâmetro de CLASSTYPE(ϒc) será SELFT, i.e. o tipo que atribuí-

mos a self.

Uma classe do tipo CLASSTYPE(ϒc) gera objectos do tipo externo IOBJTYPE(ϒc). Mas, devido

ao mecanismo da herança, seria prematuro atribuir este tipo aos resultados de CLASSTYPE(ϒc).

Na realidade, temos de nos preocupar, não só com o tipo dos objectos gerados pela classe cor-

122 OM – Uma linguagem de programação multiparadigma

rente, mas também com o tipo dos objectos gerados pelas suas subclasses. Vamos escolher pa-

ra tipo dos resultados IINTERFACE(ϒc)[SAMET][SELFT], onde SAMET e SELFT estão quantifica-

dos da forma anteriormente apresentada. Para confirmar a justeza desta escolha verifiquemos o

caso particular dos objectos gerados pela classe corrente. Tomando, SAMET≡OBJTYPE(ϒc) e

SELFT≡IOBJTYPE(ϒc), obtemos:

IINTERFACE(ϒc)[SAMET][SELFT]

= IINTERFACE(ϒc)[OBJTYPE(ϒc)][IOBJTYPE(ϒc)]

= IOBJTYPE(ϒc)

Juntando todos estes elementos, obtemos como codificação para CLASSTYPE(ϒc):

∀SAMET≤*INTERFACE(ϒc).

∀SELFT≤*IINTERFACE(ϒc)[SAMET].

(SELFT→SAMET)→SELFT→

IINTERFACE(ϒc)[SAMET][SELFT]

Para terminar esta subsecção, mostramos seguidamente que o par de tipos IOBJTYPE(ϒc),

OBJTYPE(ϒc) se encontra na relação de subtipo.

Teorema 7.2.1-1 IOBJTYPE(ϒc) ≤ OBJTYPE(ϒc).

Prova: Por unfolding, estes dois tipos podem ser assim reescritos:

OBJTYPE(ϒc) ˆ = µSAMET.INTERFACE(ϒc)[SAMET]

= pub(ϒc)[OBJTYPE(ϒc)/SAMET]

IOBJTYPE(ϒc) ˆ = µSELFT.IINTERFACE(ϒc)[OBJTYPE(ϒc)][SELFT]

= all(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

Ora SELFT não ocorre em OBJTYPE(ϒc), pelo que OBJTYPE(ϒc) pode ser vacuamente reescri-

to da seguinte forma:

OBJTYPE(ϒc) = pub(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

A asserção que se pretende provar fica assim:

all(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

≤ pub(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

Mas esta asserção resulta imediatamente da aplicação da regra [Sub …].

7.2.2 Semântica dos termosAnalisamos agora as definições dos termos de L7, começando pelas classes. Na subsecção

7.2.2.2 estabelecemos as regras de boa formação das subclasses em L7, e na subsecção 7.2.2.4

discutimos o nosso método de ocultação da parte privada dos objectos.

7 Componentes privadas e variáveis de instância 123

7.2.2.1 Semântica das classes

Na definição do termo class Rc, o gerador polimórfico aí introduzido generaliza de forma ime-

diata o termo correspondente de L5. Este gerador, parametrizado relativamente a SAMET,

SELFT, hide e self, produz objectos com tipo-objecto interno e constituídos por todas as compo-

nentes existentes, privadas e públicas. Como é regra na definição de qualquer forma derivada

de F+, a definição de class Rc faz implicitamente a validação do código da classe: se, porven-

tura, o registo Rc estiver mal tipificado, então a classe class Rc está mal tipificada.

O tratamento da herança é formalizado na equação do termo subclasse imediata class\s Rc a

qual generaliza a definição correspondente de L5. O gerador introduzido na equação está para-

metrizado em função dos nomes SAMET, SELFT, hide e self, especificamente introduzidos no

contexto da subclasse. Quanto ao código herdado da superclasse, este é adaptado ao contexto

da subclasse, aplicando S, o gerador da superclasse, aos nomes SAMET, SELFT, hide e self da

subclasse. Finalmente, o registo resultante, a que se chama super, é estendido com as compo-

nentes específicas da subclasse, produzindo-se finalmente super+Rc. Assim se cria um novo ge-

rador por extensão dum gerador existente.

7.2.2.2 Boa formação das subclasses

Consideremos agora a questão da boa tipificação do gerador que codifica o termo class\s Rc.

Neste caso, a questão que mais importa investigar é a da boa tipificação do termo que adapta

as componentes da superclasse ao contexto da subclasse:

super = S[SAMET][SELFT] hide self

Neste termo, o gerador polimórfico S correspondente à superclasse espera um tipo-objecto

SAMET que obedeça à condição SAMET≤*INTERFACE(ϒs), e ainda um tipo-objecto SELFT que

obedeça à condição SELFT≤*IINTERFACE(ϒc)[SAMET]. No entanto S é aplicado a tipos-objecto

SAMET e SELFT tais que SAMET≤*INTERFACE(ϒs⊕ϒc) e SELFT≤*IINTERFACE(ϒs⊕ϒc)[SAMET].

Portanto, para que a aplicação de S esteja bem tipificada, é necessário que os tipos-registo ϒs,

ϒc sejam tais que as seguintes condições fiquem garantidas:

SAMET≤*INTERFACE(ϒs⊕ϒc) ⇒ SAMET≤*INTERFACE(ϒs)

SAMET≤*INTERFACE(ϒs⊕ϒc) ⇒(SELFT≤*IINTERFACE(ϒs⊕ϒc)[SAMET] ⇒ SELFT≤*IINTERFACE(ϒs)[SAMET])

Estas são portanto as condições mais fracas que garantem a boa tipificação de S. No en-

tanto, veremos que estas condições podem ser substituídas com vantagem pelo seguinte par de

condições que, conjuntamente, são um pouco mais fortes (menos gerais) que as anteriores (cf.

teorema 7.2.2.2-3):

SAMET≤*INTERFACE(ϒs⊕ϒc) ⇒ SAMET≤*INTERFACE(ϒs)

SINTERFACE(ϒs⊕ϒc)≤SINTERFACE(ϒs)

124 OM – Uma linguagem de programação multiparadigma

A primeira condição deste par é idêntica à condição que obtivemos em L5, em situação

idêntica. A segunda condição, está expressa usando a relação de subtipo entre operadores de

tipos duplamente parametrizados (cf. regra [Sub Λ]), sendo por isso particularmente simples de

usar e de validar. Aparentemente, ela sofre da desvantagem de bloquear todas as decisões de

tipificação (aberta ou fechada) tomadas na parte privada das classes, tanto relativamente a

SAMET como a SELFT. Felizmente isso não é problema: como aprendemos na secção 7.1.5, as

decisões de tipificação tomadas na parte privada das classes de L7 não afectam a capacidade

das suas subclasses gerarem subtipos. É assim, com agrado, que dispensamos a utilização da

complicada condição original.

Conjugando as duas condições anteriores, a primeira definida sobre interfaces externas e a

segunda definida sobre interfaces secretas, obtemos uma condição unificada sobre interfaces

globais a que chamaremos relação de extensão geral:

Definição 7.2.2.2-1 (Relação de extensão geral) Chamamos relação de extensão

geral, gen_ext, à relação binária entre interfaces globais que se define, por tradução para F+, da

seguinte forma:

GINTERFACE(ϒc) gen_ext GINTERFACE(ϒs) ˆ = (T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs)) & SINTERFACE(ϒc)≤SINTERFACE(ϒs)

Esta condição define uma relação binária no conjunto das interfaces globais, estabelecendo

quais são as modificações de interface que originam subclasses bem formadas. A relação é

reflexiva e transitiva (cf. teorema 7.2.2.2-2), o que é essencial para que a relação de subclasse

se mantenha reflexiva e transitiva em L7.

Sinteticamente, dizemos que, em L7, “herança implica extensibilidade geral” pois sem a

verificação da restrição de extensão geral entre interfaces globais, não seria possível ter

herança no modelo.

Note que a relação de extensão geral gen_ext degenera na relação de extensão ext de L5, no

caso de classes só com componentes públicas.

Teorema 7.2.2.2-2 A relação de extensão geral gen_ext é reflexiva e transitiva.

Prova: A demonstração é trivial. Sejam GAI ˆ = GINTERFACE(ϒA), GBI ˆ = GINTERFACE(ϒB),

GCI ˆ = GINTERFACE(ϒC). Sejam AI ˆ = INTERFACE(ϒA), BI ˆ = INTERFACE(ϒB), CI ˆ = INTERFACE(ϒC).

Sejam SAI ˆ = SINTERFACE(ϒA), SBI ˆ = SINTERFACE(ϒB), SCI ˆ = SINTERFACE(ϒC).

A relação gen_ext é reflexiva pois a asserção

GAI gen_ext GAI

é equivalente à tautologia

(T≤*AI ⇒ T≤*AI) & SAI≤SAI

Para verificarmos a transitividade vamos assumir:

7 Componentes privadas e variáveis de instância 125

GAI gen_ext GBI

IBI gen_ext GCI

ou seja:(T≤*AI ⇒ T≤*BI) & SAI≤SBI

(T≤*BI ⇒ T≤*CI) & SBI≤SCI

Daqui, por transitividade de ⇒ e de ≤ obtemos:

(T≤*AI ⇒ T≤*CI) & SAI≤SCI

ou seja:GAI gen_ext GBI

Teorema 7.2.2.2-3 A relação de extensão geral gen_ext garante a boa formação das classes

em L7.

Prova: O lema 5.3-6 foi introduzido especificamente para simplificar esta demonstração, a

qual está longe de ser trivial. Considere as interfaces: Ic ˆ = INTERFACE(ϒc), IIc ˆ = IINTERFACE(ϒc),

SIc ˆ = SINTERFACE(ϒc), Is ˆ = INTERFACE(ϒs), IIs ˆ = IINTERFACE(ϒs), SIs ˆ = SINTERFACE(ϒs). Antes de

começarmos, note que a condição:

GNTERFACE(ϒc) gen_ext GNTERFACE(ϒs)

se reescreve, por definição, na condição:

SAMET≤*Ic ⇒ SAMET≤*Is & SIc≤SIs [1]

e que também se reescreve, pelo lema 5.3-6, na condição:

SAMET≤*Ic ⇒ Ic[SAMET]≤Is[SAMET] & SIc≤SIs [2]

Vamos provar que as condições de [1] ou [2] são suficientes para garantir as condições ge-

rais de boa tipificação deduzidas no início da corrente subsecção. Recordamos que essas con-

dições são as seguintes:

SAMET≤*Ic ⇒ SAMET≤*IsSAMET≤*Ic ⇒

(SELFT≤*IIc[SAMET] ⇒ SELFT≤*IIs[SAMET])

A primeira destas duas condições já faz parte de [1]. Assim só precisamos de provar a

segunda.

Começamos, então, por assumir o antecedente SAMET≤*Ic. Note que a partir dele e da pri-

meira parte de [2], se extrai imediatamente a conclusão:

Ic[SAMET]≤Is[SAMET] [3]

Vamos agora partir de SELFT≤*IIc[SAMET] para finalmente chegarmos a SELFT≤*IIs[SAMET]:

126 OM – Uma linguagem de programação multiparadigma

SELFT≤*IIc[SAMET]

⇒ SELFT≤*Ic[SAMET] & SELFT≤*SIc[SAMET] particionando a interface IIc (em Ic e SIc)

⇒ SELFT≤*Is[SAMET] & SELFT≤*SIc[SAMET] por [3] e [Sub trans]

⇒ SELFT≤*Is[SAMET] & SELFT≤*SIs[SAMET] pela segunda parte de [2] e [Sub trans]

⇒ SELFT≤*IIs[SAMET] agrupando Is e SIs em IIs

7.2.2.3 Semântica dos outros termos

Nesta subsecção, explicamos a codificação dos três termos que permitem a criação de novos

objectos e o acesso a esses objectos.

Dentro duma classe c, o termo priv_new c permite criar objectos dessa mesma classe com

tipo interno IOBJTYPE(ϒc). Para isso, o gerador polimórfico C correspondente à classe c é ins-

tanciado com: (1) o tipo-objecto externo gerado pela classe; (2) o tipo-objecto interno gerado

pela classe; (3) uma função de coerção, chamada função de ocultação do tipo IOBJTYPE(ϒc)→

OBJTYPE(ϒc). Desta instanciação obtém-se um gerador monomórfico do tipo IOBJTYPE(ϒc)→

IOBJTYPE(ϒc), ao qual se aplica o operador de ponto fixo, para se obter um novo objecto com

tipo interno.

O termo new c serve para criar a partir duma classe c, objectos dessa classe com tipo exter-

no. Começa por criar um objecto com tipo interno, usando o termo priv_new c, e depois aplica

ao objecto resultante a função de ocultação descrita na secção seguinte.

O termo que permite o acesso às componentes de objectos com tipo externo ou tipo interno

– da forma o.l em ambos os casos –, traduz-se num simples acesso a uma componente dum re-

gisto.

Um comentário final sobre a necessidade de introduzir uma função de coerção na definição

geral de classe. Já vimos que o termo priv_new c instancia o gerador polimórfico C com os tipos

OBJTYPE(ϒc) e IOBJTYPE(ϒc). Ora este facto garante imediatamente que no interior dos objectos

gerados se irá verificar sempre a condição SELFT≤SAMET (cf. teorema 7.2.1-1). Era óptimo que

fosse possível incorporar esta condição directamente na definição geral de classe: assim, fica-

ria elegantemente resolvido o problema da ocultação da parte privada dos objectos de tipo

SELFT (quando usados num contexto de tipo SAMET). Infelizmente não é possível fazer isso, já

que as variáveis SELFT e SAMET são introduzidas quantificadas e o sistema F+ não prevê a acu-

mulação de restrições sobre variáveis. Assim, tivemos de resolver o problema da ocultação

com a ajuda duma função de coerção.

7.2.2.4 Função de ocultação

A função de ocultação hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) que é introduzida nas equações dos ter-

mos new c e priv_new c serve para ocultar a parte privada dos objectos criados. No caso do

termo new c, a função de ocultação é aplicada imediatamente ao objecto, logo após a sua

7 Componentes privadas e variáveis de instância 127

criação. No caso do termo priv_new c, a função de ocultação é usada como argumento de instan-

ciação da classe c, para aplicação diferida ao objecto criado.

Definimos a função de ocultação de forma simples, com base em duas propriedades da lin-

guagem L7: (1) a propriedade da perda de informação, descrita na secção 4.3.2; (2) a proprie-

dade de IOBJTYPE(ϒc) ser subtipo de OBJTYPE(ϒc). Concretamente, para ocultar a parte privada

dum objecto do tipo IOBJTYPE(ϒc), fazemos a promoção do seu tipo interno ao supertipo

OBJTYPE(ϒc); o conservadorismo do sistema de tipos estático encarrega-se então de garantir a

privacidade das componentes secretas do objecto. Eis a definição da função de ocultação:

hide ˆ = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x)

No entanto, note que existe um potencial problema nesta abordagem. Trata-se da existência

da operação dinâmica de despromoção de tipo, introduzida na secção 4.3.5. Essa operação foi

inventada, exactamente para ultrapassar o problema da perda de informação (cf. secção 4.3.2).

A solução para propomos para impedir a recuperação do tipo interno de objectos com tipo

público é simples: introduzimos a seguinte restrição sobre a utilização da operação de des-

promoção de tipo.

Restrição 7.2.2.4-1 A operação downcast[τ] só podem ser aplicada a tipos externos.

Curiosamente, esta restrição só tem aplicação real no caso particular do tipo interno SELFT.

Todos os outros tipos internos são tipos técnicos (cf. secção 7.1.3), e já estavam proibidos de

ser explicitamente usados nos programas, em particular como argumentos de downcast.

Uma forma alternativa de definir a função de ocultação é a seguinte:

hide ˆ = λx:IOBJTYPE(ϒc).+[OBJTYPE(ϒc)‚](x, ) (concatenação de registos cf. secção 2.5.5)

= λx:IOBJTYPE(ϒc). p1=x.p1‚…‚pn=x.pn (onde p1…pn são as componentes públicas de x)

Esta função toma um objecto x com tipo interno, e cria um objecto novo (um novo registo) só

com métodos públicos, que se limita a ser uma porta de acesso indirecto e controlado a x. O

novo objecto está definido de tal forma que todas as mensagens enviadas para ele são redirec-

cionadas para o objecto original x. Além disso as variáveis de instância do novo objecto são as

variáveis de instância públicas de x (i.e. existe partilha).

Esta última técnica é uma adaptação a F+ da técnica usada por Bruce nas regras da semân-

tica operacional da sua linguagem PolyTOIL [BSG95]. Sendo compatível com a nossa formu-

lação da linguagem L7, esta técnica poderia também ter sido adoptada por nós.

7.3 A linguagem imperativa L7&

Nesta secção, apresentamos e formalizamos a linguagem L7&, uma variante imperativa da lin-

guagem L7 que introduz variáveis de instância mutáveis. A gramática e a tabela de equações

semânticas de L7& são apresentadas no final do capítulo corrente.

128 OM – Uma linguagem de programação multiparadigma

A linguagem L7& é formalizada sobre o sistema F+& (na secção 7.3.2). A nova linguagem

integra todos os ingredientes imperativos de F+& e também adopta a estratégia de avaliação

call-by-value de F+&.

A secção corrente está organizada da seguinte forma: na subsecção 7.3.1 apresentamos as

ideias essenciais sobre variáveis de instância e alguns exemplos simples; na subsecção 7.3.2

formalizamos L7& tomando como ponto de partida a formalização existente para L7; final-

mente, na subsecção 7.3.3 discutimos uma questão pertinente para L7&: a interacção entre os

tipos-referência que ocorrem nas interfaces das classes e o mecanismo de herança.

7.3.1 Variáveis de instânciaNa linguagem L7&, variáveis de instância é a designação genérica que adoptamos para as

componentes dos objectos cujos tipos são tipos-referência, ou seja tipos da forma Ref τ (cf.

secção 2.5.6). O estado dum objecto é definido como a sequência de valores das suas variáveis

de instância.

A linguagem L7& suporta variáveis de instância privadas e públicas. Isso significa que o

estado dum objecto pode ser parcialmente privado e parcialmente público (como em C++ e ao

contrário do Smalltalk).

Usando L7& de forma idiomática, é possível definir variáveis de instância semipúblicas, ou

seja, variáveis que são públicas para efeitos de leitura, mas privadas para efeitos de escrita.

Para definir uma variável de instância semipública, introduz-se primeiro uma variável de ins-

tância privada, e depois, à maneira de Reynolds, define-se um método público para leitura des-

sa variável. A vantagem das variáveis de instância semipúblicas é permitirem a um objecto

revelar partes do seu estado, sem perder controlo sobre este.

Convém dizer neste ponto que a nossa linguagem final, final, OM, suportará directamente

apenas variáveis de instância privadas e variáveis de instância semipúblicas. Naturalmente, se-

rá sempre possível simular variáveis de instância públicas, mas a sua utilização será desenco-

rajada.

Para exemplificar a utilização de variáveis de instância privadas em L7&, exibimos uma va-

riante da classe pointC (cf. secção 7.1.2), agora com duas variáveis de instância privadas, priv_x

e priv_y:

pointCR ˆ = class priv_x=ref (0:Nat), priv_y=ref (0:Nat),

sumx=λz:Unit. deref self.priv_x+deref self.priv_x,

priv_eq=λa:SELFT.(deref self.priv_x=deref a.priv_x

& deref self.priv_x=deref a.priv_x)

Para exemplificar a utilização de variáveis de instância semipúblicas em L7&, exibimos

agora uma nova variante de pointC, contendo as variáveis de instância semipúblicas (simula-

das) x e y:

7 Componentes privadas e variáveis de instância 129

pointCRsp ˆ = class priv_x=ref (0:Nat), priv_y=ref (0:Nat),

x=λz:Unit. deref self.priv_x,

y=λz:Unit. deref self.priv_y,

sum=λz:Unit. self.x ()+self.y (),

eq=λa:SAMET.(self.x ()=a.x () & self.y ()=a.y ())

Note como foi possível tornar público o método binário eq, agora que a classe passou a revelar

o estado dos seus objectos.

7.3.2 Semântica de L7&

Nesta secção discutimos os problemas envolvidos na adaptação da formalização de L7 ao caso

da linguagem L7&. As novas equações semânticas, definidas sobre F+&, encontram-se agrupa-

das na tabela colocada no final do presente capítulo.

Foram quatro os problemas com que nos confrontámos na adaptação das equações de L7.

Vamos dedicar uma subsecção a cada um desses problemas:

• 7.3.2.1: Tratamento dos pontos fixos no contexto da estratégia de avaliação call-by-va-

lue;

• 7.3.2.2: Determinação do local exacto das equações semânticas onde as variáveis de

instância devem ser criadas;

• 7.3.2.3: Permitir a ocorrência de self nas expressões de inicialização das variáveis de

instância;

• 7.3.2.4: Introdução duma constante polimórfica nil, compatível com todos os tipos-re-

gisto.

7.3.2.1 Tratamento dos pontos fixosNo sistema F+

& o operador de ponto fixo só pode ser aplicado a funções cujos tipos tenham a

forma (υ→τ)→(υ→τ). Esta é uma consequência de F+& usar a estratégia de avaliação call-by-

-value (cf. secção 2.5.6).

Assim, quando se transita da semântica de L7 para a semântica de L7& é preciso reformu-

lar, nas equações semânticas, todas as funções de F+& que sejam alvo da aplicação do operador

fix, por forma a que os tipos dessas funções passem a ter a forma (υ→τ)→(υ→τ). Duas destas

funções requerem a transformação referida. Trata-se dos dois geradores polimórficos, com tipo

CLASSTYPE(ϒc), introduzidos nas equações semânticas das classes class Rc e class\s Rc. O tipo

desses geradores tem de mudar, por forma a que a sua parte mais interna deixe de ser:

SELFT→IINTERFACE(ϒc)[SAMET][SELFT]

para passar a ser:

(Unit→SELFT)→(Unit→IINTERFACE(ϒc)[SAMET][SELFT])

130 OM – Uma linguagem de programação multiparadigma

Paralelamente, a variável de recursão dos geradores, self, tem de deixar de ser do tipo SELFT e

passar a ser do tipo Unit→SELFT.

Esta transformação corresponde ao conhecido artifício técnico que permite simular a estra-

tégia de avaliação call-by-name numa linguagem call-by-value.

Comparação com outros trabalhos - São escassos os estudos teóricos que lidam com objec-

tos mutáveis. No trabalho [Pie93b], Pierce sugere o uso da técnica que descrevemos como for-

ma de lidar com alguns dos problemas dos “objectos mutáveis”. Em [ESTZ94] apresenta-se

uma técnica alternativa baseada num operador de ponto fixo à Ladin, que tira partido das pro-

priedades das referências. Este operador resolve duma só vez, não só o problema discutido

nesta secção, como também todos os problemas que discutiremos nas secções seguintes. Tem,

no entanto, a desvantagem de requerer uma codificação dos objectos muito complexa.

7.3.2.2 Criação das variáveis de instânciaAs referências que implementam as variáveis de instância dos objectos são criadas usando o

operador ref. Mas este operador tem o problema de funcionar por efeito lateral: sempre que

uma expressão ref exp é reavaliada, cria-se uma nova referência inicializada com o valor que a

expressão exp produziu.

Como se comenta no trabalho [ESTZ94], no momento da geração dum objecto, é muito fá-

cil cair em equívocos relativamente ao momento exacto em que as suas variáveis de instância

devem ser criadas. Com efeito, não havendo cautela, estas podem ser criadas demasiado cedo,

ficando desta forma associadas à classe e não ao objecto gerado, ou podem ser criadas dema-

siado tarde, sendo neste caso recriadas sempre que se acede ao objecto.

Para que as variáveis de instância não sejam criadas demasiado cedo, as expressões onde

ocorre o operador ref devem ser reavaliadas sempre que a classe é alvo do operador new. Como

F +& usa a estratégia call-by-value, toda a classe deve ter pelo menos um parâmetro do qual de-

pendam todos os usos de ref dentro da classe. Para isso poderíamos introduzir um parâmetro

artificial do tipo Unit; mas tal não é necessário visto que a classe já possui um parâmetro cha-

mado hide.

Para que as variáveis de instância não sejam criadas demasiado tarde, o operador ref não

deve ser usado dentro do gerador monomórfico interno que será objecto da aplicação do opera-

dor de ponto fixo.

Portanto, o operador ref deve ocorrer depois da introdução do parâmetro hide, e antes do

gerador interno. Nas equações semânticas que escrevemos para as classes estas duas regras são

cumpridas. Nestas equações, confirme a localização das invocações de allocate, primitiva na

qual, por razões de organização, encapsulámos todos usos de ref.

Comparação com outros trabalhos - Os problemas aqui descritos são referidos de passagem

em [ESTZ94], embora nesse trabalho aquelas dificuldades não se coloquem em virtude do

7 Componentes privadas e variáveis de instância 131

operador de ponto fixo introduzido ser compatível com a estratégia de avaliação call-by-value

(trata-se do operador especial que já referimos na secção anterior).

O problema discutido nesta secção é propício à introdução de erros, pelo que resolvemos

testar a nossa semântica, escrevendo em Caml Light o seguinte protótipo para as equações se-

mânticas das classes.

#let rec fix f x = f (fix f) x ;; (* definição de fix *)fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>#type Counter = i: int ref;

inc:unit->Counter; get:unit->int ;;

Type Counter defined.#let CounterClass () = (* () é um argumento artificial *)

let r = ref 0 in (* criação da ref depois do argumento e antes do gerador*)fun self () -> (* () é um argumento artificial *)

i = r; inc = (fun () -> (self()).i := !((self()).i)+1; (self())); get = (fun () -> !((self()).i)) ;;

CounterClass : unit -> (unit -> Counter) -> unit -> Counter = <fun>#let new cl = fix (cl ()) () ;;new : (unit -> (unit -> 'a) -> unit -> 'a) -> 'a = <fun>#let o = new CounterClass ;;o : Counter = i = ref 0; inc = <fun>; get = <fun>#o.inc() ;;- : Counter = i = ref 1; inc = <fun>; get = <fun>#o.inc() ;;- : Counter = i = ref 2; inc = <fun>; get = <fun>#o.get();;- : int = 2

Caml Light [Ler96, Mau95] é uma implementação da linguagem CAML, a qual é, por sua vez,

uma linguagem funcional que tem a particularidade de usar a estratégia de avaliação call-by-

-value.

7.3.2.3 Inicialização das variáveis de instânciaA inicialização das variáveis de instância dum objecto pode ser efectuada pelo próprio

operador ref no momento em que este cria essas variáveis, a não ser que queiramos permitir a

ocorrência de self nas expressões de inicialização. Se o desejarmos, então confrontamo-nos

com mais uma dificuldade: as questões técnicas da secção anterior levaram-nos a transferir a

criação das referências para um contexto anterior à introdução do nome self, e nesse contexto o

nome self é desconhecido.

Resolvemos este problema de forma simples. Tomamos as várias expressões de inicializa-

ção que ocorrem na classe e usamo-las todas para compor um método de inicialização, chama-

do priv_init, que adicionamos à classe. Este método será depois usado pelo operador priv_new

para inicializar os objectos criados. Note que, tal como qualquer outro método, o método

priv_init é interpretado num contexto onde o nome self é já conhecido.

Com a introdução do método privado priv_init, a definição dos tipos GINTERFACE(ϒc),

IINTERFACE(ϒc), e SINTERFACE(ϒc) tem de ser ligeiramente alterada, pois todos estes tipos ga-

nham uma componente suplementar.

132 OM – Uma linguagem de programação multiparadigma

Resta agora a questão, bastante mais complicada, da inicialização preliminar das referên-

cias no momento em que são criadas (ver primitiva allocate). Para isso precisamos de distinguir

em cada tipo primitivo τ suportado pela linguagem L7& um elemento particular, digamos um

zero denotado por zero[τ], que possa ser usado na inicialização. É fácil encontrar um zero na

maioria dos tipos. Por exemplo, pode usar-se a seguinte definição recursiva:

zero[Nat] ˆ = 0

zero[Bool] ˆ = false

zero[υ→τ] ˆ = λx:υ.zero[τ]

zero[∀X≤*I.τ] ˆ = ∀X≤*I.zero[τ]

Um caso em que é complicado encontrar um zero é o caso dos tipos-registo. Este caso é tra-

tado na secção seguinte.

Comparação com outros trabalhos - A generalidade dos modelos da literatura que lidam

com objectos mutáveis não permitem a ocorrência de self nas expressões de inicialização de

variáveis de instância. O trabalho [ESTZ94] é uma excepção e resolve o problema através do

operador de ponto fixo especial que já referimos anteriormente.

7.3.2.4 Constante nilNa secção anterior, relativamente à questão da inicialização preliminar das referências, não

tratámos o caso das referências para tipos-registo: os tipos-registo não têm à partida um zero

natural que possa ser usado na inicialização provisória das referências correspondentes.

Por isso, vamos introduzir em L7& a constante nil que discutimos da secção 2.5.7. Isso

obriga-nos a passar a codificar os objectos usando novos registos, cujos tipos têm a forma

Unit→l–:τ–. Nas equações de L7&, os objectos criados usando priv_new passam assim a ter tipos

internos da forma Unit→IOBJTYPE(ϒc) e os objectos criados usando priv_new passam a ter tipos

externos da forma Unit→OBJTYPE(ϒc). As operações de acesso a componentes de objectos são

redefinidas.

Note que, no final, existem duas razões distintas para a ocorrência ubíqua de tipos da forma

Unit→…., nas equações de L7&: (1) a simulação de call-by-name, para ser possível usar fix; (2)

a necessidade de dispor da constante nil.

7.3.3 Tipos-referência e herançaConsiderando quaisquer ocorrências de tipos-referência na interface global duma classe, diga-

mos as ocorrências sublinhadas em GINTERFACE(f:(Ref Nat)→Bool, priv_x:Ref ), essas ocorrên-

cias não podem ser objecto de qualquer modificação na interface global de qualquer subclasse.

Esta limitação está implícita nas equações semânticas de L7 (cf. relação gen_ext) e resulta do

facto dum tipo-referência não admitir subtipos não triviais (cf. 2.5.6.2).

7 Componentes privadas e variáveis de instância 133

Facto 7.3.3-1 As ocorrências de tipos da forma Ref τ mantêm-se invariantes nas interfaces

globais das subclasses.

Uma consequência deste facto é significativa: o tipo de todas as variáveis de instância não

pode ser mudado nas subclasses, ao contrário do que se passa com os métodos.

Uma outra questão prende-se com o tipo Ref SAMET. Este é um tipo legítimo. Contudo, por

uma razão pragmática, iremos banir o seu uso da parte pública das classes. A fonte do pro-

blema é a seguinte: a ocorrência de SAMET em Ref SAMET não tem polaridade. Por isso, se

Ref SAMET aparecesse na interface pública duma qualquer classe c as consequências seriam as

seguintes:

• As subclasses de c não gerariam subtipos úteis;

• Caso fosse necessário que gerassem subtipos, o operador “+” não teria o desejado efeito

sobre c, i.e. as subclasses de +c também não gerariam subtipos úteis.

Como fazemos questão que exista sempre uma solução para o problema da geração de subti-

pos úteis, vamos introduzir a seguinte restrição:

Restrição 7.3.3-2 O tipo Ref SAMET está proibido de ocorrer em interfaces públicas.

Felizmente que as consequências da introdução desta restrição são benignas em L7&. Para

começar, a nossa restrição não afecta a possibilidade de definir variáveis semipúblicas de tipo

SAMET. Em segundo lugar, ela não impede a simulação de variáveis públicas do tipo SAMET

através da introdução de variáveis privadas do tipo SAMET e métodos públicos de leitura/escri-

ta dessas variáveis (os métodos de leitura são do tipo Unit→SAMET, onde SAMET tem polarida-

de positiva; os métodos de escrita são do tipo SAMET→Unit, onde SAMET tem polaridade nega-

tiva). Em terceiro lugar, um parâmetro de tipo ref SAMET pode sempre ser substituído por dois

parâmetros, um de tipo SAMET e outro do tipo SAMET→Unit, ou então por um parâmetro e um

resultado, ambos de tipo SAMET. Finalmente, as ocorrências naturais de Ref SAMET em inter-

faces públicas são raras, especialmente no caso da linguagem OM final, onde todas as

variáveis de instância não-privadas serão semipúblicas. Como vimos, uma variável semipúbli-

ca do tipo SAMET não oferece problema.

7.4 ConclusõesRelativamente às questões do encapsulamento e do estado, a maioria dos modelos teóricos

descritos na literatura adoptam a filosofia da linguagem Smalltalk, quer seja na sua forma

imperativa original, quer seja em alguma variante funcional. Essa filosofia é a seguinte: (1) as

restrições de visibilidade estabelecem-se ao nível de cada objecto; (2) o estado dum objecto é

privado e todos os seus métodos são públicos. Nestes modelos as questões da visibilidade e do

estado são misturadas, sendo o tratamento unificado dos dois aspectos efectuado usando abs-

134 OM – Uma linguagem de programação multiparadigma

tracção procedimental (closures) ou abstracção de tipos (tipos existenciais) [Rey78]. Alguns

dos estudos mais importantes dentro desta linha são: [ACV96, BCP99, BFSG98, Bru94,

BSG95, ACV96, CHC90, CP89, Car88, ESTZ94, Kam88, KR93, PT94, Red88]. O ponto (1)

desta abordagem conduz necessariamente à separação dos conceitos de classe e de tipo-objec-

to

Ainda, relativamente às questões do encapsulamento e do estado, muitas de linguagens prá-

ticas e alguns estudos teóricos aderem às ideias da linguagem C++. Neste caso a filosofia é a

seguinte: (1) as restrições de visibilidade estabelecem-se ao nível da classe, a qual é vista

como um tipo abstracto de dados; (2) as componentes dos objectos podem ser públicas ou

privadas, independentemente de serem variáveis de instância ou métodos. Nestes modelos,

tipicamente, introduz-se primeiro uma noção de objecto elementar com estado mas sem en-

capsulamento; depois, usa-se abstracção de tipos (tipos existenciais) para erguer barreiras de

controlo de acesso ao estado desses objectos. Dois dos estudos mais importantes dentro desta

categoria são os trabalhos [FM96, OW97]. Seguem este modelo, linguagens como o C++,

Java, Pizza, Eiffel, etc. Note que o ponto (1) desta abordagem favorece a unificação entre os

conceitos de classe e de tipo-objecto.

A linguagens L7& não se enquadra inteiramente em nenhuma das categorias anteriores. É

certo que as regras de visibilidade de L7& são essencialmente as do Smalltalk, já que existe

uma barreira de encapsulamento envolvendo cada objecto. Não obstante, cria-se uma excepção

para os objectos que são gerados dentro da sua própria classe: a estes objectos atribuímos

inicialmente o tipo SELFT, ficando assim eles sujeitos às restrições de visibilidade da lingua-

gem C++ durante a fase inicial das suas vidas. No nosso modelo, os objectos começam por ser

criados sem qualquer barreira de acesso, tal como nos modelos da abordagem que descreve-

mos em segundo lugar; depois usamos a propriedade da perda de informação para ocultar a

sua parte privada (aqui as técnicas referidas nas abordagens anteriores não são aplicáveis). Em

L7 os conceitos de classe e de tipo-objecto ficam separados, tal como em Smalltalk.

7 Componentes privadas e variáveis de instância 135

Sintaxe dos géneros,tipos e termos de L7&Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

Unit | Ref τ |SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ) |SELFT | GINTERFACE(ϒ) | IINTERFACE(ϒ) | SINTERFACE(ϒ) | IOBJTYPE(ϒ)

∀X≤*INTERFACE(ϒB).τ |priv(ϒ) | pub(ϒ) | all(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

() | ref (e:υ) | deref e | e:=e′ | e;e′ |self | super | class R | class\s R | new c | o.l | priv_new c |

checkType[τ] | downcastσ[τ] |

λX≤*INTERFACE(ϒB).e | P[τ] |priv(R) | pub(R) | all(R)

Semântica dos tipos

GINTERFACE(ϒc) :∗⇒∗ ˆ = interface global

ΛSAMET.ΛSELFT.(ϒc⊕priv_init:Unit→Unit)

IINTERFACE(ϒc) :∗⇒∗ ˆ = interface interna

ΛSAMET.ΛSELFT.all(ϒc⊕priv_init:Unit→Unit) (= GINTERFACE(ϒc))SINTERFACE(ϒc) :∗⇒∗ ˆ = interface secreta

ΛSAMET.ΛSELFT.priv(ϒc⊕priv_init:Unit→Unit)

CLASSTYPE(ϒc) :∗ ˆ = tipo-classe

∀SAMET≤*INTERFACE(ϒc).

∀SELFT≤*IINTERFACE(ϒc)[SAMET].(SELFT→SAMET)→

(Unit→SELFT)→(Unit→IINTERFACE(ϒc)[SAMET][SELFT])

Semântica dos termos

class Rc :CLASSTYPE(ϒc) ˆ = λSAMET≤*INTERFACE(ϒc).

λSELFT≤*INTERFACE(ϒc)[SAMET].λhide:SELFT→SAMET.

let Rrefs:ϒc⊕priv_init:Unit→Unit=(allocate[ϒc] Rc).λself:Unit→SELFT.

λz:Unit.all(Rrefs)class\s Rc :CLASSTYPE(ϒs⊕ϒc) ˆ =

let S:CLASSTYPE(ϒs) = s inλSAMET≤*INTERFACE(ϒs⊕ϒc).

λSELFT≤*IINTERFACE(ϒs⊕ϒc)[SAMET].λhide:SELFT→SAMET.

let Rrefs:ϒc⊕priv_init:Unit→Unit=(allocate[ϒc] Rc).λself:Unit→SELFT.

let super:Unit→IINTERFACE(ϒs)[SAMET][SELFT] =(S[SAMET][SELFT] hide self) in

λz:Unit.(super ())+all(Rrefs)*Restrição implícita: ϒs,ϒc devem ser tais que:

GINTERFACE(ϒs⊕ϒc) gen_ext GINTERFACE(ϒs)

136 OM – Uma linguagem de programação multiparadigma

priv_new c :Unit→IOBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x) inlet gen:(Unit→IOBJTYPE(ϒc))→(Unit→IOBJTYPE(ϒc)) =

(C[OBJTYPE(ϒc)][IOBJTYPE(ϒc)] hide) inlet priv_o:Unit→IOBJTYPE(ϒc) = fix gen in

let dummy:Unit = (priv_o ()).priv_init () inpriv_o

new c :Unit→OBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let priv_o = priv_new C in

let hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x) inlet o:Unit→OBJTYPE(ϒc) = λz:Unit.hide (priv_o ()) in

o

o.l :τ ˆ = let R:Unit→OBJTYPE(ϒc) = o in

(R ()).l

o.l :τ ˆ = let R:Unit→IOBJTYPE(ϒc) = o in

(R ()).l

nilOBJTYPE(ϒc) ˆ =

λz:Unit.divergeOBJTYPE(ϒc) : Unit→OBJTYPE(ϒc)

allocate[a1:Ref υ1,…,am:Ref υm, b1:τ1,…,bn:τn]a1=ref x1,…,am=ref xm, b1=y1,…,bn=ynˆ = a1=ref zero[υ1],…,am=ref zero[υm], b1=y1,…,bn=yn,

priv_init=λz:Unit.(self.υ1:=x1; …; self.υm:=xm)Notas: - os τi são tipos com a particularidade de não serem tipos-referência;

- o registo-resultado tem novas referências e um novo método priv_init;

- a expressão zero[υ] representa um elemento do tipo υ. Se υ for umtipo-objecto então zero[υ] ˆ = nilυ

8 Componentes de classe 137

Capítulo 8

Componentes de classe

Sintaxe dos géneros,tipos e termos de L8

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ) |SELFT | GINTERFACE(ϒ) | IINTERFACE(ϒ) | SINTERFACE(ϒ) | IOBJTYPE(ϒ)#INTERFACE(ϒ) | #IINTERFACE(ϒ) | #SINTERFACE(ϒ) |#OBJTYPE(ϒ) | #IOBJTYPE(ϒ) |

∀X≤*INTERFACE(ϒB).τ |#priv(ϒ) | priv(ϒ) | #pub(ϒ) | pub(ϒ) | #all(ϒ) | all(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | c#l | o.l |SELFC | SUPERC |

checkType[τ] | downcastσ[τ] |

λX≤*INTERFACE(ϒB).e | P[τ] |#priv(R) | priv(R) | #pub(R) | pub(R) | #all(R) | all(R)

Tipos-registo parciais

#priv(ϒc) ˆ = tipo-registo das componentes de classe privadaspriv(ϒc) ˆ = tipo-registo das componentes de instância privadas#pub(ϒc) ˆ = tipo-registo das componentes de classe públicaspub(ϒc) ˆ = tipo-registo das componentes de instância públicas#all(ϒc) ˆ = tipo-registo de todas as componentes de classeall(ϒc) ˆ = tipo-registo de todas as componentes de instância

Semântica dos tipos

GINTERFACE(ϒc) :∗⇒∗ ˆ = interface global

ΛSAMET.ΛSELFT. ϒc⊕#priv_new:Unit→SELFT, #priv_gen:SELFT→SELFT

#IINTERFACE(ϒc) :∗⇒∗ ˆ = interface interna

ΛSAMET.ΛSELFT.#all(ϒc)⊕#priv_new:Unit→SELFT, #priv_gen:SELFT→SELFT

#SINTERFACE(ϒc) :∗⇒∗ ˆ = ΛSAMET.ΛSELFT.#priv(ϒc)⊕#priv_new:Unit→SELFT, #priv_gen:SELFT→SELFT

#INTERFACE(ϒc) :∗⇒∗ ˆ = interface externa

ΛSAMET.#pub(ϒc)#IOBJTYPE(ϒc) :∗ ˆ = tipo-meta-objecto interno

#IINTERFACE(ϒc)[OBJTYPE(ϒc)][IOBJTYPE(ϒc)]#OBJTYPE(ϒc) :∗ ˆ = tipo-meta-objecto externo

#INTERFACE(ϒc)[OBJTYPE(ϒc)]*Verifica-se: #IOBJTYPE(ϒc)≤#OBJTYPE(ϒc) (teorema 8.2.1-1)

138 OM – Uma linguagem de programação multiparadigma

CLASSTYPE(ϒc) :∗ ˆ = tipo-classe

∀SAMET≤*INTERFACE(ϒc).

∀SELFT≤*IINTERFACE(ϒc)[SAMET].(SELFT→SAMET)→

#IINTERFACE(ϒc)[SAMET][SELFT]→#IINTERFACE(ϒc)[SAMET][SELFT]

Semântica das relaçõesRelação binária de extensão geral de L8 entre interfaces globais

GINTERFACE(ϒc) gen_ext2 GINTERFACE(ϒs) ˆ = (T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs))& SINTERFACE(ϒc)≤SINTERFACE(ϒs)& #IINTERFACE(ϒc)≤#IINTERFACE(ϒs)

Registos parciais

#priv(Rc) ˆ = registo das componentes de classe privadaspriv(Rc) ˆ = registo das componentes de instância privadas#pub(Rc) ˆ = registo das componentes de classe públicaspub(Rc) ˆ = registo das componentes de instância públicasall(Rc) ˆ = registo de todas as componentes de classe#all(Rc) ˆ = registo de todas as componentes de instância

Semântica dos termos

classRc :CLASSTYPE(ϒc) ˆ = λSAMET≤*INTERFACE(ϒc).

λSELFT≤*IINTERFACE(ϒc)[SAMET].λhide:SELFT→SAMET.

λSELFC:#IINTERFACE(ϒc)[SAMET][SELFT].λSAMEC:#IINTERFACE(ϒc)[SAMET][SELFT]. #all(Rc)

+ #priv_new = λz:Unit.(fix (SELFC.#priv_gen))+ #priv_gen = λself:SELFT.all(Rc)

class\s Rc :CLASSTYPE(ϒs⊕ϒc) ˆ = let S:CLASSTYPE(ϒs) = s in

λSAMET≤*INTERFACE(ϒs⊕ϒc).λSELFT≤*IINTERFACE(ϒs⊕ϒc)[SAMET].

λhide:SELFT→SAMET.

λSELFC:#IINTERFACE(ϒs⊕ϒc)[SAMET][SELFT].let SUPERC:#IINTERFACE(ϒs)[SAMET][SELFT] =

(S[SAMET][SELFT] hide SELFC) inSUPERC

+ #all(Rc)

+ #priv_new = λz:Unit.(fix (SELFC.#priv_gen))+ #priv_gen =

λself:SELFT.let super:SELFT=(SUPERC.#priv_gen self) in

super+all(Rc)

*Restrição implícita: ϒs,ϒc devem ser tais que:GINTERFACE(ϒs⊕ϒc) gen_ext2 GINTERFACE(ϒs)

8 Componentes de classe 139

new c :#OBJTYPE(ϒc) ˆ = let C:CLASSTYPE(ϒc) = c in

let hide:IOBJTYPE(ϒc)→OBJTYPE(ϒc) = λx:IOBJTYPE(ϒc).((λy:OBJTYPE(ϒc).y) x) inlet #gen:#IOBJTYPE(ϒc)→#IOBJTYPE(ϒc) = (C[OBJTYPE(ϒc)][IOBJTYPE(ϒc)] hide) in

let #priv_o:#IOBJTYPE(ϒc) = fix #gen inlet #hide:#IOBJTYPE(ϒc)→#OBJTYPE(ϒc) =

λx:#IOBJTYPE(ϒc).((λy:#OBJTYPE(ϒc).y)) x inlet #o:#OBJTYPE(ϒc) = #hide #priv_o in

#o

mo#l :τ ˆ = let R:#OBJTYPE(ϒc) = mo in

R.#l

o.l :τ ˆ = let R:OBJTYPE(ϒc) = o in

R.l

Na secção 8.1 deste capítulo, introduzimos e discutimos informalmente os conceitos específi-

cos da linguagem L8. Depois, na secção 8.2, formalizamos a semântica de L8. Na secção 8.3

comentamos alguns aspectos gerais da linguagem e introduzimos uma forma idiomática de

tirar partido do polimorfismo paramétrico em L8 que designaremos por polimorfismo de

classe. Finalmente, na secção 8.4, apresentamos algumas conclusões.

8.1 Conceitos e mecanismos de L8Na linguagem L8, introduzimos suporte para componentes de classe e para classes recursivas.

Nas classes recursivas, os acessos recursivos às próprias classes serão efectuados usando os

nomes ligados SELFC e SUPERC.

As componentes de classe constituem um mecanismo de grande utilidade geral: permitem

definir construtores, variáveis partilhadas, e ainda exprimir informação logicamente associada

à classe. As componentes de classe serão também muito exploradas pela faceta estática do me-

canismo dos modos (cf. secções 9.1.1 e 9.1.2.2). Na linguagem L8 existe suporte para compo-

nentes de classe privadas e componentes de classe públicas.

A existência de componentes de classe permite a introdução duma útil forma de polimorfis-

mo paramétrico – chamada polimorfismo de classe – que se baseia na passagem simultânea

duma classe não-paramétrica e do tipo-objecto por esta gerado (cf. secção 8.3).

A introdução de classes recursivas em L8 corresponde a uma necessidade prática efectiva.

De facto, um método de classe deve ter acesso a qualquer componente de classe da sua própria

classe. Além disso, qualquer instância também deve poder aceder às componentes de classe da

sua própria classe.

A questão do estado mutável já foi tratada na secção 7.3 do capítulo anterior. Relativamen-

te a esta questão, iremos assumir, nas discussões informais e nos exemplos, que a linguagem

L8 suporta variáveis de instância e variáveis de classe mutáveis, e ainda que usa a estratégia da

140 OM – Uma linguagem de programação multiparadigma

avaliação call-by-value. No entanto, ignoraremos esta dimensão da linguagem ao nível da sua

formalização. São duas as razões que estão na base desta nossa decisão: em primeiro lugar,

não gostaríamos de obscurecer as equações semânticas de L8 com excessivos tecnicismos; em

segundo lugar, não aprenderíamos nada de novo com tal formalização pois ela seria feita usan-

do as técnicas já conhecidas da secção 7.3.

Três linguagens práticas que suportam, sob alguma forma, componentes de classe e classes

recursivas, são as linguagens Smalltalk, C++ e Java.

8.1.1 Componentes de classe e meta-objectosNa linguagem L8, introduzimos um novo tipo de componentes nas classes: as componentes de

classe. Do ponto de vista lógico, estas componentes pertencem a própria classe e não às suas

instâncias. Para materializar essa associação, definimos para cada classe um objecto especial,

chamado meta-objecto, que integra todas as componentes de classe da classe respectiva.

Componentes de instância é a designação que adoptamos de agora em diante relativamente

às componentes específicas dos objectos normais. Um objecto normal, ou instância, é um

objecto que não é meta-objecto.

Em L8, cada classe descreve duas categorias de objectos em simultâneo: objectos normais

(instâncias) e meta-objectos. Os objectos normais são idênticos aos objectos de L7: eles con-

têm todas as componentes de instância definidas na sua classe-mãe e ainda as componentes de

instância herdadas. Já os meta-objectos contêm: as componentes de classe definidas na sua

classe-mãe; as componentes de classe herdadas; e ainda um método de classe primitivo priva-

do, chamado #priv_new, que é automaticamente adicionado ao meta-objecto e serve para cons-

truir objectos normais. Através do respectivo construtor privado, #priv_new, cada meta-objecto

ganha o direito exclusivo de criar instâncias da sua classe. Isso explica a parte “meta-” da desi-

gnação “meta-objecto”.

Sendo privado o construtor #priv_new, à partida um objecto normal só poderá ser criado den-

tro dos estritos limites sintácticos da sua classe-mãe, usando a notação (SELFC#priv_new ()). Mas

nada nos impede de adicionar a uma classe construtores públicos programados à custa de

#priv_new. Se uma classe definir um tal construtor público, digamos #new, então passa a ser pos-

sível criar instâncias dessa classe fora dos limites sintácticos da classe. Para criar uma tal ins-

tância escreve-se (mo#new ()), onde mo representa o meta-objecto associado à classe.

8.1.2 Utilidade das componentes de classeA utilidade mais imediata das componentes de classe é a possibilidade de definir múltiplos

métodos geradores de objectos – construtores – dentro duma classe. Por exemplo, imagine

uma classe que define pontos num espaço real a duas dimensões: faz sentido definir nessa

classe um construtor de pontos que aceite coordenadas cartesianas; um outro que aceite coor-

8 Componentes de classe 141

denadas polares; um outro que aceite um número complexo; ainda, um outro sem argumentos

que crie pontos inicializados duma forma predeterminada. Tipicamente, no corpo de todos

esses construtores começa-se por criar um novo objecto usando a expressão

(SELFC#priv_new ()), depois reinicializa-se o novo objecto (que tem tipo SELFT) atribuindo

valores às suas variáveis de instância, privadas e públicas, e finalmente retorna-se o novo

objecto. Note que só faz sentido definir construtores em linguagens que suportem objectos

mutáveis, visto que a operação de reinicialização é uma operação que pressupõe que o objecto

tem estado mutável.

As componentes de classe são também úteis para representar informação que esteja logica-

mente associada à classe e não às suas instâncias. Por exemplo se quisermos que uma classe

distinga um objecto particular do seu domínio, digamos o zero dessa classe, então devemos

adicionar à classe uma constante pública (um método de classe público sem argumentos), com

nome #zero, por exemplo, e inicializada com o objecto pretendido.

As componentes de classe ajudam ainda a organizar as entidades globais. nos programas.

Entidades globais usadas numa única classe podem ser programadas localmente como compo-

nentes de classe privadas. Entidades globais usadas em várias classes podem ser programadas

como componentes de classe públicas, tirando-se assim partido das fronteiras das classes para

particionar o espaço de nomes globais do programa. Note que uma classe só com componentes

de classe degenera num módulo (embora se trate dum módulo especial, com capacidade de

herdar componentes de outros módulos).

8.1.3 Nomeação das componentes das classesNecessitamos de estender as convenções de nomeação das componentes das classes, inicial-

mente introduzidas na secção 7.1.2. As convenções de L8 são as seguintes:

• os nomes das componentes de classe privadas são prefixados por “#priv_”;

• os nomes das componentes de instância privadas são prefixados com “priv_”;

• os nomes das componentes de classe públicas são prefixados por “#” mas não por “#priv_”;

• os nomes das componentes de instância públicas não são prefixados por “#” nem “priv_”.

Por vezes teremos a necessidade de referenciar, alguns dos tipos-registo parciais que esta

convenção determina. Dado um tipo-registo ϒ, introduzimos as seguintes definições:

#priv(ϒ) – representa o tipo-registo parcial das componentes de classe privadas;

priv(ϒ) – representa o tipo-registo parcial das componentes de instância privadas;

#pub(ϒ) – representa o tipo-registo parcial das componentes de classe públicas;

pub(ϒ) – representa o tipo-registo parcial das componentes de instância públicas;

#all(ϒ) – representa o tipo-registo parcial de todas as componentes de classe;

all(ϒ) – representa o tipo-registo parcial de todas as componentes de instância.

142 OM – Uma linguagem de programação multiparadigma

Temos assim que, para um tipo-registo ϒ qualquer, as seguintes equivalências de tipos são

válidas:

ϒ = #priv(ϒ) ⊕ priv(ϒ) ⊕ #pub(ϒ) ⊕ pub(ϒ)

#all(ϒ) = #priv(ϒ) ⊕ #pub(ϒ)

all(ϒ) = priv(ϒ) ⊕ pub(ϒ)

ϒ = #all(ϒ) ⊕ all(ϒ)

Relativamente aos registos-valores, adoptamos convenções idênticas às dos tipos-registo.

Assim, dado um registo R, definimos os seguintes registos parciais analogamente: #priv(R),

priv(R), #pub(R), pub(R), #all(R) e all(R). As seguinte equivalência é válida para qualquer registo R

de L8:

R = #all(R) + all(R) = #priv(R) + priv(R) + #pub(R) + pub(R)

8.1.4 Tipos-objecto e tipos-meta-objectoTal como em L7, em L8 associamos dois tipos a cada objecto: um tipo-objecto externo,

OBJTYPE(ϒc), que representa a visão externa do objecto e expõe as suas componentes públicas

apenas, e um tipo-objecto interno, IOBJTYPE(ϒc), que representa a visão interna do objecto e

expõe as suas componentes públicas e privadas. A definição destes tipos-objecto mantém-se

inalterada em L8. Também se mantém a propriedade: OBJTYPE(ϒc)≤IOBJTYPE(ϒc).

Mas em L8 precisamos também de lidar com as questões de visibilidade dos meta-objectos.

Por isso associamos dois tipos a cada meta-objecto: um tipo-meta-objecto externo, denotado

#OBJTYPE(ϒc), que representa a visão externa do meta-objecto e expõe apenas as suas compo-

nentes públicas, e um tipo-meta-objecto interno, denotado #IOBJTYPE(ϒc), que representa a vi-

são interna do meta-objecto e expõe as suas componentes públicas e privadas. Estes dois tipos-

-meta-objectos cumprem a condição: #OBJTYPE(ϒc)≤#IOBJTYPE(ϒc) (cf. teorema 8.2.1-1).

Os tipos tipos-meta-objecto, internos e externos, são tipos técnicos, introduzidos por ques-

tões de formalização da linguagem e não podem ser explicitamente usados nos programas. Re-

cordamos que os tipos-objecto internos também são técnicos. Portanto, das várias variantes de

tipos-objecto suportados em L8, apenas os tipos-objecto externos são tipos não-técnicos e po-

dem ser explicitamente usados nos programas.

O facto de estabelecermos que as duas formas de tipo-meta-objecto são técnicos, equivale a

dizer que decidimos impedir os programadores de manipularem explicitamente meta-objectos

nos programas.

Em L8, os tipos-objecto OBJTYPE(ϒc) e IOBJTYPE(ϒc) continuarão a ser recursivos nas

variáveis SAMET e SELFT, respectivamente. No entanto os tipos-meta-objecto #OBJTYPE(ϒc) e

#IOBJTYPE(ϒc) não serão tipos recursivos.

8 Componentes de classe 143

8.1.5 Interfaces de classePara tratar das questões relacionadas com a visibilidade de nomes em L8 necessitamos de in-

troduzir interfaces de classe, constituídas só por componentes de classe, a par de interfaces de

instância, constituídas só por componentes de instância.

Em L8 associamos sete interfaces a cada classe. Se ϒc for o tipo-registo das componentes

duma classe, então as respectivas interfaces são assim definidas:

GINTERFACE(ϒc) – interface global, regista todas as componentes duma classe;

INTERFACE(ϒc) – interface externa de instância, regista as comps. de instância públicas;

IINTERFACE(ϒc) – interface interna de instância, regista as componentes de instância;

SINTERFACE(ϒc) – interface secreta de instância, regista as comps. de instância privadas;

#INTERFACE(ϒc) – interface externa de classe, regista as componentes de classe públicas;

#IINTERFACE(ϒc) – interface interna de classe, regista as componentes de classe;

#SNTERFACE(ϒc) – interface secreta de classe, regista as componentes de classe privadas;

A interface global distingue-se das outras por incluir a assinatura de todas as componentes

duma classe, quer elas sejam de instância ou de classe, quer sejam privadas ou públicas. As

restantes interfaces são interfaces parciais e podem ser geradas a partir da interface global de

forma automática.

Na linguagem L7, cada classe tinha associadas três interfaces de instância e uma interface

global. Em L8 as três interfaces de instância mantêm-se, mas a interface global tem de ser re-

formulada face à existência das componentes de classe.

O nome SAMET pode ocorrer em toda e qualquer das sete interfaces duma classe. Já o nome

SELFT só pode ocorrer em cinco delas, ficando excluído das interfaces externas INTERFACE(ϒc)

e #INTERFACE(ϒc).

Vejamos qual é a relação entre as interfaces duma classe e os tipos-objecto gerados por essa

mesma classe. Uma classe com interface global GINTERFACE(ϒc) gera directamente meta-ob-

jectos com faceta interna de tipo #IOBJTYPE(ϒc) e faceta externa de tipo #OBJTYPE(ϒc), e gera

indirectamente instâncias com faceta interna de tipo IOBJTYPE(ϒc) e faceta externa de tipo

OBJTYPE(ϒc).

8.1.6 Recursividade das classes e SELFC

Em L8, as classes são definidas recursivamente sobre a variável de recursão SELFC, a qual fica

ligada à própria classe. Assim, dentro do meta-objecto associado a uma classe, o nome SELFC

desempenha o mesmo papel que self desempenha nos objectos normais.

Através do nome SELFC, um método de classe pode aceder a qualquer das componentes de

classe da sua classe. Em particular, pode invocar-se a si próprio. Também através de SELFC,

uma instância pode aceder a qualquer das componentes de classe da sua classe-mãe.

144 OM – Uma linguagem de programação multiparadigma

Nos métodos de classe herdados e nos métodos de instância herdados, o nome SELFC é

reinterpretado no contexto das subclasses que os acolhem. Na formalização, o nome SELFC

será tratado de forma semelhante a self em L4, no sentido em que lhe será atribuído um tipo-

-meta-objecto fixo, tal como fizemos relativamente a self na linguagem L4.

Para exemplificar, apresentamos seguidamente a classe pointClass, que define diversos méto-

dos de classe e na qual se fazem várias referencias ao nome SELFC .

pointClass ˆ = class #new=λx:Nat.λy:Nat. (let p=SELFC#priv_new () in (p.priv_x:=x; p.priv_y:=y; p))

#newOne=λz:Unit. (let p=SELFC#priv_new () in (p.priv_x:=1; p.priv_y:=1; p))

#zero=SELFC#priv_new ()

priv_x=ref (0:Nat), priv_y=ref (0:Nat),

priv_eq=λa:SELFT.(deref self.priv_x=deref a.priv_x

& deref self.priv_y=deref a.priv_y),

clone=λz:Unit. (SELFC#new (deref self.priv_x) (deref self.priv_y)),

sum=λz:Unit. deref self.priv_x+deref self.priv_y

8.1.7 Componentes de classe e herançaTal como as componentes de instância, também as componentes de classes são herdadas pelas

subclasses. Alias, tal não podia deixar de ser. Para verificar esta ideia, consideremos o método

de instância clone definido na classe pointClass da secção anterior e dependente do método de

classe #new, também definido na mesma classe. Devido a esta dependência é importante que

sempre que o primeiro seja herdado, o segundo acompanhe o primeiro. Caso contrário, clone

ficaria mal definido na subclasse.

Sendo as componentes de classe são herdadas, convém que elas também possuam a sua

própria versão do nome super. Por isso introduzimos o nome SUPERC nas subclasses. No con-

texto duma subclasse e através de SUPERC, os métodos de classe e de instância ganham acesso

às versões originais das componentes de classe que estão disponíveis na superclasse. Este

acesso pode ser útil se entretanto essas componentes tiverem sido redefinidas na subclasse.

8.1.8 Resumo dos nomes especiaisNo contexto das classes de L8 estão disponíveis diversos nomes especiais predefinidos. Como

são em grande número importa exibi-los conjuntamente numa lista. Na lista que apresentamos

seguidamente, indicamos junto de a cada nome o contexto em que esse nome pode ser usado:

self – no corpo dum método de instância;

super – numa subclasse, no corpo dum método de instância;

SELFC – no corpo dum método de instância ou dum método de classe;

SUPERC – numa subclasse, no corpo dum método de instância ou dum método de classe;

SAMET – numa classe, sem restrições;

SELFT – numa classe, fora no cabeçalho das componentes públicas.

8 Componentes de classe 145

8.2 Semântica de L8Nesta secção, formalizamos a semântica de L8. Pela razão apresentada na introdução da sec-

ção 8.1, iremos ignorar agora a questão do estado mutável. A tabela das equações semânticas

de L8 abre o presente capítulo.

Em L8, uma classe passa a ser vista como um gerador de meta-objectos polimórfico e ex-

tensível, que é activado apenas uma vez para gerar o meta-objecto associado à classe. Cada

meta-objecto de L8 tem no seu interior duas componentes primitivas: um gerador de instâncias

auxiliar, #priv_gen, e um construtor privado, #priv_new. O primeiro é usado tecnicamente na es-

pecificação de herança e não se destina a ser usado nos programas. O segundo permite criar

instâncias de classes, e, este sim, é para ser usado nos programas. Na sua definição, o constru-

tor #priv_new limita-se a aplicar o operador de ponto fixo ao gerador #priv_gen.

Os meta-objectos de L8 têm uma funcionalidade próxima dos objectos de L4 porque neles

atribui-se um tipo fixo a SELFC, como se faz na linguagem L4 relativamente a self. Os meta-

-objectos de L8 têm as seguintes diferenças relativamente aos objectos de L4: (1) o seu tipo

não é recursivo; (2) estão parametrizados em função de SAMET e de SELFT em vez de self e

super; (3) suportam componentes privadas.

Já os objectos-normais (instâncias) têm uma funcionalidade próxima dos objectos de L5,

porque neles se atribui um tipo aberto a self. Os objectos normais de L8 têm as seguintes dife-

renças relativamente aos objectos de L5: (1) os nomes SELFC e SUPERC estão disponíveis no

seu interior, juntamente com os nomes self e super; (2) suportam componentes privadas.

8.2.1 Semântica dos tiposNeste ponto, discutimos a codificação dos diversos tipos necessários à formalização de L8.

Uma classe de L8 caracteriza duas espécies de objectos em simultâneo: instâncias e meta-

-objectos. Por isso temos de considerar interfaces e tipos-objecto específicos para cada um dos

dois casos.

Recordamos que a forma geral duma classe é class Rc ou class\s Rc, onde Rc representa o re-

gisto de todas as componentes da classe. No que segue, admitiremos que o registo Rc tem o ti-

po-registo ϒc.

GINTERFACE(ϒc): Representa a interface global duma classe com componentes ϒc. Nesta

interface consideramos todas as componentes da classe. Os nomes SAMET e SELFT podem

ocorrer numa interface global. A formalização é ΛSAMET.ΛSELFT.ϒc.

INTERFACE(ϒc), IINTERFACE(ϒc), SINTERFACE(ϒc): São respectivamente as interfaces ex-

terna, interna e secreta duma classe com componentes ϒc. Registam apenas componentes de

instância. Definimo-las como em L7.

146 OM – Uma linguagem de programação multiparadigma

OBJTYPE(ϒc), IOBJTYPE(ϒc): São respectivamente o tipo externo e o tipo interno das instân-

cias duma classe com interface global GINTERFACE(ϒc). Definimo-las como em L7.

#INTERFACE(ϒc), #IINTERFACE(ϒc), #SINTERFACE(ϒc): São respectivamente as meta-inter-

faces externa, interna e secreta duma classe com componentes ϒc. Registam apenas componen-

tes de classe.

#OBJTYPE(ϒc), #IOBJTYPE(ϒc): São respectivamente o tipo externo e o tipo interno dos

meta-objectos gerados por uma classe que tenha interface global GINTERFACE(ϒc). Estes tipos

não são recursivos mas dependem dos tipos das instâncias da respectiva classe. Definimos o

primeiro como #INTERFACE(ϒc)[OBJTYPE(ϒc)]; definimos o segundo como #IINTERFACE(ϒc)[

OBJTYPE(ϒc)][IOBJTYPE(ϒc)]. Note bem: os meta-objectos são valores recursivos com tipo não-

-recursivo.

CLASSTYPE(ϒc): Representa o tipo-classe das classes com interface global GINTERFACE(ϒc).

Trata-se dum tipo-gerador de meta-objectos cuja parametrização em SAMET e SELFT imita a

parametrização correspondente usada no tipo-classe CLASSTYPE(ϒc) de L7. Como parâmetros

extra, temos ainda uma função de coerção do tipo SELFT→SAMET, e aquele que será o tipo de

SELFC. Escolhemos o tipo de SELFC e o tipo do resultado do gerador de meta-objectos através

do método usado em L4: ambos ficam com o tipo #IINTERFACE(ϒc)[SAMET][SELFT]. Juntando

tudo obtemos:

∀SAMET≤*INTERFACE(ϒc).

∀SELFT≤*IINTERFACE(ϒc)[SAMET].

(SELFT→SAMET)→#IINTERFACE(ϒc)[SAMET][SELFT]→

#IINTERFACE(ϒc)[SAMET][SELFT]

Para definir a semântica do termo new c, mais adiante, precisamos de provar a seguinte rela-

ção entre tipos-meta-objecto:

Teorema 8.2.1-1 #IOBJTYPE(ϒc) ≤ #OBJTYPE(ϒc).

Prova: A demonstração é simples e quase idêntica à demonstração do teorema 7.2.1-1.

Reescrevemos tipos #IOBJTYPE(ϒc) e IOBJTYPE(ϒc) da seguinte forma:

#OBJTYPE(ϒc) ˆ = #INTERFACE(ϒc)[OBJTYPE(ϒc)]

= pub(ϒc)[OBJTYPE(ϒc)/SAMET]

#IOBJTYPE(ϒc) ˆ = #IINTERFACE(ϒc)[OBJTYPE(ϒc)][IOBJTYPE(ϒc)]

= all(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

Ora SELFT não ocorre em #OBJTYPE(ϒc), pelo que #OBJTYPE(ϒc) pode ser vacuamente rees-

crito da seguinte forma:

#OBJTYPE(ϒc) = pub(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

A asserção que se pretende provar ganha assim a forma:

8 Componentes de classe 147

all(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

≤ pub(ϒc)[OBJTYPE(ϒc)/SAMET,IOBJTYPE(ϒc)/SELFT]

Mas esta asserção resulta imediatamente da aplicação da regra [Sub …].

8.2.2 Semântica dos termosVamos agora analisar, de forma breve, as definições dos termos de L8. A apresentação é feita

por referência a algumas das equações semânticas que se encontram na tabela que abre o pre-

sente capítulo.

8.2.2.1 Semântica das classes

Na equação que define a classe class Rc, o gerador polimórfico aí introduzido destina-se a gerar

meta-objectos de tipo-meta-objecto interno e constituídos por todas as componentes de classe

existentes na classe. A cada meta-objecto é ainda adicionado um gerador de instâncias auxiliar

#priv_gen e um construtor privado #priv_new.

A equação do termo subclasse imediata class\s Rc formaliza o tratamento da herança em L8.

A explicação que já foi feita relativamente à semântica deste termo nas linguagem L4, L5 e L7

torna redundante a maioria das explicações que pudéssemos agora produzir. Salientamos ape-

nas os seguintes dois aspectos: (1) o gerador de instâncias da superclasse #priv_gen é adaptado

ao contexto da subclasse através da sua aplicação ao nome self introduzido na subclasse (da se-

guinte forma: super=(SUPERC.#priv_gen self)); (2) em L8, o termo que adapta as componentes de

classe da superclasse ao contexto da subclasse é:

SUPERC = S[SAMET][SELFT] hide SELFC

8.2.2.2 Boa formação das subclasses

As restrições de boa tipificação que adoptamos para o termo anterior estendem as restrições

usadas em L7:

SAMET≤*INTERFACE(ϒs⊕ϒc) ⇒ SAMET≤*INTERFACE(ϒs)

SINTERFACE(ϒs⊕ϒc)≤SINTERFACE(ϒs)

#IINTERFACE(ϒs⊕ϒc)≤#IINTERFACE(ϒs)

Estas condições bloqueiam as decisões de tipificação, aberta ou fechada, tomadas nas com-

ponentes de instância privadas e nas componentes de classe privadas e públicas. Elas permi-

tem rever apenas as decisões de tipificação tomadas relativamente a componentes de instância

públicas: mas note que não precisamos de mais pois em L8, do ponto de vista do programador,

a relação de subtipo envolve apenas tipos-objecto públicos.

O conjunto das três condições anteriores define uma relação binária no conjunto das inter-

faces globais, estabelecendo quais são as modificações de interface que dão origem a subclas-

148 OM – Uma linguagem de programação multiparadigma

ses bem formadas. A relação é reflexiva e transitiva (cf. teorema 8.2.2.2-2), o que é essencial

para que a relação de subclasse se mantenha reflexiva e transitiva em L8.

Definição 8.2.2.2-1 (Relação de extensão geral de L8) Chamamos relação de

extensão geral, gen_ext2, à relação binária entre interfaces globais que se define, por tradução

para F+, da seguinte forma:

GINTERFACE(ϒc) gen_ext GINTERFACE(ϒs) ˆ = (T≤*INTERFACE(ϒc) ⇒ T≤*INTERFACE(ϒs))

& SINTERFACE(ϒc)≤SINTERFACE(ϒs)

& #IINTERFACE(ϒs⊕ϒc)≤#IINTERFACE(ϒs)

Teorema 8.2.2.2-2 A relação de extensão geral gen_ext2 é reflexiva e transitiva.

Prova: A demonstração é trivial e muito parecida com a demonstração do teorema 7.2.2.2-2.

8.2.2.3 Semântica dos outros termos

Em L8, o termo new c refere-se à criação de meta-objectos. De acordo com a respectiva equa-

ção semântica, o gerador polimórfico de meta-objectos que a classe c representa é instanciado

com os tipos externo e interno das instâncias a criar pelo gerador interno #priv_gen e ainda pela

função de coerção hide, que já foi explicada na secção 7.2.2.4. Desta instanciação resulta um

gerador monomórfico de meta-objectos do tipo #IOBJTYPE(ϒc)→#IOBJTYPE(ϒc), ao qual se apli-

ca o operador de ponto fixo para estabelecer a ligação do nome SELFC dentro do meta-objecto

assim criado. Finalmente, ocultamos a parte privada do meta-objecto usando mais uma vez a

propriedade da perda de informação e tirando partido do teorema 8.2.1-1.

Quanto aos termos que descrevem o acesso às componentes dos meta-objectos e objectos –

termos mo#l e o.l –, estes traduzem-se para um simples acesso a uma componente dum registo.

8.3 Discussão sobre L8Um aspecto interessante da hierarquia de classes de L8 é que ela também pode ser vista como

uma dupla hierarquia de classes de L7: uma hierarquia de classes geradoras objectos normais,

e uma hierarquia de classes geradoras meta-objectos. As duas hierarquias são paralelas no

sentido em que existe uma bijecção natural mútua. A interdependência entre cada par de

classes associadas é forte: as duas classes têm de ter privilégios totais de acesso mutuo às

partes privadas respectivas, e a classe que produz meta-objectos tem de estar parametrizada

sobre os tipos-objecto (interno e externo) gerados pela classe que produz instâncias.

Com a introdução das componentes de classe, surge em L8 uma interessante forma padro-

nizada de tirar partido do polimorfismo paramétrico da linguagem, que será adoptada na lin-

guagem OM final. Dedicamos a subsecção seguinte a esta questão.

8 Componentes de classe 149

8.3.1 Polimorfismo de classeJá o referimos na secção 6.3.1, no contexto duma entidade paramétrica P ˆ = λX≤*I.e existe por

vezes a necessidade de ter à disposição um pacote de constantes e funções utilitárias com tipos

dependentes de X. Só por essa via se consegue resolver o problema da inicialização de variá-

veis locais do tipo X (usando alguma constante do tipo X), ou o problema da criação de novos

objectos do tipo X.

Também já vimos que esta questão se resolve de forma simples, adicionando à entidade pa-

ramétrica P um segundo parâmetro, ops:OpsT[X], que torne o corpo de P dependente dum registo

de constantes e funções com as características pretendidas:

P ˆ = λX≤*I.λops:OpsT[X].e :∀X≤*I.OpsT[X]→τ

Esta questão sofre natural evolução em L8 pois, dado um tipo-objecto qualquer τ, qualquer

classe geradora de instâncias do tipo τ já tem associado um registo de constantes e funções so-

bre τ: referimo-nos ao meta-objecto associado a essa classe.

8.3.1.1 Definição de polimorfismo de classe

Em L8, torna-se particularmente natural introduzir um uso padronizado de polimorfismo para-

métrico que, dada uma classe s, contemple a passagem de qualquer sua subclasse c acompa-

nhada pelo respectivo tipo-objecto gerado τc. Eis uma entidade paramétrica de L8 que captura

esta ideia:

λXT≤*INTERFACE(ϒs).λXM:#INTERFACE(ϒs)[XM].e

:∀XM≤*INTERFACE(ϒs).#INTERFACE(ϒs)[XT]→τ

Chamamos polimorfismo de classe a esta forma padronizada de polimorfismo que permite

a passagem simultânea duma classe e do respectivo tipo-objecto gerado. Na linguagem OM fi-

nal, polimorfismo de classe será a única forma de polimorfismo paramétrico disponível.

Se pretendêssemos oficializar uma sintaxe para esta forma de polimorfismo, uma boa possi-

bilidade seria a seguinte:

φX≤*GINTERFACE(ϒc).e ˆ = λXT≤*INTERFACE(ϒc).λXM:#INTERFACE(ϒc)[XT].e

Do lado esquerdo, a interface-limite global GINTERFACE(ϒc) inclui apenas assinaturas de com-

ponentes de classe públicas e de componentes de instância públicas, o que permite impor si-

multaneamente restrições sobre um tipo parâmetro XT e um meta-objecto parâmetro XM. Con-

venciona-se que, no corpo da abstracção, a interpretação do nome X é dual: (1) nos contextos

da forma X#… (e.g. (X#new ()) ou X#zero) o nome X representa a classe (ou meta-objecto) XM;

(2) nos restantes contextos, o nome X é interpretado como o tipo-objecto XT.

8.3.1.2 Boa tipificação da instanciação com variáveis de tipo

Refaçamos agora a análise da secção 6.2.2 para esta forma de polimorfismo paramétrico.

150 OM – Uma linguagem de programação multiparadigma

Consideremos as duas abstracções paramétricas S e C:

S ˆ = φY≤*Gs.e

C ˆ = φX≤*Gc.S[X]

Sob que condições é que a instanciação S[X], efectuada dentro da abstracção C, está bem tipifi-

cada? Para começar, note que X tem uma interpretação dual em S[X], devendo a notação S[X]

ser considerada como abreviatura deS[XT][XM]. Como, por definição, as interfaces globais Gs e

Gc não contêm componentes privadas, uma condição suficiente que garante a boa tipificação

de S[X] é a seguinte:

Gc gen_ext2 Gs

Esta condição generaliza a condição descoberta na secção 6.2.2.

Em L8, para além de variáveis de tipo introduzidas nas abstracções paramétricas existe ain-

da a variável de tipo predefinida SAMET. Este é o caso que analisamos seguidamente.

Consideremos novamente a abstracção paramétrica S:

S ˆ = φY≤*Gs.e

Sob que condições é que a instanciação S[SAMET] (que abrevia S[SAMET][SAMEC]), efectuada

dentro duma classe c, está bem tipificada? Vamos mostrar que é suficiente que a condição

GINTERFACE(ϒc) gen_ext2 Gs seja válida.

O nome SAMET é introduzido na classe c sujeito à restrição SAMET≤* INTERFACE(ϒc). Ora

partindo da condição GINTERFACE(ϒc) gen_ext2 Gs, prova-se imediatamente, pela definição de

gen_ext2, que SAMET≤*INTERFACE(ϒs) e portanto SAMET pode ser usado como primeiro argu-

mento de S.

Relativamente a SELFC, introduzido em c como SELFC:#IINTERFACE(ϒc)[SAMET][SELFT], a

terceira parte de gen_ext2 permite-nos concluir que SAMEC:#IINTERFACE(ϒs)[SAMET][SELFT] e

portanto SAMET pode ser usado como segundo argumento de S.

8.4 ConclusõesA linguagem L8 suporta classes, objectos mutáveis com parte privada, polimorfismo paramé-

trico e componentes de classe. Comparando L8 com linguagens como o C++ ou o Java, verifi-

ca-se que L8 as supera em vários aspectos: tem sistema de tipos estático coordenado com um

um mecanismo de herança flexível; suporta polimorfismo paramétrico ao nível do sistema de

tipos (o C++ suporta apenas expansão textual de entidades paramétricas, o Java nem isso, em-

bora a sua variante experimental Pizza [OW97] já disponha desse mecanismo); tem uma se-

mântica bem definida, por redução à semântica do calculo-lambda F+.

Claro que o C++ e o Java superam a linguagem L8 noutros aspectos: os aspectos pragmáti-

cos destas linguagens estão mais desenvolvidos; o C++ inclui suporte para herança múltipla e

8 Componentes de classe 151

para programação de sistemas; o Java dispõe dum útil mecanismo de modularidade – o packa-

ge –, etc.

Em qualquer caso, podemos concluir que a linguagem L8, sem ser excessivamente compli-

cada, já incorpora um conjunto suficientemente rico de mecanismos para ser bastante usável

na prática.

Na literatura não há muitas referências a modelos tipificados que estudem o mecanismo das

componentes de classe. Só conseguimos citar os trabalhos [CHC90] e [Bru94] que, mesmo

não estudando directamente esse mecanismo, tratam um aspecto parcelar de L8: a introdução

dum nome MyClass que permite que instâncias duma classe acedam à sua própria classe para

criar objectos irmãos. Na medida em que transformam as classes em entidades recursivas, os

nomes MyClass e SELFC são análogos entre si.

No entanto, no capítulo corrente, o nome SELFC, conjuntamente com SUPERC, foi realmente

alvo de tratamento mais alargado que abrangeu também a formalização das componentes de

classe. Se desejássemos introduzir apenas a construção MyClass, o contexto apropriado para o

fazer seria o contexto da linguagem L5: bastaria adicionar à equação semântica da classe o pa-

râmetro extra MyClass:SAMET→SAMET, e introduzir na definição de new c uma aplicação suple-

mentar de fix.

Capítulo 9

Modos

Sintaxe dos géneros,tipos e termos de L9

Κ ::= ∗ | ∗⇒Κ

τυϕϒI::= Bool | Nat | υ→τ | X | ΛX.τ | ϕ[τ] | l–:τ– | ϒ⊕ϒ′ |

SAMET | CLASSTYPE(ϒ) | INTERFACE(ϒ) | OBJTYPE(ϒ) |SELFT | GINTERFACE(ϒ) | IINTERFACE(ϒ) | SINTERFACE(ϒ) | IOBJTYPE(ϒ)#INTERFACE(ϒ) | #IINTERFACE(ϒ) | #SINTERFACE(ϒ) |#OBJTYPE(ϒ) | #IOBJTYPE(ϒ) |

∀X≤*INTERFACE(ϒB).τ |MODEOP X.ϒM | ϕ TMODETYPE X≤*INTERFACE(ϒB).ϒ | Μ τ#priv(ϒ) | priv(ϒ) | #pub(ϒ) | pub(ϒ) | #all(ϒ) | all(ϒ)

efcomRP::= lτ | θτ | x | λx:υ.e | f e | rec x:τ.e | l–=e– | R.l |

self | super | class R | class\s R | new c | c#l | o.l |SELFC | SUPERC |

checkType[τ] | downcastσ[τ] |

λX≤*INTERFACE(ϒB).e | P[τ] |mode X≤*INTERFACE(ϒB).R | m τ |#priv(R) | priv(R) | #pub(R) | pub(R) | #all(R) | all(R)

Semântica dos tipos

MODETYPE X≤*INTERFACE(ϒB).ϒM :∗ ˆ = tipo-modo

∀X≤*INTERFACE(ϒB).CLASSTYPE(pub(ϒB)[X/SAMET]⊕ϒM)

onde ϒM≤$access:Unit→X

(MODETYPE X≤*INTERFACE(ϒB).ϒM) T :∗ ˆ = instanciação de tipo-modo

CLASSTYPE(ϒT⊕ϒM[T/X])

MODEOP X.ϒM :∗⇒∗ ˆ = operador de modo (gerado por modo)

ΛX.OBJTYPE(ϒM)ϕ T :∗ ˆ = instanciação de operador de modo

ϒT⊕ϕ[T]

154 OM – Uma linguagem de programação multiparadigma

Semântica dos termos

mode X≤*INTERFACE(ϒB).RM :MODETYPE X≤*INTERFACE(ϒB).ϒM ˆ = modo

λX≤*INTERFACE(ϒB).classaccess[pub(ϒB)[X/SAMET]]+RM

m T :CLASSTYPE(ϒT⊕ϒM[T/X]) ˆ = let M:MODETYPE X≤*INTERFACE(ϒB).ϒM = m in

let S:CLASSTYPE(pub(ϒB)[T/SAMET]⊕ϒM[T/X]) = M[T] inλSAMET≤*INTERFACE(ϒT⊕ϒM[T/X]).

λSELFT≤*IINTERFACE(ϒT⊕ϒM[T/X])[SAMET].λhide:SELFT→SAMET.

λSELFC:#IINTERFACE(ϒT⊕ϒM[T/X])[SAMET][SELFT].let _SUPERC:#IINTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])[SAMET][SELFT] =

(S[SAMET][SELFT] hide SELFC) in _SUPERC

+ #priv_new = λz:Unit.(fix (SELFC.#priv_gen))+ #priv_gen = λself:SELFT.(access[ϒT]

+ (_SUPERC.#priv_gen self))

onde ϒT ˆ = pub(ϒ)[T/SAMET]com T = OBJTYPE(ϒ) e OBJTYPE(ϒ)≤*INTERFACE(ϒB)ou T≤*INTERFACE(ϒ) e INTERFACE(ϒ) ext INTERFACE(ϒB)

Macro auxiliar

access[l–:τ–] ˆ = l–=λz:Unit.(self.$access ()).l

–:l

–:Unit→τ—————————

Na secção 9.1, apresentamos e discutimos informalmente o mecanismo dos modos de L9. Na

secção 9.2, formalizamos a semântica o mecanismo dos modos. Na secção 9.3 referimos, com

brevidade, outros aspectos importantes de L9. Na secção 9.4 extraímos algumas conclusões.

9.1 Conceitos e mecanismos de L9Na linguagem L9, introduzimos o mecanismo dos modos, um mecanismo de extensão semân-

tica que constituirá a base das características multiparadigma da linguagem OM. O mecanismo

dos modos é o último mecanismo de natureza dinâmica que consideramos nesta tese.

9.1.1 ModosO mecanismo dos modos é uma ferramenta de programação de nível meta que permite esten-

der a semântica da linguagem L9 usando a própria linguagem. Actua influenciando a funciona-

lidade das entidades tipificadas da linguagem, ou seja, dos objectos, variáveis, expressões,

parâmetros de função e resultados de função.

Dependendo do modo duma entidade tipificada, digamos duma variável, as suas proprie-

dades semânticas podem variar significativamente. Assim, por exemplo, uma variável com

modo lógico, log, tem a funcionalidade das variáveis simbólicas da linguagem Prolog [CM81,

Hog84]; uma variável com modo constante, const, é obrigatoriamente inicializada no ponto da

declaração e nunca mais pode ser alterada; uma variável sem modo tem a funcionalidade

primitiva das variáveis de tipo Ref τ (introduzidas linguagem L7&).

9 Modos 155

O mecanismo dos modos compreende uma faceta dinâmica e uma faceta estática:

• A faceta dinâmica concentra-se na questão da modificação ou do enriquecimento da

funcionalidade dos objectos com modo. Note que, tipicamente um objecto com modo

tem uma funcionalidade alterada relativamente a um objecto do mesmo tipo base mas

sem modo.

• A faceta estática concentra-se no controlo da funcionalidade das entidades estáticas

com modo. As entidades estáticas são as variáveis, expressões, parâmetros de funções e

resultados de funções.

As duas facetas dum modo definem-se conjuntamente numa construção sintáctica chamada

modo. Um modo tem uma estrutura semelhante a uma classe parametrizada de L8, e nele é

possível identificar uma parte dinâmica e uma parte estática:

• A parte dinâmica dum modo consiste na implementação dos objectos com esse modo.

Essa implementação é escrita usando a própria linguagem L9.

• A parte estática envolve o sistema de tipos da linguagem e questões de açúcar sintácti-

co. Na linguagem OM final, a parte estática dum modo será especificada usando os se-

guintes recursos:

- componentes específicas: elas determinam a interface global dum modo;

- métodos de coerção: estes métodos servem para indicar quais são as conversões de

modo legítimas que podem envolver o modo em questão (cf. secção 10.2.1);

- métodos “#$def_*”ou de sobreposição de sintaxe: estes métodos permitem forçar a

reinterpretação de certas construções sintácticas de OM, quando aplicadas a entida-

des com o modo em questão (cf. secção 11.3.1);

- globalização de métodos: é possível globalizar alguns métodos de classe específi-

cos para com isso certos pequenos problemas técnicos discutidos na secção 11.5.

A faceta dinâmica será o tópico essencial do capítulo 9. A linguagem L9 suporta apenas a

faceta dinâmica dos modos. Quanto à faceta estática, esta requer recursos – coerções e sobre-

posição de sintaxe – que só a partir das linguagens L10 e OM ficarão disponíveis.

9.1.2 Exemplo: o modo logAntes de tentarmos ser rigorosos relativamente a uma definição de modo, nesta secção e a títu-

lo de ilustração, vamos discutir uma versão simplificada do modo de biblioteca log. A defini-

ção completa do modo log encontra-se na secção 12.5.

O modo lógico, log, introduz as ideias de variável lógica e unificação (sintáctica e semânti-

ca). São duas as principais operações específicas que ele suportada: isVar e '=='.

Ao contrário do que se poderia esperar, o modo log não suporta um mecanismo de retroces-

so (backtracking). Essa é uma tarefa específica de outro modo, o modo gen ou modo dos gera-

dores. O modo gen tira partido da noção de gerador para introduzir retrocesso na avaliação de

156 OM – Uma linguagem de programação multiparadigma

expressões, à maneira da linguagem Icon [Gri83]. Entre as componentes específicas do modo

gen incluem-se as primitivas: repeat, fail, ‘&’, '|', '~'.

O sistema de coerções da linguagem OM final permitirá que as componentes específicas do

modo gen possam ser aplicadas a entidades com modo log. Em cada uma dessas situações, o

sistema de coerções actua, promovendo as entidades lógicas envolvidas a entidades com o

modo composto gen log. Como as propriedades deste modo composto resultam da acumulação

das propriedades dos modos componentes, é desta forma indirecta que se introduz retrocesso

na avaliação de expressões lógicas.

9.1.2.1 Faceta dinâmica do modo log

Começamos por apresentar a faceta dinâmica do modo log. Esta ocupa-se exclusivamente da

implementação de objectos lógicos sobre um tipo τ, objectos especiais cuja funcionalidade se

aproxima da funcionalidade das variáveis simbólicas da linguagem Prolog.

A classe dum objecto lógico sobre um tipo τ escreve-se log τ. O tipo dum objecto lógico

sobre um tipo τ escreve-se LogT τ. Assumimos que apenas os objectos da classe log τ têm tipo

LogT τ. Esta identificação entre a classe log τ e o tipo LogT τ pode ser estabelecida usando a téc-

nica introduzida na secção 4.3.6 (voltaremos a este assunto na secção 9.3).

Um objecto da classe log τ,tem três estados possíveis: (1) não-ligado; (2) ligado a outro

objecto lógico da classe log τ; (3) ligado a um objecto do tipo τ. Usando objectos lógicos é pos-

sível exprimir restrições lógicas complexas, à semelhança do que se faz em muitas implemen-

tações da linguagem Prolog [Aït90, Dia90, Gab85, Her89, Hog84, KB85, War77, War83,

War88].

O modo log introduz diversas operações sobre objectos lógicos que conjuntamente se desi-

gnam por componentes específicas do modo. Há duas operações particularmente importantes:

o método isVar que testa se um objecto lógico está ou não instanciado (i.e. se tem um valor), e

o método '==' que implementa uma operação de unificação.

Para além das componentes específicas do modo lógico, os objectos lógicos suportam ainda

todas as componentes públicas dos objectos de tipo τ, de forma automática. Quando uma des-

sas componentes é acedida num objecto lógico z, duas situações podem ocorrer: ou z está ins-

tanciado com um objecto, dito conexo, de tipo τ e, nesse caso, é a correspondente componente

do objecto conexo que acaba por ser acedida (digamos que z redirige aquele acesso para o ob-

jecto conexo); ou z não está instanciado e, neste caso, teria de ser gerada uma excepção (na

prática, para evitar a excepção o modo gen intervém para provocar falhanço e retrocesso: ver

definição do método #gen_from_log na secção 12.5).

Os objectos lógicos sobre τ são exemplos do que nós chamamos objectos de funcionalidade

enriquecida ou modificada. Sendo LogT τ o tipo dos objectos lógicos sobre τ, LogT τ inclui to-

das as componentes de τ e ainda as componentes específicas do modo log. No entanto, isso não

9 Modos 157

significa que tenhamos imediatamente LogT τ≤τ. Por exemplo, basta que τ tenha ocorrências

negativas de SAMET para que isso não aconteça, como sabemos do capítulo 5.

Para que seja possível definir a operação de unificação do modo lógico, qualquer tipo τ que

seja usado na instanciação deste modo tem de incluir a assinatura dum método de igualdade,

ou seja, deve verificar a condição: τ≤*'==':SAMET→Bool. Em breve, veremos que os modos

constituem entidades paramétricas ≤*-restringidas. No caso do modo lógico, a interface-limite

que lhe está associada é '==':SAMET→Bool.

Uma nota final. Os objectos lógicos são introduzidos no modo lógico por puras razões de

implementação. A intenção é que eles passem desapercebidos ao programador que usa o modo

log. Apenas a sua influência sobre a semântica da linguagem deverá notada: por exemplo, para

o programador deverá ser óbvio que uma variável declarada com modo lógico fica com pro-

priedades bem diferentes duma variável declarada com outro modo.

9.1.2.2 Faceta estática do modo log

Consideramos agora a faceta estática do modo log. A faceta estática do modo log especifica a

funcionalidade das entidades estáticas com modo log.

Começamos pela questão das coerções de modo, ilustrando-a através de dois exemplos:

Se um objecto com tipo simples, sem modo, τ for passado como argumento para um mé-

todo que espera um valor de tipo LogT τ (com modo log), o objecto original terá de ser converti-

do para ficar com o modo esperado pelo método. O modo log inclui um método de coerção

com assinatura:

#log_up:∀T≤*INTERFACE('==':SAMET→Bool).T→(LogT T)

cuja simples existência determina que aquela conversão se pode realizar para todos os tipos τ

tais que τ≤*INTERFACE('==':SAMET→Bool)). Adicionalmente, o corpo desse método descreve

como a conversão se efectua. Este método de coerção é, portanto, um elemento da faceta

estática do modo log.

O segundo exemplo é um pouco mais complicado. Se um objecto com tipo base τ e modo

composto const value log for passado para um parâmetro com o mesmo tipo base e modo com-

posto lazy log, então existirá uma coerção composta a realizar e uma cadeia de procedimentos

de conversão a aplicar. Neste caso, o modo do argumento terá de ser primeiro despromovido a

log e depois promovido a lazy log para, finalmente, ficar a par com o modo requerido pelo parâ-

metro.

Um outro aspecto estático tem a ver com a indicação de que o modo log deve suportar uma

inicialização implícita das variáveis lógicas. Para isso basta definir um construtor público de

nome #zero, já que a definição da operação de inicialização implícita, #$def_init, depende da

existência do método #zero e usa-o quando ele existe. Incidentalmente, o construtor #zero do

158 OM – Uma linguagem de programação multiparadigma

modo log gera objectos lógicos não ligados, ou seja, variáveis livres no sentido da linguagem

Prolog.

Um último aspecto estático: o modo log não suporta a operação de atribuição, impedindo

assim as variáveis lógicas de serem alvo dessa operação. Este efeito consegue-se, não definin-

do deliberadamente o método #$def_assign no modo log.

9.1.3 O que é um modo?Nesta secção vamos considerar apenas a faceta dinâmica dos modos.

Um modo é um construtor de classes especial (cf. secção 6.1.1), que permite criar classes

não-paramétricas através uma nova operação de instanciação (formalizada na secção 9.2.2.2).

Cada modo gera também um tipo-objecto paramétrico especial, que será designado por opera-

dor de modo (formalizado na secção 9.2.3).

Um modo, tem a seguinte forma sintáctica:

m ˆ = mode X≤*INTERFACE(ϒB).RM

Trata-se duma entidade paramétrica com um único parâmetro X, caracterizada por uma inter-

face-limite INTERFACE(ϒB), imposta ao tipo-parâmetro X, e por um registo de componentes pú-

blicas RM, designadas por componentes específicas do modo. Tomando o exemplo do modo

log, este modo caracteriza-se pela interface-limite INTERFACE('==':SAMET→Bool) e por diver-

sas componentes específicas definidas no seu corpo, entre as quais se encontram os métodos

isVar e '==' (cf. secção 9.1.2.1).

A operação de instanciação dum modo m com um tipo-objecto τ denota-se por (m τ). Quan-

do um modo m é instanciado com um tipo-objecto τ compatível com a sua interface-limite, o

que se obtém é uma classe não-paramétrica. Neste sentido, um modo é efectivamente um cons-

trutor de classes. Por sua vez, cada instância (m τ) do modo m gera um tipo-objecto MT τ que

depende de τ. Isto mostra que um modo gera um tipo-objecto paramétrico especial, neste caso

denotado por MT, que designaremos por operador de tipo de modo, ou, mais simplesmente,

por operador de modo. Por exemplo: dado um tipo-objecto τ compatível com a interface-limite

do modo lógico, a expressão log τ representa uma classe não-paramétrica geradora de objectos

lógicos do tipo LogT τ.

Os objectos gerados pela classe (m τ) – ditos objectos com modo m – têm uma funcionalida-

de enriquecida relativamente aos objectos do tipo τ. Os objectos da classe (m τ) suportam todas

as componentes do tipo τ e, adicionalmente, as componentes específicas do modo m. Em caso

de conflito (ou seja, de sobreposição) as componentes específicas do modo m têm prioridade

sobre as componentes do tipo τ.

Um modo m é, em muitos aspectos, semelhante a uma classe paramétrica com um único pa-

râmetro. De facto, da instanciação duma classe paramétrica P com um tipo-objecto τ compa-

9 Modos 159

tível também resulta uma classe não-paramétrica, neste caso P[τ]. Além disso as classes para-

métricas geram tipos-objecto paramétricos (cf. 6.1.1) tal como os modos. A diferença entre a

classe P[τ] e a classe (m τ) é a seguinte: enquanto que os objectos da classe P[τ] são constituídos

pelas componentes específicas da classe P apenas, os objectos da classe (m τ) contêm todas as

componentes do tipo τ mais as componentes específicas do modo m.

9.1.4 Implementação dum modoA implementação dum qualquer modo m

m ˆ = mode X≤*INTERFACE(ϒB).RM

descreve a funcionalidade privada e pública dos objectos de tipo MT X, gerados pela classe

(m X). Sobre X apenas se sabe que verifica a restrição de instanciação do modo m. Há três re-

gras que toda a implementação do modo m tem de cumprir com rigor:

• A implementação de m tem de garantir que todo o objecto z:MT X, gerado pela classe

(m X), fica internamente ligado a um objecto obj de tipo X. Nesta circunstância o objecto

obj designa-se por objecto conexo de z. O seguinte diagrama exprime a relação entre o

objecto com modo z e o seu objecto conexo:

z:MT X obj:X

• A implementação de m tem de suportar um método de instância chamado $access que

coloque à disposição do objecto z (e das equações semânticas da linguagem) uma for-

ma universal de acesso ao objecto conexo. Faz sentido requerer o método $access, pois

os detalhes do estabelecimento da ligação interna entre z e o seu objecto conexo obj va-

riam de modo para modo. Actualizamos o diagrama anterior, agora considerando a

existência do método $access:

z:MT X ob:X $access()

z:MT X ob:X $access()

z:MT X obj:X $access()

• Estabelecida a ligação entre z e o seu objecto conexo obj, e definido o método $access, o

objectivo da implementação de m é que z seja visto como uma versão enriquecida ou

modificada de obj. Não é difícil obter este efeito: todos os acessos a z que envolvam as

componentes do tipo τ são reencaminhados para obj usando o método $access (a equação

semântica do modo trata desta questão automaticamente); todos os acessos a z que en-

volvam as componentes específicas do modo m são tratados ao nível do próprio objecto

z.

Este esquema de implementação é perfeitamente satisfatório, tanto no caso dos modos sim-

ples como no caso dos modos mais complicados. É apenas preciso considerar uma circunstân-

cia suplementar que surge frequentemente durante a implementação dos modos mais sofistica-

160 OM – Uma linguagem de programação multiparadigma

dos: a necessidade de, transitoriamente, representar o objecto conexo sob uma forma descritiva

indirecta. É o que se acontece no modo log que usa restrições lógicas como descrições indi-

rectas e temporárias dos objectos conexos que são soluções dessas restrições. Nestes modos

mais complicados, o método $access tem de se preocupar com a situação em que o objecto co-

nexo se apresenta sob uma forma descritiva indirecta. Nessa situação, $access é obrigado a pro-

curar uma forma explícita para o objecto conexo e, caso não a consiga encontrar, a computa-

ção tem de ser abortada (ou, pelo menos, gerada uma excepção, com se faz na linguagem OM

final).

Para exemplificar, vamos apresentar uma implementação completa dum modo. Considera-

mos o modo mais simples que faz o mínimo de sentido – o modo neutral:

neutral ˆ = mode X≤*INTERFACE().

priv_obj=ref (nil:X),

$access=λz:Unit. (deref self.priv_obj),

#new=λa:X.(let p=SELFC#priv_new () in (p.priv_obj:=a; p))

Este modo introduz uma variável de instância privada, priv_obj, que se destina a guardar direc-

tamente o objecto conexo (note que os objectos com modo neutral são simples contentores).

Define também o método de instância $access, o qual, neste caso, se limita a produzir o valor da

variável priv_obj. Define ainda um construtor público chamado #new, sem o qual seria impossí-

vel criar objectos com modo neutral no exterior da definição.

9.2 Semântica de L9Formalizamos agora a semântica da linguagem L9. A tabela das equações semânticas de L9

encontra-se no início do presente capítulo.

Em L9 continuamos a tratar a questão do estado mutável da mesma forma que em L8 (cf.

8.1). Ou seja, nas discussões informais e nos exemplos assumimos que L9 suporta objectos

mutáveis, mas nas equações semânticas ignoramos esta faceta da linguagem. Não vale a pena

complicar, pois a formalização da linguagem L7& já mostra como essa questão pode ser trata-

da (cf. secção 7.3).

9.2.1 Semântica dos tiposUm tipo novo que surge em L9 é o tipo dos modos, ou tipo-modo, com a forma:

MODETYPE X≤*INTERFACE(ϒB).ϒM onde ϒM≤$access:Unit→X

Neste novo tipo, INTERFACE(ϒB) é a interface-limite imposta ao tipo-parâmetro X e ϒM é o

tipo-registo das componentes específicas do modo. Este último tipo-registo tem obrigatoria-

mente de incluir uma componente $access com o tipo Unit→X.

A codificação deste tipo em F+ é a seguinte:

9 Modos 161

∀X≤*INTERFACE(ϒB).

CLASSTYPE(pub(ϒB)[X/SAMET]⊕ϒM)

Ela reflecte o conhecimento parcial que existe sobre os tipos X que podem ser usados para ins-

tanciar o modo. No corpo deste tipo paramétrico encontra-se um tipo-gerador de meta-objectos

que contém: (1) as componentes públicas de X que podem ser previstas considerando a inter-

face-limite INTERFACE(ϒB), ou seja pub(ϒB)[X] (repare que X≤pub(ϒB)[X]); (2) as componentes

específicas do modo, ou seja, ϒM. Note que as componentes específicas têm precedência sobre

as componentes com origem em X.

Se o tipo dos modos fosse interpretado como o tipo duma classe paramétrica normal então

o seu tipo seria mais simplesmente:

∀X≤*INTERFACE(ϒB).

CLASSTYPE(ϒM)

o que confirma que um modo difere duma classe paramétrica apenas na forma como as com-

ponentes com origem em X são tratadas.

Adiamos para a secção 9.2.3 a introdução dos novos tipos de ordem superior, operadores

de modo, porque só nessa secção estará criado o contexto necessário à sua apresentação.

9.2.2 Semântica dos termosEm L9 existem apenas duas novas formas de termos: os modos e as instanciações de modo.

9.2.2.1 Modos

Um modo tem a forma

mode X≤*INTERFACE(ϒB).RM :MODETYPE X≤*INTERFACE(ϒB).ϒM

onde INTERFACE(ϒB) é a interface-limite a que está submetido o parâmetro X, e é RM o registo

das componentes específicas do modo. Definimos a semântica deste novo termo através da se-

guinte tradução para a seguinte classe paramétrica de L8:

λX≤*INTERFACE(ϒB).

classaccess[pub(ϒB)[X/SAMET]] + RM

Esta classe contém: (1) as componentes públicas do tipo-parâmetro X que podem ser previstas

considerando a interface-limite INTERFACE(ϒB); (2) as componentes específicas do modo, inc-

luindo o método de instância $access.

As componentes com origem no tipo-parâmetro X têm de ser introduzidas duma forma es-

pecial, visto o seu conteúdo semântico nunca ser conhecido ao nível do modo; essa informação

só estará disponível em tempo de execução nos objectos conexos dos objectos com modo. Para

aceder a essa informação em tempo de execução, usamos uma técnica simples: por cada com-

ponente l que ocorre em INTERFACE(ϒB), adicionamos à classe que resultará da instanciação do

162 OM – Uma linguagem de programação multiparadigma

modo um método público de reencaminhamento da forma l=λz:Unit.(self.$access ()).l. Recorda-

mos que o método $access define uma forma de acesso ao objecto conexo, disponível em todos

os objectos com modo. (O método de reencaminhamento tem um parâmetro artificial z:Unit,

que temos necessidade de introduzir por L9 usar a estratégia de avaliação call-by-value.).

Dentro dum modo, o programador tem acesso a todas as componentes específicas do modo,

privadas e públicas, mais as componentes indicadas na sua interface-limite: o tipo que disponi-

biliza esta funcionalidade é SELFT.

9.2.2.2 Instanciação dum modo

Tratemos agora do caso da instanciação dum modo com um tipo-objecto. Considere o seguinte

modo genérico m:

m ˆ = mode X≤*INTERFACE(ϒB).RM : MODETYPE X≤*INTERFACE(ϒB).ϒM

A instanciação deste modo m com um tipo T≤*INTERFACE(ϒB) escreve-se da seguinte forma

simples:

m T

Com o objectivo de estudar a semântica da instanciação dum modo, vamos ter de considerar

duas possibilidades, bem distintas, relativamente a T:

• T é um tipo concreto: portanto T≡OBJTYPE(ϒ) com OBJTYPE(ϒ)≤*INTERFACE(ϒB);

• T é uma variável de tipo: neste caso, previamente introduzida numa outra entidade

paramétrica sob a restrição T≤*INTERFACE(ϒ), sendo INTERFACE(ϒ) ext INTERFACE(ϒB).

Conseguiremos tratar estes dois casos de forma uniforme, mediante a introdução do seguin-

te tipo-registo ϒT:

ϒT ˆ = pub(ϒ)[T/SAMET]

ϒT inclui todas as componentes públicas estaticamente conhecidas de T. Vamos provar que

ϒT é um supertipo de T:

Teorema 9.2.2.2-1 Nas condições anteriores, verifica-se a asserção:

T≤ϒT

Prova: Se T for um tipo concreto então temos:

T=OBJTYPE(ϒ)

⇒ T=pub(ϒ)[T/SAMET] por definição de OBJTYPE(ϒ)

⇒ T=ϒT por definição de ϒT⇒ T≤ϒT por [Sub =]

Se T for uma variável, então temos:

9 Modos 163

T≤*INTERFACE(ϒ)

⇒ T≤INTERFACE(ϒ)[T] por definição de ≤*

⇒ T≤pub(ϒ)[T/SAMET] por definição de INTERFACE(ϒ)

⇒ T≤ϒT por definição de ϒT

O tipo que se atribui ao termo (m T) é um tipo-classe:

m T : CLASSTYPE(ϒT⊕ϒM[T/X])

Isso significa que pretendemos que o termo (m T) seja visto como uma classe não-paramétrica

que inclui todas as componentes de T – ou pelo menos as que se podem prever estaticamente,

no caso de T ser uma variável – mais as componentes específicas do modo m. É suficiente ins-

peccionar o tipo-classe CLASSTYPE(ϒT⊕ϒM[T/X]) para se concluir que: (1) se T for um tipo-

-objecto concreto, como T=ϒT, então todas as componentes de T estão presentes em

CLASSTYPE(ϒT⊕ϒM[T/X]); (2) se T for uma variável de tipo tal que T≤*INTERFACE(ϒ), como

T≤ϒT, então todas as componentes que se podem prever estão presentes no tipo-classe

CLASSTYPE(ϒT⊕ϒM[T/X]).

Neste momento estamos em condições de estudar a equação semântica que descreve como

se processa a instanciação dum modo m com um tipo T, para se obter a classe não-paramétrica

(m T). A equação encontra-se na tabela que se encontra no início do presente capítulo. Na equa-

ção, começamos por determinar:

S=m[T]

usando o modo m como se duma classe paramétrica normal se tratasse (repare que [.] represen-

ta a operação de instanciação de entidades paramétricas normais).

Note que a classe S=m[T] é já uma boa aproximação da classe pretendida, (m T): efectiva-

mente, S já inclui todas as componentes específicas do modo, incluindo o método de instância

$access, mais todos os métodos de reencaminhamento referentes às componentes que ocorrem

na interface-limite do modo.

Falta só o detalhe de adicionar à classe S os métodos de reencaminhamento referentes às

componentes que estão em ϒT mas não em ϒB. Ora os métodos de reencaminhamento são mé-

todos de instância públicos, pelo que precisamos de aceder ao gerador interno de instâncias de

S, #priv_gen, para com base nele criarmos um gerador interno enriquecido, e uma nova classe

com este novo gerador no seu interior. Para fazermos tudo isto, seguimos o padrão geral da co-

dificação de classes em L8. Começamos por introduzir novos nomes SAMET, SELFC, hide,

SELFC e adaptamos o gerador polimórfico S ao contexto destes novos nomes, aplicando S a

todos eles:

_SUPERC = S[SAMET][SELFT] hide SELFC

Depois extraímos o gerador de instâncias _SUPERC.#priv_gen e usamo-lo na criação dum novo

gerador de instâncias:

164 OM – Uma linguagem de programação multiparadigma

#priv_gen = λself:SELFT.(access[ϒT] + _SUPERC.#priv_gen self)

Finalmente, este novo gerador é usado para substituir aquele que se encontra em _SUPERC, ter-

minando assim a criação da nova classe. Note que o novo gerador inclui, como pretendíamos,

um método de reencaminhamento por cada componente de ϒT: a primitiva access definida na

tabela de equações trata dessa questão.

Terminada que foi a criação da nova classe (m T), verifica-se que apenas as componentes

estaticamente conhecidas de T, i.e. as que estão presentes em ϒT, ficam com um método de li-

gação na classe gerada. Isso não é problema pois as componentes estaticamente desconhecidas

de T estão, em todo o caso, inacessíveis devido à natureza estática do nosso sistema de tipos.

Note ainda que o tipo T ocorre diversas vezes no interior da equação semântica da classe

(m T), sendo portanto errónea a ideia de que T seria substituído pelo seu supertipo ϒT ao longo

de toda essa equação semântica. Aliás, se tal fosse feito a equação ficaria mal tipificada. Na

realidade T é substituído por ϒT apenas nos contextos que reflectem o facto de só componentes

estaticamente conhecidas de T terem um método de ligação na classe (m T).

9.2.2.3 Boa tipificação da equação semântica

Agora, importa ver se estão bem tipificadas as expressões M[T] e (S[SAMET][SELFT] hide SELFC)

que ocorrem na equação semântica do termo (m T).

M[T] está bem tipificada, pois as restrições que o parâmetro T tem de verificar (indicadas no

final da equação) garantem que se tem sempre T≤*INTERFACE(ϒB). Este facto é verdadeiro,

independentemente de T ser um tipo concreto ou uma variável de tipo (no caso da variável de

tipo é preciso usar a definição de ext para tirar esta conclusão).

Relativamente a (S[SAMET][SELFT] hide SELFC) é preciso verificar se as três seguintes condi-

ções são válidas:

SAMET≤*INTERFACE(ϒT⊕ϒM[T/X]) ⇒SAMET≤*INTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])

SAMET≤*INTERFACE(ϒT⊕ϒM[T/X]) ⇒SELFT≤IINTERFACE(ϒT⊕ϒM[T/X])[SAMET] ⇒

SELFT≤IINTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])[SAMET]

SAMET≤*INTERFACE(ϒT⊕ϒM[T/X]) & SELFT≤IINTERFACE(ϒT⊕ϒM[T/X])[SAMET] ⇒#IINTERFACE(ϒT⊕ϒM[T/X])[SAMET][SELFT] ≤

#IINTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])[SAMET][SELFT]

Felizmente que a validade das condições anteriores pode ser deduzida da validade da asser-

ção simples ϒT≤pub(ϒB)[T/SAMET] pois, como é fácil de ver, esta asserção implica as seguintes

três condições que são, todas elas, mais fortes que as anteriores:

9 Modos 165

INTERFACE(ϒT⊕ϒM[T/X]) ≤ INTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])

IINTERFACE(ϒT⊕ϒM[T/X]) ≤ IINTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])

#IINTERFACE(ϒT⊕ϒM[T/X]) ≤ #IINTERFACE(pub(ϒB)[T/SAMET]⊕ϒM[T/X])

Assim, só temos de provar a asserção ϒT≤pub(ϒB)[T/SAMET] para garantir a boa tipificação

do termo (S[SAMET][SELFT] hide SELFC).

Teorema 9.2.2.3-1 Sob as restrições a que o nome T está submetido na equação semântica

do termo (m T), verifica-se a propriedade:

ϒT≤pub(ϒB)[T/SAMET]

Prova: Consideremos primeiro o caso em que T≡OBJTYPE(ϒ)≤*INTERFACE(ϒB). Neste situação,

já vimos na prova do teorema 9.2.2.2-1 que T=ϒT. Assim ϒT≤*INTERFACE(ϒB), ou seja, usando

a definição de interface, ϒT≤pub(ϒB)[T/SAMET], como pretendíamos.

Consideremos agora o caso em que T é uma variável de tipo tal que T≤*INTERFACE(ϒ) com

INTERFACE(ϒ) ext INTERFACE(ϒB). Para começar, segundo o lema 5.3-6 verifica-se a condição

INTERFACE(ϒ)[T]≤INTERFACE(ϒB)[T], que se reescreve pub(ϒ)[T/SAMET]≤pub(ϒB)[T/SAMETB].

Mas sendo assim, da definição de ϒT=pub(ϒ)[T/SAMET] sai imediatamente ϒT≤pub(ϒB)[T/SAMET],

como pretendíamos.

9.2.3 Operadores de modoNa secção 9.1.3, verificámos que um modo gera uma nova forma de tipo de ordem superior

que designámos por operador de modo. Um operador de modo tem a seguinte forma sintác-

tica:

MODEOP X.ϒM

A codificação em F+ dum operador de modo é efectuada como se dum tipo-objecto paramé-

trico normal, gerado por uma classe paramétrica, se tratasse. Assim, a codificação do tipo ante-

rior é a seguinte:

ΛX.OBJTYPE(ϒM)

No entanto, a operação de instanciação dum operador de modo tem particularidades especi-

ais relativamente é operação de instanciação dum tipo-objecto paramétrico normal. A defi-

nição da nova operação de instanciação é a seguinte:

Seja ϕ um operador de modo e seja T um tipo concreto T≡OBJTYPE(ϒ) ou uma variável de

tipo T≤*INTERFACE(ϒ) (cf. secção 9.2.2.2). Seja ϒT ˆ = pub(ϒ)[T/SAMET] o tipo que introduzimos

na secção 9.2.2.2. Então a operação de instanciação (ϕ T), de ϕ com T, define-se então seguinte

forma:

ϕ T ˆ = ϒT⊕ϕ[T]

166 OM – Uma linguagem de programação multiparadigma

Esta é a definição que incluímos na tabela de equações semânticas do início do presente capí-

tulo. Note que, na expressão da direita, ϕ[T] é sempre um tipo-objecto, ou seja um tipo-registo

definido recursivamente sobre uma variável de tipo SAMET. Quanto a ϒT, trata-se dum tipo-ob-

jecto não recursivo, no qual SAMET não tem qualquer ocorrência.

9.3 Discussão sobre L9Nesta secção, essencialmente, discutimos aspectos adicionais sobre o mecanismo dos modos

que não tiveram cabimento nas subsecções anteriores.

A decisão de separar os conceitos de classe e de tipo-objecto remonta à linguagem L4 (cf.

secção 4.1.3). A partir deste capítulo, os modos serão uma excepção a esta regra. Repare na se-

guinte ideia: ao declararmos uma variável com o tipo LogT Nat, pretendemos que esta variável

assimile características semânticas, as das variáveis lógicas definidas no modo log, e não ape-

nas propriedades derivadas de informação de tipo. Ora tal só se pode garantir se identificarmos

o modo log com o operador de modo LogT. Para obter esse efeito recorremos à técnica da

secção 4.3.6: introduzimos na definição do modo log um identificador discriminante único –

$object_with_mode_log (ver a definição do modo log na biblioteca padrão da linguagem OM – cf.

secção 12.5). Relativamente aos outros modos procedemos de igual forma.

Nas equações semânticas da linguagem L9, não previmos a possibilidade dum modo herdar

componentes duma classe. Este caso não foi tratado apenas por uma questão de simplificação

da apresentação do mecanismo dos modos: imitando a equação da subclasse de L8, não é difí-

cil generalizar a equação do modo para permitir herança de componentes.

Dado um modo m e um tipo-objecto τ qualquer, a expressão m τ e representa uma classe.

Assim, em princípio, parece fazer sentido a possibilidade de herdar a partir de m τ, ou seja

declarar m τ como superclasse duma outra classe C. No entanto, acontece que, neste caso,

surge uma situação paradoxal: os objectos da subclasse C herdam a funcionalidade do modo m,

mas, no entanto, tecnicamente, são objectos sem modo pois C é uma classe normal, directa-

mente definida. Assim, tanto a linguagem L9 como a linguagem final OM proíbem herança a

partir de classes que resultem da instanciação de modos.

9.4 ConclusõesNo início dos anos 80, começaram a ser estudados e desenvolvidos sistemas de programação

reflexivos, sistemas geralmente de base dinâmica e suportando introspecção e a modificação

dos mecanismos básicos em tempo de execução [Smi83, KRB91, DG87].

A linguagem L9 é também, em certo sentido uma linguagem reflexiva, na medida em que,

usando a própria linguagem, o programador pode escrever uma nova colecção de modos que

estendem L9 semanticamente, inclusivamente modificando alguns dos mecanismos básicos da

9 Modos 167

linguagem. Contudo, estamos perante uma forma de reflexão estática já que a linguagem alte-

rada fica com uma semântica que não pode ser modificada em tempo de execução.

Repare também que num certo sentido, a própria noção de classe também suporta um certo

grau de reflexão: por exemplo em C++, um programador pode escrever um programa em C++

(uma colecção de classes) que, num certo sentido, altera as características do C++: no sentido

em que a linguagem fica funcionalmente mais rica e com mais tipos de dados.

Estas comparações têm interesse pois ajudam a situar a noção de modo no contexto dum

contínuo de reflexão que se inicia no mecanismo das classes e se estende, passando pelos mo-

dos, até aos sistemas reflexivos dinâmicos que têm os interpretadores metacirculares como li-

miar superior.

O sucesso das linguagens orientadas pelos objectos explica-se, em parte, pelas característi-

cas de extensibilidade limitada que o mecanismo das classes introduz na linguagem. O meca-

nismo dos modos encontra-se um degrau acima do mecanismo das classes, mas ainda dentro

de território estático.

Capítulo 10

Sistema de coerções

Na secção 10.1, motivamos a necessidade de suportar um sistema de coerções extensível na

linguagem L10 e determinamos alguns requisitos preliminares desse sistema que serão levados

em consideração nas secções subsequentes. Na secção 10.2, introduzimos e estudamos uma

versão preliminar do sistema de coerções chamada sistema natural. Na secção 10.3, introduzi-

mos e estudamos a versão final do sistema de coerções designada por sistema prático.

10.1 Conceitos e mecanismos de L10A faceta estática do mecanismo dos modos pressupõe a existência dum sistema de coerções

extensível, suportado a nível primitivo. É esse mecanismo que introduzimos no presente capí-

tulo como parte integrante da linguagem L10.

As coerções têm um papel importante na linguagem L10, especialmente as coerções de

modo (cf. secção10.1.3). Estas tornam a linguagem mais usável na prática, ao permitirem que

expressões cujos tipos difiram apenas no modo possam ser usadas de forma intermutável. Para

dar um exemplo, são as coerções de modo que permitem que uma expressão de tipo Int possa

ser usada directamente como argumento duma função com um parâmetro de tipo ConsT Int.

Seria possível dispensar as coerções de modo, mas nesse caso à custa do recurso continuado à

utilização de funções de conversão usadas de forma explícita.

O sistema de coerções de L10 também introduz uma forma muito básica de call-by-name

que, no contexto das classes primitivas da linguagem OM, é explorada na definição de diver-

sas primitivas dessa linguagem: comandos da linguagem, métodos '&&' e '||' da classe Bool, etc.

(cf. secção 10.2.1).

O sistema de coerções de L10 incorpora um pequeno número de regras fixas, ditas regras

básicas, e admite ser estendido com novas regras, ditas regras extra, definidas usando os deno-

minados métodos de coerção (cf. secção 10.2.1). O sistema de coerções de L10 não inclui

qualquer regra básica que especifique uma coerção de modo: todas as regras que definem coer-

ções de modo são para ser introduzidas dentro dos modos usando métodos de coerção. Isso faz

todo o sentido: quem estende a linguagem com um novo modo é que tem a possibilidade de

determinar quais são as coerções que esse novo modo deve suportar, e qual a implementação

das respectivas funções de conversão.

170 OM – Uma linguagem de programação multiparadigma

Em resultado duma decisão de desenho da linguagem, só se permite a introdução de regras

extra no nível privilegiado da linguagem (cf. secção 11.4), ou seja dentro dos modos e dentro

das classes primitivas. Para o utilizador da linguagem que não a pretenda estender, o sistema

de coerções da linguagem deve ser encarado como um sistema predefinido rígido.

Um exemplo de linguagem prática que, tal como a linguagem L10, também possui um

sistema de coerções extensível, é a linguagem C++. Contudo o C++ só suporta coerções de

tipos atómicos, o que torna triviais todos os problemas envolvidos no seu sistema de coerções.

Já na nossa linguagem L10, existe a necessidade de suportar coerções de tipos estruturados.

10.1.1 Coerções e relação de coerçãoUma coerção é uma conversão de tipo implícita que é decidida e inserida num programa em

tempo de compilação, sem qualquer intervenção do programador. Quando uma expressão

exp do tipo υ ocorre num contexto que requer um tipo τ distinto, o sistema de coerções inter-

vém para determinar se a conversão implícita de tipo υ-->τ é suportada pela linguagem. Em

caso afirmativo, a ocorrência da expressão exp é substituída pela nova expressão (f exp), onde

f:υ→τ representa a função de conversão que implementa a coerção. Se aquela conversão implí-

cita de tipo não for suportada, a ocorrência de exp é considerada um erro.

Em cada contexto Γ, o conjunto das coerções suportadas por uma linguagem de progra-

mação é um conjunto de pares ordenados de tipos, ou seja uma relação binária sobre tipos.

Designaremos essa relação por relação de coerção e usaremos o símbolo ≤c para a representar.

Uma linguagem de programação não deve ser muito generosa quanto à variedade de coer-

ções suportadas. Caso contrário existe o perigo de verdadeiros erros de tipo passarem desaper-

cebidos por serem automaticamente corrigidos pelo sistema de coerções. Quanto às coerções

efectivamente suportadas, estas devem ser conservadoras (widening), garantindo que o

conteúdo informativo dos valores convertidos não se perde [Seb93]: o contrário seria perigoso

pois as coerções actuam implicitamente. Por exemplo, a coerção natural de valores inteiros em

valores reais é conservadora, mas já a coerção inversa não o é. Na linguagem L10, as regras

básicas do sistema de coerções, assim como as regras extra da biblioteca padrão (listadas na

secção 10.2.3), suportam apenas coerções conservadoras.

10.1.2 Sistema de coerçõesUm sistema de coerções é um sistema de prova sobre juízos de coerção da forma:

Γ υ≤cτ o tipo υ é implicitamente convertível para o tipo τ no contexto Γ

Cada regra dum sistema de coerções têm uma função de conversão associada, sendo o se-

guinte o formato geral de introdução dum par <regra de coerção, função de conversão associa-

da>:

10 Sistema de coerções 171

[Coerção …]

Γ υ≤cτ(Γ υ≤cτ) ˆ = λf:υ. …

As regras do sistema de coerções permitem deduzir os juízos de coerção válidos do sistema.

Quanto às funções de conversão associadas, elas servem para construir, através de composi-

ção, as funções de conversão irão implementar os juízos de coerção válidos (cf. secção 10.2.5).

A parte dum juízo de coerção que sucede o símbolo chama-se asserção de coerção e tem

a forma geral υ≤cτ. Todas as variáveis livres que ocorrem numa asserção de coerção têm de es-

tar declaradas no contexto respectivo.

Em situações em que o contexto Γ permanece invariante, é preferível trabalhar com simples

asserções de coerção, deixando o contexto implícito, em vez de trabalhar com juízos de coer-

ção completos. Esta é uma prática comum que seguiremos em muitas ocasiões. Em conformi-

dade, definimos asserção de coerção válida como sendo um juízo de coerção válido no qual o

contexto tenha sido deixado implícito.

Mais adiante, formalizaremos a relação de coerção de L10 por meio dum sistema de coer-

ções. Estudaremos duas versões deste sistema: uma versão preliminar, designada por sistema

natural (cf. secção 10.2), e uma versão final, designada por sistema prático (cf. secção 10.3).

10.1.3 Coerções de modoUma coerção de modo é uma conversão de tipo implícita na qual os dois tipos intervenientes

diferem apenas nos seus modos. Para exemplificar, consideremos as asserções de coerção

υ≤cGenT υ e ValueT υ≤cLazyT LogT υ, ambas suportadas pela linguagem OM: a primeira asserção

permite enriquecer com gen o modo dum qualquer tipo υ; a segunda permite converter o modo

value em lazy log.

As coerções de modo têm um papel importante na linguagem OM. Elas permitem que ex-

pressões cujos tipos difiram apenas no seu modo possam ser usadas de forma intermutável, o

que facilita o uso da linguagem. De forma limitada, as coerções de modo também podem ser

usadas para introduzir um pouco açúcar sintáctico, o que permite polir um pouco mais a lin-

guagem.

Vamos apresentar um exemplo simples que envolve coerções de modo e que ilustra os dois

aspectos referidos.

O modo gen (modo dos geradores) suporta um operador de disjunção ‘|’ aplicável a um par

de geradores: em resultado da sua aplicação é produzido um novo gerador, mais complexo,

que junta os valores do primeiro gerador com os valores do segundo gerador. A assinatura do

operador ‘|’ é a seguinte:

'|':∀X≤*. GenT X→GenT X→GenT X

Consideremos agora a expressão:

172 OM – Uma linguagem de programação multiparadigma

1 | 2

Nesta expressão, o operador '|' é aplicado a dois números naturais sem modo. Contudo, o ope-

rador espera argumentos de tipo GenT X, ou seja com modo gen. Portanto, à partida, a expres-

são 1|2 é um erro de tipo.

O que muda a situação é o facto do modo gen incluir um método de coerção, concretamente

#gen_up:X→GenT X, o qual enriquece o sistema de coerções com a regra extra, X≤extraGenT X,

donde se deduz a asserção Nat≤cGenT Nat. Assim, no final, a expressão 1|2 acaba por ser auto-

maticamente convertida na expressão:

(#gen_up 1) | (#gen_up 2)

a qual já está bem tipificada.

Com a interpretação atribuída à expressão 1 | 2, esta, e outras expressões da mesma forma,

passam a poder ser consideradas como expressões literais sobre o tipo GenT Nat.

10.1.4 Funções de conversão sem redundânciaMais adiante, na secção 10.3.4, estudaremos dois procedimentos de prova no contexto do sis-

tema de coerções de L10: o procedimento de prova normalizado e o procedimento de prova

prático. O segundo procedimento será adoptado como procedimento de prova oficial do siste-

ma de coerções de L10.

O procedimento de prova prático tem a particularidade de, perante um juízo Γ υ≤cτ que

possa ser provado de diversas formas, construir para esse juízo uma árvore de prova com ta-

manho mínimo (cf. definição 10.2.5-4). Assim, o procedimento de prova prático associa a cada

juízo de coerção válido uma função de conversão que minimiza o número de passos de con-

versão elementares.

Do ponto de vista da elegância formal, da eficiência e mesmo da intuição2, a minimização

do número de passos elementares nas funções de conversão faz todo o sentido. Uma quarta ra-

zão, bem mais específica, que nos levou a considerar esse aspecto decorre da conjugação dos

dois seguintes factores:

• Os modos mais sofisticados representam o objecto conexo por meio duma forma des-

critiva indirecta (cf. secção 9.1.4);

• Ao serem aplicadas, certas coerções de despromoção de modo, e.g. LazyT Nat≤cNat, for-

çam a explicitação do objecto conexo. Esta explicitação pode ser um problema se for

efectuada prematuramente, ao arrepio da lógica de implementação do modo (pode mes-

mo fazer abortar a computação, em certos modos).

2 Quando se tenta compor, mentalmente, uma função de conversão de um tipo T1 para um tipo T2, a tendência natural é para

imaginar uma função simples, e não uma função desnecessariamente complicada.

10 Sistema de coerções 173

Se permitíssemos que as funções de conversão incluíssem passos de conversão intermédios re-

dundantes, introduzidos de forma injustificável e semialeatória, então haveria o risco de se for-

çar prematura e inesperadamente a explicitação de algum objecto conexo.

Vamos ilustrar a situação usando um exemplo simples, no qual intervém o modo lazy, um

modo que representa o objecto conexo por meio duma forma descritiva indirecta.

Consideremos o tipo ConstT LazyT Nat, com modo composto const lazy, e o tipo ValueT

LazyT Nat, com modo composto value lazy. Os métodos de coerção existentes nos modos de bi-

blioteca da linguagem OM (cf. capítulo 12), conjugados com as regras básicas do sistema de

coerções (cf. 10.2.2), permitem provar a seguinte asserção de infinitas formas diferentes (cf.

secção 10.2 .7.1):

ConstT LazyT Nat≤cValueT LazyT Nat

Às várias formas de provar esta asserção, estão associadas funções de conversão com orga-

nizações distintas (arbóreas ou lineares) e constituídas por um número variável de passos de

conversão intermédios. Vejamos dois caminhos de conversão lineares que a nossa asserção de

coerção admite.

O primeiro caminho, produzido pelo procedimento de prova normalizado, tem 4 passos de

conversão elementares (cf. demonstração do teorema 10.3.5-2):

ConstT LazyT Nat —> LazyT Nat —> Nat —> LazyT Nat —> ValueT LazyT Nat

Este caminho começa por eliminar gradualmente o modo composto de partida, const lazy, para

depois instalar, a partir do nada, um novo modo composto, value lazy. Os dois passos elementa-

res do meio são redundantes pois não provocam alteração de modo. No entanto, o primeiro de-

les, LazyT Nat—>Nat, força, por efeito lateral, a explicitação do objecto conexo. Ora isso preju-

dica gravemente a intenção que preside ao modo lazy: a intenção de suportar um mecanismo de

avaliação preguiçosa (cf. secção 12.4).

O segundo caminho, produzido pelo procedimento de prova prático, é constituído por 2

passos de conversão elementares (cf. demonstração do teorema 10.3.5-2):

ConstT LazyT Nat —> LazyT Nat —> ValueT LazyT Nat

Este caminho preserva nos objectos convertidos um núcleo fixo, de tipo LazyT Nat, que não é

sujeito a qualquer conversão. Neste caso, a forma descritiva indirecta dos objectos conexos do

modo lazy é preservada.

Este exemplo, que acabámos de apresentar, é paradigmático relativamente ao que se pode

esperar dos dois procedimentos de prova considerados:

• O procedimento de prova normalizado tende a inserir de forma injustificável passos in-

termédios desnecessários nas funções de conversão. Esses passos podem forçar desne-

cessariamente, e por vezes prematuramente, a explicitação do objecto conexo.

174 OM – Uma linguagem de programação multiparadigma

• O procedimento de prova prático nunca gera passos de conversão intermédios redun-

dantes (por construção). Uma função gerada pelo procedimento de prova prático, se

forçar a explicitação do objecto conexo, nunca o fará pelas razões espúrias do procedi-

mento de prova normalizado.

Está pois explicada a quarta razão que nos levou a optar pelo procedimento de prova práti-

co: o facto deste procedimento só gerar funções sem redundância, afinal uma medida básica de

sanidade.

10.2 O sistema naturalO sistema natural define de forma simples e intuitiva, ou seja de forma natural, uma relação

binária extensível de coerção entre tipos. Ele é deliberadamente introduzido sem grandes preo-

cupações de natureza técnica, o que faz com que padeça das seguintes deficiências: indecidibi-

lidade, indeterminismo e ambiguidade. Estes problemas serão resolvidos na secção 10.3, onde

será introduzido o sistema pratico, uma versão mais evoluída, derivada do sistema natural.

O sistema de coerções de L10 inclui um pequeno número de regras fixas, ditas regras bási-

cas, e admite ser estendido com novas regras extra, definidas usando métodos de coerção, co-

mo veremos na secção seguinte.

É o seguinte o resumo da presente secção. Na secção 10.2.1, comentamos as regras básicas

do sistema prático, as quais são listadas na secção 10.2.2. Na secção 10.2.3, apresentamos as

regras extra introduzidas na biblioteca padrão da linguagem OM. Na secção 10.2.4, introduzi-

mos um operador explícito de conversão. Nas secções 10.2.5 e 10.2.6 apresentamos diversas

noções ligadas a sistemas de prova e a procedimentos de prova. Finalmente, na secção 10.2.7,

fazemos o levantamento e análise de diversos problemas associados ao sistema natural.

10.2.1 Apresentação das regras básicas do sistema natural

Neste secção, comentamos as regras de coerção básicas do sistema natural. A lista completa

destas regras encontra-se na secção seguinte.

A regra [Coerção ≤] faz com que a relação de subtipo implique a relação de coerção. A fun-

ção de conversão associada a esta regra é a função identidade com domínio no supertipo.

Sendo a relação de subtipo reflexiva, a relação de coerção também fica reflexiva.

A extensibilidade do sistema natural assenta na regra básica [Coerção extra], a qual assenta na

possibilidade de se definirem métodos de coerção, da forma:

coercion #f = λX–

.λarg:ϑ[X–

]. e :∀X–

.ϑ[X–

]→Ω[X–

]

10 Sistema de coerções 175

Os métodos de coerção são definidos no nível privilegeado da linguagem (cf. secção 11.4).

Cada método de coerção especifica duas entidades em simultâneo: uma regra de coerção

extra, denotada por ϑ[X–

]≤extra[X–

], e uma função de conversão, denotada por (ϑ[X–

]≤extra[X–

]).

. Convencionalmente, consideramos que todas as variáveis de tipo destas expressões estão

quantificadas universalmente. Eis três exemplos de regras extra que podem ser introduzidas

usando métodos de coerção: Int≤extraFloat, X→Bool≤extraX→GenT X, X→Y≤extraX→a:Y. Como

veremos na secção 10.2.3, as regras extra estão submetidas a uma importante restrição: vistas

como padrões, elas têm de ser disjuntas duas a duas.

A regra [Coerção nname], com ajuda da função de conversão que lhe está associada, suporta a

transformação sintáctica exp-->λz:Unit.exp. Esta é uma forma simples de tirar partido do sistema

de coerções para implementar a estratégia de avaliação call-by-name. Note que, não obstante a

regra [Coerção nname] ter a forma aproximada duma regra extra, não é possível substitui-la pela

regra extra X≤extraUnit→X. Se tal fosse feito, o correspondente método de coerção seria

coercion #name = λX.λarg:X. (λz:Unit. arg) :∀X.X→(Unit→X)

mas é fácil de ver que a função de conversão que este método define não tem qualquer utili-

dade, já que o argumento arg é avaliado prematuramente em virtude da estratégia de avaliação

call-by-value de L10. Dentro da biblioteca padrão da linguagem OM, esta regra é usada nas

seguintes situações: na atribuição de semântica aos comandos da linguagem; na atribuição de

semântica à expressão condicional ?:; na definição dos métodos '&&' e '||', na classe Bool; na

definição de diversos métodos dentro dos modos lazy e gen.

A regra [Coerção →] adapta a regra [Sub →] ao contexto do sistema de coerções. É uma regra

que, na prática, tende a ser usada apenas como regra auxiliar pela regra [Coerção …]. Um raro

exemplo em usamos a regra [Coerção →] de forma mais substancial encontra-se na redefinição

do método #$def_apply, no modo gen da biblioteca padrão: o primeiro parâmetro de #$def_apply é

deliberadamente declarado com o tipo GenT (τ→(GenT σ)), para que possa aceitar argumentos do

largo espectro de tipos pretendido. Considera-se que o tipo GenT (τ→(GenT σ)) é muito geral

porque ele surge do lado direito de todos os seguintes juízos de coerção, cuja validade é fácil

de provar com a ajuda da regra [Coerção →]:

Γ (GenT τ)→σ ≤c GenT (τ→(GenT σ))

Γ τ→σ ≤c GenT (τ→(GenT σ))

Γ (GenT τ)→(GenT σ) ≤c GenT (τ→(GenT σ))

Γ τ→(GenT σ) ≤c GenT (τ→(GenT σ))

A regra [Coerção …] adapta ao contexto do sistema de coerções a regra [Sub …] da rela-

ção de subtipo. Na prática, só se recorre a esta regra para ajudar a resolver o problema da

instanciação duma entidade paramétrica com um tipo que, não sendo imediatamente compatí-

vel com a sua interface-limite, possa ser tornado compatível com ela por acção duma coerção

(cf. 6.3.2 e 6.3.2.2).

176 OM – Uma linguagem de programação multiparadigma

Para terminar, a regra [Coerção trans] serve para tornar transitiva a relação binária de coerção.

A função de conversão associada ao juízo resultante obtém-se através da composição das fun-

ções de conversão associadas aos juízos envolvidos na transitividade.

10.2.2 Regras básicas do sistema naturalO sistema natural é um sistema de prova que axiomatiza uma relação de coerção sobre os tipos

da espécie ∗, em L10. As regras do sistema natural estão definidas sobre juízos de coerção da

forma descrita na secção 10.1.2. Cada uma das regras do sistema tem uma função de conver-

são associada, definida no mesmo contexto Γ do juízo que ocorre na conclusão da regra.

O sistema natural inclui três regras terminais (cf. definição 10.2.5-1): [Coerção ≤],

[Coerção extra] e [Coerção nname].

Eis a lista integral das regras básicas que definem o sistema natural:

[Coerção ≤]

Γ υ≤τ Γ τ:∗

Γ υ≤cτ(Γ υ≤cτ) ˆ = λx:υ. (λy:τ.y)x

[Coerção extra]

ϑ[X

–]≤extraΩ[X

–] Γ σ–:∗

Γ ϑ[σ–]≤cΩ[σ–]

(Γ ϑ[σ–]≤cΩ[σ–]) ˆ = (ϑ[σ–]≤extraΩ[σ–])

[Coerção nname]

Γ τ:∗

Γ τ≤cUnit→τ(Γ τ≤cUnit→τ) exp ˆ = λz:Unit.exp

[Coerção →]

Γ υ′≤cυ Γ τ≤cτ′Γ υ→τ≤cυ′→τ′

(Γ υ→τ≤cυ′→τ′) ˆ = λf:υ→τ. λx:υ′ . (Γ τ≤cτ′) (f ((Γ υ′≤cυ)x))

[Coerção …]

Γ τ1≤cτ1′ … Γ τk≤cτk′

Γ l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′

(Γ l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′) ˆ = λr:l1:τ1‚…‚lk:τk‚…‚ln:τn.

l1=(Γ τ1≤cτ1′) rl1‚…‚lk=(Γ τk≤cτk′) r.lk

[Coerção trans]

Γ τ≤cτ′ Γ τ′≤cτ′′

Γ τ≤cτ′′(Γ τ≤cτ′′) ˆ = λx:τ. (Γ τ′≤cτ′′)(Γ τ≤cτ′)x

10.2.3 Regras de coerção extraJá vimos, na secção 10.2.1, que a regra básica [Coerção extra] permite estender o sistema natural

com regras extra e respectivas funções de conversão. Ao nível do nosso sistema de coerções

formal, as regras extra têm a forma geral

ϑ[X–

]≤extraΩ[X–

]

10 Sistema de coerções 177

Nos programas, as regras extra são denotadas usando métodos de coerção.

Introduzimos a seguinte restrição de disjunção para eliminar o indeterminismo na aplicação

de regras extra e o consequente problema de ambiguidade ligado à existência de funções de

conversão de aplicação alternativa:

Restrição 10.2.3-1 (Restrição de disjunção) Vistas como padrões, as regras extra têm

de ser disjuntas duas a duas.

Exemplificando, as regras X≤extraConstT X e Y&≤extraY obedecem à restrição de disjunção

porque não existe qualquer par de substituições que as torne iguais. No entanto, as regras

X≤extraInt→X e Y≤extraY→Int já não obedecem à restrição pois existe um par de substituições,

[Int/X] e [Int/Y], que as tornam iguais. Repare que o juízo Γ Int≤cInt→Int poderia ser deduzido a

partir de qualquer delas com a ajuda da regra [Coerção extra].

Note que esta restrição de disjunção elimina o indeterminismo apenas ao nível das regras

extra; mas não o elimina ao nível do sistema natural completo, como veremos na secção

10.2.7.1. A introdução da restrição de disjunção justifica-se apenas sua contribuição para a de-

finição dos dois procedimentos de prova deterministas que serão introduzidos na secção 10.3.

Apresentamos agora a lista completa das regras de coerção extra incluídas na versão cor-

rente da biblioteca padrão da linguagem OM.

X≤extraConstT X ConstT X≤extraX Int≤extraFloat

X≤extraValueT X ValueT X≤extraX Ref X≤extraX

Unit→X≤extraLazyT X LazyT X≤extraX

X≤extraLogT X LogT X≤extraGenT X

X→Bool≤extraX→GenT X Unit→X≤extraGenT X

Na definição da biblioteca padrão da linguagem, nos capítulos 11 e 12, podem ser consulta-

dos os métodos de coerção usados para introduzir estas regras e as respectivas funções de con-

versão.

10.2.4 Operadores de conversãoAs regras de coerção influenciam o significado dos programas de forma implícita. Com efeito,

em cada aplicação duma coerção a uma expressão, a invocação da correspondente função de

conversão é inserida automaticamente pelo compilador durante a fase de análise semântica.

Contudo, por razões de clareza, ênfase, ou então para ultrapassar alguma limitação do sistema

de coerções, por vezes seria conveniente que existisse a possibilidade de aplicar uma coerção a

uma expressão de forma explícita.

Assim, para permitir conversões explícitas, introduzimos na linguagem L10 uma família de

operadores de conversão, sintáctica e semanticamente idênticos aos operadores de cast da lin-

178 OM – Uma linguagem de programação multiparadigma

guagem C. Estes operadores têm a forma “(τ)” onde τ é um tipo. Para exemplificar, a expressão

(1+x) pode ser explicitamente convertida numa expressão do tipo Float, aplicando-lhe o opera-

dor conversão (Float), ou seja escrevendo (Float)(1+x).

A introdução dos operadores de conversão é formalizada usando a seguinte regra:

[Termo conversão]

Γ e:τ Γ τ≤cτ′

Γ (τ′)e:τ′

A equação semântica do novo termo (τ′)e é a seguinte:

(τ′)e :τ′ ˆ = let E:τ = e in (τ≤cτ′)E

10.2.5 Regras terminais e árvores de provaNesta secção adaptamos ao contexto dos sistemas de coerções algumas noções importantes li-

gadas a sistemas de prova.

Definição 10.2.5-1 (Regra terminal) Num sistema de prova sobre juízos duma dada

forma, uma regra diz-se terminal se nas suas premissas não ocorrerem juízos dessa mesma for-

ma. Nas premissas duma regra terminal podem ocorrer apenas juízos doutras formas, portanto

associados a outros sistemas de prova.

Para exemplificar, no sistema natural as regras terminais são três: [Coerção ≤], [Coerção extra]

e [Coerção nname]. Estas regras são exactamente aquelas em cujas premissas não ocorrem juízos

da forma Γ υ≤cτ; nelas ocorrem apenas juízos da forma Γ υ≤τ, Γ τ:∗ e ϑ[X–

]≤extra[X–

].

Definição 10.2.5-3 (Juízo válido) Num sistema de prova, um juízo diz-se válido se for

possível construir para ele uma árvore de prova cujas folhas sejam todas premissas válidas de

instâncias de regras terminais. Um juízo diz-se inválido se não for válido.

Para exemplificar, vejamos uma árvore de prova que mostra que, no sistema natural, o juízo

Γ Float→Ref Int≤cRef Int→Float é válido. Na construção da árvore usamos as regras básicas

[Coerção →], [Coerção trans], [Coerção extra] e, ainda, as regras extra Ref X≤extraX e Int≤extraFloat.

Ref X≤extraX Γ Int:∗Γ Ref Int≤cInt

Int≤extraFloat

Γ Int≤cFloat

Γ Ref Int≤cFloat

Ref X≤extraX Γ Int:∗Γ Ref Int≤cInt

Int≤extraFloat

Γ Int≤cFloat

Γ Ref Int≤cFloat

Γ Float→Ref Int≤cRef Int→Float

Uma árvore de prova, construída para um juízo de coerção válido, determina automatica-

mente a função de conversão que se associa a esse juízo. Essa função é construída indutiva-

mente, combinando as várias funções de conversão associadas às regras envolvidas na prova.

Para exemplificar, a função de conversão correspondente à árvore de prova anterior é:

10 Sistema de coerções 179

(Γ Float→Ref Int≤cRef Int→Float) ˆ = λf:Float→Ref Int. λx:Ref Int. (Γ Ref Int≤cFloat) (f ((Γ Ref Int≤cFloat)x))

onde:(Γ Ref Int≤cFloat) ˆ = λx:Ref Int. (Γ Int≤cFloat)(Γ Ref Int≤cInt)x

(Γ Ref Int≤cInt) ˆ = (Ref Int≤extraInt)

(Γ Int≤cFloat) ˆ = (Int≤extraFloat)

Repare que a estrutura da função de conversão segue fielmente a estrutura da árvore de prova.

A noção seguinte será usada na definição do procedimento de prova prático (cf. definição

10.3.4-2).

Definição 10.2.5-4 (Tamanho duma árvore de prova) No contexto dum sistema de

prova sobre juízos da forma Γ υ≤cτ, chama-se tamanho duma árvore de prova ao número de

nós da forma Γ υ≤cτ que integram essa árvore de prova.

Como é fácil de verificar, o tamanho duma árvore de prova corresponde ao número de nós in-

ternos dessa árvore de prova.

10.2.6 Procedimentos de provaComo é que se constrói uma árvore de prova para um juízo válido? A forma mais directa

baseia-se na aplicação directa das regras do sistema de prova. Partindo do juízo que se preten-

de provar, tenta-se construir gradualmente uma árvore de prova para ele, usando as regras do

sistema ao contrário, ou seja da conclusão para as premissas, até se alcançarem instâncias de

regras terminais. Depois, separadamente, estabelece-se a validade das premissas dessas instân-

cias de regras terminais nos respectivos sistemas de prova.

A árvore de prova apresentada na secção anterior foi por nós construída usando este méto-

do. Primeiro tivemos de descobrir que o juízo a provar coincidia com a conclusão duma ins-

tância da regra [Coerção →]; este facto permitiu a criação da primeira ramificação da árvore de

prova. Depois, recursivamente, tentámos construir subárvores de prova para as premissas des-

sa instância de regra, concretamente para os juízos Γ Ref Int≤cFloat e Γ Ref Int≤cFloat (estes são

iguais por coincidência). Continuámos a proceder desta forma até alcançarmos as folhas da

árvore. Finalmente, verificámos separadamente a validade das folhas da árvore, ou seja dos

juízos Γ Int:∗, X≤extraX, e Int≤extraFloat.

Esboçado um procedimento de prova baseado na utilização directa das regras do sistema de

prova, vamos agora considerar a forma como ele pode ser programado. Comecemos por obser-

var que o procedimento acima descrito lida com instâncias de regras, ou seja com substitui-

ções. Por esse motivo, é razoável pensar que o procedimento será mais facilmente programado

numa linguagem que suporte emparelhamento de padrões ou unificação, do que numa outra

linguagem mais convencional.

180 OM – Uma linguagem de programação multiparadigma

A programação do procedimento ficará também facilitada se todas as regras do sistema fo-

rem sintacticamente dirigidas [Pie93], isto é se verificarem a propriedade: “todas as variáveis

que ocorrem nas premissas ocorrem também na conclusão”. Neste caso é possível escrever o

procedimento de prova usando apenas emparelhamento de padrões.

Contudo, basta que no sistema de prova exista alguma regra que não seja sintacticamente

dirigida para que passe a ser necessário usar variáveis simbólicas e unificação estrutural. O

sistema natural inclui uma destas regras problemáticas: a regra [Coerção trans] cujas premissas

contêm uma variável, τ′, que não ocorre na conclusão:

[Coerção trans]

Γ τ≤cτ′ Γ τ′≤cτ′′

Γ τ≤cτ′′

Um procedimento de prova que aplique esta regra da conclusão para as premissas tem neces-

sidade de inventar um tipo intermédio τ′, o qual deverá ser representado por uma variável

simbólica.

Feita esta introdução ao tema dos procedimentos de prova, apresentamos seguidamente

algumas definições importantes relacionadas com este assunto.

Em certos sistemas de prova, quando se tenta provar um juízo usando o método atrás des-

crito pode acontecer que este emparelhe com as conclusões de diversas regras. Por exemplo,

no caso do sistema natural, o juízo Γ Int→Int≤cInt→Int emparelha com as conclusões das quatro

regras: [Coerção ≤], [Coerção →], [Coerção trans] e [Coerção extra]. A forma como um procedimento

de prova concreto lida com este tipo de situação, distingue os procedimentos deterministas dos

procedimentos indeterministas.

Definição 10.2.6-1 (Procedimento de prova determinista) Um procedimento de

prova diz-se determinista se tiver embutida uma estratégia de exploração sequencial das

regras do sistema de que resulte, em cada passo, a escolha duma delas, a usar na construção

duma árvore de prova.

Um procedimento de prova diz-se indeterminista se tiver a capacidade de explorar as regras

do sistema em paralelo, durante a construção duma árvore de prova.

(Estas duas definições são derivadas das definições de procedimento determinista e proce-

dimento indeterminista de [Rèz85] págs 108,109).

Definição 10.2.6-2 (Sucesso/falhanço duma regra) Dado juízo que se pretende pro-

var, diz-se que uma regra r sucede se o procedimento de prova conseguir construir, para esse

juízo, uma árvore de prova com uma instância de r na raiz. Caso contrário, diz-se que a regra r

falha.

A título de exemplo, um conhecido procedimento de prova determinista explora as regras

que compõem o sistema pela ordem de ocorrência e escolhe a primeira regra que suceder (se

10 Sistema de coerções 181

alguma suceder) para aplicação efectiva. Este é o procedimento de prova determinista que será

introduzido na definição 10.3.4-1. Um exemplo de procedimento de prova indeterminista é

apresentado na definição 10.2.7.3.1-1.

Um procedimento de prova, determinista ou indeterminista, pode ser incapaz de provar al-

guns dos juízos válidos dum sistema. Por exemplo, isso acontecerá se o procedimento ignorar

deliberadamente algumas regras do sistema. Assim é importante dispor do conceito de proce-

dimento de prova completo:

Definição 10.2.6-3 (Procedimento de prova completo) No contexto dum dado sis-

tema de prova, um procedimento de prova diz-se completo se conseguir provar todos os juízos

válidos desse sistema. Se não o conseguir, o procedimento diz-se incompleto.

A questão da terminação do procedimento de prova é essencial, pois normalmente existe o

objectivo de validar os juízos mecanicamente.

Definição 10.2.6-4 (Algoritmo de prova) Um algoritmo de prova é um procedimento

de prova que comprovadamente termina em todas as circunstâncias, quer seja aplicado a um

juízo válido, quer seja aplicado a um juízo inválido.

Definição 10.2.6-5 (Algoritmo de prova completo) Um algoritmo de prova comple-

to é um procedimento de prova completo que comprovadamente termina em todas as circuns-

tâncias.

10.2.7 Problemas do sistema naturalO sistema natural procura definir de forma simples e intuitiva, ou seja de forma natural, uma

relação binária de coerção entre tipos. No entanto, o sistema tem alguns problemas que reque-

rem análise e a procura de soluções razoáveis. Da discussão e tratamento destes problemas

nascerá o sistema prático, a apresentar na secção 10.3.

Os problemas do sistema natural são três: indeterminismo, ambiguidade, indecidibilidade.

Nesta secção, apenas mostramos que estes problemas existem no sistema natural. Só os tenta-

remos resolver mais adiante, na secção 10.3.

10.2.7.1 Indeterminismo

Nesta secção, mostramos que o sistema natural é indeterminista. O indeterminismo está na

origem de duas dificuldades: (1) as regras do sistema não definem automaticamente um proce-

dimento de prova determinista; (2) existe ambiguidade na determinação da função de conver-

são a associar a cada juízo de coerção válido.

182 OM – Uma linguagem de programação multiparadigma

Definição 10.2.7.1-1 (Sistema de prova indeterminista) Um sistema de prova

diz-se indeterminista se existir pelo menos um juízo que possa ser deduzido das suas regras

usando duas árvores de prova distintas. Um sistema de coerções diz-se determinista se todo o

juízo que dele se possa deduzir admitir uma única árvore de prova.

Teorema 10.2.7.1-2 O sistema natural é indeterminista.

Prova: Precisamos apenas de exibir um juízo que admita com duas árvores de prova distintas.

Vamos apresentar outra árvore de prova para o juízo Γ Float→Ref Int≤cRef Int→Float, alternativa

da que foi apresentada na secção 10.2.5. Na nova árvore, usamos o símbolo “…” para repre-

sentar uma determinada subárvore de prova que já ocorria na primeira árvore.

Γ Float≤Float Γ Float:∗Γ Float≤cFloat

Γ Ref Int≤cFloat

Γ Float→Ref Int≤cFloat→Float

Γ Ref Int≤cFloat Γ Float≤Float Γ Float:∗

Γ Float≤cFloat

Γ Float→Float≤cRef Int→Float

Γ Float→Ref Int≤cRef Int→Float

Comparando as duas provas, verifica-se que a segunda é mais complicada por incluir uma

aplicação desnecessária da regra [Coerção trans].

Este caso de indeterminismo é apenas um exemplo entre muitos outros possíveis. Na verda-

de, no sistema natural todo o juízo de coerção válido admite um número infinito de provas dis-

tintas. Isso acontece porque as utilização combinada das regras [Coerção ≤] e [Coerção trans]

permite a criação dum número arbitrário de ramificações inúteis em qualquer árvore de prova.

O seguinte exemplo ilustra esta possibilidade:

Γ Int≤Int

Γ Int≤cInt

Γ Int≤Int

Γ Int≤cInt

Γ Int≤cInt

Γ Int≤Int

Γ Int≤cInt

Int≤extraFloat

Γ Int≤cFloat

Γ Int≤cFloat

Γ Int≤cFloat

A prova de Γ Int≤cFloat, que poderia ter sido efectuada de forma directa, usando só a regra

extra Int≤extraFloat, foi antes efectuada duma forma desnecessariamente complicada recorrendo

a três utilizações combinadas inúteis das regras [Coerção ≤] e [Coerção trans].

Existem outros exemplos de indeterminismo que resultam da interacção entre a regra

[Coerção ≤] e uma das duas regras seguintes: [Coerção →] ou [Coerção …]. Por exemplo, o juízo

Γ Int→Int≤cInt→Int pode ser provado de forma imediata através duma aplicação de [Coerção ≤]

ou de forma ligeiramente menos imediata usando [Coerção →].

As regras extra, ao serem conjugadas com as regras básicas, podem também ser fonte de in-

determinismo.

As regras extra, entre si, nunca são fonte de indeterminismo, devido à restrição de disjun-

ção, introduzida na secção 10.2.3.

10 Sistema de coerções 183

10.2.7.2 Ambiguidade

Nesta secção, mostramos que o sistema natural é ambíguo. A ambiguidade é um problema que

terá de ser resolvido a todo o custo, pois a boa definição do sistema de coerções depende desse

aspecto.

Definição 10.2.7.2-1 (Sistema de coerções incoerente ou ambíguo) Um sistema

de coerções diz-se incoerente ou ambíguo se admitir que derivações alternativas dum juízo

válido produzam funções de conversão distintas.

Um sistema de coerções diz-se coerente se derivações alternativas dum juízo válido derem

sempre origem à mesma função de conversão.

Um sistema de coerções ambíguo admite a validade de pelo menos uma coerção relativa-

mente à qual não está clarificada qual a função de conversão a usar no momento da sua aplica-

ção.

Teorema 10.2.7.2-2 Todo o sistema de coerções ambíguo é indeterminista. Mas nem todo

o sistema de coerções indeterminista é ambíguo.

Prova: A primeira proposição resulta imediatamente da definição de sistema de coerções

ambíguo, pois uma condição prévia para um sistema ser ambíguo é a possibilidade de derivar

um juízo usando duas árvores de prova distintas.

A segunda proposição afirma que existem sistemas indeterministas coerentes, sistemas em

que existem juízos deriváveis usando duas ou mais árvores de prova distintas, mas a cada uma

dessas árvores está sempre associada a mesma função de conversão. O teorema 10.3.5-5 apre-

sentará um sistema com estas características.

Teorema 10.2.7.2-3 O sistema natural é ambíguo.

Prova: Precisamos apenas de exibir um exemplo de ambiguidade no sistema natural. Já que a

regra [Coerção extra] permite introduzir regras extra, podemos introduzir uma dessas regras que

se sobreponha a uma regra básica. Vamos introduzir uma regra extra que se sobrepõe parcial-

mente à regra básica [Coerção ≤].

Assim, seja a:Int, b:Int≤extraa:Int uma nova regra extra, com a seguinte função de conver-

são associada:

(a:Int, b:Int≤extraa:Int) ˆ = λx:a:Int,b:Int.a=x.a+1

Com a introdução desta regra, passam a haver pelo menos duas formas diferentes de derivar a

asserção a:Int, b:Int≤ca:Int: uma aplicando directamente a nova regra extra com função de

conversão: λx:a:Int, b:Int.a=x.a+1; outra aplicando directamente a regra [Coerção ≤], com fun-

ção de conversão: λx:a:Int, b:Int. (λy:a:Int.y)x. Como as funções de conversão são diferentes,

conclui-se que o sistema natural é ambíguo.

184 OM – Uma linguagem de programação multiparadigma

10.2.7.3 Indecidibilidade

Na linguagem L10, as coerções deverão ser validadas mecanicamente antes de serem aplicadas

pelo compilador. Assim, existe a necessidade de definir um algoritmo de prova que verifique a

derivabilidade de juízos de coerção no sistema natural.

Na secção corrente, veremos que não é possível definir um tal algoritmo para o sistema

natural. Na secção 10.3, introduziremos uma forma pragmática de ultrapassar este problema.

10.2.7.3.1 Procedimento geral de prova

O procedimento geral de prova é um procedimento de prova indeterminista que consegue

construir uma árvore de prova todo o juízo válido, no contexto de qualquer sistema de prova.

Infelizmente só se garante a terminação deste procedimento nos casos em que ele é aplicado a

juízos válidos. O que sucede quando ele é aplicado a um juízo inválido depende das regras

particulares que constituem o sistema e ainda do juízo particular em análise.

Definição 10.2.7.3.1-1 (Procedimento geral de prova) Tomando como ponto de

partida o juízo que se pretende provar, o procedimento geral de prova constrói incremental-

mente e explora sistematicamente todas as potenciais árvores de prova com essa conclusão, até

atingir uma das duas situações: (1) ou encontra um árvore de prova que valida o juízo; (2) ou

conclui que uma tal árvore não existe, sendo o juízo inválido. No primeiro caso diz-se que o

procedimento termina com sucesso, e no segundo caso que o procedimento termina com insu-

cesso.

São os seguintes os principais detalhes da gestão das árvores. Quando uma folha duma ár-

vore pode ser desenvolvida por aplicação de n≥1 diferentes regras do sistema, o procedimento

cria n-1 novos exemplares dessa árvore e expande cada uma das n árvores iguais usando uma

regra distinta. Sempre que uma folha duma árvore não pode ser desenvolvida por aplicação de

qualquer regra do sistema, se essa folha não for uma premissa válida duma regra terminal en-

tão a respectiva árvore é destruída. Se todas as árvores forem destruídas então o procedimento

termina com insucesso.

O procedimento explora as potenciais árvores de prova sistematicamente: primeiro tentan-

do encontrar uma árvore de altura 1 que prove o juízo; depois, se necessário, expandindo um

pouco mais as árvores existentes para tentar encontrar uma árvore de altura 2 que prove o juí-

zo; e assim sucessivamente.

Teorema 10.2.7.3.1-2 (Completitude do procedimento geral de prova) O pro-

cedimento geral de prova consegue construir, em tempo finito, uma árvore de prova para qual-

quer juízo válido em qualquer sistema de prova. Por outras palavras, o procedimento geral de

prova é completo no contexto de qualquer sistema de prova. No entanto, o procedimento pode

10 Sistema de coerções 185

não terminar quando aplicado a um juízo inválido. No caso particular do sistema natural, o

procedimento geral de prova nunca termina quando é aplicado a um juízo inválido.

Prova:

1ªparte: Todo o juízo válido admite uma árvore de prova ψ de altura finita. Ora o procedimen-

to geral de prova analisa as possíveis árvores de prova de forma sistemática, tentando primeiro

encontrar uma com altura 1, depois uma com altura 2, etc. Desta forma só há duas alternativas

possíveis: ou o procedimento descobre a árvore ψ atrás referida, ou então o procedimento des-

cobre uma outra árvore de prova de altura não-superior à altura de ψ.

2ª e 3ª partes: No caso do sistema natural, a regra [Coerção trans] é sempre aplicável em qual-

quer subprova, pelo que o procedimento geral de prova nunca destrói uma árvore previamente

criada. Por outro lado, um juízo inválido não admite qualquer árvore de prova. Desta forma,

nunca se chega a verificar qualquer das duas condições de paragem.

10.2.7.3.2 Propriedade da subfórmula

Na procura dum procedimento de prova para o sistema natural que termine, ou seja dum algo-

ritmo de prova, surge naturalmente a ideia da propriedade da subfórmula: Precisamos de in-

troduzir esta conhecida propriedade numa formulação mais geral do que a que se encontra, por

exemplo, em [Cas98].

Definição 10.2.7.3.2-1 (Propriedade da subfórmula) Consideremos um sistema de

prova sobre juízos da forma Γ υθτ, para um símbolo de relação θ arbitrário. Seja r uma regra

genérica desse sistema de prova com conclusão Γ′ υ′θτ′. Diz-se que a regra r satisfaz a proprie-

dade da subfórmula se todas as premissas de r que tiverem a forma Γ υθτ verificarem as duas

condições seguintes: (1) υ e τ são subfórmulas em sentido lato de υ′ e τ′, respectivamente; (2)

pelo menos uma das duas componentes, υ ou τ, é subfórmula em sentido estrito de υ′ ou τ′, res-

pectivamente.

Esta propriedade será explorada na definição do sistema prático, na secção 10.3. Note, des-

de já, que todas as regras terminais de qualquer sistema satisfazem vacuamente a propriedade

da subfórmula.

Teorema 10.2.7.3.2-2 Se todas as regras básicas dum sistema de prova satisfizerem a pro-

priedade da subfórmula, então qualquer procedimento de prova baseado na utilização dessas

regras é um algoritmo. (Assume-se que já existem algoritmos para provar a validade das pre-

missas das regras terminais).

Prova: A relação de subfórmula estrita “>f”, definida no conjunto das fórmulas, é uma relação

de ordem parcial estrita bem fundada, onde portanto não existem sequências descendentes infi-

nitas f1>ff2>f…. Se o procedimento de prova não terminasse então seriam criadas cadeias des-

cendentes infinitas.

186 OM – Uma linguagem de programação multiparadigma

Note que este teorema é válido para qualquer procedimento baseado na aplicação das regras

dum sistema de prova,. Mesmo no caso dum procedimento incompleto se continua a garantir a

sua terminação, apesar dele não conseguir provar todos os juízos válidos do sistema.

Analisando o sistema natural, verifica-se que ele inclui três regras terminais – [Coerção ≤],

[Coerção extra] e [Coerção nname] – as quais já verificam a propriedade da subfórmula. Das três

regras não-terminais, as regras [Coerção →] e [Coerção …] também verificam a propriedade da

subfórmula.

Apenas a regra não-terminal [Coerção trans] não verifica essa propriedade. Por isso importa

averiguar se é viável transformar o sistema natural num sistema equivalente, trocando a regra

não-terminal [Coerção trans] por outras regras não-terminais que satisfaçam a propriedade da

subfórmula. Infelizmente tal não é possível, como mostra o seguinte teorema.

Teorema 10.2.7.3.2-3 Consideremos uma variante do sistema natural onde a regra da tran-

sitividade foi sido substituída por uma colecção de regras não-terminais satisfazendo, todas

elas, a propriedade da subfórmula. Nesta situação, o sistema natural e sua variante não são

equivalentes, o que significa que os dois sistemas definem relações distintas.

Prova: Vamos considerar os tipos atómicos Bool, Int e Float e assumir que no sistema foram in-

troduzidas apenas as duas seguintes regras extra: Bool≤extraInt e Int≤extraFloat. Nestas circunstân-

cias, no contexto do sistema natural, a regra da transitividade permite provar Γ Bool≤cFloat,

como mostra a árvore de prova:

Bool≤extraInt

Γ Bool≤cInt

Int≤extraFloat

Γ Int≤cFloat

Γ Bool≤cFloat

Vamos considerar agora o sistema modificado que resulta da concretização da troca de re-

gras indicada no enunciado do teorema. No contexto do novo sistema, vamos determinar se

existe alguma regra que possa ser usada em último lugar numa dedução de Γ Bool≤cFloat:

Γ Bool≤cFloat

Começamos por mostrar que as três regras terminais não podem ser usadas naquela posi-

ção: a regra [Coerção ≤] não é aplicável pois o juízo de subtipo Γ Bool≤Float não é válido em F+;

a regra [Coerção extra] não é aplicável pois não existe a regra extra Bool≤extraFloat; a regra

[Coerção nname] também não é aplicável devido à estrutura da conclusão desta regra.

Analisamos agora as regras não-terminais: todas elas obedecem à propriedade da subfórmu-

la, pelo que o juízo Γ Bool≤cFloat terá de ser provado à custa das subfórmulas de Bool e das sub-

fórmulas de Float, sendo que em pelo menos um destes casos é necessário considerar subfór-

mulas estritas. Mas os tipos Bool, Float são atómicos, o que significa que não têm subfórmulas

estritas. Assim não existe qualquer regra não-terminal que possa ser aplicada em último lugar

na dedução pretendida.

10 Sistema de coerções 187

Conclui-se que o juízo Γ Bool≤cFloat, válido no sistema natural, é inválido no sistema modi-

ficado, o que significa que o novo sistema não é equivalente ao sistema natural.

No teorema anterior investigámos a possibilidade de construir um sistema de prova equiva-

lente ao sistema natural, trocando da regra da transitividade por um conjunto de regras não-

-terminais, satisfazendo a propriedade da subfórmula. O resultado foi negativo, e, infelizmen-

te, a situação também não melhora se passarmos a admitir a introdução de novas regras termi-

nais. É o que refere o teorema seguinte, cuja demonstração antecipa já um resultado da próxi-

ma secção:

Teorema 10.2.7.3.2-4 Não existe qualquer sistema de prova cujas regras básicas satisfa-

çam a propriedade da subfórmula e seja equivalente ao sistema natural.

Prova: Se um tal sistema de prova existisse então ele admitiria um algoritmo de prova (cf. teo-

rema 10.2.7.3.2-2). Mas esse seria também um algoritmo de prova para a relação de coerção

definida pelo sistema natural, e acontece que tal algoritmo não pode existir (cf. teorema

10.2.7.3.3-4, da próxima secção). Logo, esse sistema de prova alternativo não pode existir.

10.2.7.3.3 Indecidibilidade do sistema natural

Nesta secção vamos considerar a seguinte questão: “Será que existe algum algoritmo, qualquer

algoritmo, baseado ou não em regras, que permita verificar a relação de coerção definida pelo

sistema natural?” Por outras palavras: “Será que a relação de coerção definida pelo sistema na-

tural é decidível?”.

Como já se terá apercebido o leitor mais observador, a relação de coerção definida pelo sis-

tema natural é de facto indecidível. Isso deve-se a três razões: o sistema natural é extensível;

as regras extra têm um formato muito geral; existe uma regra da transitividade que permite se-

quenciar livremente a aplicação das regras extra. Realmente, estas três razões fazem com que o

sistema natural encerre em si o poder computacional duma linguagem de programação univer-

sal, donde o problema da verificação da relação de coerção definida por ele será forçosamente

equivalente ao problema da paragem (halting problem), um conhecido problema indecidível

[HU79].

A demonstração de que a relação de coerção definida pelo sistema natural é indecidível é

em grande parte rotineira. É apenas para sermos completos que a vamos apresentar, embora de

forma abreviada. Iremos mostrar que toda a máquina de dois contadores T [HU79] pode ser

codificada como um conjunto de regras extra, de tal forma que a máquina T pára sse um deter-

minado juízo de coerção for derivável no sistema natural.

Definição 10.2.7.3.3-1 (Máquina de dois contadores) [Definição ligeiramente adap-

tada de Pierce [Pie93] que por sua vez simplifica a definição do livro de Hopcroft e Ullman

[HU79]]. Uma máquina de dois contadores é um quádruplo ordenado (PC,A0,B0,I1…Iw), onde

188 OM – Uma linguagem de programação multiparadigma

PC é uma instrução, A0 e B0 são números naturais, e I1…Iw, é uma sequência de instruções eti-

quetadas. Há cinco formas de instruções: INCA⇒m, INCB⇒m, TSTA⇒m/n, TSTB⇒m/n, HALT.

Cada forma de instrução provoca um tipo diferente de transição entre máquinas, de acordo

com a seguinte tabela:

INCA⇒m (INCA⇒m,A,B,I1…Iw) ===> (Im,A+1,B,I1…Iw)INCB⇒m (INCB⇒m,A,B,I1…Iw) ===> (Im,A,B+1,I1…Iw)TSTA⇒m/n (TSTA⇒m/n,0,B,I1…Iw) ===> (Im,0,B,I1…Iw)

(TSTA⇒m/n,A,B,I1…Iw) ===> (In,A-1,B,I1…Iw) se A≠0TSTB⇒m/n (TSTB⇒m/n,A,0,I1…Iw) ===> (Im,A,0,I1…Iw)

(TSTB⇒m/n,A,B,I1…Iw) ===> (In,A,B-1,I1…Iw) se B≠0HALT (HALT,A,B,I1…Iw) ===> indefinido

Note que as instruções TSTA e TSTB são instruções que, simultaneamente, testam e decremen-

tam um contador.

Definição 10.2.7.3.3-2 (Paragem duma máquina) Uma máquina de dois contadores

(PC,A,B,I1…Iw) pára se (PC,A0,B0,I1…Iw) *===> (HALT,A′,B′,I1…Iw), ou seja se existir um n tal que

(PC,A0,B0,I1…Iw) n===> (HALT,A′,B′,I1…Iw).

Teorema 10.2.7.3.3-3 (Indecidibilidade do problema da paragem) Não existe

qualquer algoritmo que possa ser aplicado a qualquer máquina de dois contadores e decida se

ela pára ou não pára.

Prova: Ver Pierce [Pie93] e Hopcroft e Ullman [HU79].

Teorema 10.2.7.3.3-4 (Indecidibilidade do sistema natural) A relação de coerção

definida pelo sistema natural é indecidível, i.e. não existe qualquer algoritmo que verifique a

relação definida pelo sistema natural.

Prova: Vamos mostrar como toda a máquina de dois contadores T≡(I0,A0,B0,I1…Iw) pode ser

codificada como um conjunto de regras extra de tal forma que a máquina T pára sse a coerção

C[Bool,Bool,A0,B0]≤cFloat for derivável no sistema natural. Nesta prova assumimos que não exis-

tem regras extra definidas à partida.

Em primeiro lugar, temos de inventar uma codificação para os números naturais usando a

linguagem dos tipos de F+. Utilizaremos a seguinte codificação:

. :Nat→F+

0 ˆ = Bool

n+1 ˆ = Bool→ n n>0

Dada uma máquina T≡(I0,A0,B0,I1…Iw) qualquer, codificamos agora as suas instruções usan-

do regras extra. Traduzimos cada instrução Ik da sequência I0I1…Iw numa regra extra ou em

duas regras extra, de acordo com a seguinte tabela de conversão:

10 Sistema de coerções 189

(INCA⇒m)k ---> C[I, k, X, Y] ≤extra C[Bool→I, m, Bool→X, Y]

(INCB⇒m)k ---> C[I, k, X, Y] ≤extra C[Bool→I, m, X, Bool→Y]

(TSTA⇒m/n)k ---> C[I, k, Bool, Y] ≤extra C[Bool→I, m, Bool, Y]

C[I, k, Bool→X, Y] ≤extra C[Bool→I, n, X, Y]

(TSTB⇒m/n)k ---> C[I, k, X, Bool] ≤extra C[Bool→I, m, X, Bool]

C[I, k, X, Bool→Y] ≤extra C[Bool→I, n, X, Y]

HALTk ---> C[I, k, X, Y] ≤extra Float

Nestas regras extra as letras I, X e Y representam variáveis de tipo implicitamente quantifica-

das, e C, k, m e n são entidades assim definidas:

C[I,K,X,Y] ˆ = K→X→Y→I

k ˆ = k

m ˆ = m

n ˆ = n

Note que com a ajuda da regra da transitividade, este conjunto de regras extra define uma

subrelação de coerção cujos pares só podem ser provados usando estas mesmas regras (e quan-

to muito, usando de forma vácua a regra [Coerção ≤]). Realmente, para cada par da subrelação,

não é possível construir qualquer prova alternativa que seja diferente de forma substancial,

pois as regras extra têm todas o formato C[I,…]≤extraC[Bool→I,…] e acontece que o juízo

Γ I≤cBool→I é inválido para todo o tipo I e para todo o contexto Γ (lembramos que estamos a

assumir que não existia qualquer regra extra definida à partida).

Uma propriedade do sistema natural, que resulta da tradução acima, é a seguinte:

(Ik,A,B,I1…Iw) ===> (Im,A′,B′,I1…Iw) sse ∅ C[0, k, A, B] ≤c C[1, m , A′, B′]

Outra propriedade é a seguinte:

(Ik,A,B,I1…Iw) n===> (Im,A′,B′,I1…Iw) sse ∅ C[0, k, A, B] ≤c C[n, m , A′, B′]

Em particular:

(I0, A0, B0,I1…Iw) n===>(HALTm,A′,B′,I1…Iw)

sse ∅ C[0, 0, A0, B0] ≤c C[n, m , A′,B ′] e ∅ C[n, m , A′,B ′] ≤c Float

sse ∅ C[Bool, Bool, A0, B0] ≤c Float

como pretendíamos.

10.3 O sistema práticoNo estudo de sistemas de tipos, a regra da transitividade é uma conhecida fonte de problemas

(ver [CG92], por exemplo). Já contactámos dois desses problemas: no sistema natural, a regra

da transitividade é uma fonte de indeterminismo (cf. secção 10.2.7.1) e também uma fonte de

indecidibilidade (cf. teorema 10.2.7.3.3-4),.

190 OM – Uma linguagem de programação multiparadigma

Sem perderem poder de prova, alguns sistemas de tipos admitem que a regra da transitivi-

dade seja eliminada, ou, pelo menos, substituída por outras regras menos problemáticas. Um

caso em que se prova ser possível a eliminação pura e simples da regra da transitividade é dis-

cutido em [Cas98] págs 36, 37. Um caso em que se tenta substituir a regra da transitividade do

sistema F≤ por uma regra mais fraca é investigado no trabalho [CG92] de Curien e Ghelli. No

entanto, esse enfraquecimento realmente não resolveu o problema, pois, como mostrou Pierce

em [Pie93], o sistema F≤ sofre dum problema fundamental de indecidibilidade.

Essencialmente, será trabalhando sobre a regra da transitividade que ultrapassaremos o pro-

blema da indecidibilidade do sistema natural. Iremos introduzir um sistema modificado, cha-

mado sistema prático, no qual a regra da transitividade será eliminada, tomando o seu lugar

três novas regras que já verificam a propriedade da subfórmula (cf. secções 10.3.1 e 10.3.3).

Estas três regras novas cobrem pragmaticamente os casos particulares de transitividade em que

estamos mais interessados, com destaque para as coerções de modo (cf. 10.1.3). A descoberta

destas três regras foi crítica para a viabilização do nosso sistema de coerções extensível.

O sistema prático tem um poder inferior de prova relativamente ao sistema natural (cf. teo-

rema 10.3.6-4). Fatalmente, a solução do problema da indecidibilidade do sistema natural teria

de passar por um compromisso deste tipo: a indecidibilidade é um problema que não se resol-

ve: quanto muito contorna-se através da definição duma outra relação, parecida com a original,

mas não absolutamente igual.

No contexto do sistema prático, estudamos dois procedimentos de prova deterministas dis-

tintos. O procedimento de prova prático incorpora o requisito, discutido na secção 10.1.4, da

geração duma árvore de prova com tamanho mínimo. É por isso que, no final, este será o pro-

cedimento de prova oficial adoptado para o sistema prático.

Na secção 10.3.1, comentamos todas as regras básicas do sistema prático, cuja lista comple-

ta é apresentada na secção 10.3.2. Na secção 10.3.3, discutimos as consequências da elimina-

ção da regra da transitividade no sistema prático. Na secção 10.3.4, apresentamos os procedi-

mentos de prova normalizado e prático. Nas secções 10.3.5 e 10.3.6 provamos algumas pro-

priedades destes procedimentos de prova.

10.3.1 Apresentação das regras básicas do sistemapráticoO sistema prático obtém-se a partir do sistema natural, trocando duas das regras, [Coerção trans]

e [Coerção nname], pelas três novas regras [Coerção name], [Coerção extra_despro], [Coerção extra_pro].

A regra [Coerção name] do sistema prático, resulta da combinação das duas regras do sistema

natural [Coerção trans] e [Coerção nname] (cf. demonstração do teorema 10.3.6-4). Portanto, a nova

regra [Coerção name] resulta mais poderosa do que a regra [Coerção nname]

10 Sistema de coerções 191

As regras [Coerção extra_despro] e [Coerção extra_pro] resultam da combinação da regra da

transitividade com dois casos particulares da regra [Coerção extra] (cf. demonstração do teorema

10.3.6-4).

Para cada uma das três novas regras, a função de conversão associada, indicada na secção

seguinte, resulta da combinação das funções de conversão das regras que lhes deram origem.

As restantes regras do sistema prático não partilhadas com o sistema natural.

10.3.2 Regras básicas do sistema práticoNesta secção, apresentamos as regras do sistema prático.

O sistema prático é um sistema de prova que axiomatiza uma relação de coerção sobre os tipos

da espécie ∗, em L10. As regras do sistema natural estão definidas sobre juízos de coerção da

forma descrita na secção 10.1.2. Cada regra do sistema tem uma função de conversão associa-

da, definida no mesmo contexto Γ do juízo que ocorre na conclusão da regra.

O sistema prático inclui apenas duas regras terminais (cf. definição 10.2.5-1): [Coerção ≤],

[Coerção extra].

Eis a lista integral das regras básicas que definem o sistema prático:

[Coerção ≤]

Γ υ≤τ Γ τ:∗

Γ υ≤cτ(Γ υ≤cτ) ˆ = λx:υ. (λy:τ.y) x

[Coerção extra]

ϑ[X

–]≤extraΩ[X

–] Γ σ–:∗

Γ ϑ[σ–]≤cΩ[σ–]

(Γ ϑ[σ–]≤cΩ[σ–]) ˆ = (ϑ[σ–]≤extraΩ[σ–])

[Coerção extra_pro]

X≤extraΩ[X] Γ υ≤cτ

Γ υ≤cΩ[τ](Γ υ≤cΩ[τ]) ˆ = λx:υ. (τ≤extraΩ[τ]) ((Γ υ≤cτ)x)

[Coerção extra_despro]

ϑ[X]≤extraX Γ υ≤cτ

Γ ϑ[υ]≤cτ(Γ ϑ[υ]≤cτ) ˆ = λx:ϑ[υ]. (Γ υ≤cτ) ((ϑ[υ]≤extraυ) x)

[Coerção name]

Γ υ≤cτ

Γ υ≤cUnit→τ(Γ υ≤cUnit→τ) exp ˆ = λz:Unit. (Γ υ≤cτ) exp

[Coerção →]

Γ υ′≤cυ Γ τ≤cτ′Γ υ→τ≤cυ′→τ′

(Γ υ→τ≤cυ′→τ′) ˆ = λf:υ→τ. λx:υ′ . (Γ τ≤cτ′) (f ((Γ υ′≤cυ)x))

[Coerção …]

Γ τ1≤cτ1′ … Γ τk≤cτk′

Γ l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′

192 OM – Uma linguagem de programação multiparadigma

(Γ l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′) ˆ = λr:l1:τ1‚…‚lk:τk‚…‚ln:τn.

l1=(Γ τ1≤cτ1′) rl1‚…‚lk=(Γ τk≤cτk′) r.lk

10.3.3 Consequências da eliminação da regra datransitividadeAs quatro regras, [Coerção ≤], [Coerção name], [Coerção →] e [Coerção …], não são prejudicadas

pela eliminação da regra da transitividade, na medida em que o subsistema por elas constituído

é transitivo por natureza (cf. teorema 10.3.6-8). Assim, em provas de juízos de coerção, estas

quatro regras podem continuar a combinar-se entre si, como no caso do sistema natural.

Já as regras extra são seriamente afectadas pela eliminação da regra da transitividade. Con-

cretamente, nas provas de juízos de coerção, elas perdem a capacidade de se combinarem com

outras regras, sejam elas regras extra ou regras básicas. Por exemplo, da introdução das regras

extra Bool≤extraFloat e Int≤extraFloat não se pode concluir de que o juízo Γ Bool≤cFloat seja válido

(cf. demonstração do teorema 10.2.7.3.2-3).

Foi para minorar este problema que as três novas regras básicas, específicas do sistema prá-

tico, foram criadas. Elas introduzem formas limitadas de transitividade, a que podem aceder

regras extra que tenham determinadas formas preestabelecidas. Concretamente, as regras extra

com a forma X≤extraΩ[X], prevista em [Coerção extra_pro], têm a virtualidade de serem transitivas

à esquerda, e as regras extra da forma ϑ[X]≤extraX, prevista em [Coerção extra_despro], têm a van-

tagem de serem transitivas à direita (cf. teorema 10.3.6-4). Estas novas regras cobrem pragma-

ticamente os casos particulares de transitividade que mais nos interessam, com destaque para

as coerções de modo.

Para exemplificar, eis uma longa lista de juízos só são deriváveis no sistema prático (enri-

quecido as regras extra da biblioteca padrão, cf. secção 10.2.3), em virtude da transitividade

inerente às novas regras do sistema prático:

Γ Int ≤c ConstT ValueT Int

Γ ConstT ValueT Int ≤c Int

Γ Ref Int ≤c Float

Γ Ref Int ≤c GenT Float

Γ Int ≤c LogT Float

Γ ConstT LazyT Int ≤c ValueT LazyT Int

Γ ConstT ValueT LogT Int ≤c ConstT ValueT LogT Float

Γ Float→Ref Int ≤c Ref Int→Float

Γ Int ≤c Unit→Unit→Int

Γ Int ≤c Unit→Float

Γ Int ≤c ConstT (Unit→Float)

Um caso de não-transitividade envolvendo regras extra da biblioteca padrão é apresentado

no teorema 10.3.6-8.

10 Sistema de coerções 193

10.3.4 Procedimentos de prova normalizado e práticoNesta secção, introduzimos dois procedimentos de prova deterministas que se podem usar no

contexto de qualquer sistema de prova e que eliminam todo o indeterminismo e ambiguidade

potenciais desse sistema. Trata-se do procedimento de prova normalizado e do procedimento

de prova prático. O primeiro destes procedimentos será usado na construção de árvores de

prova normalizadas, apenas no contexto da demonstração do teorema 10.3.5-5; o segundo pro-

cedimento será adoptado como procedimento de prova oficial do sistema prático.

Começamos por apresentar o procedimento de prova normalizado. Trata-se dum procedi-

mento determinista, muito simples, cuja definição tira partido da forma como as regras do sis-

tema estão ordenadas. É possível tentar usar este procedimento em qualquer sistema de prova,

embora com grau de sucesso variável do ponto de vista da completitude (cf. teoremas 10.3.5-1

e 10.3.5-4).

Definição 10.3.4-1 (Procedimento de prova normalizado) É o procedimento de

prova determinista que explora as regras que compõem o sistema pela ordem em que ocorrem

no sistema, e que escolhe para aplicação efectiva a primeira regra que sucede (se alguma suce-

der). A árvore de prova é construída, como habitualmente, de forma recursiva, da raiz para as

folhas. (Esta é a conhecida estratégia de pesquisa depth-first com retrocesso, usada na lingua-

gem Prolog).

O procedimento de prova prático é um procedimento determinista que, para além de tirar

partido da ordenação das regras do sistema, incorpora ainda o requisito da geração duma árvo-

re de prova com tamanho mínimo. As árvores de provas de tamanho mínimo tem interesse

pois as funções de conversão que lhes estão associadas minimizam o número de passos de

conversão intermédios: o bom funcionamento do mecanismo dos modos depende desta pro-

priedade (cf. secção 10.1.4).

Definição 10.3.4-2 (Procedimento de prova prático) É o procedimento de prova de-

terminista que explora as regras que compõem o sistema pela ordem em que ocorrem no sis-

tema, e que escolhe para aplicação efectiva, de entre as regras que sucedem (cf. definição

10.2.6-2) e produzem árvores de tamanho mínimo (cf. definição 10.2.5-4), a regra de menor

ordem (i.e. a regra mais acima na lista de regras do sistema). A árvore de prova é construída,

como habitualmente, de forma recursiva, da raiz para as folhas.

O procedimento de prova prático envolve uma questão de minimização. Felizmente, esta

questão pode ser tratada usando a técnica da programação dinâmica, o que garante a possibili-

dade de implementar este procedimento de forma eficiente.

A utilização da ordenação das regras pelos dois procedimentos de prova serve, antes de

mais nada, para garantir o seu determinismo. Mas também proporciona um útil um mecanismo

de atribuição de prioridades às regras dum sistema de prova: por exemplo, nos sistemas natural

194 OM – Uma linguagem de programação multiparadigma

e prático, a regra [Coerção extra] foi deliberadamente colocada na segunda posição com o objec-

tivo de maximizar a aplicabilidade das regras extra, em detrimento das regras básicas (excep-

tuando a regra [Coerção ≤]).

10.3.5 Propriedades dos procedimentos de provaNesta secção apresentamos e demonstramos diversas propriedades importantes dos procedi-

mentos de prova normalizado e prático. Mostramos que ambos constituem algoritmos de prova

completos no contexto do sistema prático (cf. teoremas 10.3.5-1 e 10.3.5-3), mas que o proce-

dimento normalizado nem sempre gera árvores de prova com tamanho mínimo (cf. teorema

10.3.5-2). Provamos ainda que nenhum destes procedimentos é completo no contexto do siste-

ma natural.

Começamos por provar que, no contexto do sistema prático, o procedimento de prova nor-

malizado constitui um algoritmo de prova completo.

Teorema 10.3.5-1 No contexto do sistema prático, o procedimento de prova normalizado é

um algoritmo de prova completo.

Prova: Todas as regras básicas não-terminais do sistema prático satisfazem a propriedade da

subfórmula. Assim, de acordo com o teorema 10.2.7.3.2-2, o procedimento de prova normali-

zado constitui um algoritmo de prova no contexto do sistema prático.

Falta provar que o procedimento de prova normalizado é completo. O facto de ser completo

é afinal uma consequência directa do dois seguintes factos: este procedimento é um algoritmo

e ele usa todas as regras do sistema. Vejamos o detalhe da demonstração.

Seja ≤c a relação binária entre tipos definida pelo sistema prático. Seja ≤n a relação binária

entre tipos determinada pelo procedimento de prova normalizado ao ser usado no sistema prá-

tico. Vamos provar que Γ ≤c⊂≤n, ou seja:

• Se for possível provar Γ υ≤cτ usando uma árvore de prova ψc, então também é possível

provar Γ υ≤nτ através duma árvore de prova ψn. (Na demonstração, deixaremos o con-

texto Γ implícito).

A demonstração efectua-se por indução na altura da árvore de prova ψc.

Caso base: Se altura(ψc)=1, então a prova ψc de υ≤cτ foi efectuada através duma única aplicação

duma das suas regras terminais: [Coerção ≤] ou [Coerção extra]. Vamos mostrar que nestes casos

também é possível provar υ≤nτ.

• Caso [Coerção ≤]: Neste caso temos υ≤τ. Aplicando o procedimento de prova normaliza-

do a υ≤nτ e, portanto, tentando sequencialmente as várias regras que definem ≤n, verifi-

camos que a primeira regra tentada, a regra [Coerção ≤], sucede imediatamente.

10 Sistema de coerções 195

• Caso [Coerção extra]: Aplicando o procedimento de prova normalizado a υ≤nτ, tentamos

primeiro a regra [Coerção ≤]. Se suceder a prova está obtida. Caso contrário a regra ten-

tada a seguir, [Coerção extra], sucede de certeza.

Caso geral: Assumimos, como hipótese de indução, que a tese é válida para todas as provas ψc

com altura altura(ψc)=m. Vamos provar que a tese também é válida para todas as provas ψ′c tais

que altura(ψ′c)=m+1. Faremos uma análise de casos baseada na última regra aplicada na prova

ψ′c.

• Casos [Coerção ≤], [Coerção extra_pro]: casos impossíveis porque neste caso teríamos

altura(ψ′c)=1, o que não pode ser pois estamos a assumir que altura(ψ′c)=m+1>1.

• Caso [Coerção extra_despro]: Como esta foi a última regra aplicada, estamos perante uma

asserção da forma ϑ[υ]≤cτ, com prova ψ′c, deduzida a partir de υ≤cτ, a qual tem uma

prova com altura inferior a m+1 (nas condições da hipótese de indução). Apliquemos

então o procedimento de prova normalizado a ϑ[υ]≤nτ. Percorrendo sequencialmente as

regras que definem ≤n, tentamos sucessivamente as regras [Coerção ≤] e [Coerção extra]. Se

alguma delas suceder a prova está obtida. Se nenhuma delas suceder, e como o procedi-

mento não entrou em ciclo, por ser um algoritmo, tenta-se a regra [Coerção extra_despro],

a qual sucede de certeza.

Provemos que, efectivamente, a regra [Coerção extra_despro] sucede nesta situação. A pro-

va de υ≤cτ está nas condições da hipótese de indução pelo que existe uma prova de

υ≤nτ. Aplicando então a regra [Coerção extra_despro] a esta asserção obtém-se imediata-

mente uma prova ψ′n para ϑ[υ]≤nτ.

• Casos [Coerção extra_despro], [Coerção name]: Estes casos provam-se exactamente como o

caso anterior.

• Caso [Coerção →]: Como esta foi a última regra aplicada, estamos perante uma asserção

da forma υ→τ≤cυ′→τ′, com prova ψ′c, deduzida a partir de υ′≤cυ e de τ≤cτ′, as quais têm

provas com alturas inferiores a m+1 (nas condições da hipótese de indução). Aplique-

mos então o procedimento de prova normalizado a υ→τ≤nυ′→τ′. Percorrendo sequen-

cialmente as várias regras que definem ≤n, tentam-se sucessivamente várias regras. Se

alguma das regras tentadas antes de [Coerção →] suceder, a prova está obtida. Se

nenhuma delas suceder, e como o procedimento não entrou em ciclo, tenta-se a regra

[Coerção →], a qual sucede de certeza:

Efectivamente, as provas de υ′≤cυ, τ≤cτ′ estão nas condições da hipótese de indução

pelo que existem provas de υ′≤nυ, τ≤nτ′. Aplicando, então, a regra [Coerção →] a estas

duas asserções obtém-se imediatamente uma prova ψ′n para υ→τ≤nυ′→τ′.

• Caso [Coerção …]: Se esta foi a última regra aplicada é porque estamos perante uma

asserção da forma l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′. Este caso demonstra-se como

os anteriores.

196 OM – Uma linguagem de programação multiparadigma

Ao contrário do procedimento de prova prático, o procedimento de prova normalizado pode

não gerar árvores de prova com tamanho mínimo.

Teorema 10.3.5-2 O procedimento de prova normalizado nem sempre gera uma árvore de

prova com tamanho mínimo.

Prova: Basta apresentar um exemplo. Consideremos o sistema prático de base, enriquecido

com as seguintes 4 regras extra: ConstT T≤extraT, T≤extraLazyT T, LazyT T≤extraT, T≤extraValueT T.

Consideremos a asserção ConstT LazyT Nat≤cValueT LazyT Nat.

Para esta asserção, o procedimento de prova normalizado gera a seguinte árvore de prova,

com tamanho 5:

T≤extraValueT T

T≤extraLazyT T

ConstT T≤extraT

LazyT T≤extraT Γ Nat≤Nat

Γ Nat≤cNat

Γ LazyT Nat≤cNat

Γ ConstT LazyT Nat≤cNat

Γ ConstT LazyT Nat≤cLazyT Nat

Γ ConstT LazyT Nat≤cValueT LazyT Nat

No entanto, sem usar o procedimento de prova normalizado, é possível construir esta outra

árvore de prova, com tamanho 3, apenas:

T≤extraValueT T

ConstT T≤extraT Γ LazyT Nat≤LazyT Nat

Γ LazyT Nat≤cLazyT Nat

Γ ConstT LazyT Nat≤cLazyT Nat

Γ ConstT LazyT Nat≤cValueT LazyT Nat

Incidentalmente, esta segunda árvore é a que resulta da aplicação do procedimento de prova

prático.

No contexto do sistema prático, o procedimento de prova prático também constitui um al-

goritmo de prova completo, como vamos mostrar.

Teorema 10.3.5-3 No contexto do sistema prático, o procedimento de prova prático consti-

tui um algoritmo de prova completo.

Prova: Esta prova é parecida com a do teorema 10.3.5-1. No entanto, em alguns pontos a argu-

mentação precisa de ser alterada.

No contexto do sistema prático, o procedimento de prova prático é um algoritmo pois todas

as regras básicas não-terminais do sistema prático satisfazem a propriedade da subfórmula (cf.

teorema 10.2.7.3.2-2).

Para provar que o procedimento de prova prático é completo, representemos por ≤c a rela-

ção binária entre tipos definida pelo sistema prático e por ≤p a relação binária entre tipos deter-

10 Sistema de coerções 197

minada pelo procedimento de prova prático ao ser usado no sistema prático. Vamos, portanto,

provar que Γ ≤c⊂≤p, ou seja:

• Se for possível provar Γ υ≤cτ usando uma árvore de prova ψc, então também é possível

provar Γ υ≤pτ através duma árvore de prova ψp. (Na demonstração, deixaremos o con-

texto Γ implícito).

A demonstração efectua-se por indução na altura da árvore de prova ψc.

Caso base: Se altura(ψc)=1, então a prova ψc de υ≤cτ foi efectuada através duma única aplicação

duma das suas regras terminais: [Coerção ≤] ou [Coerção extra]. Vamos mostrar que em cada um

destes casos também é possível provar υ≤pτ.

• Caso [Coerção ≤]: Neste caso temos υ≤τ. Aplicando o procedimento de prova prático a

υ≤pτ e, portanto, explorando sequencialmente as várias regras que definem ≤p, desco-

brimos que a primeira regra tentada [Coerção ≤] sucede imediatamente. Obtemos assim

uma árvore de prova ψc≡ψp para υ≤cτ. Já não vale a pena tentar as regras seguintes pois

esta é já a árvore de prova pretendida: tem tamanho 1 e a regra [Coerção ≤] é a regra de

menor ordem do sistema.

• Caso [Coerção extra]: Aplicando o procedimento de prova prático a υ≤pτ, tentamos pri-

meiro a regra [Coerção ≤]. Se suceder, a árvore de prova pretendida está obtida: é uma

árvore de tamanho 1 e a regra [Coerção ≤] é a de menor ordem no sistema. Caso contrá-

rio a regra tentada a seguir, [Coerção extra], sucede de certeza e produz uma árvore de ta-

manho 1 usando a segunda regra do sistema.

Caso geral: Assumimos, como hipótese de indução, que a tese é válida para todas as provas ψc

com altura altura(ψc)=m. Vamos provar que a tese também é válida para todas as provas ψ′c tais

que altura(ψ′c)=m+1. Faremos uma análise de casos baseada na última regra aplicada na prova

ψ′c. Apresentamos apenas o caso [Coerção extra_despro], uma vez que, relativamente ao teorema

10.3.5-1, as provas de todos os outros casos seriam alvo de adaptação idêntica:

• Caso [Coerção extra_despro]: Como esta foi a última regra aplicada, estamos perante uma

asserção da forma ϑ[υ]≤cτ, com prova ψ′c, deduzido a partir de υ≤cτ, a qual tem uma

prova com altura inferior a m+1 (nas condições da hipótese de indução). Apliquemos

então o procedimento de prova prático a ϑ[υ]≤pτ, tentando sequencialmente todas as re-

gras que definem ≤p. Pelo menos uma destas regras sucede: a regra [Coerção extra_despro].

Além disso, as regras que falham não fazem o procedimento entrar em ciclo, pois este é

um algoritmo. Finalmente, sendo não-vazio o conjunto de regras que sucedem, é pos-

sível escolher nesse conjunto a regra de menor ordem que produz uma árvore de tama-

nho mínima.

Provemos que, efectivamente, a regra [Coerção extra_despro] sucede nesta situação. A pro-

va de υ≤cτ está nas condições da hipótese de indução pelo que existe uma prova de

198 OM – Uma linguagem de programação multiparadigma

υ≤pτ. Aplicando a regra [Coerção extra_despro] a esta asserção obtém-se imediatamente

uma prova ψ′p (não necessariamente de tamanho mínimo) para ϑ[υ]≤pτ.

No contexto do sistema natural, nem o procedimento de prova normalizado nem o procedi-

mento de prova prático têm poder suficiente para provar todos os juízos de coerções válidos do

sistema. A reordenação das regras do sistema também não é suficiente para tornar estes proce-

dimentos completos. O procedimento geral de prova (cf. definição 10.2.7.3.1-1) já teria o po-

der de prova necessário, mas não tem interesse prático por não ser um algoritmo.

Teorema 10.3.5-4 No contexto do sistema natural, nem o procedimento de prova normali-

zado nem o procedimento de prova prático são completos.

Prova: Vamos assumir que foram introduzidas no sistema as duas seguintes regras extra, ape-

nas: Float→(Float→Bool)≤extra e ≤extraBool→(Bool→Float). Usando estas duas regras, e ainda a

regra da transitividade, pode provar-se a asserção Float→(Float→Bool)≤cBool→(Bool→Float) da se-

guinte forma:

Float→(Float→Bool)≤extra

Γ Float→(Float→Bool)≤c

≤extraFloat→(Float→Bool)

Γ ≤cBool→(Bool→Float)

Γ Float→(Float→Bool)≤cBool→(Bool→Float)

No entanto, perante a asserção Float→(Float→Bool)≤cBool→(Bool→Float), tanto o procedimento

de prova normalizado como o procedimento de prova prático tentam usar a regra [Coerção →]

antes de tentar usar a regra [Coerção trans]. A aplicação inicial de [Coerção →] leva à tentativa de

lidar com a subprova de Γ Float≤cBool, a qual, sendo inválida, faz os procedimentos entrar em

ciclo. De facto, não existe árvore de prova para Γ Float≤cBool mas, apesar de tudo, a regra

[Coerção trans] é sempre aplicável e faz crescer a árvore de prova de forma ilimitada.

Podemos tentar reordenar as regras do sistema natural por forma que a regra [Coerção trans]

surja antes da regra [Coerção →]. Mas agora, no novo sistema, é também fácil encontrar asser-

ções válidas que fazem os procedimentos entrar em ciclo. Por exemplo, tomando a regra extra

Int≤extraFloat, a asserção válida Float→Int≤cInt→Float faz os procedimentos entrar em ciclo, pois a

regra da transitividade [Coerção trans] é sempre tentada antes da regra [Coerção →].

10.3.6 Propriedades do sistema práticoNesta secção provamos diversas propriedades do sistema prático. Eis uma lista comentada des-

sas propriedades:

• Decidibilidade (cf. teorema 10.3.6-1): Alcançar esta propriedade foi a razão de ser da

introdução do sistema prático.

• Indeterminismo e ambiguidade (cf. teorema 10.3.6-1): A eliminação destas proprieda-

des indesejáveis efectua-se recorrendo ao procedimento de prova prático (cf. regras

10.3.6-2 e 10.3.6-3).

10 Sistema de coerções 199

• Menor generalidade do sistema prático face ao sistema natural (cf. teorema 10.3.6-4):

Esta propriedade resulta do facto do sistema prático ser decidível e do sistema natural

não o ser.

• Coerência e transitividade do subsistema do sistema prático na ausência de regras extra

(cf. teoremas 10.3.6-5 e 10.3.6-7): Esta propriedade mostra que o sistema prático inclui

um núcleo de regras com excelentes propriedades.

• Ambiguidade e não-transitividade da versão do sistema prático caracterizada pelas re-

gras extra da biblioteca padrão (cf. teoremas 10.3.6-6 e 10.3.6-8): Os casos de ambigui-

dade que surgem por culpa das regras extra que foram incluídas na biblioteca padrão

são raros e estão, à partida, resolvidos pela regra 10.3.6-3; embora se possam construir

exemplos de não transitividade do sistema prático estendido com as regras extra da bi-

blioteca padrão (cf. 10.3.6-8), os casos de transitividade mais importantes são suporta-

dos pelas novas três regras básicas, introduzidas no sistema prático (cf. secção 10.3.1).

O primeiro teorema desta secção não diz nada de muito novo. Ele justifica-se por razões de

documentação:

Teorema 10.3.6-1 O sistema prático é decidível, mas também indeterminista e ambíguo.

Prova: Pelo teorema 10.3.5-1 existe um algoritmo de prova completo para o sistema prático.

Portanto, a relação de coerção definida pelo sistema prático é decidível. Já vimos dois algo-

ritmos de prova completos para o sistema prático: o procedimento de prova normalizado (cf.

teorema 10.3.5-1) e o procedimento de prova prático (cf. teorema 10.3.5-3)

O sistema prático é indeterminista: as fontes de indeterminismo que são referidas nos dois

últimos parágrafos da secção 10.2.7.1 mantêm-se no sistema prático.

O sistema prático é ambíguo: o exemplo de ambiguidade apresentado na secção 10.2.7.2

aplica-se também ao caso do sistema prático.

O indeterminismo do sistema prático faz com que as suas regras não definam automatica-

mente um procedimento de prova determinista. No entanto, já sabemos que o procedimento de

prova prático constitui um algoritmo de prova completo no contexto do sistema prático. Por

essa razão, não perdemos poder de prova se adoptarmos este algoritmo como procedimento de

prova oficial do sistema prático. Assim, introduzimos a seguinte regra:

Regra 10.3.6-2 (Eliminação do indeterminismo) No sistema prático, adoptamos, co-

mo procedimento de prova oficial, o procedimento de prova prático. No contexto do sistema

prático, a partir de agora, só serão consideradas árvores de prova geradas por este procedimen-

to.

A regra anterior, ao resolver o problema do indeterminismo, tem o efeito indirecto de tam-

bém resolver o problema da ambiguidade. Eis o enunciado independente duma regra de resolu-

ção de ambiguidade:

200 OM – Uma linguagem de programação multiparadigma

Regra 10.3.6-3 (Eliminação da ambiguidade) No sistema prático, a função de con-

versão que se associa a cada juízo de coerção é aquela que corresponde à árvore de prova gera-

da pelo procedimento de prova prático.

O sistema prático foi criado para resolver o problema da indecidibilidade do sistema natu-

ral. Vamos confirmar que todas as asserções prováveis no sistema prático são asserções váli-

das do sistema natural.

Teorema 10.3.6-4 (sistema prático ⊂ sistema natural) A relação de coerção defini-

da pelo sistema prático está estritamente contida na relação de coerção definida pelo sistema

natural.

Prova:

1ªparte: Para mostrar a inclusão da primeira relação na segunda, basta verificar que as três re-

gras novas, introduzidas no sistema prático, podem ser demonstradas usando as regras origi-

nais do sistema natural.

Vejamos uma demonstração da nova regra [Coerção extra_pro] usando apenas regras do siste-

ma natural. Note como, partindo das premissas da nova regra se chega à conclusão dessa mes-

ma regra, usando as regras [Coerção extra] e [Coerção trans]:

Γ υ≤cτ X≤extraΩ[X] Γ τ

Γ τ≤cΩ[τ]

Γ υ≤cΩ[τ]

Eis, agora, uma demonstração da nova regra [Coerção extra_despro] usando apenas as regras

do sistema natural [Coerção extra] e [Coerção trans]:

ϑ[X]≤extraX Γ υ:∗Γ ϑ[υ]≤cυ

Γ υ≤cτ

Γ ϑ[υ]≤cτ

Finalmente, eis uma demonstração da nova regra [Coerção name] usando só as regras do siste-

ma natural [Coerção nname] e [Coerção trans]:

Γ υ≤cτ Γ τ:∗

Γ τ≤cUnit→τΓ υ≤cUnit→τ

2ªparte: Para mostrar a não equivalência dos sistemas, basta considerar o teorema 10.2.7.3.2-3.

Note que o sistema prático é gerado a partir do sistema natural, usando a manobra descrita no

enunciado daquele teorema.

O seguinte importante teorema mostra que se fosse eliminada a extensibilidade do sistema

prático (ou do sistema natural), o sistema resultante seria coerente, mesmo permanecendo

indeterminista.

10 Sistema de coerções 201

Teorema 10.3.6-5 (Coerência no sistema ≤/name/→/…) O sistema ≤/name/→/…,

mesmo sendo indeterminista, é coerente.

Prova: O sistema ≤/name/→/… é constituído pelas quatro regras: [Coerção ≤], [Coerção name],

[Coerção →] e [Coerção …].

Vamos mostrar que, neste sistema, a função de conversão associada a um juízo de coerção

não depende da árvore de prova usada para provar esse juízo. Como árvore de referência para

o estabelecimento de comparação usaremos a árvore de prova gerada pelo procedimento de

prova normalizado. (Não usamos, na prova, o procedimento de prova prático pois a demons-

tração ficaria mais complicada).

Vamos, representar por ≤t a relação binária definida pelo sistema ≤/name/→/…, e por ≤n a

relação binária determinada pelo procedimento de prova normalizado ao ser usado no sistema

≤/name/→/….

Indeterminismo: Para mostrar que o sistema ≤/name/→/… é indeterminista mostramos que o

juízo trivial Γ →≤t→ admite duas árvores de prova distintas:

Γ →≤→

Γ →≤t→

Γ ≤

Γ ≤t

Γ ≤

Γ ≤t

Γ →≤t→

Coerência: Vamos provar a seguinte proposição:

• Seja ψt uma árvore de prova para um juízo Γ υ≤tτ no sistema ≤/name/→/…. Então o

procedimento de prova normalizado gera uma árvore de prova ψn para o juízo Γ υ≤nτ

tal que (Γ υ≤tτ)=(Γ υ≤nτ). (Na demonstração, deixaremos o contexto Γ implícito).

Note que, por acidente, esta proposição também mostra a completitude do procedimento de

prova normalizado no sistema ≤/name/→/….

A demonstração é por indução na altura da árvore de prova ψt.

Caso base: Se altura(ψt)=1 então a prova ψt de υ≤tτ consiste numa única aplicação de [Coerção ≤]:

• Caso [Coerção ≤]: Neste caso υ≤τ. No procedimento normalizado de prova a primeira

regra a ser tentada é também [Coerção ≤], portanto com sucesso garantido. Obtemos

assim uma prova normalizada ψn para υ≤nτ, com ψn=ψt, donde (Γ υ≤tτ)=(Γ υ≤nτ), como

pretendíamos.

Caso geral: Assumindo, como hipótese de indução, que o teorema é válido para todas as pro-

vas ψt com altura altura(ψt)=m, vamos provar que também é válido para todas as provas ψ′t tais

que altura(ψ′t)=m+1. Faremos uma análise de casos baseada na última regra aplicada na prova

ψ′t. Os casos a estudar são três: [Coerção name], [Coerção →] e [Coerção …].

• Caso [Coerção name]: Se esta foi a última regra aplicada, é porque estamos perante uma

asserção da forma τ≤tUnit→τ′, com prova ψ′t, deduzida a partir da asserção τ≤tτ′, a qual

202 OM – Uma linguagem de programação multiparadigma

tem uma prova com altura inferior a m+1 (nas condições da hipótese de indução). Apli-

quemos então o procedimento normalizado de prova a τ≤nUnit→τ′. Percorrendo sequen-

cialmente as várias regras que definem ≤n, verifiquemos quais são as que podem ser

aplicadas neste caso:

•Tentativa [Coerção ≤]: Caso impossível. Se esta regra sucedesse, ficaríamos a saber que

τ≡Unit→υ≤Unit→τ′ com υ≤τ′. Desta forma τ≤tτ′ teria a forma Unit→υ≤tτ′. Mas não é pos-

sível ter ao mesmo tempo υ≤τ′ e Unit→υ≤tτ′. (Ideia da prova: Imaginando que υ se inicia

por k≥0 ocorrências de “Unit→” temos de ter υ≡[Unit→]kα e τ′≡[Unit→]kβ, onde α e β não

têm qualquer “Unit→” inicial. Isso significa que a regra Unit→υ≤tτ′ toma a forma

[Unit→]k+1υ≤t [Unit→]kβ. Mas, agora, demonstra-se facilmente, por indução em k, que

esta asserção é inválida para qualquer inteiro k).

•Tentativa [Coerção name]: A regra anterior falhou, mas esta regra sucede com toda a

certeza. Efectivamente, a prova de τ≤tτ′ está nas condições da hipótese de indução pelo

que existe uma prova de τ≤nτ′ com altura inferior a m+1. Aplicando, então, a regra

[Coerção name] a esta asserção obtém-se imediatamente uma prova ψ′n para τ≤nUnit→τ′.

Outra consequência da aplicação da hipótese de indução é o facto de (τ≤tτ′)=(τ≤nτ′).

Daqui se deduz imediatamente:

(τ≤tUnit→τ′) exp

= λz:Unit. (τ≤tτ′) exp por [Coerção name]

= λz:Unit. (τ≤nτ′) exp por [Coerção name]

= (τ≤nUnit→τ′) exp

• Caso [Coerção →]: Se esta foi a última regra aplicada, é porque estamos perante uma

asserção da forma υ→τ≤tυ′→τ′, com prova ψ′t, deduzida a partir das asserções υ′≤tυ e

τ≤tτ′, as quais têm provas com alturas inferiores a m+1 (condições da hipótese de indu-

ção). Apliquemos então o procedimento normalizado de prova a υ→τ≤nυ′→τ′. Percor-

rendo sequencialmente as várias regras que definem ≤n, verifiquemos quais são as que

podem ser aplicadas neste caso:

•Tentativa [Coerção ≤]: Se esta regra suceder ficamos com uma prova ψ′n para

υ→τ≤nυ′→τ′ com altura(ψ′n)=1. Ficamos também a saber que υ→τ≤υ′→τ′ e como esta

asserção resultou da aplicação de [Coerção ≤] temos υ′≤υ e τ≤τ′.

Vamos provar que (υ→τ≤tυ′→τ′) = (υ→τ≤nυ′→τ′):

(υ→τ≤tυ′→τ′)= λf:υ→τ. λx:υ′. (τ≤tτ′) (f ((υ′≤tυ)x)) por [Coerção →]

= λf:υ→τ. λx:υ′. (τ≤nτ′) (f ((υ′≤nυ)x)) por hipótese de indução

= λf:υ→τ. λx:υ′. (τ≤τ′) (f ((υ′≤υ)x)) *

= λf:υ→τ. λx:υ′. (λy:τ′.y) (f ((λy:υ.y)x)) por definição de (τ≤τ′), (υ′≤υ)

= λf:υ→τ. (λg:υ′→τ′.g) f

(* na prova de τ≤nτ′ tenta-se primeiro usar a regra [Coerção ≤], com sucesso imediato, pois sabe-

mos que τ≤τ′; o mesmo para υ≤nυ′).

10 Sistema de coerções 203

(υ→τ≤nυ′→τ′)= (υ→τ≤υ′→τ′)= λf:υ→τ. (λg:υ′→τ′.g) f por [Coerção ≤]

•Tentativa [Coerção name]: Se a tentativa anterior falhou, pode acontecer que esta regra

suceda e nesse caso ficamos com uma prova ψ′n para υ→τ≤nυ′→τ′. Ficamos também a

saber que a forma de υ′→τ′ é υ′→τ′≡Unit→υ′′ com υ→τ≤nυ′′. Portanto υ→τ≤tUnit→υ′′ e

υ→τ≤nυ′′. Como a primeira destas duas asserções resultou da aplicação de [Coerção →]

concluímos que Unit≤tυ, τ≤tυ′′ (esta asserção está nas condições da hipótese de indução),

υ→τ≤nυ′′. Como tem de ser υ≡Unit, e usando a hipótese de indução, ficamos com υ≡Unit,

τ≤nυ′′ e Unit→τ≤nυ′′.

Finalmente, poderemos ainda dizer que υ′′≡[Unit→]kυ′′′ com τ≤nυ′′′, onde [Unit→]k repre-

senta k>0 ocorrências de “Unit→”, depois de investigarmos como é que Unit→τ≤nυ′′ foi

provada. Vamos assumir que Unit→τ≤nυ′′ resultou de, exactamente, k aplicações termi-

nais sucessivas de [Coerção name] que não podiam ser substituídas por aplicações de

[Coerção ≤], podendo ser k=0. Neste caso, υ′′≡[Unit→]k→σ′′, sendo Unit→τ≤n[Unit→]k→σ′′

demonstrado a partir de Unit→τ≤rσ′′. Indo ainda mais atrás no estudo da demonstração

de Unit→τ≤nυ′′, façamos agora uma análise de casos sobre a última regra usada na prova

de Unit→τ′≤rσ′′:

•Subcaso [Coerção≤]: Neste caso Unit→τ≤σ′′≡Unit→υ′′′ com τ≤υ′′′.

•Subcaso [Coerção →]: Neste caso Unit→τ≤nυ′′≡Unit→υ′′′ com τ≤nυ′′′.

•Subcaso [Coerção name]: Subcaso impossível pois [Coerção name] não foi certa-

mente a última regra usada na demonstração de υ′→τ′≤rσ′′. Recordamos que as k

aplicações terminais de υ′→τ′≤rυ′′ já foram alvo de tratamento.

•Subcaso [Coerção …]: Subcaso impossível pois υ′→τ′≤rσ′′ nunca poderia resul-

tar da aplicação da regra [Coerção …].

Finalmente, vamos provar que (υ→τ≤tυ′→τ′) = (υ→τ≤nυ′→τ′):

(υ→τ≤tυ′→τ′) fexp

= (Unit→τ≤tUnit→υ′′) fexp com fexp ∈ Unit→τ= (λf:Unit→τ. λx:Unit. (τ≤tυ′′) (f ((Unit≤tUnit)x)) fexp por [Coerção →]*

= λx:Unit. (τ≤tυ′′) (fexp ((Unit≤tUnit)x)) por [Termo= β λx]

= λx:Unit. (τ≤nυ′′) (fexp x) por hipótese de indução e (Unit≤tUnit)=id

= λx:Unit. (τ≤n[Unit→]kυ′′′) (fexp x) porque υ′′≡[Unit→]kυ′′′= [λx:Unit.]k+1 (τ≤nυ′′′) (fexp x) por [Coerção name]**

(* aplicamos a regra [Coerção →] pois é essa regra que corresponde à assunção do caso corrente)

(** não há qualquer hipótese de τ≤[Unit→]kυ′′′ para algum k≥1 pois isso violaria o pressuposto da

análise de Unit→τ≤nυ′′ efectuada atrás. Assim a regra que se tenta a seguir é [Coerção name], e

neste caso sempre com sucesso)

204 OM – Uma linguagem de programação multiparadigma

(υ→τ≤nυ′→τ′) fexp

= (Unit→τ≤nUnit→υ′′) fexp com fexp ∈ Unit→τ= λz:Unit. (Unit→τ≤nυ′′) fexp por [Coerção name]*

= λz:Unit. (Unit→τ≤n[Unit→]kυ′′′) fexp porque υ′′≡[Unit→]kυ′′′= [λz:Unit.]k (Unit→τ≤nUnit→υ′′′) fexp por [Coerção name]**

se Unit→τ≤Unit→υ′′′então

= [λz:Unit.]k (Unit→τ≤Unit→υ′′′) fexp por [Coerção ≤]

= [λz:Unit.]k (λf:Unit→τ. λx:Unit. (τ≤υ′′′) (f ((Unit≤Unit)x))) fexp

usando Tentativa [Coerção ≤]

= [λz:Unit.]k λx:Unit. (τ≤nυ′′′) (fexp x) por [Termo= β λx] e (Unit≤tUnit)=id

senão

= [λz:Unit.]k (λf:Unit→τ. λx:Unit. (τ≤nυ′′′) (f ((Unit≤nUnit)x))) fexp

por [Coerção →]

= [λz:Unit.]k λx:Unit. (τ≤nυ′′′) (fexp x) por [Termo= β λx] e (Unit≤tUnit)=id

(* aplicamos [Coerção name] pois é essa regra que corresponde à assunção da tentativa corrente)

(** não há qualquer hipótese de Unit→τ≤[Unit→]kυ′′′ para algum k≥2 pois isso violaria o pressu-

posto da análise de Unit→τ≤nυ′′, efectuada atrás. Assim a regra que se tenta a seguir é a regra

[Coerção name], e neste caso sempre com sucesso)

Note que todas estas manipulações são compatíveis com passagem de parâmetros call-

-by-name já que o argumento fexp é efectivamente uma função de tipo Unit→τ.

•Tentativa [Coerção →]: Mesmo que as regras anteriores tenham falhado, esta regra suce-

de de certeza. Efectivamente, as provas de υ′≤tυ, τ≤tτ′ estão nas condições da hipótese

de indução pelo que existem provas de υ′≤nυ, τ≤nτ′ com alturas inferiores a m+1. Apli-

cando então a regra [Coerção →] a estas duas asserções obtém-se imediatamente uma

prova ψ′n para υ→τ≤nυ′→τ′. Outra consequência da aplicação da hipótese de indução é o

facto de (υ′≤tυ)=(υ′≤nυ) e (τ≤tτ′)=(τ≤nτ′). Daqui se deduz imediatamente:

(υ→τ≤tυ′→τ′)= λf:υ→τ. λx:υ′. (τ≤tτ′) (f ((υ′≤tυ)x)) por [Coerção →]

= λf:υ→τ. λx:υ′. (τ≤nτ′) (f ((υ′≤nυ)x)) porque (υ′≤tυ)=(υ′≤nυ) e

(τ≤tτ′)=(τ≤nτ′)= (υ→τ≤nυ′→τ′)

• Caso [Coerção …]: Se esta foi a última regra aplicada é porque estamos perante uma

asserção da forma l1:τ1‚…‚lk:τk‚…‚ln:τn≤cl1:τ1′‚…‚lk:τk′. Este caso demonstra-se como

o anterior. Tentam-se as várias regras que definem a relação ≤t sequencialmente: a re-

gra [Coerção ≤] pode suceder; as regras [Coerção name] e [Coerção →] falham de certeza;

finalmente, regra [Coerção …] sucede sempre. Tanto no caso [Coerção ≤] como no caso

[Coerção …] é possível tirar as conclusões que o teorema exige usando a hipótese de

indução.

10 Sistema de coerções 205

Com o teorema anterior ficamos a saber que no sistema prático, a ocorrerem situações de

ambiguidade, elas serão sempre provocadas pelas regras extra. Vamos exibir um caso de ambi-

guidade que ocorre na biblioteca padrão da linguagem OM. Este exemplo tem apenas interesse

académico, pois já resolvemos o problema da ambiguidade mediante a introdução da regra

10.3.6-3.

Teorema 10.3.6-6 (Ambiguidade da biblioteca padrão) Existe pelo menos um

caso de ambiguidade provocado pelas regras extra da biblioteca padrão da linguagem OM (e

resolvido pela regra 10.3.6-3).

Prova: Vamos mostrar que o juízo de coerção Γ Bool→Bool≤cBool→GenT Bool admite duas ár-

vores de prova, cada uma delas com procedimentos de conversão associados distintos.

Se o juízo for provado usando a regra [Coerção →] de forma directa, a função de conversão

que lhe fica associada é a seguinte:

(Γ Bool→Bool≤cBool→GenT Bool) ˆ = λf:Bool→Bool. λx:Bool. (Γ Bool≤cGenT Bool) (f x)

= λf:Bool→Bool. λx:Bool. single (f x)

Se o juízo for provado usando directamente a regra extra X→Bool≤extraX→GenT X, a função

de conversão que lhe fica associada é a seguinte:

(Γ Bool→Bool≤cBool→GenT Bool) ˆ = λf:Bool→Bool. λx:Bool. (if f x then single x else fail)

As duas funções de conversão são diferentes. Basta tomar f ˆ = λx:true para ver isso.

O próximo teorema é importante pois mostra que se fosse eliminada a extensibilidade do

sistema prático (ou do sistema natural), a relação binária por ele definida seria transitiva (mes-

mo não existindo regra da transitividade explícita).

Teorema 10.3.6-7 (Transitividade do sistema ≤/name/→/…) A relação definida pelo

sistema ≤/name/→/… é transitiva.

Prova: Seja ≤r a relação definida pelas quatro regras [Coerção ≤], [Coerção name], [Coerção →], e

[Coerção …]. Vamos provar a seguinte proposição:

• Para quaisquer tipos τ, τ′, τ′′, se for possível provar Γ τ≤rτ′ (usando uma árvore de prova

ψ1) e também Γ τ′≤rτ′′ (usando uma árvore de prova ψ2), então também é possível pro-

var Γ τ≤rτ′′ (usando uma árvore de prova ψ3). (Na demonstração, deixaremos o contex-

to Γ implícito).

Faremos a demonstração por indução na altura máxima das árvores de prova ψ1 e ψ2. A de-

monstração não depende da ordenação das regras, nem de qualquer procedimento de prova es-

pecífico.

Caso base: Se max(altura(ψ1),altura(ψ2))=1 então a prova ψ1 de τ≤rτ′ e a prova ψ2 de τ′≤rτ′′ consis-

tem ambas em utilizações directas da regra [Coerção ≤]. Verifica-se portanto que τ≤τ′ e τ′≤τ′′. Por

206 OM – Uma linguagem de programação multiparadigma

transitividade de ≤ obtém-se τ≤τ′′. Finalmente, aplicando novamente [Coerção ≤] concluímos

τ≤rτ′′, como pretendíamos.

Caso geral: Assumindo como hipótese de indução a validade do teorema para todos os pares

de provas ψ1 e ψ2 tais que max(altura(ψ1),altura(ψ2))=m, vamos provar a validade do teorema para

todos os pares de provas ψ′1 e ψ′2 tais que max(altura(ψ′1),altura(ψ′2))=m+1.

Faremos uma análise de casos baseada na última regra aplicada na prova de ψ′1 e na última

regra aplicada na prova de ψ′2. Havendo quatro regras no sistema que define ≤r, há dezasseis

casos a considerar (a demonstração é longa e não-trivial):

• Caso [Coerção ≤], [Coerção ≤]: Este caso não ocorre pois estamos a assumir que as provas

ψ′1 e ψ′2 verificam a condição max(altura(ψ′1), altura(ψ′2))=m+1>1.

• Caso [Coerção ≤], [Coerção name]: Neste caso temos υ≤υ′ e υ′≤rUnit→υ′′, demonstradas a

partir de υ′≤rυ′′. Esta última asserção está nas condições da hipótese de indução. Mas,

através da regra [Coerção ≤] é possível provar a asserção auxiliar υ≤rυ′. Como esta prova

tem altura 1, esta asserção também se encontra nas condições da hipótese de indução.

Aplicando a hipótese de indução a υ≤rυ′, υ′≤rυ′′ obtemos υ≤rυ′′. Finalmente, usando a re-

gra [Coerção name] obtemos υ≤rUnit→υ′′, como pretendíamos.

• Caso [Coerção name], [Coerção ≤]: Vamos considerar que a primeira asserção resultou de k

aplicações terminais da regra [Coerção name], com k≥1. Desta forma a primeira asserção

tem a forma υ≤rUnitk→υ′ e a segunda a forma Unitk→υ′≤Unitk→υ′′, demonstradas a partir

de υ≤rυ′ e υ′≤υ′′. Estas duas asserções estão nas condições da hipótese de indução. Faça-

mos agora uma análise de casos sobre a última regra usada na demonstração de υ≤rυ′:

•Subcaso [Coerção ≤]: Neste caso temos υ≤υ′, e imediatamente, por transitividade de ≤,

obtemos υ≤υ′′. Usando a regra [Coerção ≤] obtemos υ≤rυ′′. Usando a regra [Coerção name] k

vezes obtemos obtemos υ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção →]: Neste caso υ≤rυ′ toma a forma ι→σ≤rι′→σ′, demonstrada a partir

de ι′≤rι e σ≤rσ′. Assim υ′≤υ′′ toma a forma ι′→σ′≤ι′′→σ′′ com ι′′≤ι′ e σ′≤σ′′. Usando a regra

[Coerção ≤] obtemos ι′′≤rι′ e σ′≤rσ′′, provadas com árvores de altura 1. Temos assim que

ι′≤rι, σ≤rσ′, ι′′≤rι′ e σ′≤rσ′′ estão nas condições da hipótese de indução. Aplicando esta

hipótese duas vezes obtemos ι′′≤rι e σ≤rσ′′. Usando agora a regra [Coerção →] obtemos

ι→σ≤rι′′→σ′′, ou seja υ≤rυ′′. Usando a regra [Coerção name] k vezes obtemos υ≤rUnitk→υ′′,

como queríamos.

•Subcaso [Coerção …]: Neste caso υ≤rυ′ toma a forma a:α,b:β,c:χ≤ra:α′,b:β′, de-

monstradas a partir de α≤rα′ e β≤rβ′. Assim υ′≤υ′′ toma a forma a:α′,b:β′≤a:α′′ com

α′≤α′′. Usando a regra [Coerção ≤] obtemos α′≤rα′′, provada usando uma árvore de altura

1. Temos assim que α≤rα′ e α′≤rα′′ estão nas condições da hipótese de indução. Aplican-

do-a obtemos α≤rα′′. Usando agora a regra [Coerção …] obtemos a:α,b:β,c:χ≤ra:α′′ ou

10 Sistema de coerções 207

seja υ≤rυ′′. Usando a regra [Coerção name] k vezes obtemos obtemos υ≤rUnitk→υ′′, como

pretendíamos.

•Subcaso [Coerção name]: Subcaso impossível pois [Coerção name] não foi certamente a

última regra usada na demonstração de υ≤rυ′. Recordamos que as k aplicações terminais

da primeira asserção já foram alvo de tratamento.

• Caso [Coerção name], [Coerção name]: Vamos considerar que a segunda asserção resultou

de k aplicações terminais da regra [Coerção name], com k≥1. Desta forma a primeira

asserção tem a forma υ≤rUnit→υ′ e a segunda a forma Unit→υ′≤Unitk→υ′′, demonstradas

a partir de υ≤rυ′ e de Unit→υ′≤rυ′′. Estas duas asserções estão nas condições da hipótese

de indução. Façamos agora uma análise de casos sobre a última regra usada na demons-

tração de Unit→υ′≤rυ′′:

•Subcaso [Coerção ≤]: Neste caso temos Unit→υ′≤υ′′≡Unit→σ′′ com υ′≤σ′′. Usando agora a

regra [Coerção ≤] prova-se usando uma árvore de altura 1 que υ′≤rσ′′. Esta asserção está

nas condições da hipótese de indução. Aplicando esta hipótese a υ≤rυ′ e υ′≤rσ′′ obtemos

υ≤rσ′′. Usando agora k+1 vezes [Coerção name] obtemos υ≤rUnitk→Unit→σ′′, ou seja

υ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção →]: Neste caso temos Unit→υ′≤rυ′′≡Unit→σ′′ com υ′≤rσ′′. Esta asserção

está nas condições da hipótese de indução. Aplicando esta a υ≤rυ′ e υ′≤rσ′′ obtemos

υ≤rσ′′. Usando agora k+1 vezes [Coerção name] obtemos υ≤rUnitk→Unit→σ′′, ou seja

υ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção …]: Caso impossível pois Unit→υ′≤rυ′′ nunca poderia ter sido

provada usando esta regra.

•Subcaso [Coerção name]: Subcaso impossível pois [Coerção name] não foi certamente a

última regra usada na demonstração de Unit→υ′≤rυ′′. Recordamos que as k aplicações

terminais da segunda asserção já foram alvo de tratamento.

• Caso [Coerção name], [Coerção →]: Neste caso temos υ≤rUnit→υ′, Unit→υ′≤rUnit→υ′′,

demonstrados a partir de υ≤rυ′ e de υ′≤rυ′′. Estas duas asserções estão nas condições da

hipótese de indução. Aplicando-lhes a hipótese de indução obtemos υ≤rυ′′ e, final-

mente, usando a regra [Coerção name] obtemos υ≤rUnit→υ′′, como pretendíamos.

• Caso [Coerção →], [Coerção name]: Vamos considerar que a segunda asserção resultou de

k aplicações terminais da regra [Coerção name], com k≥1. Desta forma a primeira asserção

tem a forma υ→σ≤rυ′→σ′ e a segunda asserção tem a forma υ′→σ′≤Unitk→υ′′, demons-

tradas a partir de υ′≤rυ, σ≤rσ′, υ′→σ′≤rυ′′. Estas três asserções estão nas condições da

hipótese de indução. Façamos agora uma análise de casos sobre a última regra usada na

demonstração de υ′→σ′≤rυ′′:

•Subcaso [Coerção ≤]: Neste caso temos υ′→σ′≤υ′′≡υ′′′→σ′′′ com υ′′′≤υ′, σ′≤σ′′′. Através da

regra [Coerção ≤] é possível provar as asserções auxiliares υ′′′≤rυ′ e σ′≤rσ′′′ usando ár-

208 OM – Uma linguagem de programação multiparadigma

vores de altura 1: estas asserções estão portanto nas condições da hipótese de indução.

Aplicando a hipótese de indução a υ′≤rυ, σ≤rσ′, υ′′′≤rυ′ e σ′≤rσ′′′ obtemos υ′′′≤rυ e σ≤rσ′′′.

Usando [Coerção →], obtemos υ→σ≤rυ′′′→σ′′′, ou seja υ→σ≤rυ′′. Finalmente, aplicando k

vezes a regra [Coerção name] a esta última asserção obtemos υ→σ≤rUnitk→υ′′, como

pretendíamos.

•Subcaso [Coerção →]: Neste caso temos υ′→σ′≤rυ′′≡υ′′′→σ′′′ com υ′′′≤rυ′, σ′≤rσ′′′. Através

da regra [Coerção ≤] é possível provar as asserções auxiliares υ′′′≤rυ′ e σ′≤rσ′′′. Estas

asserções estão nas condições da hipótese de indução. Aplicando a hipótese de indução

a υ′≤rυ, σ≤rσ′, υ′′′≤rυ′ e σ′≤rσ′′′ obtemos υ′′′≤rυ e σ≤rσ′′′. Usando [Coerção →], obtemos

υ→σ≤rυ′′′→σ′′′, ou seja υ→σ≤rυ′′. Finalmente, aplicando k vezes a regra [Coerção name] a

esta última asserção obtemos υ→σ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção …]: Subcaso impossível pois υ′→σ′≤rσ′′ nunca poderia resultar da

aplicação da regra [Coerção …].

•Subcaso [Coerção name]: Subcaso impossível pois [Coerção name] não foi certamente a

última regra usada na demonstração de υ′→σ′≤rσ′′. Recordamos que as k aplicações ter-

minais da segunda asserção já foram alvo de tratamento.

• Caso [Coerção →], [Coerção →]: Neste caso temos υ→σ≤rυ′→σ′ e υ′→σ′≤rυ′′→σ′′, demons-

trados a partir de υ′≤rυ, σ≤rσ′, υ′′≤rυ′, σ′≤rσ′′. Estas quatro asserções estão nas condições

da hipótese de indução. Aplicando duas vezes a hipótese de indução obtemos υ′′≤rυ,

σ≤rσ′′ donde através da regra [Coerção →] obtemos υ→σ≤rυ′′→σ′′, como pretendíamos.

• Caso [Coerção ≤], [Coerção →]: Neste caso temos υ→σ≤υ′→σ′ e υ′→σ′≤rυ′′→σ′′, demons-

trados a partir de υ′≤υ, σ≤σ′, υ′′≤rυ′, σ′≤rσ′′. Aplicando a regra [Coerção ≤] às duas primei-

ras asserções obtemos υ′≤rυ, σ≤rσ′, υ′′≤rυ′, σ′≤rσ′, as quais estão todas nas condições da

hipótese de indução. Usando esta hipótese obtemos υ′′≤rυ, σ′′≤rσ. Finalmente, usando

[Coerção →] obtemos υ→σ≤rυ′′→σ′′, como pretendíamos.

• Caso [Coerção →], [Coerção ≤]: Neste caso temos υ→σ≤rυ′→σ′ e υ′→σ′≤υ′′→σ′′, demons-

trados a partir de υ′≤rυ, σ≤rσ′, υ′′≤υ′, σ′≤σ′′. Aplicando a regra [Coerção ≤] às duas últimas

asserções obtemos υ′≤rυ, σ≤rσ′, υ′′≤rυ′, σ′≤rσ′, as quais estão todas nas condições da

hipótese de indução. Usando esta hipótese obtemos υ′′≤rυ, σ′′≤rσ. Finalmente, usando

[Coerção →] obtemos υ→σ≤rυ′′→σ′′, como pretendíamos

• Caso [Coerção …], [Coerção name]: Esta demonstração tem muitas semelhanças com o

caso [Coerção →], [Coerção name]. Sem perder generalidade, vamos assumir que a

primeira asserção tem a forma: a:α,b:β,c:χ≤ra:α′,b:β′. Vamos também considerar que

a segunda asserção resultou de k aplicações terminais da regra [Coerção name], com k≥1.

Tomos assim a:α,b:β,c:χ≤ra:α′,b:β′, a:α′,b:β′≤rUnitk→υ′′, demonstradas a partir de

α≤rα′, β≤rβ′, a:α′,b:β′≤rυ′′. Estas três asserções estão nas condições da hipótese de

indução. Façamos agora uma análise de casos sobre a última regra usada na demons-

tração de a:α′,b:β′≤rυ′′:

10 Sistema de coerções 209

•Subcaso [Coerção ≤]: Neste caso temos a:α′,b:β′≤υ′′≡a:α′′ com α′≤α′′. Através da regra

[Coerção ≤] é possível provar a asserção auxiliar α′≤rα′′ usando árvores de altura 1: esta

asserção está portanto nas condições da hipótese de indução. Aplicando a hipótese de

indução a α≤rα′, α′≤rα′′ obtemos α≤rα′′. Usando [Coerção …], obtemos a:α,b:β,c:χ≤r

a:α′′, ou seja a:α,b:β,c:χ≤rυ′′. Finalmente, aplicando k vezes a regra [Coerção name] a

esta última asserção obtemos a:α,b:β,c:χ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção →]: Subcaso impossível pois a:α′,b:β′≤rυ′′ nunca poderia resultar da

aplicação da regra [Coerção →].

•Subcaso [Coerção …]: Neste caso temos a:α′,b:β′≤rυ′′≡a:α′′ com α′≤rα′′. Estas última

asserção está nas condições da hipótese de indução. Aplicando a hipótese de indução a

α≤rα′, α′≤rα′′ obtemos α≤rα′′. Usando [Coerção …], obtemos a:α,b:β,c:χ≤ra:α′′, ou seja

a:α,b:β,c:χ≤rυ′′. Finalmente, aplicando k vezes a regra [Coerção name] a esta última

asserção obtemos a:α,b:β,c:χ≤rUnitk→υ′′, como pretendíamos.

•Subcaso [Coerção name]: Subcaso impossível pois [Coerção name] não foi certamente a

última regra usada na demonstração de a:α′,b:β′≤rυ′′. Recordamos que as k aplicações

terminais da segunda asserção já foram alvo de tratamento.

• Caso [Coerção …], [Coerção …]: Demonstração estruturalmente idêntica à demonstra-

ção do caso [Coerção →], [Coerção →].

• Caso [Coerção name], [Coerção …]:

• Caso [Coerção →], [Coerção …]:

• Caso [Coerção …], [Coerção →]: Casos que não podem ocorrer devido à estrutura das

conclusões das regras.

• Caso [Coerção ≤], [Coerção …]:

• Caso [Coerção …], [Coerção ≤]: Demonstrações estruturalmente idênticas às demonstra-

ções dos casos [Coerção ≤], [Coerção →] e [Coerção →], [Coerção ≤].

O teorema anterior mostra que a não-transitividade da relação binária entre tipos definida

pelo sistema prático resulta exclusivamente da possibilidade de introdução de regras extra (e

claro, da inexistência duma regra de transitividade explícita). Além disso, nem todas as regras

extra são não-transitivas: de facto, como já vimos na secção 10.3.3, o sistema prático suporta

uma forma limitada de transitividade, a que têm acesso regras extra com certas formas prede-

terminadas.

De qualquer forma, fora destes limites conseguem-se encontrar exemplos de não-transitivi-

dade, inclusivamente no âmbito restrito na biblioteca padrão da linguagem, como mostramos a

seguir.

Teorema 10.3.6-8 (Não-transitividade da biblioteca padrão) No contexto do sis-

tema prático, as regras extra da biblioteca padrão dão origem a casos de não-transitividade.

210 OM – Uma linguagem de programação multiparadigma

Prova: Para provar o teorema, basta mostrar que existem três tipos τ, τ′, τ′′ tais que τ≤cτ′ e

τ′≤cτ′′ mas não τ≤cτ′′. Vamos assumir que o sistema prático inclui apenas as regras extra da

biblioteca padrão e vamos tirar partido da regra extra X→Bool≤cX→GenT X.

Tomemos então τ≡Float→Bool, τ′≡Int→Bool, τ′′≡Int→GenT Int. As asserções τ≤cτ′ e τ′≤cτ′′ são vá-

lidas como mostram as seguintes árvores de prova:

Int≤extraFloat

Γ Int≤cFloat

Γ Bool≤Bool

Γ Bool≤cBool

Γ Float→Bool≤cInt→Bool

X→Bool≤extraX→GenT X Γ Int:∗Γ Int→Bool≤cInt→GenT Int

No entanto a asserção τ≤cτ′′ não pode ser deduzida do sistema, sendo portanto inválida.

Imaginando uma prova de τ≤cτ′′ construída da conclusão para as premissas,

Γ Float→Bool≤cInt→GenT Int

verificamos que nenhuma das regras pode ser aplicada em último lugar: a regra [Coerção →] não

o permite pois é falso que Γ Bool≤cGenT Int; a regra [Coerção ≤] também não é aplicável pois é

falso que Γ Float→Bool≤Int→GenT Int; as regras [Coerção name], [Coerção …] e as regras extra não

são aplicáveis pois a estrutura das suas conclusões é incompatível com a estrutura da asserção

que se pretendia provar.

Capítulo 11

Linguagem OM

A linguagem abstracta L10 e o respectivo modelo semântico foram desenvolvidos em paralelo

ao longo dos capítulos precedentes. A linguagem OM, que vamos introduzir no capítulo cor-

rente, concretiza um sistema de programação prático baseado na linguagem abstracta L10.

A linguagem OM integra todas as componentes de L10, nomeadamente:

• Objectos mutáveis com parte privada (introduzidos no capítulo 7). Classes e tipos-ob-

jecto (introduzidos nos capítulos 5 e 8). Relação de subtipo (introduzida no capítulo 4).

Mecanismo de herança flexível (introduzido no capítulo 5). Polimorfismo de classe (in-

troduzido no capítulo 6 e na secção 8.3.1). Mecanismo dos modos (introduzido no capí-

tulo 9). Sistema de coerções extensível (introduzido no capítulo 10).

Além disso, a linguagem prática OM introduz diversos elementos suplementares:

• Amplia os mecanismos de especificação da faceta estática dos modos: concretamente,

introduz sobreposição de parte da sintaxe básica (cf. secção 11.3) e componentes glo-

balizadas (cf. secção 11.5);

• Preocupa-se com os seguintes pragmáticos: sintaxe (cf. secção 11.1), regras de nomea-

ção de classes e tipos (cf. secção 11.2), regras de resolução de nomes (cf. secção 11.6),

introdução dum nível privilegiado no qual a linguagem pode ser estendida ou alterada

(cf. secção 11.4);

• Introduz uma biblioteca de classes (cf. secção 11.7).

O presente capítulo é dedicado à apresentação da linguagem concreta OM, incluindo a des-

crição destes novos elementos. A leitura deste capítulo é indispensável para se compreender a

definição dos modos de biblioteca que se encontram no capítulo que se segue a este.

11.1 Sintaxe da linguagem OMA sintaxe da linguagem OM é baseada na sintaxe das linguagens C e C++. Não formalizamos

aqui essa sintaxe: apresentaremos apenas alguns exemplos e chamaremos a atenção para certos

pormenores importantes.

Mostramos primeiro como se traduzem para OM diversos tipos e termos que já nos eram

familiares na linguagem L10:

212 OM – Uma linguagem de programação multiparadigma

L10 OM Ref Bool --> Bool&Unit --> () ou void() --> não tem representação literalλx:Bool.x --> Bool fun(Bool x) return x ;

id ˆ = λX.λx:X.x --> [X] X id(X x) return x ; o.m … --> o.m(…)self.m … --> self.m(…) ou m(…)SELFC#m … --> SELFC#m(…) ou #m(…)C ˆ = class… --> class C …

C ˆ = class\s… --> class C : s …

C ˆ = λX≤*I.∀Y≤*J.class… --> class C[X<I,Y<J] …

M ˆ = mode X≤*I. … --> mode M[X<I] …

Referimos agora diversos aspectos sintácticos importantes, e qual o seu significado.

Na linguagem OM, o operador '=' está definido: a atribuição é expressa usando o operador

':=', e a igualdade usando o operador '=='.

No interior das classes as componentes privadas são declaradas usando a palavra reservada

“priv”. No caso das componentes públicas, o uso da declaração “pub” é opcional; recordamos

que as variáveis (de instância ou de classe) ditas públicas, são na realidade semipúblicas no

sentido por nós usado na secção 7.3.1.

Os nomes de tipo predefinidos SAMET e SELFT estão disponíveis no interior de qualquer

classe, na qual representam respectivamente o tipo público e o tipo privado gerados por essa

classe (ou por qualquer das suas subclasses, já que estes nomes são reinterpretados nas compo-

nentes herdadas, cf. capítulos 5 e 7).

A respeito de SELFT, recordamos que, dentro duma classe, os objectos dessa mesma classe

são criados como objectos de tipo SELFT, ou seja como objectos que não protegem a sua parte

privada. Assim facilita-se a inicialização desses objectos. Além disso não se prejudica a segu-

rança desses objectos pois quando um objecto do tipo SELFT abandona a classe onde foi

criado, o seu tipo muda automaticamente para SAMET (cf. capítulo 7).

Em OM, um programa é uma colecção de classes e modos que, obrigatoriamente, inclui

uma classe chamada Main contendo pelo menos um método de classe público com assinatura

#main:Unit→Unit. Todo o programa é activado usando a expressão Main#main().

11.2 Nomeação das classes e tipos-objectoNa linguagem OM, existem três categorias de entidades com a capacidade de gerar tipos ou

operadores de tipo. São elas:

• Classes não-paramétricas – geram tipos-objecto e têm a forma:class <nome_da_classe> : <superclasse> …

• Classes paramétricas – geram operadores de tipo e têm a forma:class <nome_da_classe>[X1<I1, …,Xn<In] : <superclasse> …

11 Linguagem OM 213

• Modos – geram operadores de modo (cf. secção 9.1.3) e têm a forma:mode <nome_do_modo>[X<I] …

Como se pode observar nos três esquemas sintácticos acima apresentados, a introdução de

uma classe ou de um modo obriga sempre à atribuição dum nome para essa entidade.

Nas subsecções seguintes, vamos apresentar e discutir as regras de nomeação das entidades

geradoras de tipo (classes e modos) e aos tipos por elas gerados. Essas regras também se

estendem às variáveis de tipo. A ideia base é a seguinte: um nome de tipo representa sempre

também um nome de classe e vice-versa, mesmo no caso em que o nome é uma variável de

tipo.

11.2.1 Regras de nomeaçãoNa linguagem OM, é a seguinte a regra base de nomeação de entidades geradoras de tipo e

dos respectivos tipos gerados:

- O nome duma classe ou modo é partilhado com o tipo ou operador que eles geram.

Só se podem introduzir novos tipos de forma indirecta, através da definição de novas

classes e novos modos. Por isso, a regra inversa da anterior também é verdadeira:

- Todo o nome de tipo ou de operador é o nome da classe ou modo que os geraram.

A linguagem OM suporta polimorfismo de classe, uma variante de polimorfismo paramétri-

co que introduzimos na secção 8.3.1. Trata-se duma forma de polimorfismo P ˆ = λX≤*I.e que

permite abstrair simultaneamente uma classe não-paramétrica e o respectivo tipo-objecto gera-

do: a variável de abstracção X representa um par <classe-não-paramétrica, tipo-objecto>. As-

sim, introduzimos uma nova regra, relativa a variáveis de tipo:

- Uma variável de tipo é também variável de classe e vice-versa.

Finalmente, introduzimos a seguinte convenção relativa à variável de tipo SAMET:

- A variável de tipo SAMET também representa a classe não-paramétrica SELFC.

Esta última regra destina-se a permitir que uma abstracção paramétrica P ˆ = λX≤*I.e possa ser

instanciada com a variável de tipo SAMET, já que, em OM, os argumentos de instanciação

duma abstracção paramétrica terão sempre de representar um par <classe não-paramétrica, um

tipo-objecto>.

11.2.2 Justificação das regras de nomeaçãoTodas estas regras de nomeação destinam-se a tornar mais prático o uso da linguagem. Elas

permitem eliminar dos programas todas as declarações de tipo (as quais já estão implícitas na

estrutura das classes e modos) e reduzir a metade o número de argumentos nas abstracções

paramétricas (devido à interpretação dual dos nomes de tipo na linguagem).

214 OM – Uma linguagem de programação multiparadigma

11.2.3 Aspectos práticosUsa-se o contexto para determinar qual a interpretação duma variável de tipo requerida em

cada caso.

• No contexto X#…, a expressão X representa uma classe não-paramétrica;

• No contexto C[X] e no contexto M X, a expressão X representa um par <tipo-objecto,

classe não-paramétrica>;

• Nos restantes contextos X representa um tipo ou um operador.

As nossas regras de nomeação não comprometem o princípio, que prosseguimos, de separar

os conceitos de classe e de tipo-objecto (cf. secções 4.3.1, 4.3.6, 7.4). Repare no seguinte con-

traste, onde C representa o nome duma classe c:

• Se c implementar um construtor público #pub_new, então a expressão C#pub_new() pro-

duz apenas objectos implementados pela classe c;

• No entanto, uma variável declarada com o tipo C pode conter qualquer objecto com a

estrutura pública prevista no tipo C, portanto objectos não necessariamente da classe c.

11.2.4 ExemploVejamos um pequeno exemplo de nomeação, baseado no modo de biblioteca log.

Em OM, a expressão “log Int” tem uma interpretação dual. Ela representa a classe dos ob-

jectos lógicos sobre o tipo Int, mas também representa o tipo desses mesmos objectos. (Lem-

bramos que, no capítulo 9 e 10, usámos a notação diferenciada “LogT τ” para representar o tipo

dos objectos lógicos (cf. secção 9.1.2.1); agora, em OM, adoptamos uma notação unificada).

Para perceber o que está em causa, vamos analisar a expressão de tipo genérica M C.

Seja m um modo e ϕm o operador de modo gerado por m. Suponhamos que o nome atribuí-

do ao modo foi M. Nestas condições o nome M fica com uma interpretação dual, representando

tanto o modo m como o operador de modo ϕm.

Seja c uma classe não-paramétrica e τc o tipo-objecto gerado por c. Suponhamos que o no-

me atribuído à classe foi C. Nestas condições o nome C fica com uma interpretação dual, repre-

sentando tanto a classe c como o tipo-objecto τc.

Com estes pressupostos, a expressão M C fica também com uma interpretação dual, repre-

sentando tanto a classe classe não-paramétrica (m <τc,c>), como o tipo-objecto (ϕm τc).

11.3 Sobreposição da sintaxe de OMNa linguagem OM, todas as construções estruturantes – i.e. classes, modos, abstracções para-

métricas, métodos e funções – têm semântica predefinida: exactamente a semântica das cons-

11 Linguagem OM 215

truções correspondentes da linguagem L10. Na linguagem OM, o novo comando return e as

expressões literais também têm semântica predefinida.

No entanto, as restantes expressões e comandos da linguagem OM não têm semântica pre-

definida.

Chamamos construções de semântica variável às construções sintácticas de OM que não

têm semântica predefinida. A atribuição de semântica a estas construções efectua-se nas clas-

ses primitivas e nos modos da linguagem, da forma que apresentamos a seguir.

11.3.1 Atribuição de semântica às construções desemântica variávelO mecanismo de atribuição de semântica às construções de semântica variável de OM é sim-

ples. As construções de semântica variável são simplesmente vistas como açúcar sintáctico

para a invocação de certos métodos de classe especiais, cujo nome se inicia pelo prefixo

“#$def_”.

Chamaremos a esse métodos, métodos “#$def_*”. Todos os métodos “#$def_*” são definidos

no nível privilegiado (cf. secção 11.4). Assim, uma classe normal não pode definir directa-

mente métodos “#$def_*”, mas repare que os pode herdar das classes privilegeadas $CoreObject

ou Object, por exemplo.

A tradução implícita das construções de semântica variável para métodos “#$def_*” precede

qualquer análise estática do programa, incluindo a aplicação de coerções e a resolução de no-

mes. As regras gerais de tradução são as seguintes:

( f:T->R )(exp) --> T#$def_apply(f, exp)( obj:T ).label --> T#$def_select(obj, label)T var = val ; --> T#$def_init(var, val)( var:T& ) := exp: ; --> T#$def_assign(var, exp)comm1 comm2 --> $Void#$def_seq(comm1, comm2)(exp:()->T) ; --> T#$def_comm(exp); --> $Void#$def_nop()( cond:T ) ? thenP : elseP --> T#$def_cond(cond, thenP, elseP)if( cond:T ) thenP else elseP --> T#$def_ifthenelse(cond, thenP, elseP)if( cond:T ) thenP --> T#$def_ifthenelse(cond, thenP, true)while( cond:()->T ) body --> T#$def_while(cond, body)do body while( cond:()->T ) ; --> T#$def_dowhile(body, cond)for( ini ; cond ; inc ) body --> ini; while(cond) body inc; switch( exp:T ) body --> T#$def_switch(exp, body)raise (exc:T) ; --> T#$def_raise(T#exc)try comm with( exc:T ) do --> T#$def_trywith(comm, T#exc, do)T var ; --> T#$def_init_implicit(var)

Convencionalmente, nas expressões com a forma “#$def_select(exp,n)” e “$raw_select(exp,n)” a

subexpressão exp nunca é sujeita a qualquer coerção: a linguagem ficaria muito confusa se tal

fosse permitido.

Note que a tradução de qualquer construção da linguagem OM gera uma expressão que

refere sempre uma classe concreta, na qual se assume que o método “#$def_*” referido está dis-

216 OM – Uma linguagem de programação multiparadigma

ponível. Como essa classe é determinada pelo tipo duma certa expressão que ocorre na cons-

trução original, isso significa que a linguagem OM suporta sobreposição da sua sintaxe básica

(overloading), sendo no caso trivial a regra de resolução dessa sobreposição.

O aproveitamento deste mecanismo de sobreposição efectua-se no nível privilegiado e ma-

terializa-se nas decisões que se tomam relativamente à definição de métodos “#$def_*” nas

classes primitivas e nos modos.

Existe uma pequena particularidade prevista no esquema de tradução: dentro de métodos

declarados com a palavra reservada especial raw (cf. secção 11.4) a tradução efectua-se direc-

tamente para primitivas “$raw_*” (cf. secção 11.3.2) em vez de métodos “#$def_*”.

Note ainda que a determinação e o tratamento das ocorrências de identificadores que refe-

renciam implicitamente o objecto self só pode ser efectuada durante a fase de resolução de

nomes do compilador, ficando adiada para esse momento. Cada ocorrência dum identificador n

que tenha estas características é tratada como uma abreviatura de self.n, sendo traduzida para

#$def_select(self,n) (cf. secção 11.6).

11.3.2 Matéria-prima semânticaA linguagem fornece o seguinte conjunto de primitivas paramétricas como matéria prima para

a definição dos métodos “#$def_*” descritos na secção anterior. O nome das primitivas paramé-

tricas inicia-se sempre pelo prefixo “$raw_” (primitivas “$raw_*”):

As primitivas paramétricas definem-se por tradução para L10. Eis a lista integral das primi-

tivas paramétricas suportadas pela linguagem OM:

[X,R] R $raw_apply(X->R f, X a) ˆ = ∀X.∀R.λf:X→R.a:X. (f a)[X,R] R $raw_select(X obj, X->R label) ˆ = ∀X.∀R.λobj:X.λlabel:X→R. (label obj)[X] X $raw_init(X& var, X val) ˆ = ∀X.λx:Ref X.λy:X. (x := y)[X] X $raw_assign(X& var, X val) ˆ = ∀X.λx:Ref X.λy:X. (x := y)[X,Y] () $raw_seq(()->X x, ()->Y y) ˆ = ∀X.∀Y.λx:Unit→X.λy:Unit→Y. (x ();y ();())[X] () $raw_comm(()->X x) ˆ = ∀X..λx:Unit→X. (x ();())() $raw_nop() ˆ = ()[X] X $raw_deref(X& var) ˆ = ∀X.λx:Ref X. (deref x)[X] X $raw_cond(Bool cond, ()->X thenP, ()->X elseP)

ˆ = ∀X.λb:Bool.λx:Unit→X.λy:Unit→X. (if b then x () else y ())[X,Y] () $raw_ifthenelse(Bool cond, ()->X thenP, ()->Y elseP)[X] () $raw_while(()->Bool cond, ()->X body)[X] () $raw_dowhile(()->X body, ()->Bool cond)[X] () $raw_switch(X exp, X->() body)[Z] () $raw_raise(Z exc)[X,Y,Z] () $raw_trywith(()->X comm, Z exc, ()->Y do)

As primitivas cuja codificação não apresentamos podem ser facilmente definidas usando

técnicas apresentadas em [Sto77, Sch86].

11 Linguagem OM 217

11.4 Nível privilegiado e recursos especiaisNo contexto dos modos e das classes primitivas, o nível de programação diz-se privilegiado.

No contexto das classes não-primitivas, o nível de programação diz-se normal.

No nível privilegiado estão disponíveis certos recursos especiais que permitem que a lin-

guagem seja estendida. Alguns desses recursos destinam-se, especificamente, a suportar a defi-

nição da faceta estática dos modos.

O primeiro recurso especial que referimos é bastante modesto: consiste na possibilidade de

se usarem identificadores começados pelo carácter “$”. Esses identificadores são usados na

nomeação de entidades públicas introduzidas no nível privilegiado, a que se pretenda vedar o

acesso a partir do nível normal. As classes primitivas $CoreObject e $DYNAMIC_TYPE são exem-

plos de tais entidades.

O segundo recurso especial é também muito simples: convencionalmente, no nível privile-

giado, a operação de atribuição pode ser aplicada uma variável de qualquer tipo, mesmo que o

tipo em causa não preveja a operação de atribuição (é o caso do tipo const Int, por exemplo).

Neste caso, a inexistente operação de atribuição é automaticamente substituída pela operação

de inicialização explicita, a qual está disponível em todos os tipos.

O terceiro recurso especial consiste na operação $UNCHECKED_RETYPING, uma operação

de mudança de tipo de expressões, não verificada. Pode ser aplicada a qualquer expressão para

mudar o seu tipo sem mudar o seu valor. O sistema de tipos não valida esta operação, sendo

esta a única operação insegura que a linguagem suporta.

Os restantes recursos especiais do nível privilegiado são acedidos por meio das palavras re-

servadas especiais: global, coercion, raw, macro, primitive. Vamos indicar quais as suas condi-

ções de aplicação e qual o efeito de cada uma delas:

• global – Pode ser aplicada a qualquer componente de classe pública. Faz com que essa

componente seja globalizada, ou seja, colocada no espaço de nomes global para ficar

acessível a partir de todas as classes. Uma componente globalizada continua a poder

ser usada como uma componente de classe normal. Mais detalhes sobre as componen-

tes globalizadas na secção 11.5.

• coercion – Pode ser aplicada a qualquer método de classe público com um argumento e

um resultado. Faz com que esse método, dito de coerção, seja assimilado pelo sistema

de coerções como uma regra de coerção extra (cf. secção 10.2.1). O método continua a

poder ser usado como um método de classe normal.

• raw – Pode ser aplicada a qualquer método de instância ou de classe. Faz com que, no

corpo desse método, a atribuição de significado à sintaxe concreta de OM (cf. secção

11.3) seja efectuada usando apenas primitivas “$raw_*”, e não os habituais métodos

“#$def_*”. Também faz com que todas as invocações desse método sejam efectuadas

218 OM – Uma linguagem de programação multiparadigma

usando a primitiva $raw_apply e não o habitual método #$def_apply. As declarações raw

servem para evitar que a semântica da linguagem entre em ciclo.

Na biblioteca são usadas das definições da maioria dos métodos “#$def_*” (e também

na definição nos métodos $dup, de que o modo value necessita). Ainda, os métodos

$access dos modos (cf. secção 9.1.3) são automaticamente considerados raw para efeitos

de invocação. Não existe a primitiva $raw_default_init(.) pelo que todas as variáveis locais

têm de ser explicitamente inicializadas num método raw.

• macro – Pode ser aplicada a qualquer método de instância ou de classe. Serve para indi-

car que esse método será herdado sob a forma textual, e recompilado em toda a sub-

classe que o herde. Dentro dum método sujeito à declaração macro podem ser usadas as

duas seguintes primitivas especiais:

MACROWellTyped(exp) – Em cada recompilação, expande-se em true ou false, consoante

a expressão exp esteja bem ou mal tipificada.

MACROForEveryInstVarPair[X](a, b)(ob1,ob2) com – Expande numa sequência de reescritas

do comando com, sendo efectuada uma reescrita por cada par de variáveis de

instância dos objectos ob1, ob2 da classe corrente. Funciona como uma espécie

de iterador, no qual o par de variáveis a e b percorre os sucessivos pares de va-

riáveis de instância de ob1 e ob2. X denota o tipo das variáveis de instância cor-

rentemente consideradas (representadas por a e b).

• primitive – (classe primitiva) Etiqueta todas as classes primitivas da biblioteca. As clas-

ses primitivas são suportadas pelo sistema de forma especial, que lhes insere funciona-

lidade primitiva e predefine para elas literais específicos. Dentro duma classe primitiva

o nível de programação é privilegiado. As classes primitivas são: $CoreObject,

$ZeroObject, $AsgnObject, Object, Nil, $DYNAMIC_TYPE, Equality, Int, Float, Bool, Char, Str,

Array[T], Fun[T,R], Void, Ref[V], Exception. Este é conjunto fixo e predefinido de classes.

• primitive – (método primitivo) Dentro das classes primitivas, etiqueta obrigatoriamente

todos os métodos primitivos que aí sejam introduzidos. Um método primitivo escreve-

-se sem corpo, pois é o sistema que se encarrega de instalar o código destes métodos.

Este é conjunto fixo e predefinido de métodos.

As três palavras reservadas global, coercion e raw podem ser usadas de forma conjugada, em

todas as combinações possíveis. As palavras reservadas macro e primitive são sempre usadas

isoladamente.

11.5 Componentes globalizadasNo nível privilegiado é possível globalizar qualquer componente de classe pública, para que

esta fique acessível no espaço de nomes global. Globaliza-se uma componente usando a

palavra reservada especial global (cf. secção 11.4).

11 Linguagem OM 219

Se uma componente de classe pública com nome genérico “#g” for globalizada, então ela é

adicionada ao espaço de nomes global com o nome “g”. Não se permitem nomes repetidos no

espaço global. Assim, duas classes distintas não podem globalizar duas componentes que

tenham a mesma denominação.

Uma componente de classe globalizada ganha autonomia face à classe, ou ao modo, onde

foi introduzida. Por isso, toda a parametrização pública implícita na classe, ou modo, tem de

ser copiada para a nova entidade global.

Geralmente, tem interesse globalizar apenas métodos de classe. No entanto, também se per-

mite a globalização de variáveis de classe públicas. A classe gen inclui dois raros exemplos de

variáveis de classe globalizadas: as variáveis fail e repeat, ambas inicializadas com geradores

constantes particulares.

11.5.1 UtilidadeNo nível privilegiado, a adição de novas operações primitivas à linguagem pode ser efectuada

por meio de duas técnicas distintas: (1) através da definição de métodos de instância públicos;

(2) através da definição de métodos de classe públicos globalizados.

A primeira técnica é em geral preferível pois evita enriquecer o espaço de nomes global

com demasiados nomes. A biblioteca da linguagem OM segue esta recomendação, dentro do

possível. Eis alguns exemplos de primitivas definidas usando métodos de instância públicos:

identity(), copy(.), clone(), isNil(), '<=', '+', etc.

Mas os métodos de instância têm três particularidades que em alguns casos são inconve-

nientes, ou até obstrutivas, relativamente à introdução de certas primitivas: (1) eles têm de ser

invocados usando a notação de envio de mensagem, o.m(…) (questão cosmética apenas); (2) o

receptor da mensagem não pode ser uma referência per se (pois essa referência seria automati-

camente desreferenciada); (3) para efeito da aplicação de coerções, existe assimetria no trata-

mento do receptor face aos argumentos.

Os métodos de classe globalizados não sofrem destes inconvenientes, e foi só por esse mo-

tivo que foram introduzidos na linguagem. Para exemplificar:

• A classe $CoreObject globaliza as primitivas checkType[.](.) e downcast[.](.), neste caso ape-

nas por razões cosméticas: pretende-se apenas que a notação introduzida em L4 para

estas primitivas se mantenha;

• A classe $AsgnObject é obrigada a globalizar a primitiva swap(.,.), pois todos os parâme-

tros desta são referências; cosmeticamente, é também mais elegante escrever swap(a,b)

do que escrever a.swap(b);

• O modo gen introduz uma primitiva de disjunção, '|', a qual é globalizada para que haja

simetria no tratamento dos argumentos: por exemplo, dadas duas expressões a:gen Int e

b:Int, então são válidas as expressões a|b e b|a.

220 OM – Uma linguagem de programação multiparadigma

11.6 Resolução de nomesA linguagem OM prevê diversos espaços de resolução de nomes, também chamados de níveis

sintácticos:

• um espaço de nomes global;

• um espaço de nomes de instância associado a cada tipo-objecto;

• um espaço de nomes de classe associado a cada classe;

• um espaço de nomes locais associado a cada função (é possível aninhamento entre es-

paços de nomes locais pois existe são suportadas funções locais).

Em cada espaço de nomes individual não podem ocorrer nomes repetidos, o que significa

que a linguagem OM não suporta entidades com nomes sobrepostos; ao contrário do C++, por

exemplo, que suporta overloading de métodos.

Num programa, a resolução de nomes só ocorre depois desse programa ter sido sujeito à

tradução descrita na secção 11.3.

A forma como a resolução dum nome n se articula com a exploração dos vários espaços de

nomes é definida pelas seguintes regras:

• Se n ocorrer numa expressão da forma “C#n” ou “#n”, então n é um nome de classe. No

primeiro caso, a resolução de n efectua-se no espaço de nomes de classe da classe C; no

segundo caso, efectua-se no espaço de nomes de classe da classe SELFC.

• Se n ocorrer numa expressão da forma “#$def_select(exp,n)”, então trata-se dum nome de

instância. Neste caso, a resolução de n efectua-se no espaço de nomes de instância

associado ao tipo da expressão exp (note que neste contexto a expressão exp nunca será

sujeita a qualquer coerção, de acordo com o que afirmamos secção 11.3).

• Nos casos restantes, começa-se por tentar resolver o nome n localmente: primeiro no

espaço de nomes locais corrente, e depois, sucessivamente, nos vários espaços de no-

mes locais envolventes (a linguagem usa escopo estático). Se esta resolução local falhar

então tenta-se resolver o nome n no espaço de nomes de instância do tipo SELFT. Se

esta resolução suceder então n é reescrito como #$def_select(self,n). Finalmente, se

também esta última diligência falhar, é tentado o espaço de nomes global.

São muitas as entidades paramétricas definidas nas classes e nos modos da biblioteca

padrão. O programador também tem a liberdade de introduzir entidades paramétricas nos seus

programas. Devido à abundância de entidades paramétricas, seria desejável que o mecanismo

de resolução de nomes incluisse um módulo de inferência de argumentos de instanciação que

libertasse o programador da necessidade de os explicitar. Esse módulo deveria ser articulado

com o sistema de coerções da linguagem, para que um maior número de alternativas de resolu-

ção de nomes pudessem ser considerado: por exemplo, no caso do gerador 1|2 discutido na sec-

ção 10.1.3, a descoberta da instanciação para o parâmetro X de gen X envolve necessariamente

a utilização do sistema de coerções.

11 Linguagem OM 221

Limitamo-nos a identificar esta questão da inferência de argumentos de instanciação, pois

não a trataremos nesta dissertação.

11.7 Biblioteca de classes mínimaApresentamos aqui uma proposta de biblioteca de classes mínima para a linguagem OM.

A biblioteca mínima consiste numa hierarquia de classes, a maioria das quais se dizem

classes primitivas por serem suportadas de forma especial pela infra-estrutura básica da lin-

guagem. A biblioteca mínima não contém qualquer modo predefinido. Assim, as classes da

biblioteca mínima não assumem a existência de qualquer modo.

Na raiz da hierarquia da biblioteca mínima encontra-se a classe primitiva $CoreObject, uma

classe que suporta a funcionalidade básica dos objectos e variáveis, com e sem modo. Um ob-

jecto básico possui um método de identidade que permite determinar se dois objectos são o

mesmo. As variáveis do tipo $CoreObject admitem inicialização explícita, mas não inicialização

implícita e atribuição.

A classe Object merece também especial referência pois suporta a funcionalidade genérica

dos objectos e variáveis sem modo. Qualquer objecto da classe Object possui um método de

identidade, identity, e um método que permite verificar se dois objectos são da mesma classe,

sameClasse. As variáveis do tipo Object admitem inicialização implícita, inicialização explícita e

atribuição. Obrigatoriamente, todas as classes não primitivas herdam, directa ou indirecta-

mente, da classe Object.

Apresentamos seguidamente a biblioteca mínima da linguagem OM. Algumas das classes

são apenas esboçadas pois não se justifica gastar muito espaço com certas classes menos im-

portantes. A própria biblioteca mínima inclui alguma da sua documentação mais essencial.

alias defaultInterf = #startup :()->(),coreIdentity:$CoreObject->Bool,identity:$CoreObject->Bool,IsNil :()->Bool,#$def_init :(SAMET&, SAMET)->SAMET,[R] #$def_select :(SAMET, SAMET->R)->R,[R] #$def_apply :(SAMET->R, SAMET)->R,#$def_switch :(SAMET, SAMET->())->(),#$def_comm :(()->SAMET)->() ;

alias zeroInterf = defaultInterf + #zero :()->SAMET, #$def_init_implicit :SAMET&->SAMET ;

alias asgnInterf = zeroInterf + #$swap :(SAMET&,SAMET&)->(),#$def_assign :(SAMET&,SAMET)->SAMET ;

alias eqInterf = defaultInterf + '==' :(SAMET)->Bool ;alias noInterf = defaultInterf ;alias objectInterf = defaultInterf + asgnInterf +

sameClass :$CoreObject->Bool, copy :Object->SAMET, clone:()->SAMET ;

// A classe $CoreObject suporta a funcionalidade básica// de todos os objectos, com ou sem modo.// Todas as subclasses da classe $CoreObject geram subtipos do tipo $CoreObject.// Esta classe suporta inicialização explicita.// Esta classe não suporta um zero.// Esta classe não suporta inicialização implícita.

222 OM – Uma linguagem de programação multiparadigma

// Esta classe não suporta atribuição.primitive class $CoreObject

// Regras especiais// No modo privilegiado, a atribuição funciona sempre. Se for aplicada// a uma variável dum tipo T que não suporte atribuição usa-se nesse// caso a inicialização explicita.

// Variáveis$DYNAMIC_TYPE #$mytype = $BUILD_MY_TYPE() ;

// Literais, constantes e zero// não tem zero

// Construtores e inicializaçõespriv primitive SELFT #new();() #startup()

// Serviços baseprimitive Bool coreIdentity($CoreObject arg)

return $SAME_ADRESS(self, arg) ;Bool identity($CoreObject arg);Bool isNil()

return false ;

// Métodos auxiliarespriv [T] Bool checkMyType()

return #$mytype.subtype(T#$mytype) ;

// Componentes globaisglobal [T] Bool #checkType(SAMET arg)

return arg.checkMyType[T]() ;global [T] T #downcast(SAMET arg)

if( checkType[T](arg) )return $UNCHECKED_RETYPING[T](arg) ;

else raise xConversion ;

// Métodos #$def-*raw SAMET #$def_init(SAMET& var, SAMET val)

return $raw_init(var, val) ;raw [R] R #$def_select(SAMET obj, SAMET->R label)

if( obj.isNil() ) raise xNil ;return $raw_select(obj, label) ;

raw [R] R #$def_apply(SAMET->R f, SAMET exp)

return $raw_apply(f, exp) ;raw () #$def_switch(SAMET exp, SAMET->() body)

return $raw_switch(exp, body) ;raw () #$def_comm(()->SAMET exp)

return $raw_comm(exp) ;

// Relativamente a $CoreObject introduz zero abstracto, inicialização implícita.primitive class $ZeroObject : $CoreObject

// Literais, constantes e zeroSAMET #zero()

raise xAbstract ;

11 Linguagem OM 223

// Métodos #$def-*raw SAMET #$def_init_implicit(SAMET& var)

return $def_init(var, SAMET#zero()) ;

// Relativamente a $ZeroObject introduz a atribuição.primitive class $AsgnObject : $ZeroObject

// Componentes globaisglobal () #swap(SAMET& var1, SAMET& var2)

SAMET aux = var1 ;var1 := var2 ;var2 := aux ;

// Métodos #$def-*raw SAMET #$def_assign(SAMET& var, SAMET val)

return $raw_assign(var, val) ;

// A classe Object suporta a funcionalidade básica dos objectos sem modo.// Directa ou inderectamente, dela herdam todas as classes não-primitivas.// Todas as subclasses da classe Object geram subtipos do tipo Object.// As subclasses directas de Object não requerem declaração explícita dasuperclasse.primitive class Object : $AsgnObject

// VariáveisNil $object_with_no_mode ;

// Literais, constantes e zeroSAMET #zero()

return nil ;

// Serviços baseprimitive Bool sameClass($CoreObject);macro SAMET copy(Object arg) // cópia superficial (shallow copy) // Os objectos têm de ser da mesma classe! // Não basta que sejam do mesmo tipo.

if( self.sameClass(arg) ) SELFT g = $UNCHECKED_RETYPING[SELFT](arg) ;MACROForEveryInstVarPair[X](a,b)(self, g)

if( MACROWellTyped(a := b) ) a := b ;else #$def_init(a,b)

return self ;

else raise xClass ;

SAMET clone() // produz duplicado superficial (shallow clone)

return #new().copy(self) ;

primitive class Nil : Object

// Literais, constantes e zero// literais: nil

// Serviços baseNil clone() return nil ; Nil copy(Object arg)

if(arg.isNil()) return nil;else raise xType;

// Métodos específicos

224 OM – Uma linguagem de programação multiparadigma

Bool isNil() return true ;

primitive class $DYNAMIC_TYPE : Object

// Variáveis// representação dos tipos-objecto de OM

// Métodos auxiliares…

// Métodos específicosBool subtype($DYNAMIC_TYPE arg) …

primitive class Equality : Object

// Métodos específicos // Esta igualdade por defeito requer que os objs sejam da mesma classe. // Se esta restrição não interessar à subclasse, ela deve redefinir '=='.macro Bool '=='(SAMET arg) // igualdade por defeito: compara todas as vars.

if( self.sameClass(arg) ) SELFT g = $UNCHECKED_RETYPING[SELFT](arg) ;MACROForEveryInstVarPair[X](a,b)(self, g)

if( MACROWellTyped(a == b) ) if( !(a == b) ) return false ;

else if( !(a.identity(b)) ) return false ;

return true ;

else return false ;

Bool '!='(SAMET arg)

return !(self == arg) ;

class Order : Equality

// Métodos específicosBool '<'(SAMET arg) raise xAbstract ; Bool '<='(SAMET arg) return !(self > arg) ; Bool '>'(SAMET arg) return arg < self ; Bool '>='(SAMET arg) return !(self < arg) ; SAMET max(SAMET arg) return self > arg ? self : arg ; SAMET min(SAMET arg) return self > arg ? arg : self ;

primitive class Int : Equality

// Literais, constantes e zero// literais: 0, 2, -100, 555, …Int #zero() return 0 ; Int #maxInt = … ;Int #minInt = … ;

// Métodos específicosprimitive Int '+'(Int n) ;primitive Int '-'(Int n) ;primitive Int '-'() ;primitive Int '*'(Int n) ;primitive Int '/'(Int n) ;primitive Int '<<'(Int n) ;primitive Int '>>'(Int n) ;…

primitive class Float : Equality

// Literais, constantes e zero// literais: 0.0, 1.3, -2.6e4, …

11 Linguagem OM 225

Float #zero() return 0.0 ; Float #fPi = 3.14159265358979323846 ;Float #fE = 2.71828182845904523536 ;

// Métodos específicos

// aceitam também Int porque Int≤cFloatprimitive Float '-'() ;primitive Float log() ;primitive Float cos() ;primitive Float #Float_from_Int_conversion(Int i) ;…

// Componentes globais

// aceitam também Int porque Int≤cFloatglobal primitive Float #'+'(Float a, Float b) ;global primitive Float #'-'(Float a, Float b) ;global primitive Float #'*'(Float a, Float b) ;global primitive Float #'/'(Float a, Float b) ;global primitive Float #power(Float a, Float b) ;

// Coerçõescoercion Float #Float_from_Int(Int arg)

return #Float_from_Int_conversion(arg) ;

primitive class Bool : Equality

// Literais, constantes e zero// literais: false, trueBool #zero() return false ;

// Métodos específicosprimitive Bool '&&'(Bool ()->b) ;primitive Bool '||'(Bool ()->b) ;primitive Bool '!'() ;…

// Métodos #$def-*raw [X,Y] () #$def_ifthenelse(Bool cond, ()->X thenP, ()->Y elseP)

return $raw_ifthenelse(cond, thenP, elseP) ;raw [X] () #$def_while(()->Bool cond, ()->X body)

return $raw_while(cond, body) ;raw [X] () #$def_dowhile(()->X body, ()->Bool cond)

return $raw_dowhile(body, cond) ;raw [X] X #$def_cond(Bool cond, ()->X thenP, ()->X elseP)

return $raw_cond(cond, thenP, elseP) ;

primitive class Char : Equality

// Literais, constantes e zero// literais: '\0', '\n', '\'', '\"', 'a', 'b', 'c', …Char #zero() return '\0' ;

// Métodos específicosprimitive Char succ() ;primitive Char pred() ;primitive Char toUpper() ;primitive Int code() ;…

primitive class Str : Equality

// Literais, constantes e zero// literais: "", "ola", "om", "sol", …

226 OM – Uma linguagem de programação multiparadigma

Str #zero() return "" ;

// Métodos específicosprimitive Str '+'(Str s) ; // concatenaçãoprimitive Str slice(Int i, Int j) ;primitive Str includes(Char c) ;primitive Char& '[]'(Int i) ;…

alias arrayInterf = asgnInterf + zeroInterf ;primitive class $Array[T < arrayInterf] : Equality

// Notação especial// T[] = $Array[T]

// Literais, constantes e zero// literais (usa-se a notação do C++): , 1,5,7, …T[] #zero() return ;

// Métodos específicosprimitive T& '[]'(Int i);[R] R[] map(T->R f) … …

primitive class $Fun[T,R] : Object

// Notação especial// (T1,…,Tn)->R = $Fun[T1,$Fun[…,$Fun[Tn,R]…]]

// (T1,…,Tn)->() = $Fun[T1,$Fun[…,$Fun[Tn,$Void]…]]

// T->R = $Fun[T,R]// T->() = $Fun[T,$Void]// ()->R = $Fun[$Void,R]// ()->() = $Fun[$Void,$Void]

// Literais, constantes e zero// literais: (R)fun(T arg) return arg ; ()fun() return ; T->R #zero() return (R)fun(T arg) return arg ; ;

// Métodos específicos[S] (T->S) compose(R->S f) … …

primitive class $Void : $CoreObject

// Notação especial// () = $Void

// Regras especiais// Uma função f de tipo ()->X é invocada usando a notação especial f().// Dentro de fun do tipo X->() existe a forma expecial exclusiva “return ;”// ()=$Void não pode ser usado para instanciar entidades paramétricas

// Literais, constantes e zero// literais: não tem -> repare: o termo () não é suportado em OM!// não há zero

// Métodos #$def-*raw () #$def_nop()

return $raw_nop() ;raw [X,Y] () #$def_seq(()->X c1, ()->Y c2)

return $raw_seq(c1, c2) ;

primitive class $Ref[V] : $CoreObject

11 Linguagem OM 227

// Notação especial// V& = $Ref[V]

// Literais, constantes e zero// literais: todos os identificadores de variável e parâmetros de função// não há zero

// Coerçõescoercion V #Value_from_Ref(V& var)

return $raw_deref(var) ;

// A classe Exception é primitiva apenas para não ser subclasse de Object.// O modo gen suporta geradores de referências: gen X&primitive class Exception : $CoreObject

// Variáveispriv Str msg ;

// Construtores e inicializaçõespriv SELFT #new(Str str)

SELFT s = #new() ;s.msg := str ;return s ;

// Literais, constantes e zero// não tem literais// não há zeroException #xAbort = #new("abort") ;Exception #xNil = #new("nil accessed") ;Exception #xDivByZero = #new("division by zero") ;Exception #xAbstract = #new("abstract method called") ;Exception #xType = #new("bad type") ;Exception #xClass = #new("bad class") ;Exception #xDowncast = #new("bad downcast") ;Exception #xNotConneted = #new("connected object not available") ;…

// Métodos #$def-*raw () #$def_raise(Exception exc)

return $raw_raise(exc) ;raw [X,Y] () #$def_trywith(()->X comm, Exception exc, ()->Y do)

return $raw_trywith(comm, exc, do) ;

Capítulo 12

Modos da biblioteca padrão

Neste capítulo apresentamos os cinco modos predefinidos que decidimos incluir na biblioteca

padrão da linguagem OM. Genericamente, eles são ortogonais entre si, exceptuando o modo

value que obriga todos os outros modos, presentes e futuros, a tomarem-no em consideração.

O modo const suporta a introdução de identificadores que denotam valores constantes.

O modo value introduz semântica de não-partilha na linguagem.

O modo lazy suporta avaliação preguiçosa, uma técnica que permite adiar a avaliação duma

expressão até ao momento em que o valor dessa expressão é realmente necessário.

O modo log introduz variáveis lógicas e unificação sintáctica e semântica.

O modo gen introduz retrocesso (backtraking) na linguagem, pela via da noção de gerador.

12.1 Componentes adicionadas a $CoreObjectAs definições dos modos value e gen requerem a introdução de diversas componentes novas na

classe $CoreObject, assim como a modificação de dois dos métodos “#$def_*”. Apresentamos se-

guidamente essas modificações, as quais só poderão ser compreendidas quando estudadas em

ligação com os modos que as requerem.

primitive class $CoreObject // Componentes adicionadas

// VariáveisBool #$expanded ; // para valuepriv $OBJ #$trailTop = nil ; // para genpriv $OBJ $trailPrev ; // para gen

// Construtores e inicializações() #startup()

#$expanded := false ;

// Serviços para outros modosraw SAMET $dup() // para value

return self ;$CoreObject #getTrailLevel() // para gen

return #$trailTop ;() #restoreTrail($CoreObject l) // para gen

230 OM – Uma linguagem de programação multiparadigma

while( ! #$trailTop.orig_ident(l) ) #$trailTop.onBack() ;#$trailTop := #$trailTop.$trailPrev ; ;

() trail() // para gen

$trailPrev := #$trailTop ;#$trailTop := self ;

() onBack() // para gen

raise xAbstract ;

// Métodos #$def-*raw SAMET #$def_init(SAMET& var, SAMET val) // para value

SAMET arg := #$expanded ? val.$dup() : val ;return $raw_init(var, arg) ;

raw R #$def_apply(T->R f, T exp) // para value

T arg := T#$expanded ? exp.$dup() : exp ;R res := $raw_apply(f, arg) ;return R#$expanded ? res.$dup() : res ;

12.2 Modo constO modo const permite que valores constantes possam ser denotados por identificadores.

O modo const influência as propriedades duma única categoria de entidades tipificadas: as

variáveis. Relativamente às restantes categorias de entidades tipificadas, o modo const é neu-

tral.

As propriedades essenciais das variáveis com modo const são duas: (1) essas variáveis têm

de ser inicializadas no ponto de declaração (quer sejam variáveis locais, de instância ou de

classe); (2) elas não podem ser alvo do operador de atribuição.

Prova-se facilmente que o modo const verifica a propriedade: τ≤σ ⇒ const τ≤const σ.

A realização da parte dinâmica do modo const é trivial, quase idêntica à realização do modo

neutral apresentado no final da secção 9.1.4. A realização da parte estática deste modo é igual-

mente trivial, já que é simplesmente determinada pela não definição deliberada dos dois se-

guintes métodos: #$def_init_defaut e #$def_assign.

alias constInterf = defaultInterf ;mode const[T < constInterf] :$CoreObject

// VariáveisNil $object_with_mode_const ;priv T obj ;

// Literais, constantes e zero// não tem zero

// Construtores e inicializações() #startup() // para value

#$expanded := T#$expanded ;

12 Modos da biblioteca padrão 231

priv SELFT #new(T arg) SELFT s = #new() ;s.obj := arg ;return s ;

// Serviços baseBool identity($CoreObject arg)

return checkType[SAMET](arg) && obj.identity(arg.$access()) ;T $access()

return obj ;

// Serviços para outros modosraw SAMET $dup() // para value

SELFT s = #new() ;s.obj := obj.$dup() ;return s ;

// Métodos auxiliares//

// Métodos específicos//

// Componentes globais//

// Coerçõescoercion const T #const_up(T arg) // T ≤extra const T

return #new(arg) ;coercion T #const_down(const T arg) // const T ≤extra T

return arg.$access() ;

// Métodos #$def-*//

12.3 Modo valueO modelo de manipulação de dados básico da linguagem OM é um modelo que se diz ser de

partilha, pois nele os objectos básicos possuem uma identidade podendo um objecto ser parti-

lhado por duas variáveis distintas.

O modo value introduz um modelo de manipulação de dados baseado em valores, i.e. em

elementos de dados sem identidade e não-partilháveis. Neste modelo, sempre que um valor é

usado ele é implicitamente duplicado: por exemplo a atribuição x:=y liga x a uma cópia do

valor da variável y; também a aplicação f(y) obriga a duplicar o valor da variável y no momento

da passagem do parâmetro. A única situação em que um valor é usado sem ser duplicado

ocorre no caso dum objecto que é alvo da aplicação dum método: por exemplo, a expressão

y.m() não duplica o objecto a que a variável y está ligada.

Um modelo baseado em valores é útil para tratar situações em que um ou mais objectos não

devam ser partilhados: por exemplo, na representação dum automóvel, cada uma das suas

232 OM – Uma linguagem de programação multiparadigma

quatro rodas constitui um subobjecto que não deve ser partilhado com qualquer outro auto-

móvel.

As variáveis com modo value suportam inicialização implícita e atribuição. Como a inicia-

lização implícita é suportada, o modo value só pode ser instanciado com classes que definam o

método de classe #zero(). Quando aplicado a objectos com modo value, o método de identity

retorna sempre false.

O modo value verifica a propriedade: τ≤σ ⇒ value τ≤value σ.

Do ponto de vista da realização este é um modo de biblioteca bastante complexo e intrusi-

vo. Obriga a redefinir os métodos #$def_init e #$def_apply ao nível da classe $CoreObject, requer a

introdução da variável de classe #$expanded também na classe $CoreObject, e, finalmente, exige a

colaboração de todos os outros modos, os quais têm a responsabilidade de inicializar a variável

#$expanded e de definir um método de duplicação propagada chamado $dup.

Num modo composto que tenha value por componente, a influência de value fica localizada

no modo atómico que imediatamente lhe sucede. Concretamente, a propriedade da não-parti-

lha fica associada aos objectos conexos (cf. secção 9.1.4) do modo atómico que sucede a value.

Para exemplificar, consideremos o modo composto value log. Este modo composto suporta a

operação de atribuição. Se x e y forem variáveis com modo value log, o efeito da atribuição x:=y

é o seguinte:

• Se y for uma variável livre então x torna-se numa variável livre independente (não-par-

tilhada);

• Se y tem valor, então x torna-se numa variável independente (sem partilha), instanciada

com uma cópia do valor de x;

• Nestes dois casos, o valor de x é alterado e não será reposto quando ocorrer retrocesso.

Para terminar, vejamos como é que os modos const, lazy e log lidam com a existência de dois

modelos de manipulação de dados. Os modos const e lazy respeitam o modelo de manipulação

associado ao tipo usado na sua instanciação, qualquer que seja o modelo usado: assim, por

exemplo, os modos compostos const value, lazy value ou const lazy value regem-se pelo modelo

baseado em valores. Quanto ao modo log, este força sempre a utilização do modelo básico de

partilha: por exemplo, tomando duas variáveis lógicas não instanciadas mas mutuamente liga-

das, basta instanciar uma delas para que a outra fique imediatamente ligada ao mesmo valor.

alias valueInterf = zeroInterf ;mode value[T < valueInterf] : $AsgnObject

// VariáveisNil $object_with_mode_value ;priv T obj ;

// Literais, constantes e zeroSAMET #zero()

return #new(T#zero()) ;

12 Modos da biblioteca padrão 233

// Construtores e inicializações() #startup() // para value

#$expanded := true ;priv SELFT #new(T arg)

SELFT s = #new() ;s.obj := arg ;return s ;

// Serviços baseBool identity($CoreObject arg)

return false ;T $access()

return obj ;

// Serviços para outros modosraw SAMET $dup() // para value

SELFT s = #new() ;s.obj := obj.$dup() ;return s ;

// Métodos auxiliares//

// Métodos específicos//

// Componentes globais//

// Coerçõescoercion value T #value_up(T arg) // T ≤extra value T

return #new(arg) ;coercion T #value_down(value T arg) // value T ≤extra T

return arg.$access() ;

// Métodos #$def-*raw value T #$def_assign((value T)& var, value T val)

return $raw_assign(var, val.$dup()) ;

12.4 Modo lazyO modo lazy suporta avaliação preguiçosa, uma técnica que permite adiar a avaliação duma

expressão até ao momento em que o valor dessa expressão é realmente necessário. O modo

lazy é útil para definir estruturas de dados infinitas. O modo lazy também permite declarar parâ-

metros de funções que devam ser avaliados de forma preguiçosa, o que permite obter uma

variante de passagem de parâmetros call-by-name.

As variáveis com modo lazy são obrigatoriamente inicializadas no ponto de declaração e

não podem ser alvo de atribuição. Em retrocesso, o estado de avaliação adiada dum objecto

lazy é reposto.

O modo lazy verifica a propriedade: τ≤σ ⇒ lazy τ≤lazy σ.

234 OM – Uma linguagem de programação multiparadigma

Usando o modo lazy, eis uma forma elegante de definir o gerador infinito que produz a se-

quência de todos os números naturais:

lazy gen Int nats = 0 | (nats + 1)

Eis uma forma alternativa, de definir o mesmo gerador sem usar o modo lazy:

Int i ;

gen Int nats = (i:= 0) | repeat & (i:= i+1)

alias lazyInterf = defaultInterf ;mode lazy[T < lazyInterf] :$CoreObject

// VariáveisNil $object_with_mode_lazy ;priv ()->T f ;priv Bool hasValue ;priv T obj ;

// Literais, constantes e zero// não tem zero

// Construtores e inicializações() #startup() // para value

#$expanded := T#$expanded ;priv SELFT #new(()->T arg)

SELFT s = #new() ;s.f := arg ;s.hasValue := false ;return s ;

priv SELFT init(T arg)

obj := arg ;hasValue := true ;trail() ;return self ;

priv SELFT initz()

hasValue := false ;return self ;

// Serviços baseBool identity($CoreObject arg)

return coreIdentity(arg) ;T $access()

if( !hasValue ) init(f()) ;return obj ;

// Serviços para outros modosraw SAMET $dup() // para value

SELFT s = #new() ;s.f := f ;s.hasValue := hasValue ;if( hasValue ) s.obj := obj.$dup() ;return s ;

() onBack() // para gen

initz() ;

// Métodos auxiliares

12 Modos da biblioteca padrão 235

//

// Métodos específicos//

// Componentes globais//

// Coerçõescoercion lazy T #lazy_up(()->T arg) // ()->T ≤extra lazy T

return #new(arg) ;coercion T #lazy_down(lazy T arg) // lazy T ≤extra T

return arg.$access() ;

// Métodos #$def-*//

12.5 Modo logO modo log já foi alvo de apresentação preliminar na secção 9.1.2. Este modo introduz as

noções de variável lógica e unificação, e as operações específicas isVar e '=='. O modo log não

suporta qualquer mecanismo especial de avaliação baseado em retrocesso: é o modo gen o

modo responsável por esse mecanismo.

As variáveis com modo log não suportam atribuição, mas suportam inicialização implícita:

as variáveis lógicas são implicitamente inicializadas como variáveis livres. O modo value só

admite ser instanciado com classes que suportem uma igualdade do tipo '==':SAMET->Bool.

O modo log não verifica a propriedade: τ≤σ ⇒ log τ≤log σ. A razão é simples: a definição

deste modo inclui uma ocorrência negativa de SAMET na assinatura do método '=='.

A igualdade '==' definida nos objectos lógicos implementa uma operação de unificação

[Hog94]. Só a parte superficial desta operação é especificada dentro do modo log: trata-se da

parte correspondente aos pares variável-variável, variável-objecto e objecto-variável. O par

restante, objecto-objecto, é tratado no método de igualdade que está definido nos objectos

conexos do modo log.

Note que a definição por defeito de '==' na classe Equality, produz o tradicional algoritmo de

unificação sintáctica, já que, como se pode observar na biblioteca de classes, quando está em

causa um par objecto-objecto, este método compara recursivamente as componentes respec-

tivas dos dois objectos envolvidos. Contudo, redefinindo '==' nas classes desejadas, é possível

obter um algoritmo de unificação semântica específico dessas classes.

Este nosso algoritmo de unificação omite a verificação occur-check [Hog94]. Assim, ex-

pressões como I==#f(I), onde I é uma variável lógica e #f um construtor, podem dar origem a

estruturas cíclicas, relativamente às quais o programador se deve acautelar.

Em OM, é possível simular com eficácia a funcionalidade dos termos da linguagem Prolog.

Um functor Prolog representa-se usando uma classe paramétrica C com as seguintes caracterís-

236 OM – Uma linguagem de programação multiparadigma

ticas: C é uma subclasse de Equality; C inclui um construtor público; se n for a aridade do func-

tor, n é o número de variáveis de instância públicas com modo log a incluir a classe C. Por

exemplo, o functor f/3 pode ser simulado usando a classe:

class f3[T1<logInterf, T2<logInterf, T3<logInterf] : Equality

log T1 x1 ; log T2 x2 ; log T3 x3 ;

#create(log T1 a1, log T2 a2, log T3 a3)

x1 == a1 ; x2 == a2 ; x3 == a3 ;

Eis a tradução para OM do termo-prolog f(1, 2.5, "ola") (assumindo que se realizou inferência

dos argumentos de instanciação T1, T2 e T3):

f3#create(1, 2.5, "ola")

alias logInterf = eqInterf ;mode log T[T < logInterf] :$ZeroObject

// VariáveisNil $object_with_mode_log ;T obj ;Bool hasValue ;SAMET next ;

// Literais, constantes e zeroSAMET #zero()

return #new().initz() ;

// Construtores e inicializações() #startup() // para value

#$expanded := false ;priv SELFT #new(T arg)

SELFT s = #new() ;s.init(arg) ;return s ;

priv SELFT init(T arg)

obj := arg ;hasValue := true ;next := nil ;trail() ;return self ;

priv SELFT initz()

hasValue := false ;next := nil ;return self ;

priv SELFT initv(SAMET arg)

if( !coreIdentity(arg) ) // se não forem já iguais então …next := arg ;trail() ;

return self ;

// Serviços baseBool identity($CoreObject arg)

if( checkType[SAMET](arg) )

12 Modos da biblioteca padrão 237

SAMET s = drf(), a = arg.drf() ;if( s.hasValue && a.hasValue )

return s.obj.identity(a.obj) ;else

return a.coreIdentity(s) ;else return false

T $access()

SAMET s = drf() ;if( s.hasValue ) return s.obj ;else raise xNotConneted ;

// Serviços para outros modosraw SAMET $dup() // para value

SELFT s = #new() ;SAMET d = drf() ;s.hasValue := d.hasValue ;s.next := nil ;if( s.hasValue ) s.obj := d.obj ;return s ;

() onBack() // para gen

initz() ;

// Métodos auxiliaresSAMET drf()

return next == nil ? self : next.drf() ;

// Métodos específicosBool '=='(SAMET arg)

SAMET s = drf(), a = arg.drf() ;if( s.hasValue )

if( a.hasValue )return s.obj == a.obj ;

else a.init(nil) ; $raw_assign(a.obj,s.obj) ; // para valueelse

if( a.hasValue ) s.init(nil) ; $raw_assign(s.obj,a.obj) ; // para value

else s.initv(a) ;return true ;

Bool isVar()

return !drf().hasValue ;

// Componentes globais//

// Coerções// Não existe a regra extra log T ≤extra Tcoercion log T #log_up(T arg) // T ≤extra log T

return #new(arg) ;coercion gen T #gen_from_log(log T arg) // log T ≤extra gen T

try return single(arg.$access()) ;with(xNotConneted) return fail ;

// Métodos #$def-*// As coerções não bastam no caso seguinte pois a definição original de// #$def_select abrange este caso, com semântica indesejada.// Sem esta definição o.x poderia produzir a excepção "xNotConneted" em vez// de falhar.raw [R] gen R #$def_select(log T o, T->R label)

return (gen T)#$def_select(#gen_from_log(o), label) ;

238 OM – Uma linguagem de programação multiparadigma

12.6 Modo genPela via da noção de gerador, e um pouco à maneira da linguagem Icon [Gri83], o modo gen

introduz a possibilidade de usar retrocesso (backtracking) na avaliação das expressões da lin-

guagem. O modo gen colabora com o modo log no suporte do paradigma lógico pela linguagem

OM.

Deliberadamente, as variáveis com modo gen não suportam inicialização implícita nem atri-

buição directa.

O modo gen verifica a propriedade: τ≤σ ⇒ gen τ≤gen σ.

Entre as numerosas componentes específicas do modo gen incluem-se as conhecidas opera-

ções da linguagem Prolog: repeat, fail, ‘&’, '|', '~'.

Cut, o mecanismo não estruturado de controlo da linguagem Prolog, não é suportado pelo

modo gen. Esta ausência resulta tanto duma decisão de concepção (preferimos usar um meca-

nismo de controlo estruturado), como duma questão de praticabilidade. A alternativa ao uso do

cut, fomos buscá-la à linguagem UNL-Prolog [PPT87], uma linguagem que propõe a substitui-

ção do cut por uma colecção de predicados de controlo de alto nível (e também um mecanismo

de implicação exclusiva que não aproveitamos aqui). O modo gen suporta a maioria dos predi-

cados de controlo de alto nível da linguagem UNL-Prolog, nomeadamente: possible, sideeffects,

once, dowhile e until.

Com a introdução do modo gen passam a haver duas formas de descrever computações: (1)

usando comandos, expressões e iteração; (2) usando geradores e retrocesso. Para conciliar

estas duas formas, introduzimos as seguintes três primitivas: every – dado um gerador e um

comando, gera todos os valores do gerador e executa o comando para cada um deles; freegen –

a partir dum gerador normal cria um gerador especial que pode ser explorado usando iteração

em diversos pontos do programa, à maneira das co-expressions da linguagem Icon [Gri83];

parallel – a partir de dois geradores cria um novo gerador que permite explorar em paralelo os

dois geradores originais, usando retrocesso.

A realização da parte dinâmica do modo gen é complexa e foi efectuada usando continua-

ções [Sto77]. Cada gerador é codificado numa função com dois argumentos: o primeiro argu-

mento é a continuação-principal que representa toda a actividade futura do programa em caso

de sucesso; o segundo argumento é uma continuação-alternativa que representa toda a activi-

dade futura do programa em caso de falhanço. Quando uma continuação-principal é activada,

propagamos através dela o último resultado intermédio gerado e a continuação-alternativa, a

qual só será activada em caso de falhanço da continuação-principal.

12 Modos da biblioteca padrão 239

Na nossa implementação todas as continuações-alternativa são criadas dentro do método

thenelse do modo gen. A nossa codificação de geradores tem algumas semelhanças com as codi-

ficações, apresentadas em [MH83], [Sch86], de algumas facetas da linguagem Prolog.

Para registar os objectos cujo estado deve ser reposto em retrocesso, introduzimos na classe

$CoreObject uma pilha de objectos designada por trail. Em caso de retrocesso, o método thenelse

activa o procedimento de restauração #restoreTrail, o qual envia a mensagem onBack para todos

os objectos cujo estado deva ser restaurado.

alias genInterf = defaultInterf ;mode gen [T < genInterf] :$CoreObject

alias Alt = ()->() ;alias Cont[X] = (X, Alt)->() ;alias CA[X] = (Cont[X], Alt)->() ;alias PCA[X] = (X, Cont[X], Alt)->() ;

// VariáveisNil $object_with_mode_gen ;priv CA[T] objGen ;

// Literais, constantes e zero// não tem zeropriv CA[T] #ca0 =

()fun(Cont[T] _, Alt _) ;priv Cont[T] #k0 =

()fun(T _, Alt _) ;priv Alt #a0 =

()fun() ;

// Construtores e inicializações() #startup() // para value

#$expanded := false ;priv SELFT #new(CA[T] ca)

SELFT s = #new() ;s.objGen := ca ;return s ;

CA[T] getCA()

return objGen ;

// Serviços baseBool identity($CoreObject arg)

return coreIdentity(arg) ;T $access()

if( first(.t) ) return t ;else raise xNotConneted ;

// Serviços para outros modosraw SAMET $dup() // para value

SELFT s = #new() ;s.objGen := objGen ;return s ;

// Métodos auxiliarespriv CA[T] thenelse(PCA[T] g1, CA[T] g2)

$CoreObject l = #getTrailLevel() ;return

()fun(Cont[T] k, Alt a)self.objGen(()fun(T t, Alt al) g1(t,k,al) ; ,

240 OM – Uma linguagem de programação multiparadigma

()fun() #restoreTrail(l) ; g2(k,a) ; ) ;

;priv Bool first(T& var)

Bool res = false ;self.thenelse(

()fun(T t, Cont[T] _, Alt _) var := t ; res := true ; ,#ca0)(#k0, #a0) ;

return res ;

// Métodos específicos//

// Componentes globaisglobal Bool #check(gen T g)

return g.first(.z) ;global gen T #single(()->T t)

return #new(()fun(Cont[T] k, Alt a)

T v = t() ; // false = failif( !v.coreIdentity(false) ) k(v,a) ; else a() ;

) ;global gen T #fail =

#new( ()fun(Cont[T] _, Alt a) a() ; ) ;global gen Nil #repeat =

single(nil) | repeat ;global [X < genInterf] gen T '#&'(gen X g1, gen T g2)

return #new(()fun(Cont[T] k, Alt a)

g1.getCA()(()fun(X _, Alt al) g2.getCA()(k, al) ; ,a) ;

) ;global gen T '#|'(gen T g1, gen T g2)

return #new(g1.thenelse(()fun(T t, Cont[T] k, Alt a) k(t,a) ; ,

g2.getCA())) ;

global gen Nil '#~'(gen T g)

return check(g) ? fail : single(nil) ;global gen Nil #possible(gen T g)

return ~~g ;global gen Nil #sideeffects(gen T g)

check(g) ;return single(nil) ;

global gen T #once(gen T g)

return g.first(.t) ? single(t) : fail ;global [X < genInterf] gen T #dowhile(gen T g, gen X gc)

return #new(()fun(Cont[T] k, Alt a)

g.getCA()(()fun(T t, Alt aa) if( check(gc) ) k(t,aa) ; else a()) ; ,

a) ;) ;

global [X < genInterf] gen T #dowhile2(gen X g, gen T gc)

return #new(()fun(Cont[T] k, Alt a)

g.getCA()(()fun(X _, Alt aa) if(gc.first(.t) ) k(t,aa); else a()) ; ,

a) ;

12 Modos da biblioteca padrão 241

) ;global [X < genInterf] gen T #until(gen T g, gen X gc)

return #new(()fun(Cont[T] k, Alt a)

g.getCA()(()fun(T t, Alt aa) if( check(gc) ) k(t,a) ; else k(t,aa) ; ,

a) ;) ;

global [X] () #every(gen T g, ()->X body)

g & single(body) & fail ;global gen T #freegen(gen T g)

gen T aux ; // aux e gv vars locais de closure()->() gv = ()fun()

g.thenelse(()fun(T t, Cont[T] _, Alt aa)

aux := single(t) ; gv := ()fun() a() ; ; ,()fun(Cont[T] _, Alt _) aux := fail ; gv:=()fun() ; ; )

(#k0,#a0) ; ;

return #new(()fun(Cont[T] k, Alt a) gv() ; aux(k, a) ; ) ;global [X < genInterf] gen T #parallel(gen X g1, gen T g2)

return dowhile2(g1, freegen(g2)) ;

// Coerções// Não existe a regra extra gen T ≤extra Tcoercion (T->gen T) #gen_from_pred(T->Bool f) // T->Bool ≤extra T->gen T

return(gen T)fun(T t) return (f(t) ? single(t) : fail) ;

coercion gen T #gen_up(()->T arg) // ()->T ≤extra gen T

return single(arg) ;

// Métodos #$def-*[X] gen T #$def_cond(gen T gc, ()->X thenP, ()->X elseP)

return check(gc) ? thenP() : elseP() ;[X,Y] () #$def_ifthenelse(gen T gc, ()->X thenP, ()->Y elseP)

if( check(gc) ) then thenP() else elseP() ;[X] () #$def_while(()->(gen T) gc, ()->X body)

while( check(gc()) ) body() ;[X] () #$def_dowhile(()->X body, ()->(gen T) gc)

do body() while( check(gc()) ) ;() #$def_switch(gen T g, T->() body)

if( g.first(.t) )switch( t ) body() ;

[R] gen R #$def_select(gen T go, T->R label)

return #new(()fun(Cont[R] k, Alt a)

go.getCA()(()fun(T o, Alt aa)k(T#$def_select(o,label),aa) ; ,

a) ;) ;

() #$def_comm(()->(gen T) g)

check(g()) ;global () #$def_init(T& var, gen T g)

// a variável não é alterada se o gerador falharif( g.first(.t) ) T#$def_init(var,t) ;

242 OM – Uma linguagem de programação multiparadigma

global [X < genInterf+asgnInterf] gen X #$def_assign(gen (X&) gv, gen X g) return #new(

()fun(Cont[X] k, Alt a)gv.getCA()(()fun(X& v, Alt aa)

g.getCA()(()fun(X x, Alt aaa)k(v:=x, aaa) ; ,

aa),a) ;

) ;global [R] gen R #$def_apply(gen (T->gen R) gf, gen T g)

return #new(()fun(Cont[R] k, Alt a)

gf.getCA()(()fun(T->gen R f, Alt aa)g.getCA()(()fun(T t, Alt aaa)

(f t).getCA()(k,aaa) ; ,aa),

a) ;) ;

12.6.1 Protótipo do modo gen

O modo gen é tão complexo que tivemos a necessidade de escrever um protótipo para validar

as nossas ideias e corrigir os erros. Eis o protótipo que escrevemos em CAML:

type trvar = prev:trvar; var: int ref ; ;;let rec varbase = prev=varbase; var=ref 0 ;;let vars = ref varbase ;;

let rec trail v = (vars := prev= !vars; var=v ) ;;and getlevel () = !vars ;;and restoreuntil level =

if !vars == level then ()else ( !vars.var := -1 ; vars := !vars.prev ; restoreuntil level )

let ca0 = fun k aa-> () ;;let k0 = fun x aa-> () ;;let a0 = fun ()-> () ;;let z0 = ref 0 ;;

let rec bifthenelse gc g1 g2 k a =let h = getlevel() in

gc (fun n aa-> g1 n k aa) (fun ()-> restoreuntil h ; g2 k a)and first g var =

let res = ref false inbifthenelse g (fun n _ _->var:=n; res:=true) ca0 k0 a0 ;!res

and bcheck g =first g z0

and bsingle n k =k n

and bvar v k =k !v

and bfail k a =a ()

and brepeat k =bor (bsingle (-1)) brepeat k

and band g1 g2 k =g1 (fun _->g2 k)

and bor g1 g2 k =bifthenelse g1 (fun n->k n) g2 k

and borlist l k =match l with

12 Modos da biblioteca padrão 243

[] -> (bfail) k| (x::xs) -> (bor (bsingle x) (borlist xs)) k

and bif gc g1 g2 =if first gc z0 then g1 else g2

and bnot g =if first g z0 then bfail else (bsingle (-1))

and bpossible g =bnot (bnot g)

and bsideeffects g =first g z0 ; (bsingle (-1))

and bonce g =if first g z0 then (bsingle !z0) else bfail

and bdowhile g gc k a =g (fun n aa-> if first gc z0 then (k n) k aa else bfail k a) a

and bdowhile2 g gc k a =g (fun _ aa-> if first gc z0 then (k !z0) k aa else bfail k a) a

and buntil g gc k a =g (fun n aa-> if first gc z0 then (k n) k a else (k(t,a) n) k aa) a

and bevery g q =bsideeffects (band g (band q bfail))

and bfreegen g = let aux = ref bfail in let rec gv = ref (fun ()->

bifthenelseg(fun n _ aa-> aux:=(bsingle n); gv:=fun ()->aa ())(fun _ _-> aux:=bfail; gv:=fun ()->())k0a0)

in (fun k a-> !gv (); !aux k a)and bpar g1 g2 =

bdowhile2 g1 (bfreegen g2)and basgn v g k =

g (fun n aa-> v:=n; k n aa)and brasgn v g k =

g (fun n aa-> trail(v); v:=n; k n aa)and bprint g k =

g (fun n aa-> print_int n ; print_newline(); k n aa)and run g =

(band g bfail) k0 a0and runp g =

run (bprint g);;

(*runp (bsingle 5) ;;runp (bfail) ;;runp (band (bsingle 5) (bsingle 7)) ;;runp (bor (band (bsingle 5) (bsingle 7)) (bsingle 6)) ;;runp (let v = ref 0 in bor (basgn v (bsingle 6)) (bvar v)) ;;runp (let v = ref 0 in bor (brasgn v (bsingle 6)) (bvar v)) ;;runp (btry (bsingle 5)) ;;runp (btry bfail) ;;

let rr = (borlist [9; 10; 11; 12; 13]) ;;let ss = (borlist [21; 22; 23]) ;;run (bor (bprint rr) (bprint ss)) ;;run (band (bprint rr) (bprint ss)) ;;run (bpar (bprint rr) (bprint ss)) ;;runp (bpar rr ss) ;;*)

Capítulo 13

Exemplo

A linguagem de programação OM tem diversos aspectos positivos:

• Tem um mecanismo de herança flexível que convive pacificamente com a relação de

subtipo (a expressividade da linguagem depende bastante deste aspecto);

• Introduz o mecanismo dos modos, um mecanismo de meta-nível que foi possível fazer

surgir de forma natural no contexto duma teoria de objectos e classes, e cujas potencia-

lidades foram exploradas na construção da linguagem OM.

• Está bem fundamentada teoricamente, e de uma forma razoavelmente simples e abor-

dável.

No entanto, esquecendo as questões técnicas e a elegância formal da forma como foi intro-

duzida, não se pode dizer que a versão actual da linguagem OM seja particularmente original

no que diz respeito aos paradigmas suportados. Efectivamente, os paradigmas que escolhemos

para ilustrar as capacidades de extensão da linguagem foram os sempre habituais: paradigma

orientado pelos objectos, paradigma funcional e paradigma lógico.

De qualquer forma, justifica-se que apresentemos um exemplo de dimensão média que ilus-

tre uma aplicação concreta da linguagem e ajude a esclarecer se o nosso método de integrar

paradigmas deu, ou não, origem a uma linguagem de utilidade prática. É isso o que faremos no

presente capítulo.

13.1 O problema dos padrõesPara ilustrar a acção combinada de vários paradigmas, procurámos um exemplo não trivial que

envolvesse um tipo de dados multiforme (para exibir os aspectos organizacionais do paradi-

gma orientado pelos objectos), e que beneficiasse da disponibilidade dos modos log, gen e lazy

(que suportam os paradigmas lógico e funcional).

Dentro destes limites pensámos em várias alternativas. Um problema possível seria o da

representação e manipulação das expressões da própria linguagem OM, incluindo a escrita

duma função de avaliação de expressões com retrocesso (esta seria uma versão sofisticada do

exemplo da secção 5.5.2.1). Outro problema interessante seria o da reconstrução em OM da

hierarquia Collection da linguagem Smalltalk, com a adição de métodos geradores às classes, a

246 OM – Uma linguagem de programação multiparadigma

introdução de versões lógicas em algumas estruturas, a criação duma classe de listas infinitas e

duma classe de grafos infinitos, etc.

Acabámos por nos decidir pelo seguinte problema de emparelhamento de padrões sobre ca-

deias de caracteres.

Problema 13.1-1 (Problema dos padrões) Capturar num sistema de classes a funcio-

nalidade essencial das expressões do conhecido analisador lexical Lex [Lev92], enriquecendo,

no processo, essa funcionalidade com os seguintes três elementos adicionais: retrocesso, para

aumentar a capacidade de reconhecimento dos padrões; variáveis lógicas, para permitir criar

contextos dinâmicos dentro dos padrões; condições, para ser possível influenciar o processo de

emparelhamento, explorando por exemplo os valores correntes das variáveis lógicas.

Este tipo de funcionalidade geral (excluindo as variáveis lógicas) é suportado especifica-

mente pela componente de emparelhamento de padrões da linguagem SNOBOL4 [Gri71], e

também, a um certo nível, pela linguagem Icon [Gri83]. Budd no seu livro [Bud95] apresenta

uma implementação fiel dos mecanismos do SNOBOL4 usando a sua linguagem Leda. O nos–

so exemplo irá diferir dos trabalhos referidos nos seguintes aspectos: na especificidade das pri-

mitivas do Lex; (2) no suporte para variáveis lógicas; (3) na linguagem usada na implemen-

tação.

13.1.1 PadrõesComeçamos por introduzir as noções de padrão e emparelhamento de padrões no contexto do

caso específico das cadeias de caracteres:

• Um padrão é uma representação compacta de um conjunto de cadeias de caracteres;

• Emparelhamento de padrões é um mecanismo que determina se uma dada cadeia de

caracteres, ou um seu segmento, pertence ao conjunto representado por um dado pa-

drão.

Os padrões genéricos que o nosso sistema de classes irá suportar são os que indicamos a se-

guir. A maioria deles tem tradução directa em Lex:

Forma do padrão Lex significado PChar#new(c) c representa o carácter c (conjunto singular)PAny#new() . representa todos os caracteresPStr#new(str) str representa a string str (conjunto singular)PSet#new(cstr) [cstr] conjunto dos caracteres contidos na string cstrPNSet#new(cstr) [~cstr] conjunto dos caracteres não contidos na string strPSeq#new(p1,p2) p1p2 padrão p1 imediatamente seguido de p2POr#new(p1,p2) p1|p2 padrão p1 ou padrão p2PZeroOrMore#new(p) p* padrão p repetido zero ou mais vezesPOneOrMore#new(p) p+ padrão p repetido uma ou mais vezesPBeg#new() ^ representa o início da linhaPEnd#new() $ representa o fim da linhaPTimes(p,n) - padrão p repetido n vezesPUnify(p,v) - igual ao padrão p, mas unifica resultado com var. vPCheck(p,b) - igual ao padrão p se depois do emparelhamento, a exp.

b produzir true; senão não emparelha, e retrocede

13 Exemplo 247

Agora, exemplificamos alguns de padrões concretos e mostramos o seu significado:

PChar#new('a') letra 'a'POneOrMore#new(PChar#new('a')) sequências não vazias de 'a'sPSeq#new(PBeg#new(),PEnd#new()) linha vaziaPSeq#new(PEnd#new(),PBeg#new()) linha vaziaPOneOrMore#new(PSet#new("abcdefghijklmnopqrstuvwxyz"))

palavras não vazias constituídas por minúsculasPOneOrMore#new(PNSet#new(" ")) palavras não contendo qualquer espaço em brancoPSeq#new( PSeq#new(PChar#new(''),PNSet#new('')), PChar#new('')

cadeia delimitada por chavetas e sem chavetas no interior

Vejamos agora um exemplo envolvendo variáveis lógicas. O padrão twoEqual representa

dois caracteres consecutivos iguais. Para funcionar, a variável v tem de se encontrar livre no

momento em que o padrão é activado:

log Str v ;Pattern twoEqual = PSeq#new( PUnify#new(PAny#new(),v), PUnify#new(PAny#new(),v) )

Finalmente um exemplo que envolve uma condição. O padrão wordInDict representa uma

palavra qualquer que se encontra num dicionário exterior ao padrão:

Pattern pal = POneOrMore#new(PSet#new("abcdefghijklmnopqrstuvwxyz"))log Str v ;Pattern wordInDict = PCheck#new(PUnify#new(pal,v), dict.belongs(v))

13.1.2 Uso dos padrõesNo nosso programa, cada padrão será representado por um objecto com habilidade suficiente

para conseguir detectar numa linha de texto as cadeias por ele representadas. O emparelha-

mento efectua-se da esquerda para a direita, considerando-se, em casos de ambiguidade, sem-

pre a sequência de caracteres mais longa em primeiro lugar. Os outros emparelhamentos pos-

síveis podem ser gerados usando retrocesso.

Normalmente, activa-se um padrão enviando-lhe a mensagem match(line,matched). Na defi-

nição do método match, o parâmetro line é de entrada e representa a linha de texto com a qual

o padrão se tenta emparelhar; matched é um parâmetro do tipo cadeia de caracteres com modo

lógico e representa o resultado do emparelhamento. Consoante matched esteja ou não instan-

ciado no momento da activação de match(line,matched), assim o método match funcionará em

modo de verificação ou em modo de descoberta (como é habitual nos programas em lógica).

Vejamos um exemplo no qual um padrão, twoEqual, é aplicado, usando match, a todas as li-

nhas dum ficheiro de texto. Neste caso consideramos apenas o primeiro emparelhamento que é

possível efectuar dentro da cada linha:

class Main () #main()

TextFile f = TextFile#Open("foo") ;while( line = f.nextLine() )

log Str v, matched ;Pattern twoEqual =

PSeq#new( PUnify#new(PAny#new(),v), PUnify#new(PAny#new(),v) );if( twoEqual.match(line, matched) )

248 OM – Uma linguagem de programação multiparadigma

matched.printNl() ;else

"///".printNl() ;f.Close() ;

Vejamos um segundo exemplo no qual o mesmo padrão, twoEqual, é igualmente aplicado,

usando match, a todas as linhas dum ficheiro. Agora, a diferença reside no facto de considerar-

mos todos os emparelhamento que é possível efectuar dentro da cada linha.

class Main () #main()

TextFile f = TextFile#Open("foo") ;while( line = f.nextLine() )

log Str v, matched ;Pattern twoEqual =

PSeq#new( PUnify#new(PAny#new(),v),PUnify#new(PAny#new(),v) ) ;every( twoEqual.match(line, matched) )

matched.printNl() ;"---".printNl() ;

f.Close() ;

13.1.3 A classe abstracta PatternCada tipo de padrão é implementado numa classe distinta. Como é típico no paradigma orien-

tado pelos objectos, a funcionalidade comum a todas as diferentes classes que implementam os

padrões é capturada numa superclasse partilhada. Evita-se assim redundância e consegue-se

dar um estatuto material à ideia abstracta de padrão. Chamamos Pattern a essa superclasse.

A classe Pattern é, portanto, uma classe abstracta. Como todas as classes abstractas, ela

ignora deliberadamente as variações dos aspectos concretos das suas subclasses, a ponto de

não haver interesse em criar instâncias directas suas: seriam entidades incompletas do ponto de

vista computacional, tendencialmente equivalentes ao termo divergeτ de F+ (cf. secção 2.3.3.1).

Apesar da superclasse Pattern ser abstracta, ao seu nível é já possível definir diversos méto-

dos, nomeadamente o método público match, atrás referido, e dois novos métodos privados

auxiliares chamados matchRange e range. Apenas um novo terceiro método, matchAt, de quem

matchRange dependerá directamente, terá de ser definido de forma diferente em cada subclasse

concreta, pois é nesse método que serão codificadas as características específicas de cada for-

ma de padrão particular.

Quando a mensagem matchAt(start,line) é enviada para um padrão, o receptor tenta em-

parelhar-se com algum segmento de line que comece no índice fixo start. Se conseguir, o mé-

todo retorna o índice do carácter que se segue imediatamente ao texto emparelhado. Se não

conseguir, então o método falha. Existem padrões cuja natureza particular permite que possam

emparelhar de mais do que uma forma. Nesse caso, cada um desses padrões deve prever, no

13 Exemplo 249

respectivo método matchAt, as computações alternativas a serem activadas em caso de retro-

cesso (usando o operador de disjunção “|” e recursividade).

O método/gerador privado matchRange(line,start,end) tem a função de ir gerando suces-

sivamente todos os índices da linha line e de ir activando o método matchAt para tentar um

emparelhamento. Sempre que matchAt sucede, os parâmetros lógicos start e end são unificados

com os índices delimitadores do segmento de linha que emparelhou. O método matchRange

incorpora a seguinte pequena optimização: no caso do parâmetro start já vir instanciado,

matchRange usa directamente o valor de start na invocação de matchAt.

O método público match(line,matched) constitui essencialmente uma interface cómoda para

o método matchRange: ele limita-se a tentar unificar o parâmetro lógico matched com cada seg-

mento que resultar do emparelhamento efectuado por matchRange.

class Pattern priv gen Int range(Int start, Int end) // gera: start, start+1, …, end-1

start := start - 1 ;return dowhile(repeat & start:=start+1, start < end) ;

// definição alternativa// priv gen Int range(Int start, Int end) // return start < end & (start | range(start + 1, end)//

priv gen Int matchAt(Int start, Str line) raise xAbstrAct ;

priv gen Int matchRange(Str line, log Int start, log Int end)

Int i ;if( start.isVar() )

return i := range(0, line.len()) // gera 0,1,2,…,(len-1)& end == matchAt(i, line) & start == i & end

elsereturn end == matchAt(start, line)

gen Str match(Str line, log Str matched)

log Int start, end ;return matchRange(line, start, end)

& matched == line.slice(start, end-1) ;

13.1.4 As classes concretasTodas as classes concretas definidas no programa têm a mesma estrutura geral: elas declaram

as variáveis de instância necessárias à representação de cada tipo de padrão; definem um cons-

trutor público chamado #new cuja aridade varia com o tipo de padrão; definem um método pri-

vado chamado matchAt cuja funcionalidade geral foi descrita na secção anterior.

A parte mais interessante das classes concretas é a forma como cada uma delas concretiza a

implementação do método básico de emparelhamento matchAt(start,line). A título de ilustra-

ção, vamos agora comentar a implementação de matchAt(start,line) nas classes PChar e

POneOrMore.

250 OM – Uma linguagem de programação multiparadigma

O método matchAt(start,line) da classe PChar só emparelha com sucesso se o índice start

indicar uma posição realmente dentro da linha line, e se na posição start se encontrar o carác-

ter pretendido. Neste caso retorna start+1. Caso contrário falha.

O método matchAt(start,line) da classe POneOrMore pode emparelhar de múltiplas formas.

Quando activado pela primeira vez, o método procura uma sequência de emparelhamentos su-

cessivos do padrão interior p, o mais longa possível, Depois, em cada retrocesso, o método vai

emparelhando com sequências sucessivamente mais curtas de emparelhamentos de p até, final-

mente, tentar um emparelhamento simples de p.

Para exemplificar uma utilização de POneOrMore considere o seguinte padrão, que representa

todos os advérbios de modo da Língua Portuguesa.

PSeq#new( POneOrMore#new(PSet#new(“abcdefghijklmnopqrstuvwxyz”)), PStr#new("mente"))

Note que quando este padrão é activado, perante um advérbio de modo bem formado, o seu

primeiro subpadrão começa por tentar sequências de caracteres excessivamente longas: tão

longas que capturam segmentos iniciais do sufixo “mente” e impedem qualquer reconheci-

mento com sucesso. Só à quinta tentativa, quando o primeiro subpadrão tenta finalmente uma

sequência que deixa de fora a subcadeia “mente”, é que o emparelhamento sucede.

Dadas estas explicações, apresentamos agora todas as classes concretas que integram o nos-

so programa:

class PChar : Pattern // Carácter fixo. lex:.

priv Char c;SAMET #new(Char arg) SELFT s = #new() ; s.c := arg ; return s ; priv gen Int matchAt(Int start, Str line)

return start < line.len()& line[start] == c& start + 1 ;

class PAny : Pattern // Qualquer carácter. lex:.

SAMET #new() return SUPERC#new() ; priv gen Int matchAt(Int start, Str line)

return start < line.len()& start + 1 ;

class PStr : Pattern // Cadeia de caracteres fixa. lex:"..."

priv Str str ;SAMET #new(Str arg) SELFT s = #new() ; s.str := arg ; return s ; priv gen Int matchAt(Int start, Str line)

Int end = start + str.len() ;return str == line.slice(start, end-1) & end ;

class PSet : Pattern // Conjunto de caracteres. lex:[...]

priv Str chars ;

13 Exemplo 251

SAMET #new(Str arg) SELFT s = #new() ; s.chars := arg ; return s ; priv gen Int matchAt(Int start, Str line)

return start < line.len()& chars.includes(line[start])& start + 1 ;

class PNSet : Pattern // Conjunto complementar. lex:[~...]

priv Str chars ;SAMET #new(Str arg) SELFT s = #new() ; s.chars := arg ; return s ; priv gen Int matchAt(Int start, Str line)

return start < line.len()& !chars.includes(line[start])& start + 1 ;

class PSeq : Pattern // Sequência de padrões. lex:P1P2

priv Pattern fir, sec ;SAMET #new(Pattern f, Pattern x) SELFT s = #new() ; s.fir := f ; s.sec := x ; return s ; priv gen Int matchAt(Int start, Str line)

return sec.matchAt(fir.matchAt(start, line), line) ;

class POr : Pattern // Padrões alternativos. lex:P1|P2

priv Pattern fir, sec ;SAMET #new(Pattern f, Pattern x) SELFT s = #new() ; s.fir := f ; s.sec := x ; return s ; priv gen Int matchAt(Int start, Str line)

return fir.matchAt(start, line)| sec.matchAt(start, line) ;

class PZeroOrMore : Pattern // Zero ou mais vezes. lex:p* // Primeiro a sequência mais longa…

priv Pattern p ;SAMET #new(Pattern arg) SELFT s = #new() ; s.p := arg ; return s ; priv gen Int matchAt(Int start, Str line)

return matchAt(p.matchAt(start, line), line)| start ;

class POneOrMore : Pattern // Uma ou mais vezes. lex:p+ // Primeiro a sequência mais longa…

priv Pattern p ;SAMET #new(Pattern arg) SELFT s = #new() ; s.p := arg ; return s ; priv gen Int matchAt(Int start, Str line)

return matchAt(p.matchAt(start, line), line)| p.matchAt(start, line) ;

class PBeg : Pattern // Início da linha. lex:^

SAMET #new() return SUPERC#new() ; priv gen Int matchAt(Int start, Str line)

return start == 0& start ;

252 OM – Uma linguagem de programação multiparadigma

class PEnd : Pattern // Fim da linha. lex:$

SAMET #new() return SUPERC#new() ; priv gen Int matchAt(Int start, Str line)

return start == line.len()& start ;

class PUnify : Pattern // Se o emparelhamento suceder unifica o texto // emparelhado com a variável lógica

priv Pattern p ;priv log Str var ;SAMET #new(Pattern arg, log Str x) SELFT s = #new() ; s.p := arg ; s.var == x ; return s ; priv gen Int matchAt(Int start, Str line)

Int end ;return (end := p.matchAt(start, line))

& var == line.slice(start, end-1)& end ;

class PCheck : Pattern // Se o emparelhamento suceder verifcicsa a // condição suplementar test

priv Pattern p ;priv lazy Bool test ; // Note que usamos aqui o modo lazySAMET #new(Pattern arg, lazy Bool x) SELFT s = #new() ; s.p := arg ; s.line == x ; return s ; priv gen Int matchAt(Int start, Str line)

Int end ;return (end := p.matchAt(start, line))

& test& end ;

class PTimes : Pattern // n vezes

priv Pattern p ;priv Int n ;SAMET #new(Pattern arg, Int x) SELFT s = #new() ; s.p := arg ; s.n == x ; return s ; priv gen Int matchAt(Int start, Str line)

Int i, end ;for( i:=1, end:=start ; i < n ; i:=i+1 )

if( end := p.matchAt(end, line) ) ;else return fail ;

return end ;

Conclusões

Consideremos um projecto de programação constituído por múltiplas componentes heterogé-

neas, nomeadamente, um módulo de manipulação duma bases de dados, uma interface WWW,

um sistema pericial, etc. Num projecto com tão elevado grau de diversificação é frequente o

programador ver-se confrontado com a impossibilidade de expressar de forma directa algumas

das soluções que idealizou para algumas das componentes do projecto. A razão é a seguinte: a

linguagem de programação adoptada não cobre toda a gama de conceitos a que o programador

precisaria de recorrer para conseguir lidar de forma natural com uma gama tão diversificada de

problemas. As linguagens multiparadigma e as linguagens extensíveis têm um campo de apli-

cação privilegiado neste tipo de projectos, ao multiplicarem as ferramentas conceptuais que

colocam à disposição do programador.

Foi nesta linha de preocupações com a expressividade que concebemos a linguagem de pro-

gramação multiparadigma OM. Nesta linguagem explorámos a ideia de usar um mecanismo

uniforme de extensão – o mecanismo dos modos – como veículo para a introdução de novos

conceitos e mecanismos na linguagem. Este mecanismo introduz homogeneidade na lingua-

gem e evita que tenhamos de congelar à partida quais os paradigmas que a linguagem OM

deve suportar.

Constituiu nosso objectivo inicial o desenvolvimento dum mecanismo de extensão de base

estática, integrado no contexto duma linguagem orientada pelos objectos estaticamente tipifi-

cada. Assim, o modelo semântico que elaborámos, para a linguagem OM, foi um modelo

tipificado.

O mecanismo dos modos, sendo relativamente simples, encerra um apreciável potencial,

como o demonstram os diversos modos que definimos na biblioteca padrão da linguagem.

Quanto às limitações deste mecanismo, existem duas que importa referir: é um mecanismo

síncrono, logo não adaptado à expressão de concorrência (é possível definir um modo de coro-

tinas, mas não mais do que isso); a natureza do mecanismo não permite que ele seja usado em

domínios de valores específicos (por exemplo, não se consegue criar uma sublinguagem de

restrições sobre números reais mediante a simples introdução dum modo).

A maior parte do nosso trabalho, em termos de volume e esforço, foi dedicada ao desenvol-

vimento em paralelo da linguagem e do seu modelo tipificado. Este tipo de desenvolvimento

em paralelo é sempre recomendável, na medida em que o modelo ajuda a compreender a lin-

guagem e permite tomar decisões fundamentadas quanto à sua evolução. No culminar deste

254 OM – Uma linguagem de programação multiparadigma

processo, tivemos a satisfação de verificar a naturalidade com que foi possível fazer surgir o

mecanismo dos modos através de mais uma extensão do nosso modelo modelo.

O modelo semântico que desenvolvemos para a linguagem OM integra muito do saber-

-fazer acumulado ao longo da última década no campo dos modelos tipificados para lingua-

gens de objectos e classes. As principais virtualidades do nosso modelo são as seguintes: ele

cobre de forma organizada um largo espectro de mecanismos; é razoavelmente abordável;

suporta variantes melhoradas de certos mecanismos (por exemplo, a técnica de ocultação da

informação que facilita a inicialização de objectos (cf. capítulo 7)); considera uma solução

específica para o conhecido problema do conflito entre um mecanismo de herança geral e a

relação de subtipo (cf. capítulo 5).

Em dois pontos desta dissertação fomos obrigados a lidar com problemas de indecidibi-

lidade: primeiro, no cálculo-lambda polimórfico F+ que usámos na escrita do modelo como

veículo de expressão semântica (cf. secção 2.3.4.5); depois, no sistema de coerções extensível

que introduzimos no capítulo 10.

Consideramos que a linguagem OM já atingiu uma forma estável, exceptuando possivel-

mente os mecanismos descritos no capítulo 11, os quais continuamos a tentar fazer evoluir no

sentido duma ainda maior simplificação.

O principal ponto que ficou em aberto, e que irá requerer trabalho adicional, foi o problema

identificado no final da secção 11.6: o problema da inferência de argumentos de instanciação

de entidades paramétricas em presença dum sistema de coerções.

Outro ponto em aberto consiste na questão do desenvolvimento duma implementação para

a linguagem OM. Para a realização desta tarefa, tencionamos usar a conhecida técnica de boot-

strapping (descrita em [ASU85], por exemplo) pois gostaríamos de exercitar a escrita do com-

pilador de OM usando a própria linguagem OM. Pretendemos codificar as equações semânti-

cas de OM em CAML como ponto de partida do processo de bootstrapping, não obstante este

método nos obrigar, numa primeira fase, a tratar OM como uma linguagem não-tipificada –

note que o CAML é um sistema de segunda ordem, logo menos poderoso do que o sistema de

ordem superior F+ usado originalmente para definir a linguagem OM.

Bibliografia

[AC93] R.Amadio, L.Cardelli. Subtyping recursive types. ACM Transactions on Programming Langua-ges and Systems, 15(4):575-631, 1993.

[AC94] M.Abadi, L.Cardelli. A semantics of object types. Proc. Ninth Annual IEEE Symposium onLogic in Computer Science, Paris, France, 1994.

[AC96a] M.Abadi, L.Cardelli. A Theory of Objects. Springer, 1996.

[AC96b] M.Abadi, L.Cardelli. On subtyping and matching. ACM Transactions on Programming Lan-guages and Systems, 18(4):401-423, 1996.

[ACC93] M.Abadi, L.Cardelli, P.Curien. Formal Parametric Polymorphism. Proc. 20th Annual ACMSymposium on Principles of Programming Languages, 1993.

[ACPP91] M.Abadi, L.Cardelli, B.Pierce, G.Plotkin. Dynamic typing in a statically typed language.ACM Transactions on Programming Languages and Systems, 13(2):237-268, 1991.

[ACPR92] M.Abadi, L.Cardelli, B.Pierce, D.Rémy. Dynamic typing in polymorphic languages . Proc.SIGPLAN Workshop on ML and its Applications, 1992.

[ACV96] M.Abadi, L.Cardelli, R.Viswanathan. An interpretation of objects and object types. Confe-rence Record of POPL'96: The 23rd ACM SIGPLAN-SIGACT Symposium on Principles of Pro-gramming Languages, 1996.

[AF96] M.Abadi, M.Fiore. Syntactic Considerations on recursive types. Proceedings of the EleventhAnnual IEEE Symposium on Logic in Computer Science. págs. 242-252. IEEE ComputerSociety, 1996.

[AG98] K.Arnold, J.Gosling. The Java Programming Language. Addison-Wesley, Reading, MA, 2ndedition, 1998.

[Aït90] H.Aït-Kaci. The WAM: A (Real) Tutorial, Digital, Paris Research Laboratory, 1990.

[Ama91] R.Amadio. Recursion over realisable structures. Information and Computation. 91(1):55-86,1991.

[AP90] M.Abadi, G.Plotkin. A PER model of polymorphism and recursive types. Proc. IEEE Sympo-sium on Logic in Computer Science, págs. 355-365, 1990.

[ASU85] A.Aho, R.Sethi, J.Ullman. Compilers: Principles, Techniques, and Tools. Addison-WesleyPub Co, 1985.

[Bar84] H.Barendret. The Lambda Calculus. Its Syntax and Semantics. North-Holland, 1984.

[BB85] C.Böhm, A.Berarducci. Automatic synthesis of typed λ-programs on term algebras . Theore-tical Computer Science, 39, págs. 135-154, 1985.

[BCC+96] K.Bruce, L.Cardelli, G.Castagna. The Hopkins Objects Group, G.Leavens, B.Pierce. On binarymethods. Theory and Practice of Object Systems, 1(3):221-242, 1996.

[BCD+93] K.Bruce, J.Crabtree, A.Dimock, R.Muller, T.Murtagh, R. van Gent. Safe and decidable typechecking in an object-oriented language. In Proc. ACM Symp. on ACM Conf. on Object--Oriented Programming: Systems, Languages and Applications. págs. 29-46, 1993.

[BCK94] K.Bruce, J.Crabtree, G.Kanapathy. An operational semantics for TOOPLE: A statical-ly-typed object-oriented programming language. Em Mathematical Foundations of Program-ming Semantics. págs. 603-626. LNCS 802. Springer-Verlag, 1994.

[BCP99] K.Bruce, L.Cardelli, B.Pierce. Comparing Object Encodings. Information and Computation,1999.

256 OM – Uma linguagem de programação multiparadigma

[BFSG98] K.Bruce, A.Fiech, A.Schuett, R.Gent. PolyTOIL: A type-safe polymorphic object-orientedlanguage. Versão actualizada de [BSG95]. Williams College, 1998.

[BHJL86] A.Black, N.Hutchinson, E.Jul, H.Levy. Object structure in the Emerald system. In Proc. ACMSymp. on ACM Conf. on Object-Oriented Programming: Systems, Languages and Applications.págs. 78-86, 1986.

[BL90] K.Bruce, G.Longo. A modest model of records, inheritance and bounded quantification. In-formation and Computation. 87(1/2):196-240, 1990.

[BM92] K.Bruce, J.Mitchell. PER models of subtyping, recursive types and higher-order polymor-phism. Proc. ACM Symp. on Principles of Programming Languages. págs. 316-327, 1992.

[BPF97] K.Bruce, L.Petersen, A.Fiech. Subtyping is not a good “Match” for object-oriented langua-ges”. ECOOP '97 Proceedings, LNCS 1241, Springer-Verlag, págs. 104-127, 1997.

[Bru94] K.Bruce. Safe type checking in a statically-typed object-oriented programming language. 20Proc. ACM Symp. on Principles of Programming Languages. págs. 285-298, 1994.

[BSG95] K.Bruce, A.Schuett, R.Gent. PolyTOIL: A type-safe polymorphic object-oriented language.Em Proc. 9th European Conference on Object Oriented Programming. págs. 26-51. Aarhus, Di-namarca, 1995.

[Bud95] T.Budd. Multiparadigm Programming in Leda. Addison Wesley Longman, 1995.

[Car86] L. Cardelli. Amber. Em Combinators and Functional Programming Languages. LNCS 242.Sprin0.ger Verlag, 1986.

[Car88] L. Cardelli. A semantics of multiple inheritance. Information and Computation 76:138-164.1988. Versão anterior em: Semantics of Data Types, LNCS 173, págs. 51-67. Springer Verlag.1984, 1988.

[Car88] L. Cardelli. Structural subtyping and the notion of power type. 23rd ACM Symposium onPrinciples of Programming Languages, 1988.

[Car89] F.Cardone. Relational semantics for recursive types and bounded quantification. ICALP,Berlin. LNCS 372:164-178. Springer, 1989.

[Car90] L. Cardelli. Notes about Fω≤

. Notas não publicadas disponíveis na página WWW do autor, 1990.

[Car94] L.Cardelli. Extensible records in a pure calculus of subtyping. C.A. Gunter and J.C. Mitchell,Editors. Theoretical Aspects of Object-Oriented Programming. MIT Press. págs. 373-425, 1994.

[Car97] L.Cardelli. Type Systems. Em Handbook of Computer Science and Engineering, chapter 103.CRC Press, 1997.

[Cas96] G.Castagna. Integration of parametric and “ad hoc” second order polymorphism in a calcu-lus with subtyping. Formal Aspects of Computing, 8(3):247-293, 1996.

[Cas98] G.Castagna. Foundations of Object-oriented Programming. Tutorial. ETAPS’98, Lisboa,1998.

[CCH+89] P.Canning, W.Cook, W.Hill, J.Mitchell, W.Olthoff. F-bounded polymorphism for object--oriented programming. Proc. of. Conf. on Functional Programming Languages and ComputerArchitecture, 1989.

[CDJ+89] L. Cardelli, J. Donahue, M. Jordan, B. Kaslow, G. Nelson. The Modula-3 type system. Conf.Rec. ACM Symp. on Principles of Programming Languages. págs. 202-212, 1989.

[CG92] P.Curien,G.Ghelli. Coherence of subsumption, minimum typing and the type checking in F≤.Mathematical Structures in Computer Science, 2(1), 1992.

[CGL92] G.Castagna, G.Gelli, G.Longo. A calculus for overloaded functions with subtyping. ACMConference on LISP and Functional Programming. págs. 182-192, 1992.

[CHC90] W.Cook, W.Hill, P.Canning. Inheritance is not subtyping. Proc. ACM Symp. on Principles ofProgramming Languages, 1990.

Bibliografia 257

[Chu36] A.Church. An Unsolvable Problem of Elementary Number Theory. American Journal of Ma-thematics. 58:345-363, 1936.

[Chu40] A.Church. A formulation of the simple theory of types. Journal of symbolic Logic. 5:56-68,1940.

[CM81] W.Clocksin, C.Mellish. Programming in Prolog. Springer-Verlag, Berlin, 1981.

[CM91] L.Cardelli, J.Mitchell. Operations on records . Mathematical Structures in Computer Science,1(1):3-48. 1991. In Theoretical Aspects of Object-Oriented Programming, MIT Press. 1994,1991.

[CMMS94] L.Cardelli, J.Mitchell, S.Martini, A.Scedrov. An extension of system F with subtyping. Infor-mation and Computation, 109(1/2):4-56, 1994.

[Coo89] W.Cook. A proposal for making Eiffel type-safe. ECOOP '89 Proceedings. págs. 57-72, 1989.

[Cop85] M.Coppo. A completeness theorem for recursively-defined types. Proc. ICALP, Berlin. LNCS194. Springer, 1985.

[CP89] W.Cook, J.Palsberg. A denotational semantics of inheritance and its correctness. Proc. ACMConf. on Object-Oriented Programming: Systems, Languages and Applications, 1989.

[CP94] G.Castagna, B.Pierce. Decidable Bounded Quantification. 21st Annual Symposium on Princi-ples of Programming Languages. Portland, 1994.

[CW85] L.Cardelli, P.Wegner. On understanding types, data abstraction and polymorphism . ACMComputing Surveys 17(4):471-522, 1985.

[dB72] N. de Bruijn. Lambda-calculus notation with nameless dummies. Indag. Math. 34(5):381-392,1972.

[DFP86] NJ.Darlington, A.Field. H.Pull. The Unification of Functional and Logic Languages. Em"Logic Programming: Functions, Relations and Equations". D.DeRoot, G.Lindstrom (editors).Prentice-Hall, 1986.

[DG87] L.DeMichiel, R.Gabriel. Common Lisp Object System overview. ECOOP '87 Proceedings.págs. 151-170. Springer, 1987.

[Dia90] A.M.Dias. Implementação de um sistema de programação em Lógica Contextual. Relatóriotécnico 4.91. Dep. Informática, Fac. de Ciências e Tecnologia, Univ. Nova de Lisboa, 1990.

[ES90] M.Ellis, B.Stroustrup. The Annotated C++ Reference Manual. Addison-Wesley, 1990.

[ESTZ94] J.Eifrig, S.Smith, V.Trifonov, A.Zwarico. Application of OOP Type Theory: State, Decidabi-lity, Integration. Proc. OOPSLA 94. págs. 16-30, 1994.

[Fel90] M.Felleisen. On the Expressive Power of Programming Languages, 1990.

[FHM94] K.Fisher, F.Honsell, J.Mitchell. A lambda calculus of objects and method specialisation. Nor-dic J. Computing. 1:3-37, 1994.

[Flo79] R.Floyd. The Paradigms of Programming. Communications of the ACM, 22(8):455-460,1979.

[FM95] K.Fisher, J.Mitchell. The Development of Type Systems for Object-Oriented Languages.Theory and Practice of Object Systems, 1(3), págs. 189-220, 1995.

[FM96] K.Fisher, J.Mitchell. Classes = Objects + Data Abstraction, 1996.

[FR99] K.Fisher, FJ.Reppy. The design of a class mechanism for Moby. Proc. SIGPLAN Conferenceon Programming Language Design and Implementation. Atlanta. Georgia, 1999.

[Gab85] J.Gabriel, T.Lindholm, E.Lusk, R.Overbeek. A Tutorial on the Warren Abstract Machine. Ar-gonne National Laboratory, 1985.

[Ghe93] G.Ghelli. Recursive types are not conservative over F≤. TLCA’93, International Conferenceon Typed Lambda Calculi and Applications, LNCS 664:146-162. Springer, 1993.

258 OM – Uma linguagem de programação multiparadigma

[Gir71] J.Girard. Une extension de l’interprétation de Gödel à l’analyse, e son application à l’élimi-nation des coupures dans l’analyse el la théorie des types. Proceedings of the Second Scandi-navian Logic Symposium, J.Fenstad ed. págs. 63-92. North-Holland, 1971.

[Gir72] J.Girard. Interprétation functionelle el élimination des coupures de l’arithmétique d’ordresupérieus . PhD thesis. Université Paris VII, 1972.

[GM83] A.Goldberg, D.Robson. Smalltalk-80: The Language and its Implementation. Addison-Wes-ley, 1983.

[Goo81] J.Goodwin. Why Programming Environments Needs Dynamic Data Types. IEEE Transa-ctions on Software Engineering, val.SE-7. No.5, 1981.

[GP96] G.Ghelli, B.Pierce. Bounded Existentials and Minimal Typing. Relatório preliminar, 1996.

[Gri71] R.Griswold, J.Poage, I.Polonsky. The SNOBOL4 Programming Language. 2ª edição. Engle-wood Cliffs, N.J. Prentice-Hall Inc, 1971.

[Gri83] R.Griswold, M.Griswold. The Icon Programming Language. Prentice Hall, 1983.

[Gun92] C.Gunter. Semantics of Programming Languages: Structures and Techniques. The MITPress, 1992.

[Har89] S.Haridi, S.Janson, J.Montélius, P.Brand, B.Hausman. The Andorra Project. Swedish Instituteof Computer Science. Research project proposal, 1989.

[Her89] M.Hermenegildo. High-Performance Prolog Implementation: the WAM and Beyond . Tuto-rial. 6º ICLP. Lisboa. Portugal, 1989.

[Hen92] F.Henglein. Dynamic typing. European Symposium on Programming. Rennes. França, 1992.

[Hog84] C.Hogger. Introduction to Logic Programming. Academic Press, 1984.

[HP95] M.Hofmann, B.Pierce. Positive subtyping. Proc. 22th Annual ACM Symposium on Principlesof Programming Languages, 1995.

[HU79] J.Hopcroft, J.Ullman. Introduction to Automata Theory, Languages, and Computation.Addison-Wesley, 1979.

[JG86] M.Jenkins, J.Glasgow. Programming Styles in Nial. IEEE Software. Janeiro, 1986.

[KB85] F.Kluzniak, J.Bien. Prolog for Programmers. Academic Press, 1985.

[KR78] B.Kernighan, D.Ritchie. The C Programming Language . Prentice-Hall, 1978.

[Kra83] G.Krasner. Smalltalk-80, Bits of History, Words of Advice. Addison Wesley, 1983.

[KRB91] G.Kiczales, des Rivières, D.Bobrow. The Art of the Metaobject Protocol. The MIT Press,1991.

[KMMN87] B.Kristensen, O.Madsen, B.Moller-Pedersen, K.Nygaard. The Beta Programming Language.Em Research Directions in Object-Oriented Programming. págs. 7-48. MIT Press. Cambridge,MA, 1987.

[KR93] S.Kamin, U.Reddy. Two Semantic Models of Object-Oriented Languages. Em C.Gunter,J.Mitchell. Theoretical Aspects of Object-Oriented Programming: Types, Semantics, and Lan-guage Design. The MIT Press, 1993.

[Lan65] P.Landin. A Correspondence Between Algol 60 and Church’s Lambda-Notation. Communi-cations of the ACM, 8, 1965.

[Ler96] X.Leroy. The Caml Light system, release 0.71 (reference manual). INRIA, França, 1996.

[Lev92] J.Levine, et al. Lex & Yacc. O'Reilly & Associates, 1992.

[LP91] W.LaLonde, J.Pugh. Subclassing ≠ subtyping ≠ is-a. Journal of Object-Oriented Programming,págs. 57-62, 1991.

[LRVD99] X.Leroy, D.Rémy, J.Vouillon, D.Doligez. The Objective Caml system release 2.04. INRIA,1999.

Bibliografia 259

[LSA77] B.Liskov, A.Snyder, R.Atkinson, C.Schaffert. Abstraction Mechanisms in CLU. Comm. ACM20:564-576, 1977.

[Mac62] J.MacCarty. Lisp 1.5 Programmer's Manual. MIT Press. Cambridge. Mass, 1962.

[Mau95] M.Mauny. Functional programming using Caml Light (tutorial). INRIA. França, 1995.

[Mey88] B.Meyer. Object-Oriented Software Construction. Prentice-Hall, 1988.

[Mey92] B.Meyer. Eiffel: the Language. Prentice-Hall, 1992.

[MH83] C.Mellish, S.Hardy. Integrating Prolog Into the Poplog Environment. IJCAI 1983. Karlsruhe.West Germany, 1983.

[Mit90] J.Mitchell. Toward a typed foundation for method specialisation and inheritance. Proc. 17thACM Symposium on Programming Language. págs. 109-124, 1990.

[Mor73] Morris. Types are not Sets. First ACM Symposium on Principles of Programming Languages.págs. 120-124, 1973.

[MP85] J.Mitchell, G.Plotkin. Abstract types have existential type. Proc. 12th Annual ACM Sympo-sium on Principles of Programming Languages, 1985.

[MR91] Q.Ma, J.Reynolds. Types, abstraction, and parametric polymorphism, part 2. Proc. Mathe-matical foundations of Programming Semantics. Springer-Verlag, 1991.

[MTH90] R.Milner, M,Tofte, R.Harper. The Definition of Standard ML. MIT Press, 1990.

[Omo92] S.Omohundro: The Sather 1.0 Specification. International Computer Science Institute, Berk-eley, 1992.

[OW97] M.Odersky, P.Wadler: Pizza into Java: Translating theory into practice. 24th ACM Sympo-sium on Programming Languages, Paris, França, 1997.

[PAC94] G.Plotkin, M.Abadi, L.Cardelli. Subtyping and parametricity. Proceedings, Ninth AnnualIEEE Symposium on Logic in Computer, Paris, France, 1994.

[Pas86] G.Pascoe. Encapsulators: A New Software Paradigm in Smalltalk-80. OOPSLA’86 Procee-dings. Portland, 1986.

[Pie93] B.Pierce. Simple type-theoretic foundations for object-oriented programming. Jounal ofFunctional Programming, 4(2):207-248, 1993.

[Pie93b] B.Pierce. Mutable Objects. “Working draft” disponível no sítio da Internet do autor, 1993.

[Pla91] J.Placer. Multiparadigm Research: A new direction in language design. ACM SIGPLANNotices, 26:3. págs. 9-17, 1991.

[Plo81] G.Plotkin. A Structural Approach to Operational Semantics. DAIMI Technical ReportFN-19. Computer Science Department. Aarhus University, 1981.

[PPT87] A.Porto, L.M.Pereira, L.Trindade. UNL Prolog User’s Guide Manual. Departamento de Infor-mática, Universidade Nova de Lisboa, 1987.

[PT93] B.Pierce, D.Turner. Statically Typed Friendly Functions via Partially Abstract Types. Rela-tório técnico ECS-LFCS-93-256, Universidade de Edinburgo, 1993.

[PT94] B.Pierce, D.Turner. Simple type-theoretic foundations for object-oriented programming.Jounal of Functional Programming, 4(2):207-248, 1994.

[Rém89] D.Rémy. Typechecking records and variants in a natural extension of ML . Conf. Rec. ACMSymp. on Principles of Programming Languages, 1989.

[Rém92] D.Rémy. Typing record concatenation for free. 19th ACM Symp. on Principles of Program-ming Languages. págs. 166-176, 1992.

[Rèv85] G.Rèvész. Introduction to Formal Languages. McGraw-Hill, 1985.

[Rey74] J.Reynolds. Towards a theory of type structure. in Colloquium sur la programmation. págs.408-423. LNCS 19. Springer-Verlag, 1974.

260 OM – Uma linguagem de programação multiparadigma

[Rey78] J.Reynolds. User Defined Types and Procedural Data Structures as ComplementaryApproaches to Data Abstraction. reimpressão em C.A. Gunter and J.C. Mitchell, Editors.Theoretical Aspects of Object-Oriented Programming. MIT Press, 1978.

[Rey80] J.Reynolds. Using Category Theory to Design Implicit Conversions and Generic Operators.In Theoretical Aspects of Object-Oriented Programming, MIT Press, 1980.

[Rey83] J.Reynolds. Types, abstraction, and parametric polymorphism. in Information Processing.R.Mason ed. North Holland. págs. 513-523, 1983.

[RV98] D.Rémy, J.Vouillon. Objective ML: An effective object-oriented extension to ML. TAPOS, 4,págs. 27-50, 1998.

[SCB+86] C. Schaffert, T. Cooper, B. Bullis, M. Kilian, C. Wilpolt. An introduction to Trellis/Owl. Proc.ACM Conf. on Object-Oriented Programming: Systems, Languages and Applications, 1986.

[Sch86] D. Schmidt. Denotational Semantics. W.C.Brown. Dubuque. Iowa, 1986.

[Sch94] D. Schmidt. The Structure of Typed Programming Languages. Foundations of ComputingSeries. The MIT Press, 1994.

[Sco73] D.Scott. Models for Various Type-free Calculi. Em Logic, Methodology and Philosophy ofScience IV. North-Holland. Amsterdam, 1973.

[Seb93] R.Sebesta. Concepts of Programming Languages. The Benjamin/Cummings Publishing Com-pany, Inc, 1993.

[Sha94] D.Shang. Covariant Specification. ACM SIGPLAN Notices. 29:12. págs. 58-65, 1994.

[Sha95] D.Shang. Covariant Deep Subtyping Reconsidered. ACM SIGPLAN Notices. 30(5):21-28,1995.

[SF94] A. Sabry, J.Field. Reasoning about Explicit and Implicit Representations of State. ACMSIGPLAN Workshop on STATE in Programming Languages, 1994.

[Sit] D.Sitaram. Programming in Schelog. http://cs-tr.cs.rice.edu/~dorai/.

[Smi83] MB.Smith. Reflexion and Semantics in Lisp. Proceedings of the 1984 ACM Principles of Pro-gramming LanguagesConference, 1983.

[SP94] M.Steffen, B.Pierce. Higher-Order Subtyping. IFIP Working Conference on ProgrammingConcepts, Methods and Calculi (PROCOMET), 1994.

[SS86] L.Sterling. E.Shapiro. The Art of Prolog. MIT Press, 1986.

[Ste84] G.Steele: Common List: The language. Digital Press, 1984.

[Sto77] J.Stoy: Denotational Semantics: The Scott-Strachey Approach to Programming LanguageTheory. The MIT Press, 1977.

[Str67] C. Strachey. Fundamental concepts in programming languages. Lecture notes for the Interna-tional Summer School in Computer Programming. Copenhagen, 1967.

[Sun 95] Sun Microsystems. The Java™ Language specification, version 1.0Beta. Sun Microsystems,1995.

[Wad89] P.Wadler. Theorems for free! Proc. 4th International Symposium on Functional ProgrammingLanguages and Computer Architecture. Springer-Verlag, 1989.

[Wan87] M.Wand. Complete type inference for simple objects. Proc. 2nd Annual IEEE Symposium onLogic in Computer Science. 19. Springer-Verlag, 1987.

[Wan89] M.Wand. Type inference for record concatenation and multiple inheritance. Proc. IEEESymposium on Logic in Computer Science. págs. 92-97, 1989.

[War77] D.Warren. Implementing Prolog: compiling predicate logic programs, DAI Report 39-40.University of Edinburgh, 1977.

[War83] D.Warren. An Abstract Prolog Instruction Set. SRI Technical Note 309. SRI International,1983.

Bibliografia 261

[War88] D.Warren. Implementation of Prolog, Lecture notes, Tutorial nº 3, Symp. on Logic Program-ming. Seattle. USA, 1988.

[Wei95] MM.Weiss. Data Structures and Algorithm Analysis. Benjamin Cumminngs Publishing Com-pany, 1995.

[Win93] G.Winskel. The Formal Semantics of Programming Languages: An Introduction. MITPress, 1993.