CAP. 1 – INTRODUÇÃO 1.1. História Originário do departamento de defesa dos USA, para a área de aplicações de sistema embebidas.

Começou em 1974. Em 1983 culmina com a edição do ANSI Satndard for Ada. Uma revião começou em 1988. Tem grandes preocupações com a portabilidade, de modo a dar em todas as arquitecturas. Em 1995, Ada 95 compreende uma linguagem Core mais um pequeno nº de Anexos especializados. 1.2. ENGENHARIA DE SOFTWARE Não se deve pensar que o Ada é apenas mais uma linguagem de programação. Ada é acerca de Engenharia de Software. Há 2 problemas maiors com o desenvolvimento do software: a necessidade de reutilização de componentes de software e a necessidade de se estabelecerem modos disciplinados de trabalhar. O Ada é fiável. A tipagem forte e características relacionadas asseguram que os programas tenham poucas surpresas. reduz os custos e riscos do desenvolvimento de software. Adiciona flexibilidade extra, por ex. com os tipos tagged extendidos, a facilidade da livraria hierárquica, e a grande habilidade para manipular ponteiro e referências. Como consequência, Ada 95 incorpora os benefícios das linguagens OO sem incorrer nas penetrantes sobrecargas de outras linguagens tais como o Smalltalk ou a insegurança trazida pela bas do C, no caso do C++. Uma outra inovação são os tipos protegidos para o modelo de tarefas. Isso permite uma mais natural e eficiente implementação de paradigams standard de acesso a dados partilhados. Isto beneficia a velocidade fornecendo primitivas de baixo nível como os semáforos. mais, a visão claramente orientda aos dados pelos tipos protegidos, casa naturalmente com o espírito do paradigam OO. Dois tipos de aplicação são muito sensíveis ao Ada – os muito grandes e os meuito críticos. Os primeiros exigem uma cooperação de vários elementos em equipa e a inegridade é crítica e mantida através da compilação. As mudanças de requisitos também são bem recebidas pelo Ada 95. A flexibilidade completa do Ada não é apropriada para os sistemas críticos, mas a fiabilidade intrínseca do kernel fortemente tipado é exactamente o que é requerido. 1.3. EVOLUÇÃO E ABSTRACÇÃO Cada avanço parece estar associado com a introdução de um nível de abstracção que remove detalhes desnecessários e nocivos do programa. No início dos 50s o Fortran introduziu a ‘abstracção de expressões’, o que permitiu expressões do tipo : X = A + B(I), e então o uso de registos máquina para avaliar a expressão foi completamente escondido do programador. O 2º avanço diz respeito à ‘abstracção de controlo’, com o algol 60; o fluxo de controlo é estruturado e pontos de controlo individuais não têm de ser numerados ou nomeados: if X = Y then P:=Q else A:0B e o compilador gera os goto e etiquetas. O 3º avanço foi a ‘abstracção de dados’. Isto significa separar os detalhes da representação dos dos dados das operações abstractas definidas sobre os dados. O Pascal introduziu uma certa quantidade de abstracção de dados como os tipos enumerados. Uma outra forma de abstracção de dados tem a ver com a visibilidade. O Ada foi, provavvelmente, a primeira linguagem prática a juntar todoas estas abstracções de dados. o Simula também foi importante niosto, com o seu conceito de classe. Não há definição precisa de OOP, mas a sua essência é uma forma flexível de abstracção de dados fornecendo a cpacidade de definir novas abstracções de dados em termos de antigos e permitindo selecção dinâmica de tipos. todos os tipos no Ada 83 são estáticos -> OBL. Contudo, o Ada 95 inclui as funcionalidades essenciais associadas com o OOP, tais como extensão de tipos e polimorfismo dinâmico. Resta saber como o Ada 95 fornece o que chamamos de ‘abstracção de objectos’ CAP. 2 – CONCEITOS SIMPLES 2.1. Metas Chave Ada é uma linguagem grande. Algumas das características do Ada são: - Legibilidade - Tipagem Forte – isto assegura que cada objecto tem um claramente definido conjunto de valores que pode tomar e previne confusão entre conceitos logicamente diferentes. Como consequência

muitos erros são detectados pelo compilador o qual em outra slinguagens (como o C) pode conduzir a um executável mas programa incorrecto. - Programação em Progaramas Grandes – encapsulação, compilação separada, gestão de biblioteca são necessa´rios para escrever programas grandes e portáveis e de fácil manutenção - Gestão de Excepções - Abstracção de Dados - portabilidade e mennutentabilidade extra pode ser conseguida se os detalhes da representação dos dados forem separdos das especificações das operações lógicas com os dados. - Programação Orientada a Objectos – para reutilização. Extensão de tipos (herança), polimorfismo e ligação tardia. - Tarefas – actividades paralelas - Unidades Genéricas – em muitos casos a lógica de parte de um programa é independente dos tipos de valores a sertem manipulados -> único template. particularmente útil para a construção de livrarias. - Interfaces – para comunicar com programas em outras linguagens. 2.2. Estrutura Geral Um objectivo importante da Engenharia de Software é a reutilização de pedaços de programa já existentes, de modo a que o novo código detalhado é mantido num mínimo. O conceito de uma biblioteca de componentes de programa emerge naturalmente e um aspecto importante de uma linguagenm de programação é pois a sua capacidade de aceder a itens de biblioteca. Um programa de Ada é um (sub)programa principal – main – que chama por serviços de outras unidade de biblioteca. O subprograma principal toma a forma de procedimento. As unidade de livraria podem ser subprogramas (procedimento ou funções) mas o mais provável é que sejam pacotes/packages. Um pacote é um grupo de itens relacionados tais como subprogramas mas podem conter também outras entidades. with Sqrt, Simple_IO; procedure Print_Root is use Simple_IO; X: Float; begin Get(X); Put(Sqrt(X)); end Print_Root; Entre is e begin podemos escrever declarações, que introduzem entidades que queremos manipular. Um valor é associado a X pelo chamar do procedimento get que está no nosso pacote Simple_IO. Escrevendo use Simple_IO, dános acesso imediato a facilidades no pacote Simple_IO. Se o tivéssemos omitido, teríamos de escrever Simple_IO.Get(X). Há 69 palavras chave. procedure e is são indicadas paraindicar a estrutura do programa. put e x são indicados como identificadores. Para clareza, escreveremos as palavras reservadas em minúsculas e bold e usaremos maiúsculas para iniciais das outras todas. Para toda uma série de nºs e impressões de cada resposta numa linha separada. podemos parar o programa algo arbitrariamente dando-lhe um valor de zero: with Sqrt, Simple_IO; procedure Print_Root is use Simple_IO; X: Float; begin Put(“Roots of various numbers”); New_Line(2); loop Get(X); exit when X = 0.0; Put(“Root of”); Put(X); Put(“is”); if X < 0.0 then Put(“not calculabel”); else Put(Sqrt(X));

end if; New_Line; end loop; New_Line; Put(“Program finished”); New_Line; end Print_Roots; os procedimentos New_Line e Put fazem parte do pacote Simple_IO. Ter mais de um subprograma com o mesmo nome é o que se chama sobrecarga. ex. Put com strings e com floats. A estrutura geral de parênteses deve ser observada; loop joga com end loop e if com end if. Todas as estruturas de controlo do Ada tem esta forma fechada em vez da forma aberta do Pascal e c que podem conduzir a programas incorrectos e/ou pobremente estruturados. A função Sqrt terá uma estrutura similar àquela do nosso subprograma principal. function sqrt(F:Float) return Float is R:Float; begin -- compute value of Sqrt(F) in R return R; end Sqrt; Vemos aqui a descrição dos parâmetros formais (neste caso apenas um) e o tipo do resultado. Os comentários começam com um duplo hífen. Note a distinção entre uma função, que retorna um resultado e é chamada como parte de uma expressão e um procedimento, que não tem um resultado e é chamado por uma instrução. O package Simple_IO terá 2 partes: a especificação que descreve a sua interface ao mundo exterior, e o corpo que contém os detalhes de como será implementado. Pode ser: package Simple_IO is procedure Get(F. out Float; procedure Put(F: in Float); procedure Put(S: in string); procedure New_Line(N: in Integer :=1); end Simple_IO; O parâmetro de Get é um out porque o efeito de uma chamada tal como Get(X); é transmitir um valor para fora do procedimento para o parâmetro actual X. Os outros são in porque o parâmetro vai para dentro do procedimento. Se for omitido é in por defeito. As funções só podem ter in. Note como o valor por defeito de 1 para o parâmetro de New_Line é indicado. O corpo do package para a Simple_IO conterá os corpos completos dos procedimentos e outro material de suporte à implementação e será algo do tipo: with Ada.Text_IO; package body Simple_IO is; … procedure Get(F: out Float) is … end Get; -- outros procedimentos similarmente end Simple_IO; A notação indica que Text_IO é um pacote filho do pacote Ada. De realçar que a ideia dos pacotes é uma das mais importantes conceitos em Ada. Um progaram deve ser concebido como um nº de componentes que fornecem serviços e que recebem serviços uns dos outros. O acesso ao pacote Satndard é automático. Exercício 2.2. – pg.16 2.3. O MODELO DOS TIPOS ESCALARES Um dos benefícios chave do Ada é a tipagem forte. podemos ilustrar com o tipo enumerado. declare type Colour is (Red, Amber, Green); type Fish is (Cod, Hake, Salmon);

X, Y: Colour; A, B: Fish; begin X:= Red; --ok A:= Hake; --ok B:= X; --illegal … end; A regra fundamental da tipagem forte é que não podemos associar um valor de um tipo a uma variável de um tipo diferente. 3 tipos enumerados são predefinidos no pacote Standard. Um é o type Boolean is (False, True); que desempenha um papel fundamental no controlo da fluxo: if X < 0.0 then. Os outros são Character e Wide_Caracther. Todos os outros tipos de dados são construídos a partir de tipos enumeriados e tipos numéricos. Um dos problemas dos tipos numéricos é como obter portabilidade e eficiência. É conveniente introduzir o conceito de tipo derivado. type Light is new Colour; são logicamente distintos. Podem ser convertidos. declare type Light is new Colour; C: Colour; L: Light; begin L:= Amber; -- a luz amber não a cor C:= L --ilegal C:=Colour(L) --conversão explícita ... end; Se escrevermos type My_Float is new Float; então My_Float terá todas as operações (+,-,etc.) de Float. Suponha que vai para um computador diferente, onde não há Float mas sim Long_Float. Então type My_Float is new Long_Float, será a única alteração necessária. melhor ainda, podemos definir type My_Float is digits 7; que fará com que My_Float seja baseado no mais pequeno tipo definido com pelo menos 7 dígitos. podemos também definir type My_Integer is range –1000_000..+1000_000. O ponto de tudo isto é que não é boa prática usar tipos numéricos predefinidos directamente quando se escrevem programas profissionais que precisem de ser portáveis. 2.4. ARRAYS E REGISTOS Os arrays podem ser anónimos mas os registos têm sempre um nome de tipo. Como ex. do uso de arrays suponha que queremos computar as sucessivas linhas do Triângulo de Pascal – as 10 promeiras linhas. podemos declarar: Pascal: array(0..10) of Integer e agora assumir que os valores correntes do array Pascal correpondem à linha n-1, com o componente Pascal(0) sendo 1, então a próxima linha pode ser calculada num array similar Next: Next(0) := 1; for I in 1 .. N-1 loop Next(I) := Pascal(I-1) + Pascal(I); end loop; Next(N) := 1; e depois o Next poderia ser copiado para o array Pascal. De notar que o array intermédio Next podia ser evitado se iterarmos para trás: Pascal(N) := 1; for I in reverse 1 .. N-1 loop Pascal(I) := Pascal(I-1) + Pascal(I); end loop; Podemos também declarar arrays de várias dimensões: Pascal2: array (0 .. 10, 0 .. 10) of Integer; e ficaríamos com:

Pascal2(N,0) := 1; for I in 1 .. N-1 loop Pascal2(N,I) := Pascal2(N-1,I-1) + Pascal2(N-1,I); end loop; Pascal2(N,N) := 1; se quiséssemos ter declarado com um nome: type Row is array (0 .. Size) of Integer; Pascal, Next: Row; type String is array (Positive range <>) of character; ilustra uma forma de declaração de tipo que se diz indefinido porque não diz qual a fronteira do array. Isso terá de ser fornecido quando o objecto é declarado: A_Buffer: String(1..80); O identificador Positive indica um subtipo do Integer. Um registo é um objecto compreendendo um nº de componentes com nome, tipicamente de diferentes tipos. type Buffer is record Data: String(1..80); Start, Finish: Integer; end record; um buffer individual pode então ser declarado por My_Buffer: Buffer; e para manipular… My_Buffer.Start := 1; My_Buffer.Finish := 3; My_Buffer.Data(1..3) := “XYZ”; Pascal(0..4) := (1, 4, 6, 4, 1); My_Buffer := ((‘X’, ‘Y’, ‘Z’, others => ‘ ‘), 1, 3); Exercício 2.4. pg.21 2.5. TIPOS DE ACESSO É o nome em Ada para tipos ponteiro. Permitem processamento de listas e são usados tipicamente com tipos record. O Ada fornece um elevado grau de fiabilidade e flexibilidade considerável através dos tipos de acesso. Os tipos de acesso têm de explicitamente indicar o tipo de dados a que se referem. Vamos restringir-nos àquele tipo de dados declarados numa pool de armazenamento 8o termo Ada para heap). Suponhamos que queremos declarar vários Buffers: type Buffer_Ptr is acess Buffer; Handle: Buffer_Ptr; … Handle:= new Buffer; isto aloca um buffer na pool de armazenamento e coloca uma referência para ele na variável Handle. Podemos depois referir-nos aos vários componentes do buffer indirectamente: Handle.Start := 1; Handle.Finish := 3; e podemos referir-nos ao registo completo como Handle:all. Os tipos de acesso são de particular valor para processamento de listas onde uma estrutura de registo contém um valor de acesso a outra estrutura de registo. Ex. cla´ssico: type Cell; type Cell_Ptr is acess Cell; type Cell is record Next: Cell_Ptr; Value: Data; end record; 2.6. ERROS E EXCEPÇÕES O aparecimento de uma excepção indica que algo de inabitual aconteceu e a sequência normal de execução foi quebrada. No nosso caso (raiz quadrada de valor negativo) a excepção pode ser Constraint_error que é predefinida e declarada no pacote Standard. Se não pensarmos nesta

hipótese, o programa termina. mas podemos, contudo prever uma excepção e tomar acções de remediação se ela ocorrer. De facto, podemos substituir a instrução condicional por: begin Put(Sqrt(X)); exception when Constraint_Error => Put(“not calculable”); end; Há 4 categorias de erros: - muitos erros são detectados pelo compilador – erros de pontuação, violaç~ºoes das refgras de tipos - Outros são detectatdos quando o programa é executado – divisão por zero -> aparece uma excepção. - O programa quebra as regras da linguagem – por ex. um programa não deve usar uma variável antes de ela ter um valor associado. Nestes caso o comportamento não é previsível e os erros são chamados de fronteira. - em situações mais extremas , há certos tipos de erros que leval a comportamentos imprevisíveis – diz-se que o comportameto é erróneo. Dissémos que o Ada tenta encontrar os erros o mais cedo possível. type Signal is (Danger, caution, Clear); if The_Signal = Clear then Open_Gates; Start_Train; end if; Os tipos enumerados em C não são fortemente tipados e essencialmente fornecem nomes para constantes inteiras com valores 0, 1 e 2 representando os 3 estados. Isto é porencialmente perigoso pois podemos associar o valor 4 enquanto em Ada tal correspondÊncia não é possível. Se pusermos um ponto e vírgula a mais, o programa em C abre sempre as portas – perogoso. Outra possibilidade é que o sinal == seja escrito, por engano, =. A igualdade torna-se numa associação e também retorna o resultado como o argumento do teste e assim as portas são sempre abertas e o comboio posto em movimento – perogoso. Claro que muitos erros não podem ser detectados em tempo de compilação: Index:=81; ... My_Buffer.Data(Index) := ‘x’; Tal é verificado em run time e aparece uma Constraint_Error. mas o C, sem dúvida, escreveria por cima de uma peça adjacente. Podemos declarar: subtype Buffer_Index is Integer range 1..80; Index: Buffer_Index ou Index: Integer range 1..80; e o erro seria detectado mais cedo. 2.7. TERMINOLOGIA Um glossário de termos está no apêndice 2. O termo estático refer-se a coisas que podem ser determinadas em tempo de compilação. Enquanto o termo dinâmico se refere a coisas determinadas durante a execução. Uma marca é algo que não é parte do programa mas mais como uma pista, o que é feito pelo constructo pragma. pragma Optimize(Space); CAP: 3 type Buffer is record Data: String(1..80); Start: Integer; Finish: Integer; end record; ABSTRACÇÃO

Start e Finish indexam as pontas da parte do buffer contendo informação útil. A associação e leitura pode ser feita directamente como vimos. Esta ssociação directa pode não ser muito avisada pois o utilizador pode inadvertidamente colocar valores inconsistentes nos componentes ou ler componentes do array sem nexo. Uma muito melhor aproximação é criar um Tipo Abstracto de Dados (ADT) e assim o utilizador não pode ver os detalhes internos do tipo mas pode apenas aceder através de várias chamdas a subprogramas que definem um protocolo apropriado. Isto pode ser feito usando um pacote contendo um tipo priovado. Vamos supor que o protocolo nos permite carregar o buffer e lê-lo um caracter de cada vez: package Buffer_System is --parte visível type Buffer is private; procedure Load(B: out Buffer; S: in String); procedure Get(B: in out Buffer; C: out Character); private --parte privada Max: constant Integer:=80; type Buffer is record Data: String(1..Max); Start: Integer := 1; Finish: Integer .= 0; end record; end Buffer_System; package body Buffer_System is procedure Load(B: out Buffer; S: in String) is begin B.Start := 1; B.Finish := S’length; B.Data(B.Start .. B.Finish) := S; end Load; procedure Get(B: in out Buffer; C: out Character) is begin C:= B.Data(B.Start); B.Start := B.Start+1; end Get; end Buffer_System; Há pois 2 visões do tipo Buffer: o cliente externo e os subprogramas servidores. O efeito lateral é que o utilizador pode declarar e manipular um buffer escrevendo simplesmente: My_Buffer: Buffer; … Load(My_Buffer, Some_String); … Get(My_Buffer, A_Chracter); Há 2 vantagens: uma é que o utilizador não pode inadvertidamente empregar mal o buffer e a 2ª é que a estrutura interna do tipo privado pode ser rearranjada se necessário e,desde que o protocolo seja mantido, o programa utilizador não precisa ser alterado. O design de protocolos de interface é a chave do desenvolveimento e posterior manutenção de programas grandes. Error: exception if S’Lenght > Max or B.Start <= B.Finish then raise Error; end if; Há uma excepção se houver uma tentativa de escrever em ciam de dados ou a string for muito grande. Get é marcada com in e out porque o procedimento lê o valor inicial do Buffer e actualiza-o. Exercício 3.1. – pg.29 3.2. – OBJECTOS E HERANÇA

O utilizador externo não pode ver os componentes mas pode apenas manipular o buffer através de vários subprogramas associados com o tipo. As operações primitivas (subprogramas declarados com o tipo privado e o próprio tipo) são chamados métodos em algumas linguagens. Outras ideias importantes no OOP são: - A capacidade de definir um tipo em termos de outro e especialmente como uma extensão de um outro; isto é extensão de tipos. - A capacidade de esse tipo derivado de herdar as operações primitivas do seu pai e também adicionar e substituir tais operações; isto é herança. - A capacidade de distinguir o tipo específico de um objecto em run time entre vários tipos relacionados e em particular seleccionar uma operação de acordo com o tipo específico; isto é polimorfismo (dinâmico). A forma mais natural de tipo para os propósitos de extensão é claro o registo onde podemos considerar a extensão como uma simples adição de componentes. O outro ponto é que se nós precisarmos de distinguir o tipo em run time então o objecto tem de conter uma indicação do seu tipo. Isto é fornecido por um componente escondido chamado tag. type Object is tagged record X_Coord: Float; Y_Coord: Float; end record; a palavra reservada tagged indica que os valores do tipo carregam uma tag em tempo de execução e que o tipo pode ser extendido. type Circle is new Object with record Radius: Float; end record; se não quisermos adicionar componentes: type Point is new Object with null record; Isto significa que é sempre claro numa delaração se o tipo é tagged ou não desde que contenha ambos tagged e with se o for. As operações primitivas de um tipo são aquelas declaradas na mesma especificação de pacote do tipo e que têm parâmetros ou resultados do tipo. Na derivação estas operações são herdadas. Podem ser sobrecarregadas e novas operações podem ser adicionadas. function Distance(O: in Object) return Float is begin return Sqrt(O.X_Coord**2 + O.Y_Coord**2); end Distance; function Area(O: in Object) return Float is begin return 0.0; end Area; function Area(C: in Circle) return Float is begin return Pi * C.Radius**2; end Area; que sobrecarrega. podemos resumir estas ideias dizendo que a especificação é sempre herdada enquanto a implementação pode ser herdada mas pode ser substituída. Um tipo derivado deve ser declarado no mesmo pacote do seu pai. package Geometry is type Object is tagged ...; function Distance(O: in Object) return Float; function Area(O: in Object) return Float; type Circle is new Object with …; function Area(C: in Circle) return Float; type Point is new Object with null record; end Geometry;

package body Geometry is … --corpos da função Distance e das 2 funções Area end Geometry; para converter: O: Object := (1.0, 0.5); C: Circle := (0.0, 0.0, 43.7); O := Object(C); C:= (O with 41.2); Concluimos dizendo que um tipo privado pode também ser tagged type Shape is tagged private; Se quisermos que o tipo Shape seja derivado de Objecto e mantenha os componentes adicionais escondidos: with Geometry; package Hidden_Shape is type Shape is new Geometry.Object with private; --visão do cliente … private type Shape is new Geometry.Object with --visão doservidor record -- os componentes privados end record; end Hidden_Shape; Exercício 3.2 – pg. 34 3.3. CLASSES E POLIMORFISMO Contudo, é muito importante notar que uma operação não pode ser tirada enm um componete pode ser removido. Desde que têm estas propriedades comuns é natural que devemos ser capazes de manipular um valor de qualquer tipo na hierarquia sem saber exactamente que tipo é, desde que usemos apenas as propriedades comuns. Esta manipulação é feita através do conceito de classe. Um conjunto de tipos como os que vimos é uma classe. Associado com cada classe está um tipo chamado a class wide type, que para o conjunto com raiz em Object é Object’Class. function Moment(OC: Object’Class) return Float is begin return OC.X_Coord * Area(OC); end Moment; Esta função tem um parâmetro formal da CWT Object’Class. Isto significa que pode ser chamada com um parâmetro actual cujo tipo seja um qualquer tipo específico da classe, compreendendo o conjunto de todos os tipos derivados de Object. Assim, podemos escrever: C: Circle... M: Float; M:=Moment( C ); Note que a função particular Area a ser chamada não é conhecida até à execução. A escolha depende do tipo específico do parâmetro e isso é determinado pela tag. Esta selecção é conhecida co o despacho e é um aspecto vital do comportamento dinâmico fornecido pelo polimorfismo. O que acontece se: function Moment(O: Object) return Float is begin return O.X_Coord * Area(O); end Moment; Isto retirna sempre zero porque a função Area para um Object retorna sempre zero. Podemos claro sobrecarregar: function Moment(C: Circle) return Float is begin return C.X_Coord * Area(C); end Moment;

Mas isto é tedioso pois requer duplicação desnecessária de código. Uma maior vantagem de usar operações em class wide tal como Moment é que um sistema usando-o pode ser sobrecarregado, compilado e tstado sem saber todos os tipos específicos a que será aplicado. mais, podemos então adicionar mais tipos ao sistema sem recompilação. Isto cria uma interface flexível e xtensível ideal para construir um sistema a partir de componentes reutilizáveis. Uma dificuladade com a flexibilidade fornecida pelas CWT é que não podemos saber que espaço vi ser ocupado por um objecto arbitrário porque o tipo pode ser extendido. Assim, sempre que declaramos um objecto de uma CWT tem de ser inicializado. Uma restrição similar é que não podemos ter um array de CW componentes (mesmo que inicializados) porque os componentes podem ser de diferentes tipos específicos e assim de diferentes tamanhos e impossível de indexar eficientemente. Uma consequência destas desnecessa´rias restrições é que é natural usar tipos de acesso com OOP pois não há problema em apontar objectos de diferentes tamanhos em alturas diferentes. type Pointer is acess Object’Class; type Cell; type Cell_Ptr is acess Cell; type Cell is record Next: Cell_Ptr; Element: Pointer; end record; Podemos agora facilmente processar os objectos da lista e por ex. computar o momento total do conjunto de objectos chamando a função: function Total_Moment(The_List: Cell_Ptr) return Float is Local: Cell_Ptr:= The_list; Result: Float := 0.0; begin loop if Local = null then --fim da lista return Result; end if; Result := Result + Moment(Local.Element.all); Local := Local.Next; end loop; end Total_Moment; Terminamos considerando os tipos abstractos. por vezes convém declarar um tipo como fundação de uma classe de tipos com certas propriedades comuns mas sem permitir que objectos do tipo original sejam declarados. package Objects is type Object is abstract tagged record X_Coord: Float; Y_Coord: Float; end record; function Distance(O: in Object) return Float; function area(O: in Object) return Float is abstract; end Obejcts; é ilegal declarar um objecto de um tipo abstracto e um subprograma abstracto não em corpo e assim não pode ser chamado. Exercício 3.3. – pg. 39 3.4. GENERICIDADE Nesta secção introduziremos o conceito complementar de polimorfismo estático: a escolha do tipo é feita estaticamente em tempo de compilação. O objectivo é a reutilização. Precisamos de um meio de escrever peças de software que possam ser parametrizadas como requerido por diferentes tipos. Em Ada isto é feito pelo mecanismo genérico.

generic type Num is digits <>; package Float_IO is … procedure Get(Item: out Num; … ); procedurte Put(Item: in Num; …); … end Float_IO; O único parâmetro genérico é Num e a notação digits<> indica que que tem de ser um tipo de float point. Uma isntanciação pode ser: package My_Float_IO is new Float_IO(My_Float); 3.5. TERMINOLOGIA DA ORIENTAÇÃO A OBJECTOS O únco termo comum com o Smalltalk e C++ é a herança. Tipos de registo não tagged são chamdos de constructos nas outras linguagens. Muitas linguagnes usam class para denotar o que o Ada chama um tipo específico tagged (ou mais especificamente um ADT consistindo num tipo tagged mais as operações primitivas). A razão para esta diferença é que o Ada usa a palavra class para se referir a um grupo de tipos relacionados. Algumas linguagnes usam o termo class para ambos tipos específicos e o grupo de tipos. Operações primitivas de tipos tagged em Ada são chamados métodos ou funções virtuais pelas outras linguagens. Tipos abstractos correspondem a classes abstractas em muitas linguagnes. O conceito de subtipo não tem correspondência em linguagens que não têm verificação de gamas e não tem relação com subclasse. 3.6. TAREFAS ... estudar só se for preciso... ver TFs CAP. 4 PROGRAMAS E BIBLIOTECAS 4.1. A BIBLIOTECA HIERÁRQUICA Um programa completo é fomado por várias unidades compiladas separadamente. Para evitar colisão de nomes (tipo Error_Messages, Debug_Info, que todos os programadores te^m tendência a usar em diversos síotios do programa), o Ada tem um esquema de nomes hierárquico no nível de livriaria. Assim, um pacote Parent pode ter um pacote filho com o nome Parent.Child. De modo a diminuir o perigo de colisão de nomes a biblioteca predefinida compreende apenas 3 pacotes cada um dos quais com um nº de filhos: System, Interfaces e Ada. O pacote Ada, que é o que nos interessa agora, é simplesmente: package Ada is pragma Pure(Ada); --tão branco como aneve end Ada; Pacotes filhos importantes são: Numerics – contém a biblioteca maemática Charcater – contém pacotes para manipular as strings Text_IO, Sequential_IO e Direct_IO – fornecem facilidade de input/output Muitas unidades de biblioteca são pacotes e mesmo os subprogramas são pacotes. Uma unidade filho pode aceder à informação na área privada do seu pai. Deve-se notar ainda que um pacote filho não precisa de uma cláusula with ou use para o seu pai. 4.2. INPUT – OUTPUT Todas as entradas e saídas são executadas em termos de outras características da linguagem. Input-Output é apenas um serviço fornecido por um ou mais pacotes do Ada. Esta filosofia arrisca aportabilidade, daí o ARM que descreve certos pacotes standard que estarão disponíveis em todas as implementações. Vamos restringir-nos, para já, a texto simples.

A menos que especificado, toda a comunicação é entre 2 ficheiros satndadrd e vamos assumir que o input é do teclado e a saída o écran. Não vamos ver todos os detalhes do Text_IO, mas algumas coisas, sim. with Ada.IO_Exceptions; package Ada.Text_IO is type Count is … -- um tipo integer … procedure New_Line(Spacing: in Count:=1); procedure Set_Col(To: in count); function Col return Count; … procedure Get(Item: out Character); procedure Get(Item: out String); procedure Put(Item: in Character); procedure Put(Item: in String); procedure Put_Line(Item: in String); … -- packages integer_IO and float_IO … end Ada.Text_IO; ex: Set_Col(Col + 10); Put(‘A’); Put(“This is a string of characters”); Vamos supor que o tipo My_Float foi declarado tendo 7 dígitos decimais. Put(12.34); -- “s1.234000E+01” Put(12.34,3,4,2); -- “ss1.2340E+1” Put(12.34, 3, 4, 0); -- “s12.3400” O campo defeito é o menor que acomoda todos os valores do tipo My_Integer permitindo ainda um sinal de menos/negativo. No nosso caso/ex: o campo defeito de My_Integer é 8: Put(123); -- “sssss123” Put(-123); -- “ssss-123” Put(123, 4); -- “s123” Put(123, 0); -- “123” C: Character; .. ex. de comunicação com o utilizador. Put(“Do you want to stop? answer Y if so.); Get(C); if C = ‘Y’ then Para pequenos programas que nãoprecisam ser portáteis, como o Ada tem uma série de pacotes não genéricos (ex: Ada.Integer_Text_IO e Ada.Float_Text_IO), podemos simplesmente usá-los: use Ada.Integer_Text_IO, Ada.Float_Text_IO; e depois podemos simplesmente chamar Put e Get. Exercício 4.2. – pg.50 4.3. BIBLIOTECA NUMÉRICA package Ada.Numerics is pragma Pure(Numerics); Argument_Error: exception; Pi: constant := 3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511; e: constant := 2.71828_18284_59045_23536_02874_71352_66249_77572_47093; end Ada.Numerics; Um filho deste fornece as funções elementares, tais como Sqrt e é uma outra ilustração do uso do mecanismo de genericidade. A sua especificação é: generic type Float_Type is digits <>; package Ada.Numerics.Generic_Elementary_Functions is

function Sqrt(X: Float_Type’Base) return Float_Type’Base; … -- and so on end; Outra vez há apenas um parâmetro genérico simples dando o tipo float. Em ordem a chamar a função Sqrt temos primeiro de instanciar o pacote genérico talo como fizémos para Float_IO, assim: package My_Elementary_Functions is new Generic_Elementary_Functions(My_Float); use My_Elementary_Functions; E podemos então escrever uma chamada a Sqrt directamente. Dois outros pacotes importantes são aqueles para geração de nºs aleatórios. Cada contém uma função Random. Um etorna um valor do tipo Float entre 0 e 1 e o outro retorna um valor aleatório de um tipo discreto. A especificação é: generic type Result_Subtype is (<>); package Ada.Numbers.Discrete_Random is type Generator is limited private; function Random(Gen: Generator) return Result_Subtype; … --mais outras facilidades end Ada.Numerics.discrete_Random; A forma do parâmetro formal genérico, indica que o tipo actual tem de ser um tipo discreto. Uma utilização pode ser: use Ada.Numerics; type Coin is (Heads, Tails); package Random_Coin is new Discrete_Random(Coin); use Random_Coin; G: Generator; C: Coin; … loop C := Random(G); … end loop; … 4.4. EXECUTANDO UM PROGRAMA tabuada da multiplicação até 10. with Ada.Text_IO, Ada.Integer_Text_IO; use Ada.Text_IO, Ada.Integer_Text_IO; procedure Multiplication_Tables is begin for Row in 1 .. 10 loop for Column in 1 .. 10 loop Put(Row * Column, 5); end loop; New_Line; end loop; end Multiplication_Tables; No ex: seguinte vamos reescrver o procedimento Print_Roots da secção 2.2. with Ada.Text_IO; with Ada.Float_Text_IO; with Ada.Numerics.Elementary_Functions; procedure Print_Roots is use Ada.Text_IO; use Ada.Float_Text_IO; use Ada.Numerics.Elementary_Functions; X: Float;

begin Put(“Roots of various numbers”); … -- ando so on as before end Print_Roots; Se quisermos uma versão mais portável: with Ada.Text_IO; with Ada.Numerics.Elementary_Functions; procedure Print_Roots is type My_Float is digits 7; package My_Float_IO is new Ada.Text_IO.Float_IO(My_Float); use My_Float_IO; package My_Elementary_Functions is new Ada.Numerics.Generic_Elementary_Functions(My_Float); use My_Elementary_Functions; X: My_Float; begin Put(“Roots of various numbers”); … -- ando so on as before end Print_Roots; Para evitar escrever e compilar o mesmo código, vamos colocá-lo num pacote nosso, pronto a usar sempre que necessário. with Ada.Text_IO; with Ada.Numerics.Generic_Elementary_Functions; package Etc is type My_Float is digits 7; type My_Integer is range –1000_000 .. + 1000_000; package My_Float_IO is new ada.Text_IO.float_IO(My_Float); package My_Integer_IO is new Ada.Text_IO.Integer_IO(My_Integer); package My_Elementary_Functions is new Ada.Numerics.Generic_Elementary_Functions(My_Float); end Etc; Depois de compilar Etc, uma possível utilização é: with Ada.Text_IO, Etc; use Ada.Text_IO, Etc; procedure Program is use My_Float_IO, My_Integer_IO, My_Elementary_Functions; … … end Program; Uma aproximação alternativa a declarar tudo no pacote etc é primeiro compilar um pacote contendo apenas os tipos My_Float e My_Integer e depois compilar a s várias instâncias como pacotes individuais de biblioteca (relembrar que uma instanciação pode ser uma unidade de biblioteca). Com esta aproximação apenas precisamos de incluir (com cláusulas with) os pacotes particulares que são requeridos pelo nosso programa. Um dos maiores benefícios do Ada é que a consistência é assegurada entre unidades compiladas separadamente. um assunto relacionado é o das dependências: - Um body depende da correspondente especificação - um child depende da especificação do pai - uma unidade depende da especificação daquelas que são mencionadas com cláususlas with A regra chave é que uma unidade pode apenas ser compilada se todas de que depende estão presentes no ambiente da biblioteca. Exercício 4.4 – pg. 56

PARTE 2 – ASPECTOS ALGORÍTMICOS O cap. 5 lida com os detalhes lexicais e pode ser saltado. O cap. 8 é longo devido à notação nomeada para agregados de arrays que é uma característica importante para escrever programas legíveis. O cap. 10 é acerca dos tipos de acesso que são mais flexíveis que em Pascal e C/C++ para além de prevenirem as referências dangling que facilmente causam crashes em C. Por outro lado, aceder a tipos subprograma é particularmente importante para escrever programas que interfaceiam com sistemas em outras linguagens. CAP: 5 – ESTILO LEXICAL 5.1. ELEMENTOS LÈXICOS As letras do alfabeto podem ser acentuadas. Outros caracteres gráficos tais como ! podem também ser usados em strings e literais. Os delimitadores compostos não devem ter espaços: => usado em agregados, casos, etc. .. para gamas/ranges ** exponenciação := atribuição /= não igual >= maior que ou igual <= menor que ou igual << e >> label brackets <> a caixa para parametrização de tipos 5.3. IDENTIFICADORES identifier ::= identifier_letter {[underline] letter_or_digit} letter_or_digit ::= identifier_letter | digit Vemos que têm de começar por letra. identificadores que difiram apenas no facto de serem maiúsculas ou minúsculas são considerados o mesmo. Mas, uma boa convenção é usar minúsculas para palavras reservadas e capitalizar todas as outras. 5.4. NÚMEROS Ou literais numéricos. Os literais reais contêm sempre um ponto decimal enquanto os literais inteiros nunca. O Ada é restrito na mistura de tipos. É ilegal usar um literal inteiro onde o contexto pede um literal real e vice-versa. 123_456_789 só para tornar mais fácil a leitura. Nos reais deve haver obrigatoriamente pelo menos um dígito de cada lado do ponto decimal. 9.84E1 98.4e0 984.0e-1 0.984E+2 984e-1 não é permitido. 19E2 190e+1 1900E+0 mas não 19000e-1 nem 1900E-0 No caso da base ser diferente de 10 2#111# 16#A#E2 é 10x162=2560 2#101.11# Note que um literal numérico não pode ser negativo. Uma forma como –3 consiste de um literal precedido pelo operador unário menos. 5.5. COMENTÁRIOS Não há hipótese de, no Ada, inserir um comentário no meio de uma linha. CHECKLIST 5 O facto de termos mínusculas ou maiúsculas é irrelevante em todos os contextos excepto em strings e literais de caracteres. Carácter underline são significativos em identificadores mas não em literias numéricos Não são permitidos espaços em elementos léxicos, excepto em strings, literais de caracteres e comentários. A presença ou ausência de um pomto distingue os literias reais dos inteiros Os literias numéricos não podem ser sinalizados Zeros não significativos são permitidos em todas as partes de um literal numérico. CAP.6 – TIPOS ESCALARES 6.1. DECLARAÇÕES DE OBJECTOS E ATRIBUIÇÕES Os objectos são variáveis ou constantes. I: Integer; P: Integer := 38;

I, J, K: Integer; P ,Q, R: Integer := 38; Neste 2º caso a todos (P,Q e R) é dado o inicial valor de 38. Se um programa usar o valor indefinido de uma variável não inicializada, o seu comportamento será imprevisível – o programa terá um erro de fronteira/bounded error. Uma maneira comum de dar um valor a uma variável é usando uma atribuição: I := 36; P := Q + R; Não é possível a uma instrução de atribuição dar o mesmo valor a várias variáveis. Só na declaração, daí a diferença entre declaração (é uma forma abreviada de várias atribuições) e atribuição. Claro que uma constante deve ser inicializada na sua declaração, caso contrário será inútil: Pi: constant Float := 3.14159_26536; Pi: constant := 3.14159_26536; é uma boa prática. Mas note-se que o tipo não pode ser omitido nas declarações de variáveis numéricas. Tecnicamente isto é uma declaração de um nº e meramente fornece um nome para o nº. 6.2. BLOCOS E ÂMBITOS / SCOPES O mais simples fragmento de um texto que inclua declarações e instruções é um bloco. declare I: Integer := 0; begin I := I + 1; end; Os blocos pode ser indefinidamente aninhados. Um bloco é uma instrução. No fim do bloco todas as coisas que foram declaradas nesse bloco deixam de existir. declare I: Integer := 0; K: Integer := I; begin é permitido. A ordem é importante – em jargão, ‘elaboração linear de declarações’. Scope e Visibilidade são diferentes. Scope é a região do texto onde uma entidade tem algum efeito. No caso de um bloco o scope de uma variável (ou constante) estende-se do início da sua declaração até ao fim do bloco. Dizemos que é visível num dado ponto se o seu nome puder ser utilizado para se referir a ela nesse ponto. Um objecto é escondido por uma declaração de um novo objecto com o mesmo identificador. 6.3. TIPOS Um tipo é caracterizado por um conjunto de valores e um conjunto de operações primitivas. Todos os tipos têm um nome o qual é introduzido na declaração. A ideia de uma forma lexical representar 2 ou mais coisas diferentes é conhecida por overloading/sobrecarga. A tipagem forte permite detectar muitos erros cedo, em compilação. type Colour is (Red, Amber, Green); 6.4. SUBTIPOS Um subtipo caracteriza um conjunto de valores que é apenas um subconjunto de valores de algum tipo. O subconjunto é definido por meio de uma restrição. O subtipo toma todas as operações. subtype Day_Number is Integer range 1..31; 1..31 é a restrição de gama. D: Day_Number; I: Integer; … D := I; é legal embora possa dar Constraint_Error. I := D; funciona sempre. D: Integer range 1..31; podia ter sido escrito e evitar a def. de um subtipo. subtype Day_Number is Integer; é legal mas não tem grande utilidade. subtype Feb_Day is Day_Number range 1.29; é legal – subtipo de subtipo.

Os exemplos acima mostraram restrições com froneiras estáticas. Mas, em geral, as fronteiras podem ser dadas por expresões arbitrárias e assim o conjunto de valores de um subtipo não necessita de ser estático. Mas o tipo é. Em capítulos posteriores, encontraremos vários contextos nos quais uma restrição explícita não é permitida; um subtipo tem de ser introduzido para estes casos. Referimo-nos a um nome de um subtipo como uma marca de subtipo e à forma consisitindo de uma marca de subtipo seguida de uma restrição opcional como uma indicação de subtipo como se mostra pela sintaxe: subtype_mark ::= subtype_name subtype_indication ::= subtype_mark [constraint] Ou seja, há casoonde se pode usar uma marca mas a indicação é mais geral e é permitida em declarações de objectos. O objectivo dos subtipos é detectar erros mais cedo e aumentar a eficiência do programa. 6.5. TIPOS NUMÉRICOS SIMPLES P: Integer range 1..I+J; é legal. O mínimo valor do tipo Integer é dado por Integer’First e o máximo valor por Integer’Last. Estes são os nosso primeiros exemplos de atributos. Dois subtipos úteis são: subtype Natural is Integer range 0..Integer’Last; subtype Positive is Integer range 1..Integer’Last; subtype Chance is Float range 0.0 .. 1.0; / é a divisão inteira e trunca na direcção de zero. /= é diferente. Se tivermos um nº Integer e outro Float, temos de escrever Float(I) + F ou Integer(F) + I. Conversões de Float para Integer arredondam e não truncam. 7/3 = 2 7 rem 3 = 1 (-7)/3 = -2 (-7) rem 3 = -1 7/(-3) = -2 7 rem (-3) = 1 (-7)/(-3) = 2 (-7) rem (-3) = -1 o resto e o quociente estão sempre relacionados por (I/J) * J + I rem J = I O sinal do resto é sempre igual ao do primeiro operando, no rem. A operação mod, por outro lado, tem um comportamento de incremento uniforme. Podemos olhar para mod como dando o resto correspondente à divisão com truncagem na direcção do menos infinito. Assim: 7 mod 3 = 1 7 mod (-3) = -2 (-7) mod 3 = 2 (-7) mod(-3) = -1 O sinal do resultado é sempre igual ao do 2º operando. Não podemos A**B**C temos de usar parênteses 6.6. TIPOS ENUMERADOS type Colour is (Red, amber, Green); type Stone is (Amber, Beryl, Quartz); Normalmente tiramos qual é do contexto, mas em casos em que isso não seja possível, podemos qualificar o literal colocando-o entre parênteses e precedê-lo por uma marca de subtipo e um ‘ Colour’(Amber) Um tipo enumerado vazio não é permitido. subtype weekday is Day range Mon..Fri; D: Weekday; ou D: Day range Mon..Fri; Os atributos First e Last aplicam-se a tipos enumerados e subtipos: Colour’First = Red Há atributos funcionais predefinidos/construídos: Colour’Succ(Amber) = Green Colour’Pos(Red) = 0 O oposto de Pos é Val Colour’Val(0) = Red Red < Green is True 6.7. O TIPO BOOLEANO type Boolean is (False, True);

if Today = sun then Tomorrow := Mon; else Tomorrow := Day’succ(Today); end if; As precedências de and, or e xor são iguais mas menores que qualquer outro operador. B and C or D é ilegal O not tem precedência maior. 6.8. CLASSIFICAÇÃO DE TIPOS Os records podem ser taggados e parametrizados com os chamados discriminantes, como veremos. Os atributos Pred e Succ (mas não Pos e Val9 também se aplicam a tipos reais, retornando o número implementado adjacente. S’Range é equivalente a S’First..S’Last. Integer´Min(5,10) = 5 Float(I) é uma conversão enquanto Integer’(I) é uma qualificação, que se usa normalmente para eliminar a ambiguidade. Assim Positive’(I) checa se I é positivo. 6.9. SUMÁRIO DAS EXPRESSÕES in e not in permitem-nos testar se um valor está dentro de uma gama específica ou não (incluindo os valores fronteira) ou se satisfaz uma restrição implicada por um subtipo: I not in 1..10 I in Positive Today in Weekday Notar que há uma das situação em que temos de usar uma marca de subtipo em vez de uma indicação de subtipo. Não podíamos Today in Day range Mon..Fri mas podíamos Today in Mon..Fri No and e no or não há ordem de avaliação dos operandos. No caso do and then e or then o operando esquerdo é sempre avaliado primeiro. Suponhamos que temos de testar I/J > K e queremos evitar o risco de J ser zero: J/=0 and then I/J > K. Também não podem ser misturados sem parênteses. Constraint_Error aparece/aplica-se a todos os tipos de violações de gamas. CHECKLIST 6 Declarações e instruções são terminadas por ponto e vírgula Inicialização, tal como atribuição, usa := Qualquer valor inicial é avaliado para cada objecto numa declaração Elaboração de declarações é linear O identificador de um objecto não pode ser usado na sua própria declaração Cada definição de tipo introduz um tipo bem distinto Um subtipo não é um novo tipo mas meramente uma “abreviatura” para um tipo com uma possível restrição Um tipo é sempre estático, um subtipo não precisa de o ser Não é permitido modo misto na aritmética Distinguir mod e rem para números negativos Exponenciação com expoente negativo apenas se aplica a tipos reais Tomar cuidado com as precedências dos operadores unários Um tipo escalr não pode ser vazio, um subtipo pode Max, Min, Pos, Val, Succ e Pred em subtipos são o mesmo que nos tipos base First e Last são diferentes para subtipos Qualificação usa um ‘ Ordem de avaliação de operadores binários não está definida Distinguir entre and e or e and hen e or then CAP. 7 – ESTRUTURAS DE CONTROLO Há 3 estruturas de controlo sequencial: if, case e loop 7.1. INSTRUÇÃO IF if Hungry then

Eat; end if; Notar que o end if é sempre precedido de um ponto e vírgula. Isto é porque os pontos e vírgula terminam instruções em vez de as separar como no Pascal. if Today = Sun then Tomorrow:=Mon; else Tomorrow := Day’Succ(Today); end if; O Ada não é uma linguagem de expressões, por isso não podemos Tomorrow := if Today = Sun then Mon else Day’succ(Today) end if; if A = 0.0 then -- caso linear else if B**2 – 4.0*A*C >= 0.0 then -- raízes reais else -- raízes complexas end if; isto é feio, logo: if A=0.0 then -- caso linear elseif B**2 – 4.0*A*C >= 0.0 then -- raízes reais else -- raízes complexas end if; Os elseifs não são a solução pois obscurecem a simetria e a exclusão mútua dos 4 casos. 7.2. INSTRUÇÃO CASE case order is when Left => Turn_Left; when Right => Turn_Right; when Back => Turn_Back; when On => null; end case; case Today is when Mon | Tue | Wed | Thu => Work; when Fri => Work; Party; when Sat | Mon => null; end case; Se valores sucessivos tiverem a mesma acção: when Mon..Thu => Work; case Today is when Mon..Thu => Work; when Fri => Work; Party; when others => null; end case; Exemplos de gamas discretas são: Mon..Thu Day range Mon..Thu Weekday Weekday range Mon..Thu case Weekday’(Today) is when Mon..Thu => Work; when Fri => Work; Party; end case; Mas se Today tomar um valor não em Weekday, aparece uma Constraint_Error. . Todos os possíveis valores da expressão depois de case têm de ser cobertos uma e só uma vez. . Todos os valores e gamas depois de when devem ser estáticas - Se others é usado deve ser o último

7.3. INSTRUÇÃO DE LOOP loop sequence_of_sattements end loop;

10.7. ACESSO A SUBPROGRAMAS A capacidade de passar subprogramas como parâmetros de outros subprogramas. Assim , no Ada 95 um tipo de acesso pode referir-se a um subprograma; tal acesso a um valor de um subprograma pode ser criado pelo atributo Access e um subprograma pode ser chamado indirectamente por desreferenciação de tal valor de acesso: type Math_Function is acess function (F: Float) return Float; Do_It: Math_Function; X, Theta: Float; e o Do_It pode então apontar para funções tais como Sin, Cos, Tan e Sqrt que assumimos que tenham especificações tais como: function Sin(X: Float) return Float; podemos depois atribuir um acesso apropriado ao valor de um subprograma: Do_It := Sin’Acess; e depois: X:= Do_It(Theta); equivalente a X:= Do_It.all(Theta) mas o .all só é preciso se não houver parâmetros. Este mecanismo pode ser usado para programar selecção dinâmica geral e para passar subprogramas como parâmetros. O procedimento ex. seguinte aplica a função passada com parâmetro a todos os elementos de um array. procedure Iterate(Func: in Math_Function; V: in out Vector) is begin for I in V’Range loop V(I) := Func(V(I)); end loop; end Iterate; A_Vector: Vector := (100.0, 4.0, 0.0, 25.0); .. Iterate(Sqrt’Acess, A_Vector); --A_Vector é agora (10.0, 2.0, 0.0, 5.0) o mesmo tipo de mecanismo se pode aplicar a procedimentos. type Integrand is acess function (X: Float) return Float; function Integrate(F: Integrand; A,B : Float) return Float; e então: Area:= Integrate(Log’Acess, 1.0, 2.0); Um paradigma comum dentro da indústria de controlo de processos é implementtar controlo sequencial através de sucessivas chamadas de um nº de acções. Um compilador sequencial pode interactivamente construir um array dessas acções a que se obedecerá: type Action is access procedure; Action_Sequence: array(1..N) of Action; .. construir o array e depois obedecer a ele: for I in Action_Sequence’Range loop Action_Sequnce(I).all; end loop; Consideremos agora um possível fragmento de um sistema que conduz os controlos no cockpit de uma mítica Ada Airlines. Há um nº de botões físicos na consola e queremos associar acções diferentes a cada: type Button; type Response_Ptr is acess procedure (B: in out Button); type Button is record Reponse: Response_Ptr; ... – outros aspectos do botão end record; procedure Associate(B: in out Button; ...); procedure Push(B: in out Button); procedure Set_Response(B: in out Button; R: in Response_Ptr); os bodies podem ser: procedure Push(B: in out Button) is begin

B.Response(B); --chamada indirecta end Push; procedure Set_Response(B: in out Button; R: in Response_Ptr) is begin B.Response:= R end Set_Response; Podemos agora especificar o conjunto de acções que queremos que sejam feitas quando se prime um botão: Big_Red_Button: Button; procedure Emergency (B: in out Button) is begin Broadcast(“mayday”); … Eject(Pilot); end emergncy; … Associate(Big_Red_Button, …); Set_Response(Big_Red_Button, Emergency’Acess); … Push(Big_Red_Button); Isto tudo não pode ser aplicado a subprogramas considerados intrínsecos. CHECKLIST 10 - uma declaração incompleta pode apenas ser usada num tipo acesso - o âmbito de um objecto alocado é o memso do tipo de acesso - objectos de acesso tem o valor inicial null por defeito - um alocador num agregado é avaliado para cada valor do index - um alocador com um valor inicial completo usa um ‘ - um tipo geral de acesso tem all ou constant na sua definição - acesso pode apenas ser aplicado a subprogramas não intrínsecos e a objectos aliasados - cuidado com Unchecked_Acess - um parâmetro de acesso nunca pode ser null - conversão para um tipo de acesso específico de pool não é permitido.

CAP. 13 PROGRAMAÇÃO ORIENTADA A OBJECTOS Vamos ver as características básicas do Ada que suportam a OOP: a capacidade de estender um tipo com novos componentes e operações; identificar um tipo específico em run time e seleccionar uma operação particular dependendo do tipo específico. A maior meta é a reutilização. 13.1. EXTENSÃO DE TIPOS Na secção 11.3 vimos que era possível declarar um novo tipo como derivado de um existente e como isso permite a tipagem forte. As operações herdadas podem ser sobrecarregadas eoutras operaçõe primitivas podem ser adicionadas se a derivação estiver numa especificação de pacote. Agora iremos ver uma derivação mais flexível onde podemos adicionar componentes extra a um record. Isto conduz-nos a uma árvore de tipos onde cada tem os componentes do pai mais outros componentes. E o ideal é determinar o tipo da árvore em run time. A informação adicional que permite isso é um componente escondido chamado tag. Assim, os tipos record podem ser extendidos na derivação desde que estejam marcados como tag. package Objects is type Object is tagged -- raiz da árvore record X_Coord: Float; Y_Coord: Float; end record; function Distance(O:Object) return Float; function Area(O:Object) return Float; end Objects; with Objects; use Objects; package Shapes is type Circle is new Object with record Radius: Float; end record; function Area(C:Circle) return Float; type Point is new Object with null record; type Triangle is new Object with record A, B, C: Float; end record; function Area(T: Triangle) return Float; end Shapes; Todos os tipos taggados têm tagged ou with na sua declaração. A função Area para Object devia ser abstracta de modo a que não possa acidentalmente herdada. A conversão de tipos é sempre permitida na direcção da raiz, mas um agergado de extensão é requerido na direcção oposta, em ordem a dar os valores para os componentes adicionais. O: Object := (1.0, 0.5); C: Cricle := (0.0, 0.0, 34.7); T: Triangle; P: Point; … O := Object(C); ... C := (O with 41.2); T := (O with A => 3.0, B => 4.0, C => 5.0); P := (O with null record); type Cylinder is new Circle with record Height: Float;

end record; Cyl := (O with Radius => 41.2, Height => 231.6); Cyl := (C with Height => 231.6); Cyl := (Object(T) with 41.2, 231.6); Por vezes não é possíevl dar uma expressaõ de um tipo antecessor. Nessa ocasião podemos dar uma marca de subtipo. Os componetes correspondentes ao tipo antecessor são inicializados com os valores default. C:=(Object with Radius => 41.2); e as coordenadas de C não são definidas. type Object is tagged record X_Coord: Float := 0.0; Y_Coord: Float := 0.0; end record; e então o Circle estará também, por defeito, na origem. Vejamos as semelhanças e diferenças entre extensão de tipos normais e extensão de tipos taggados. - componentes existentes são herdados - herança, sobrecarga e adição de operações primitivas são permitidas nos memsos lugares; operações adicionais são apenas permitidas se a derivação ocorre no mesmo pacote. - derivação pode ocorrer no mesmo pacote do pai e herda todas as operações primitivas mas não mais operações primitivas podem ser adicionadas ao pai. Diferenças: - apenas o tipo record pode ser taggado e apenas um tipo taggado pode ter componentes adicionais - conversão de tipos apenas é permitida na direcção do antecessor para um tipo taggad, e em ambas as direcções para tipos não taggados. - operações herdadas de tipos taggados não são intrínsecas e assim o atributo de acesso pode ser-lhes aplicado. - Se uma operação herdada é asobrecarregada então a conformidade de requisitos é diferente: no caso do tipo taggado deve haver conformidade de subtipos, no não taggado apenas conformidade de tipos. - um tipo derivado deve estar ao mesmo nível de acesso do tipo pai no caso do taggado, enquanto pode estar em qualquer lado no âmbito do pai nos não taggados. Isto quer dizer que não podemos fazer extensão de tipos num bloco interior ou subprograma. Exemplo do uso de tipos taggados para construir um sistema como uma hierarquia de tipos: package Reservation_System is type Position is (Aisle, Window); type Meal_Type is (Green, White, Red); type Reservation is tagged record Flight_Number: Integer; Date_Of_Travel: Date; Seat_Number: String(1..3) := “ “; end record; procedure Make(R: in out Reservation); procedure Select_Seat(R: in out Reservation); type Basic_Reservation is new Reservation with null record; type Nice_Reservation is new Reservation with record Seat_Sort: Position; Food: Meal_Type; end record; procedure Make(NR: in out Nice_Reservation); --sobrecarrega procedure Order_Meal(NR: in Nice_Reservation); type Posh_Reservation is new Nice_Reservation with record Destination: Address;

end record; procedure Make(PR: in out Posh_Resrvation); procedure Arrange_Limo(PR: in Posh_Reservation); end Reservation_System; package body Reservation_System is procedure Make(R: in out Reservation) is begin Select_Seat(R); end Make; procedure Make(NR: in out Nice_Reservation) is begin Make(Reservation(NR)); -- make as plain reservation Order_Meal(NR); end Make; procedure Make(PR: in out Posh_Reservation) is begin Make(Nice_reservation(PR)); -- make as nice reservation Arrange_Limo(PR); end make; procedure Select_Seat(R: in out Reservation) is separate; procedure Order_Meal(NR: in Nice_Reservation) is separate; procedure Arrange_Limo(PR: in Posh_Reservation) is separate; end Reservation_System; Cada distinto corpo para Make contém apenas o código relevante para o tipo e delega outro processamento para o seu pai usando o tipo explícito de converão. Isto evita repetição de código e simplifica a manutenção. Se mais tarde houver outros tipos de viagem, não é preciso recompilação nem resteste. with Reservation_System; package Supersonic_Reservation_System is type Supersonic_Reservation is new Reservation_System.Reservation with record Champagne: Vinatge; -- outros components do supersónico end record; procedure Make(SR: in out Supersonic_Reservation); … end Supersonic_Reservation_System; 13.2 POLIMORFISMO Precisamos agora de um meio de manipular qualquer tipo de Reservation e processá-lo de acordo. Isso faz-se através da introdução da noção de tipos de classes largas que fornecem polimorfismo. Cada tipo taggado T tem um tipo associado T’Class, que compreeende a união de todos os tipos na árvore. Por ex. um valor de qualquer dos tipos de reservas pode ser convertido para Reservation’Class. Cada valor do tipo de classe larga tem um tag que identifica o seu tipo particular em run time. O tipo T’Class é tratado como um tipo indefinido (por causa do espaço). Como consequência, apesar de podermos declarar um objecto de um tipo de classe larga, ele tem de ser restringido, mas a restrição não é dada explicitamente mas sim inicializando-o com um valor de um tipo específico: NR: Nice_REservation; … RC: Reservation’Class := NR; apesar de isto não ser muito útil. De mais importância é o facto de um parâmetro formal pode ser de um TCL e o parâmetro actual de um tipo específico. Imaginemos que temos uma fila de reservas para processar. Isso pode ser feito por uma rotina central que só saberá o tipo particular em run time: procedure Process_Reservation(RC: in out Reservation’Class) is

… begin … Make(RC); --despacha de acordo com o tag .. end Process_Reservation; Pode ser implementado eficientemente, por exemplo por uma tabela indexada pelo tag, que tem um apontador para o início do código correspondente. Todas as operações não podem ser removidas na derivação; só modificadas ou adicionadas. As razões para as restrições na derivação de tipos taggados mencionadas na secção anterior podem agora ser explicadas. Uma operação sobrecarregada tem de ter conformidade de subtipo de modo a que a chamada funcione sempre dinamicamente. E a extensão tem de ter o mesmo nível de acessibilidade pois as operações de despacho estão ao memso nível; isto evita protenciais problemas com variáveis não locais. O procedimento anterior não é operação primitiva e por isso não é herdada 8nem sobrecarregada). É frequente vantagem usar uma classe larga em vez de uma operação primitiva se, por natureza, se aplicar a todos os tipos da classe. Ex: function Distance(O: Object’Class) retrun Float; Podemos escrever type Reservation_Ptr is access all Reservation’Class; A flexibilidade dos tipos de acesso é um factor chave na programação de classe largas. Uma lista heterogénea pode ser feita da forma óbvia usando: type Cell; type Cell_Ptr is access Cell; type Cell is record Next: Cell_Ptr; Element: Reservation_Ptr; end record; e a rotina central pode então manipular as reservas usando um valor de acesso co o parâmetro. procedure Process_Reservation(RP: in Reservation_Ptr) is … begin … Make(RP.all); --despacha para o Make apropriado ... end Process_Reservation; … List: Cell_Ptr; -- lista de resrevas ... while List /= null loop Process_reservation(List.Element); List := List.Next; end loop; É fundamental na programação de classes largas manipular objectos via referÊncais; isto porque os objectos podem ser de tamanhos diferentes. type Element; type element_ptr is access all Element’Class type Element is tagged record Next: Element_Ptr; end record; type Reservation is new Element with record Flight_Number: Integrer; Date_Of_Travel: Date; Seat_Number: String(1..3) := “ “;

end record; … igual ao anterior. As várias reservas podem agora ser juntas para formar uma lista (fig. 13.3.) A manipulação destas listas ou filas é comum em OOP, pelo que devemos ter um pacote com operações para isso: package Queues is Queue_Error: exception; type Queue is limited private; type Element is tagged private; type Element_Ptr is access all Element’Class; procedure Joint(Q: access Queue; E: in Element_ptr); function Remove(Q: access Queue) return Element_Pyr; function Length(Q:Queue) return Integer; private type Element is tagged record Next: Element_Ptr; end record; type Queue is limited record First, Last: Element_Ptr; Count: Integer :=0; end record; end Queues; Este pacote ilustra muitos pontos. Escondemos o interior de Queue e Element, fazendo-os privados. O primeiro é também limited pois a atribuição não deve ser permitida (baralharia os ponteiros), e também para garantir que a implementação do corpo não tente também fazer. Isto é importante pois os filhos também vêem a parte privada. O Element é tagged o que permite ao utilizador extendê-lo mesmo sem conhecer os detalhes. A passagem de ponteiro para a função Remove, em vez de um parâmetro in out melhora a função mas temos de criar uma referência para uma queue o que significa marcá-la como aliasada ou criá-la com um alocador. with Queues; package Reservation_System is … type Reservation is new Queues.Element with record … end record; … end Reservation_System; e depois criar e colocar reservas numa fila com instruções do tipo: type Queue_Ptr is access Queue; The_Queue: Queue_Ptr := new Queue; … New_Resvn: Reservation_Ptr := new Nice_Reservation; … Join(The_Queue, Element_Ptr(New_Resvn)); … Podemos também remover Next_Resvn: Reservation_Ptr; ... Next_Resvn := Reservation_Ptr(Remove((The_Queue)); Process_Reservation(Next_Resvn); Resumindo as regras quanto à conversão de tipos e tipos taggadso: - Conversão entre dois tipos específicos só é permitida na direcção da raiz. - Conversão de um tipo específico para uma classe tipo wide de qualquer tipo antecessor é permitido. S não é antecessor, não é.

- O contrário é permitido desde que o tipo do valor actual seja descendente do tipo específico. é requerido checagem dinâmica. - Conversão entre 2 classes wide types é permitida desde que o valor actual esteja na classe alvo. É requerido checagem dinâmica. - Conversão entre tipos de acesso é permitida desde que o tipo designado possa ser convertido na mesma direcção. - Parâmetros de tipos taggados são sempre passados por referência e considerados aliasados. 13.3. TIPOS ABSTRACTOS É declarado apenas como fundação de uma posterior derivação. Não se pode declarar objectos dele. Um tipo abstracto pode ter subprogramas primitivos abstractos; não têm corpo e não podem ser chamados. São apenas contentores. prosseguindo com o nosso exemplo, o pacote básico pode ser apenas: package Reservation_System is type Reservation is abstract tagged null record; type Reservation_Ptr is access all Reservation’Class; procedure Make(R: in out Reservation) is abstract; end Reservation_System; package Reservation_System.Subsonic is type Position is (Aisle, Window); type Meal_Type is (Green, White, Red); type Basic_Resrvation is new Reservation with record Flight_Number: Integr; Date_Of_Travel: Date; Seat_Number: String(1..3) := “ “; end record; -- agora fornecer um subprograma concreto para o make abstracto procedure Make(BR: in out Basic_reservation); procedure Select_Seat(BR: in out Basic_Reservation); type Nice_reservation is new Basic_reservation with record Seat_Sort: Position; Food: Meal_Type; end record; procedure Make(NR: in out Nice_Reservation); procedure Order_Meal(NR: in Nice_Reservation); type Posh_Reservation is new Nice_reservation with record destination: Address; end record; procedure Make(PR: in out Posh_reservation); procedure Arrange_Limo(PR: in Posh_Reservation); end Reservation_System.Subsonic; outro ex: type Person is abstract tagged record Birth: Date; end record; e depois derivar os tipos Homem e Mulher. É possível derivar um tipo abstracto de um concreto ou de outro abstracto. 13.4. OPERAÇÕES PRIMITIVAS Um objecto carrega uma indicação da identificação do seu tipo com ele. type Person is abstract tagged record

Birth: Date; end record; type Man is new Person with record bearde: Boolena; end record; type Woman is new Person with record Children: Integr; end record; O tag pode ser implicitamente testado para saber se é homem ou mulher. if P in Woman then -- processamento especial para as mulheres end if; onde P é do tipo da classe larga Person’Class. Também é possível testar uma tag explicitamente: if P’Tag = Woman then o valor do atributo tag é privado (mas não limitado) do tipo Tag declarado no package Ada.Tags. podemos declarar variábveis do tipo Tag da maneira habitual. Assim, Expanded_Name(Obj’Tag) retorna a string “OBJECTS.CIRCLE” por ex. também podemos escrever if P in Woman’Class then que cobre todos os tipos derivados de Woman também. Melhor porque o programa pode ser extensível. IMP: - O despacho só ocorre quando se chama uma operação primitiva e o parâmetro actual é de uma classe wide – é o parâmetro de controlo. Assim, Make(RC) é uma chamada ao despacho. Make(Reservation(NR)); não é. Uma operação primitiva pode ter vários parâmetros de controlo mas têm de ser todos do mesmo tipo, logo não é possível declarar: procedure Something(C: Circle; T: Triangle); na especificação do package shapes. Pode ser noutro package mas não será uma operação primitiva.

Sign up to vote on this title
UsefulNot useful