SlideShare uma empresa Scribd logo
Programming Languages Paradigms

      Jos´ de Oliveira Guimar˜es
         e                    a
     Computer Science Department
                UFSCar
            S˜o Carlos, SP
             a
                 Brazil
       e-mail: jose@dc.ufscar.br

         16 de mar¸o de 2007
                  c
Sum´rio
   a

1 Introduction                                                                                                                                               4
  1.1 Basic Questions . . . . . . . . . . . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    4
  1.2 History . . . . . . . . . . . . . . . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    4
  1.3 Reasons to Study Programming Languages . . . . . .                            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    5
  1.4 What Characterizes a Good Programming Language ?                              .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    6
  1.5 Compilers and Linkers . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    8
  1.6 Run-Time System . . . . . . . . . . . . . . . . . . . .                       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
  1.7 Interpreters . . . . . . . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   11
  1.8 Equivalence of Programming Languages . . . . . . . .                          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   12

2 Basic Concepts                                                                                                                                            15
  2.1 Types . . . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
      2.1.1 Static and Dynamic Type Binding             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
      2.1.2 Strong and Static Typing . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
  2.2 Block Structure and Scope . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
  2.3 Modules . . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
  2.4 Exceptions . . . . . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   25
  2.5 Garbage Collection . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31

3 Linguagens Orientadas a Objeto                                                                                                                            35
  3.1 Introdu¸˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
              ca                                                                                                . . . .         .   .   .   .   .   .   .   35
  3.2 Prote¸˜o de Informa¸˜o . . . . . . . . . . . . . . . . . . . . . . . .
           ca              ca                                                                                   . . . .         .   .   .   .   .   .   .   36
  3.3 Heran¸a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
            c                                                                                                   . . . .         .   .   .   .   .   .   .   40
  3.4 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                   . . . .         .   .   .   .   .   .   .   44
  3.5 Modelos de Polimorfismo . . . . . . . . . . . . . . . . . . . . . . .                                      . . . .         .   .   .   .   .   .   .   50
      3.5.1 Smalltalk . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                   . . . .         .   .   .   .   .   .   .   50
      3.5.2 POOL-I . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                    . . . .         .   .   .   .   .   .   .   52
      3.5.3 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                   . . . .         .   .   .   .   .   .   .   53
      3.5.4 Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                  . . . .         .   .   .   .   .   .   .   54
      3.5.5 Compara¸˜o entre os Modelos de Polimorfismo e Sistema de
                       ca                                                                                       Tipos           .   .   .   .   .   .   .   55
  3.6 Classes parametrizadas . . . . . . . . . . . . . . . . . . . . . . . . .                                  . . . .         .   .   .   .   .   .   .   57
  3.7 Outros T´picos sobre Orienta¸˜o a Objetos . . . . . . . . . . . . .
                o                     ca                                                                        . . . .         .   .   .   .   .   .   .   60
      3.7.1 Identidade de Objetos . . . . . . . . . . . . . . . . . . . . .                                     . . . .         .   .   .   .   .   .   .   60
      3.7.2 Persistˆncia . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    e                                                                                           . . . .         .   .   .   .   .   .   .   61
      3.7.3 Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                  . . . .         .   .   .   .   .   .   .   62
  3.8 Discuss˜o Sobre Orienta¸˜o a Objetos . . . . . . . . . . . . . . . .
              a                 ca                                                                              . . . .         .   .   .   .   .   .   .   62




                                                    1
4 Linguagens Funcionais                                                                                                                                              65
  4.1 Introdu¸˜o . . . . . . . . . . . . . . . . .
              ca                                             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   65
  4.2 Lisp . . . . . . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   69
  4.3 A Linguagem FP . . . . . . . . . . . . .               .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   70
  4.4 SML - Standard ML . . . . . . . . . . .                .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   71
  4.5 Listas Infinitas e Avalia¸˜o Pregui¸osa .
                               ca          c                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   75
  4.6 Fun¸˜es de Ordem Mais Alta . . . . . .
          co                                                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   76
  4.7 Discuss˜o Sobre Linguagens Funcionais
              a                                              .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   76

5 Prolog — Programming in Logic                                                                                                                                      79
  5.1 Introdu¸˜o . . . . . . . . . . . . .
             ca                              .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   79
  5.2 Cut e fail . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   87
  5.3 Erros em Prolog . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   90
  5.4 Reaproveitamento de C´digo . .
                               o             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   91
  5.5 Manipula¸˜o da Base de Dados .
               ca                            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   92
  5.6 Aspectos N˜o L´gicos de Prolog .
                 a o                         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   93
  5.7 Discuss˜o Sobre Prolog . . . . . .
             a                               .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   96

6 Linguagens Baseadas em Fluxo de Dados                                                                                                                              98




                                                             2
Preface
    This book is about programming languages paradigms. A language paradigm is a way of thinking
about a problem, restricting the ways we can build a program to specific patterns that are better
enforced by a language supporting that paradigm. Then, the object-oriented paradigm forces one to
divide a program into classes and objects while the functional paradigm requires the program to be
split into mathematical functions.
    Programming languages books usually explain programming language paradigms through several
representative languages in addition to the main concepts of the field. There is, in general, a great em-
phasis on real languages which blurs the main points of the paradigms/concepts with minor languages
particularities. We intend to overcome these difficulties by presenting all concepts in a Pascal-like
syntax and by explaining only the fundamental concepts. Everything not important was left out.
This idea has been proven successful in many programming language courses taught at the Federal
University of S˜o Carlos, Brazil.
                a
    This book is organized as follows. Chapter 1 covers a miscellany of topics like programming
language definition, history of the field, characteristics of good languages, and some discussion on
compilers and computer theory. Basic programming language concepts are presented in Chapter 2.
The other chapters discuss several paradigms like object oriented, functional, and logic.




                                                   3
Cap´
   ıtulo 1

Introduction

1.1    Basic Questions
A programming language is a set of syntactic and semantic rules that enables one to describe any
program. What is a program will be better described at the end of this chapter. For a moment,
consider a program any set of steps that can be mechanically carried out.
    A language is characterized by its syntax and semantics. The syntax is easily expressed through a
grammar, generally a context-free grammar. The semantics specifies the meaning of each statement
or construct of the language. The semantics is very difficult to formalize and in general is expressed
in a natural language as English. As an example the syntax for the while statement in C++ is
      while-stat ::= while ( expression ) statement
and its semantics is “while the expression evaluates to a value different from 0, keep executing state-
ment”. Semantics is a very trick matter and difficult to express in any way. In particular, natural
languages are very ambiguous and not adequate to express all the subtleties of programming lan-
guages. For example, in the semantics of while just described it is not clear what should happen if
expression evaluates to 0 the first time it is calculated. Should statement be executed one time or
none ?
    There are formal methods to define a language semantics such as Denotational Semantics and
Operational Semantics but they are difficult to understand and not intend to be used by the common
language programmer.
    As a consequence, almost every language has an obscure point that gets different interpretations
by different compiler writers. Then a program that works when compiled by one compiler may not
work when compiled by other compilers.


1.2    History
The first programming language was designed in 1945 by Konrad Zuse, who build the first general
purpose digital computer in 1941 [13]. The language was called plankalk¨l and only recently it has
                                                                         u
been implemented [12].
    Fortran was the first high level programming language to be implemented. Its design began in 1953
and a compiler was released in 1957 [13]. Fortran, which means FORmula TRANslation, was designed
to scientific and engineering computations. The language has gone through a series of modifications
through the years and is far from retiring.
    Algol (ALGOrithm Language) was also designed in the 1950’s. The first version appeared in 1958
and a revision was presented in a 1960 report. This language was extensively used mainly in Europe.


                                                  4
Algol was one of the most (or the most) influential language already designed. Several languages that
have been largely used as C, C++, Pascal, Simula, Ada, Java and Modula-2 are its descendents. Algol
introduced begin-end blocks, recursion, strong typing, call by name and by value, structured iteration
commands as while, and dynamic arrays whose size is determmined at run time.
    COBOL, which stands for COmmon Business Oriented Language, was designed in the late 1950’s
for business data processing. This language was adequate for this job at that time but today it is
obsolete. It has not left any (important) descendent and is being replaced by newer languages.
    Lisp ( LISt Processing) was designed in 1960 by John MacCarthy. The basic data structure of
the language is the list. Everything is a list element or a list, including the program itself. Lisp had
greatly influenced programming language design since its releasing. It introduced garbage collection
and was the first functional language.
    Simula-67 was the first object-oriented language. It was designed in 1967 by a research team in
Norway. Simula-67 descends from Simula which is an Algol extension. Simula was largely used to
simulation in Europe.
    Alan Kay began the design of Smalltalk in the beginning of 1970’s. The language was later refined
by a group of people in XEROX PARC resulting in Smalltalk-76 and Smalltalk-80, which is the current
standard. Smalltalk influenced almost every object-oriented language designed in the last two decades.
    The first computers were programmed by given as input to them the bytes representing the in-
structions (machine code). Then, to add 10 to register R0 one would have to give as input a number
representing the machine instruction “mov R0, 10”. These primitive machine languages are called
“first generation languages”.
    Then the first assembly languages appeared. They allowed to write instructions as
      mov R0, 10
      add R0, R1
that were late translated to machine code by a compiler. The assembly languages are second generation
languages.
    The third generation was born with Plankalk¨l1 and encompasses languages as Fortran, Algol,
                                                    u
Cobol, PL/I, Pascal, C, and Ada. These languages were the first to be called “high-level languages”.
    Fourth generation languages have specific purposes as to handle data bases. They are used in
narrow domains and are very high level. These languages are not usually discussed in programming
language books because they lack interesting and general concepts.
    There is no precise definition of language generation and this topic is usually not discussed in
research articles about programming languages. The fact a language belongs to the fourth or fifth
generation does not make it better than a third or even a second generation language. It may only be
a different language adequate to its domain.


1.3       Reasons to Study Programming Languages
Why should one take a programming language course ? Everyone in Computer Science will need to
choose a programming language to work since algorithms permeate almost every field of Computer
Science and are expressed in languages. Then, it is important to know how to identify the best
language for each job. Although more than eight thousand languages have been designed from a
dozen different paradigms, only a few have achieved widespread use. That makes it easier to identify
the best language for a given programming project. It is even easier to first identify the best paradigm
for the job since there are a few of them and then to identify a language belonging to that paradigm.
Besides this, there are several motives to study programming languages.
  1
      It seems the third generation was born before the second !



                                                            5
• It helps one to program the language she is using. The programmer becomes open to new ways
        of structuring her program/data and of doing computation. For example, she may simulate
        object-oriented constructs in a non-object oriented language. That would make the program
        clearer and easier to maintain. By studying functional languages in which recursion is the main
        tool for executing several times a piece of code, she may learn how to use better this feature
        and when to use it. In fact, the several paradigms teach us a lot about alternative ways of
        seeing the world which includes alternative ways of structuring data, designing algorithms, and
        maintaining programs.

      • It helps to understand some aspects of one’s favorite language. Programming language books
        (and this in particular) concentrates in concepts rather than in particularities of real languages.
        Then the reader can understand the paradigm/language characteristics better than if she learns
        how to program a real language. In fact, it is pretty common a programmer ignore important
        conceptual aspects of a language she has heavily used.

      • It helps to learn new languages. The concepts employed in a programming language paradigm
        are applied to all languages supporting that paradigm. Therefore after learning the concepts
        of a paradigm it becomes easier to learn a language of that paradigm. Besides that, the basic
        features of programming languages such as garbage collection, block structure, and exceptions
        are common to several paradigms and one needs to learn them just one time.


1.4        What Characterizes a Good Programming Language ?
General purpose programming languages are intended to be used in several fields such as commercial
data processing, scientific/engineering computations, user interface, and system software. Special
purpose languages are designed to a specialized field and are awkward to use everywhere. Smalltalk,
Lisp, Java and C++ are general purpose languages whereas Prolog, Fortran, and Cobol are special
ones. Of course, a general purpose language does not need to be suitable for all fields. For example,
current implementations of Smalltalk makes this language too slow to be used for scientific/engineering
computations.
    Now we can return to the question “What characterizes a good programming language ?”. There
are several aspects to consider, explained next.

      • The language may have been designed to a particular field and therefore it contains features
        that make it easy to build programs in that field. For example, AWK is a language to handle
        data organized in lines of text, each line with a list of fields. A one-line program in AWK may
        be equivalent to a 1000-line program in C++.

      • Clear syntax. Although syntax is considered a minor issue, an obscure syntax makes source code
        difficult to understand. For example, in C/C++ the statement
               *f()[++i] = 0["ABC" + 1];
        is legal although unclear.

      • Orthogonality of concepts. Two concepts are orthogonal if the use of one of them does not
        prevent the use of the other. For example, in C there are the concept of types (int, float, user
        defined structs2 ) and parameter passing to functions. In the first versions of this language, all
        types could be passed to functions by value (copying) except structs. One should always pass a
        pointer to the struct instead of the structure itself.
  2
      Structs are the equivalent of records in other languages as Pascal.


                                                             6
Lack of orthogonality makes the programmer use only part of the language. If she has doubts
        about the legality of some code, she usually will not even try to use that code [16]. Besides
        being underused, a non-orthogonal language is more difficult to learn since the programmer has
        to know if two concepts are valid together or not. In fact, non-orthogonality is an obstacle to
        learning greater than the language size. A big and orthogonal language may be easier to learn
        than a median-size and non-orthogonal language.
        On the other side, full orthogonal languages may support features that are not frequently used
        or even not used at all. For example, in some languages values of any type can be passed to
        procedures by reference and by value. Then it is allowed to pass an array to a procedure by
        value. This is completly unnecessary3 and maked the language harder to implement.
      • Size of the language. Since a big language has too many constructs, there is a high probability
        it will not be orthogonal. Since it is difficult for a programmer to master all the peculiarities
        of a big language, she will get more difficult-to-fix compiler errors and therefore she will tend
        to use only part of the language. In fact, different people will use different language subsets, a
        situation that could be avoided by designing a main language with several specialized languages
        based in it [6].
        It is hard to implement a big language not only because of the sheer number of its constructs
        but mainly because these constructs interact with each other in many ways. For example, in
        Algol 68 a procedure can return values of any type. To the programmer, to declare a procedure
        returning a type is as easy as to declare it returning other type. However the compiler may need
        to treat each type separately when doing semantic analysis and generating code. Two types may
        need two completely different code generation schemes. Then the compiler has to worry about
        the iteration between “return value” and “type”. The complexity of this iteration is hidden from
        the programmer.
        Because of problems as described above, big language compilers frequently are slow, expensive
        or flawed. Languages are troublesome to specify unambiguously and having a big language make
        things worse. The result may be different compilers implementing the same language constructs
        in different ways.
        On the other side, small languages tend to lack important features such as support to separate
        compilation of modules. This stimulates each compiler writer to introduce this feature by herself,
        resulting in dozen of language dialects incompatible to each other.

    When selecting a language to use, one should also consider factors external to the languages such
as the ones described below.

      • Availability of good compilers, debuggers, and tools for the language. This may be the determi-
        nant factor in choosing a language and it often is. Several good languages are not largely used
        because they lack good tools. One of the reasons Fortran has been successful is the existence of
        very good optimized compilers.
      • Portability, which is the ability to move the source code of a program from one compiler/machine
        to another without having to rewrite part of it. Portability involves a series of factors such as
        the language itself, the language libraries, the machine, and the compiler. We will briefly explain
        each of these topics.
        Badly specified languages free compiler writers to implement ambiguously defined constructs in
        different ways. A library furnished with a compiler may not be available with another one, even
  3
      The author of this book has never seen a single situation in which this is required.


                                                             7
A.c                           B.c
                          void f() ...                 void g() ...
                          ...                          ...
                          f();                         f();
                          ...                          ...
                          g();                         g();



                                 Figura 1.1: Two files of a C program


      in the same machine. Differences in machines such as byte ordering of integers or error signaling
      may introduce errors when porting a program from one machine to another. For example, the
      code
            while ( w->value != x && w != NULL )
               w = w->suc;
      in C would work properly in old micro computers. If w is NULL, w->value would not cause a core
      dump since old micro computers do not support memory protection. Finally, different compilers
      may introduce language constructs by themselves. If a program uses these constructs, it will
      hardly be portable to other compilers.

   • Good libraries. The availability of good libraries can be the major factor when choosing a
     language for a programming project. The use of suitable libraries can drastically reduce the
     development time and the cost of a system.


1.5     Compilers and Linkers
A compiler is a program that reads a program written in one language L1 and translates it to another
language L2 . Usually, L1 is a high language language as C++ or Prolog and L2 is assembly or machine
language. However, C as been used as L2 . Using C as the target language makes the compiled code
portable to any machine that has a C compiler. If a compiler produces assembler code as output, its
use is restricted to a specific architecture.
    When a compiler translates a L1 file to machine language it will produce an output file called
“object code” (usually with extension “.o” or “.obj”). In the general case, a executable program
is produced by combining several object codes. This task is made by the linker as in the following
example.
    Suppose files “A.c” and “B.c” were compiled to “A.obj” and “B.obj”. File “A.c” defines a proce-
dure f and calls procedure g defined in “B.c”. File “B.c” defines a procedure g and calls procedure f.
There is a call to f in “A.c” and a call to g in “B.c”. This configuration is shown in Figure 1.1. The
compiler compiles “A.c” and “B.c” producing “A.obj” and “B.obj”, shown in Figure 1.2. Each file is
represented by a rectangle with three parts. The upper part contains the machine code corresponding
to the C file . In this code, we use
        call 000
for any procedure call since we do not know the exact address of the procedure in the executable
file. This address will be determined by the linker and will replace 000. The middle part contains
the names and addresses of the procedures defined in this “.obj” file. Then, the file “A.obj” defines
a procedure called f whose address is 200. That is, the address of the first f machine instruction is


                                                  8
B.obj
                                                          0
                          A.obj
                      0                                 100                   g
                    200                      f
                                                        300 call 000
                    400 call 000

                    600 call 000                        700 call 000


                    800                              1000
                          f          200                      g       100
                          400          f                      300         f
                          600          g                      700         g
                                ...                                 ...


                                    Figura 1.2: Object file configurations

                                     .exe
                                0
                              200                   f

                            400      call 200
                            600      call 900
                            800
                            900                     g
                          1100       call 200
                          1500       call 900


                          1800


                                            Figura 1.3: Executable file


200 in “A.obj”. The lower rectangle part of “A.obj” contains the names of the procedures called in
“A.obj” together with the call addresses. Then, procedure f is called at address 400 and g is called in
address 600. To calculate the previous numbers (200, 400, 600) we assume the first byte of “A.obj”
has address 0.
   To build the executable program, the linker groups “A.obj” and “B.obj” in a single file shown in
Figure 1.3. As “B.obj” was put after “A.obj”, the linker adds to the addresses of “B.obj” the size of
“A.obj”, which is 800. So, the definition of procedure g was in address 100 and now it is in address
900 (100 + 800).
   Since all procedures are in their definitive addresses, the linker adjusted the procedure calls using
the addresses of f and g (200 and 900). File “A.c” calls procedures f and g as shown in Figure 1.1.
The compiler generated for these calls the code

...
call 000     /* call to f. This is a comment */


                                                        9
...
call 000        /* call to g */
...
in which 000 was employed because the compiler did not know the address of f and g. After calculating
the definitive addresses of these procedures, the linker modifies these calls to
...
call 200        /* call to f */
...
call 900        /* call to g */
...
   To execute a program, the operating system loads it to memory and adjusts a register that keeps
where the program code begins. Then a call to address 200 means in fact a call to this number plus
the value of this register.


1.6       Run-Time System
In any executable program there are machine instructions that were not directly specified in the
program. These instructions compose the run-time system of the program and are necessary to
support the program execution. The higher level the language, the bigger its run-time system since
the language nees to perform a lot of computations that were concealed from the programmers to
make programming easier. For example, some languages support garbage collection that reduces
errors associated to dynamic memory handling by not allowing explicit memory deallocation by the
program (free of C, delete or dispose of other languages). The program becomes bigger because
of the garbage collector but the programming is at a higher level when compared with languages with
explicit memory deallocation.
    Some responsibilities of run-time system are enumerated below.

      • When a procedure is called, the RTS allocates memory to its local variables. When a procedure
        returns, the RTS frees this memory.
      • The RTS manages the stack of called procedures. It keeps the return addresses of each procedure
        in known stack positions.
      • When the program begins, the command line with which the program was called is passed to the
        program.4 This command line is furnished by the operating system and passed to the program
        by the run-time system.
      • Casting of values from one type to another can be made by the run-time system or the compiler.
      • In object-oriented languages, it is the RTS that does the search for a method in message sends.
      • When an exception is raised, the RTS looks for a “when” or “catch” clause in the stack of called
        procedures to treat the exception.
      • In C++, the RTS calls the constructor of a class when an object is created and calls the destructor
        when the object is destroyed. In Java, the RTS one calls the constructor when the object is
        created.
  4
    In C++ the command line is handled by the arguments argc and argv of function main. In Java, static method
main of the main class has an array parameter with the arguments.


                                                     10
• The RTS does the garbage collecting.

    Part of the run-time system is composed by compiled libraries and part is added by the compiler
to the compiled code. In the first case is the algorithm for garbage collection and the code that gets
the command line from the operating system. In the second case are all other items cited above.


1.7       Interpreters
A compiler generates machine code linked by the linker and executed directly by the computer. An
interpreter generates bytecodes that are interpreted by the interpreter itself. Bytecodes are instructions
for a virtual machine. In general, the bytecodes are closely related to the language the interpreter
works with.
    There are several tradeoffs in using compilers/linkers or interpreters, discussed in the following
items.

  1. It is easy to control the execution of a program if it is being interpreted. The interpreter can
     confer the legality of each bytecode before executing it. Then the interpreter can easily prevent
     illegal memory access, out-of-range array indeces, and dangerous file system manipulations;

  2. It is easier to build a debugger for a interpreted program since the interpreter is in control of
     the execution of each bytecode and the virtual machine is much simpler than a real computer.

  3. Interpreters are easier to build than compilers. First they translate the source code to bytecodes
     and compilers produce machine code. Bytecodes are in general much simpler than machine
     instructions. Second, interpreters do not need a linker. The linking is made dynamically during
     the interpretation. Third the run-time system is inside the interpreter itself, written in a high-
     level language. At least part of the run-time system of a compiled program needs to be in
     machine language.

  4. The sequence Edit-Compile to bytecodes-Interpret is faster than the sequence Edit-Compile-
     Link-Execute required by compilers. Interpreters do not need linking and changes in one of the
     files that compose the program do not demand the recompilation of other files. To understand
     this point consider a program is composed by several files already compiled to bytecodes. Suppose
     the programmer does a small change in one of the files and asks the interpreter to execute the
     program. The interpreter will translate the changed file to bytecodes and will interpret the
     program. Only the modified file should be recompiled.
        If compilation is used, a small change in a file may require recompilation of several files that
        depend on this. For example, if the programmer changes the value of a constant declared as
              const Max = 100;
        in C++ or other languages, the compiler should recompile all files that use this constant.5
        After compiling all the files of a program, they are linked to produce the executable file. Com-
        pared with the interpretation environments, compilation takes more programmer’s time. Inter-
        preters are fast to respond to any programmer’s change in the program and are heavily used for
        rapid prototyping.

  5. Compilers produce a much more faster code than interpreters since they produce code that is
     executed directly by the machine. Usually the compiled code is 10 to 20 times faster than the
     interpreted equivalent code.
  5
      In C++, the compiler should recompile all files that include the header file that defines this constant.


                                                           11
From the previous comparison we conclude that interpreters are best during the program’s devel-
opment phase. In this step it is important to make the program run as fast as possible after changing
the source code and it is important to discover as many run-time errors as possible. After the program
is ready it should run fast and therefore it should be compiled.


1.8     Equivalence of Programming Languages
There are thousands of programming languages employing hundreds of different concepts such as
parallel constructs, objects, exception, logical restrictions, functions as parameters, generic types, and
so forth. So one should wonder if there is a problem that can be solved by one language and not by
another. The answer is no. All languages are equivalent in their algorithm power although it is easier
to implement some algorithms in some languages than in others.
    There are languages

   • supporting parallel constructs. Two or more pieces of the same program can be executed in two
     or more machine processors;

   • without the assignment statement;

   • without variables;

   • without iterations statements such as while’s and for’s: the only way to execute a piece of code
     more than one time is through recursion;

   • without iterations statements or recursion. There are pre-defined ways to operate with data
     structures as arrays that make while/for/recursion unnecessary.

    Albeit these differences, all languages are equivalent. Every parallel program can be transformed
in a sequential one. The assignment statement and variables may be discarded if all expressions are
used as input to other expressions and there are alternative ways to iterative statements/recursion in
handling arrays and other data structures that require the scanning of their elements.
    Every program that uses recursion can be transformed in another without it. This generally
requires the use of a explicit stack to handle what was once a recursive call.
    In general there is no doubt if a given programming language is really a language. If it has a
iteration statement as while’s (or recursion) and some kind of if statement, it is a real language.
However, if it is not clear whether a definition L is a language, one can build an interpreter of a known
language K in L. Now all algorithms that can be implemented in K can also be implemented in L. To
see that, first implement an algorithm in K resulting in a program P. Then give P to the interpreter
written in L. We can consider that the interpreter is executing the algorithm. Therefore the algorithm
was implemented in L. Based in this reasoning we can consider interpreters as programs capable of
solving every kind of problem. It is only necessary to give them the appropriate input.
    The mother of all computeres is the Turing Machine, devised in 1936. In fact, computability has
been defined in terms of it by the Church-Turing thesis:

      Any computable function can be computed by the Turing Machine

or, alternatively,

      Any mechanical computation can be made in a Turing Machine


                                                   12
In fact, this “thesis” is an unproven hypothesis. However, there has never been found any algorithm
that cannot be implemented by a Turing machine or any programming language.
    There are theoretic limitations in what a language can do. It is impossible, for example, to devise
an algorithm P that takes the text of another algorithm Q as input and guarantees Q will finish. To
prove that, suppose P exists and P(text of A) returns true if A finish its execution, false otherwise.
If Q is defined as

proc Q()
  begin
  while P(text of Q) do
    ;
  end

what will happen ? The ; after the while is the null statement.
   If P(text of Q) returns true that means P says Q does stop. But in this case Q will not stop
because the while statement will be equivalent to
      while true do
        ;

    If P(text of Q) returns false that means P says Q will never stop. But in this case Q will stop.
    The above reasoning leads to nonsense conclusions. Then we conclude P does not exist since all
the other logical reasonings are correct.
    If Q takes one or more parameters as input, the reasoning above has to be slightly modified. First,
all of the parameters should be represented as a single integer number — this can be done although
we will no show how. Assume that the language permits integers of any length. Procedures are also
represented as integer numbers — to each procedure is associated a single integer.
    Now suppose function P(n, d) returns true if the procedure associated to number n halts6 when
taking the integer d as parameter. P(n, d) returns false otherwise. If Q is defined as

proc Q(d)
  begin
  while P(d, d) do
    ;
  end

and called as Q(number of Q), there is a paradox. If Q(number of Q) halts, P(d, d) returns true and
the while statement above will never stop. Therefore Q(number of Q) would not halt. If Q(number
of Q) does not halt, P(d, d) returns false and the while statement above will be executed a single
time. This means Q will soon halts. Contradiction, P cannot exists.
    In the general case, an algorithm cannot deduce what other one does. So, an algorithm cannot
tell another will print “3” as output or produce 52 as result. It is also not possible for an algorithm
to decide whether two programs or algorithms are equivalent; that is, whether they always produce
the same output when given the same input.
    All these restrictions to algorithm power apply to algorithms and not to programs implemented in
a computer. The former assumes memory is infinite and in computers memory is always limited. It is
therefore possible to devise a program S that takes a program P as input and discover if P will stop when
executed in a given machine. Program S considers all bits of the machine memory (including registers,
  6
      Finish its execution at some time, it does not matter when.


                                                           13
internal flags, I/O ports) as as single number describing the current memory state. Almost every P
statement modifies this number even if only the program counter or some internal flag.7 Program S
can simulate P execution and record the numbers resulted after the execution of each statement. This
would result in a list
       n1 , n2 , n3 , ... nk
If a number is equal to same previous number (e.g. nk == n2 ) then program P has an infinite loop.
The sequence
       n2 , n3 , ... nk
will be repeated forever. This happen because computers are deterministic: each computer state
(number) is followed by exactly one other state.
    If this sequence reaches a number corresponding to the last program statement, the program stops.
Assume all input was put in the computer memory before the execution started.




  7
      In fact, the only statement we found that does not modify anything is “L: goto L”


                                                           14
Cap´
   ıtulo 2

Basic Concepts

This chapter explains general concepts found in several programming languages paradigms such as
strong and dynamic typing, modules, garbage collection, scope, and block structure. In the following
sections and in the remaining chapters of this book we will explain concepts and languages using a
Pascal-like syntax as shown in Figure 2.1. To explain each concept or language we will add new syntax
to the basic language that will be called S. We will describe only a few constructs of S since most of
them can be understood easily by any programmer.

   • A procedures in S is declared with the keyword proc and it may have return value type as
     procedure fat in the example. In this book, the word procedure is used for subroutine, routine,
     function, and subprogram.

   • Keyword return is used to return the value of a procedure with return value type (in fact, a
     function). After the execution of this statement the procedure returns to the callee.

   • begin and end delimits a group of statements inside a procedure, a command for, or while.
     The if statement does not need begin-end since it finishes with endif.

   • The assignment statement is “=”. Language S uses “==” for equality comparison.

   • The execution of a program begins in a procedure called main.

   • There are four basic types in S: integer, boolean, real, and string.


2.1     Types
A type is a set of values together with the operations that can be applied on them. Then, type integer
is the set of values -32768, ... 0, 1, ... 32767 plus the operations
       + - * / % and or
in which % is the remainder of division and and and or are applied bit by bit.

2.1.1   Static and Dynamic Type Binding
A variable can be associated to a type at compile or run time. The first case occurs in languages
requiring the type in variable declarations as C++, Java and Pascal:
      var i : integer;
These languages are said to support static type binding.


                                                 15
{ global variable }
const max = 100;

proc fat( n : integer ) : integer
  { return the factorial of n }
begin
if n <= 1
then
  return 1;
else
  return n*fat(n-1);
endif
end

proc print( n : integer; s : String )
  var i : integer;
begin
for i = 1 to n do
  write(s);
end

proc main()
     { program execution starts here }
  var j, n : integer;
begin
write("Type a number");
read(n);
if n == 0 or n > max
then
  n = fat(1);
endif
j = 1;
while j < n do
  begin
  print(j, "Hello");
  write("*****");
  j = j + 1;
  end
end


                           Figura 2.1: A program in language S




                                           16
Dynamically typed languages do not allow types to be specified in variable declarations. A variable
type will only be know at run time when the variable refers to some number/string/struct or object.
That is, the binding variable/type is dynamic. A variable can refer to values of any type since the
variable itself is untyped. See the example below in which b receives initially a string and then a
number or array.

  var a, b;
begin
a = ?;
b = "Hello Guy";
if a <= 0
then
  b = 3;
else
      { array of three heterogeneous elements }
  b = #( 1.0,    false, "here" );
if a == 0
then
  b = b + 1;
else
  write( b[1], " ", b[3] );    { 1 }
end.

In this example, if the ? were replaced by
a) -1, there would be a run-time error;

b) 0, there would be no error;

c) 1, there would not be any error and the program would print
            1.0 here


    Since there is no type, the compiler cannot enforce the operations used with a variable are valid.
For example, if ? is replaced by -1, the program tries to apply operation [] on an integer number —
b receives 3 and the program tries to evaluate b[1] and b[3]. The result would be a run-time error
or, more specifically, a run-time type error. A type error occurs whenever one applies an operation on
a value whose type does not support that operation.
    Static type binding allows the compiler to discovers some or all type errors. For example, in
  var s : string;
begin
s = s + 1;
...
end
the compiler would sign an error in the first statement since type string does not support operation
“+” with an integer value.
   It is easier for a compiler to generate efficient code to a static type binding language than to a
dynamically typed one. In the first case, a statement
      a = b + c;

                                                 17
proc search(v, n, x)
    { searchs for x in v from v[0] to v[n-1]. Returns the first i such
      that v[i] == x or -1 if x is not found in v. }
  var i;
begin
i = 0;
while i < n do
  if v[i] == x
  then
    return i;
  else
    i = i + 1;
  endif
return -1;
end


                                  Figura 2.2: A polymorphic procedure


results in a few machine statements like
      mov a, b
      add a, c
In a dynamically typed language the compiler would generate code to:

   1. test if b and c types support operation +;

   2. select the appropriate operation to execute. There are at least three different operations that
      can be used in this example: + between integers, reals, and strings (to concatenation).

   3. execute the operation.

    Clearly the first generated code is much faster than the second one. Although dynamically typed
languages are unsafe and produce code difficult to optimize (generally very slow), people continue to
use them. Why ? First these languages free the programmer from the burden of having to specify
types, which makes programming lighter. On may think more abstractly since one of the programming
concepts (types at compile time) has been eliminated.1
    Other consequence of this more abstract programming is polymorphism. A polymorphic variable
can assume more than one type during its lifetime. So, any untyped variable is polymorphic. Poly-
morphic variables lead to polymorphic procedures which have at least one polymorphic parameter. A
polymorphic value has more than one type, as NULL in C++, null in Java or nil in other languages.
    Polymorphic procedures are important because their code can be reused with parameters of more
than one type. For example, procedure search of Figure 2.2 can be used with arrays of several types
as integer, boolean, string, or any other that support the operation “==”. search can even be used
with heterogeneous arrays in which the array elements have different types. Then the code of search
was made just one time and reused by several types. In dynamic typed languages polymorphism comes
naturally without any further effort from the programmer to create reusable code since any variable
is polymorphic.
  1
    Although it is pretty common programmers to introduce types in their variable names as aPerson, aList, and
anArray.



                                                     18
2.1.2   Strong and Static Typing
A strongly typed language prevents any run-time type error from happening. Then a type error is
caught by the compiler or at run time before it happens. For example in a language with static type
binding, the compiler would sign an error in
       a = p*3;
if p is a pointer. In a dynamically typed language this error would be pointed at run time before it
happens. Languages with dynamic typing are naturally strongly typed since the appropriate operation
to be used is chosen at run time and the run-time system may not find this operations resulting in
a run-time type error. In a non-strongly typed language the statement above would be executed
producing a nonsense result. Some authors consider a language strongly typed only if type errors are
caught at compile time. This appear not to be the dominant opinion among programming language
people. Therefore we will consider in this book that a language is strongly typed if it requires type
errors to be discovered at compile or run time.
    A language is statically typed if all type errors can be caught at compile time. Then all statically
typed languages are also strongly typed. In general languages in which variables are declared with a
type (Pascal, C++, Java) are statically typed. The type error in the code

var i : integer;
begin
i = "hello";
...
end

would be discovered at compile time.
    The definitions of static and strong typing are not employed rigorously. For example, C++ is
considered a statically typed language although a C++ program may have type errors that are not
discovered even at run time. For example, the code

int *p;
char *s = "Hi";

p = (int *) s;
*p = *p*3;

declares a pointer p to int and a pointer s to char, assigns s to p through a type conversion, and
multiply by 3 a memory area reserved to a string. In this last statement there is a type error since a
memory area is used with an operation that does not belong to its type (string or char *).


2.2     Block Structure and Scope
Algol-like languages (like Pascal, Modula, and Ada) support the declaration of procedures inside other
procedures as shown in Figure 2.3 in which Q is inside P. Block structure was devised to restrict the
scope of procedures. Scope of an identifier is the program region in which it is defined. That is, the
region in which it can potentially be used. In general, the scope of a global variable such as max of
Figure 2.3 extends from the point of declaration to the end of the file. Then max can be used in P and
Q. The scope of a local variable of a procedure X is the point where it is declared to the end of X. This
same rule applies to local procedures. Then, the scope of variable i of P is from 2 to 6 and the scope
of Q is from 3 to 6. Variable k can be used only inside Q.


                                                   19
var max : integer;        { 1 }

proc P( n : integer )
  var i : integer;          { 2 }
  proc Q()                  { 3 }
    var k : integer;        { 4 }
  begin { Q }
  ...
  end   { Q }               { 5 }
begin   { P }
...
end     { P }               { 6 }
...
                            { 7 - end of file }


                             Figura 2.3: An example of procedure nesting


    A procedure may declare a local variable or procedure with name equal to an outer identifier. For
example, one could change the name of variable k of Q to max. Inside Q, max would mean the local
variable max instead of the global max. Nearest declared identifiers have precedence over outer scope
ones.
    Visibility of an identifier is the program region where it is visible. In the example above with k
changed to max, the scope of the global max is from 1 to 7. However, max is not visible inside Q.
    Lifetime of a variable is the time interval during the program execution in which memory was
allocated to the variable. Then, the lifetime of a global variable is all program execution whereas a
variable local to a procedure is created when the procedure is called and destroyed when it returns.
    According to the definitions above, scope and visibility are determined at compile time and lifetime
at run time. However, in some languages the scope of a variable varies dynamically at run time. When
a procedure is called, its local variables are created in a stack. If the procedure calls another ones,
these can access its local variables since they continue to be in the stack. The local variables become
invisible only when the procedure returns. This strange concept is called dynamic scope.
    An example of it is shown in Figure 2.4. The program begins its execution in the main procedure
where Q is called after statement “max = 5”. At the end of Q, after the if statement, procedure P
is called resulting in the call stack of Figure 2.5 (a). Inside P the variables max, n, and i are visible.
Then it is fine p use n in the for statement. After P returns and Q returns the execution continues in
the main procedure and P is called at point { 1 }, resulting in the call stack of Figure 2.5 (b). Now
P tries to use the undefined variable n resulting in a run-time error. Note that could have been an
error even if n existed at that point because p might have tried to use n as if it had a different type
as string.
    Dynamic scope is intrinsically unsafe as shown in the previous example. It is dangerous to use
a variable that is not in the static scope such as n in P. So this should never be done. But then
dynamic scope is unuseful since it degenerates into static scope ! Why then do people use it ? By our
knowledge it is used only because it makes it easier to build interpreters to the language: variables
are only checked at run time using an already existing stack of local variables.
    Now we return to block structures. An example is in Figure 2.6 whose correspondent tree is in
Figure 2.7. In this tree, an arrow from A to B means B is inside A. The variables visible in a procedure
X are the global variables plus all local variables of the procedures in the path from X to the root of


                                                   20
proc P()
  var i : integer;
begin
for i = 1 to n do
  write(i);
end

procedure Q()
  var n : integer;
begin
if max < 10
then
  n = max;
else
  n = 10;
endif
P();
end;

proc main()
  { main procedure. Program execution starts here }
  var max : integer;
begin
max = 5;
Q();
P();   { 1 }
end.


                         Figura 2.4: Example of dynamic scope




                                P     i
                                Q     n                   P     i
                              main    max                main   max


                               (a)                       (b)
                                Figura 2.5: Call stack




                                            21
var max : integer;

proc A()
  var min : integer;
  proc B() : char
    var g : integer;
    proc C()
      var ok : boolean;
    begin { C }
    ...
    end { C }
  begin { B }
  ...
  end { B }
  proc D()
    var j : integer;
  begin { D }
  ...
  end { D }
begin { A }
...
end { A }



                          Figura 2.6: Nesting of procedures




                                     ¨
                                     A
                                     ©
                                     t
                                      t
                                ¨)
                                       t ¨
                                        ”
                                B
                                ©        D
                                         ©


                                ¨
                                 c
                                C©
                                


                              Figura 2.7: Nesting tree




                                         22
the tree. Then, the variables visible in C are the global ones plus those of C itself, B, and A.
    The objective of block structure is to create abstraction levels by hinding procedures from the rest
of the program. Albeit this noble goal, there are several problems with block structures:

      • it requires extra work to find the correct procedure nesting;

      • it becomes difficult to read the source code since the procedure header with the local variables
        and formal parameters is kept far from the procedure body;

      • the lowest level procedures are, in general, the deepest nested ones. After all they are the building
        blocks for the procedures higher in the tree. This is a problem because low level procedures are,
        in general, the most generic ones. Probably the deepest nested procedures will be needed in other
        parts of the program. And it may be not easy to move them to outside all nestings because
        they may use local variables of the procedures higher in the tree. For example, procedure C in
        Figure 2.6 may use variable g of B and min of A. When moving C outside all nestings, variables
        g and min must be introduced as parameters. This requires modifying all calls to C in B.

      • it becomes confusing to discover the visible variables of a procedure. These variables are clearly
        identified with the help of a tree like that of Figure 2.7 but difficult to identify by reading the
        source code.


2.3       Modules
A module or package2 is a set of resources such as procedures, types, constants, classes3 , and variables.
Each resource of a module can be public or private. Public resources can be used by other modules
that import this one through a special command discussed next. Private resources are only visible
inside the module itself.
    Modules are declared as shown in Figure 2.8 which presents module Window that:

      • imports modules WinData and StringHandling;

      • defines in the public section two procedures (makeWindow and closeAll) and two constants
        (maxWidth and maxHeight).

    By importing module StringHandling, all resources defined in the public section of this module
become visible in module Window. Then type String defined in the public section of StringHandling
can be used in the header of procedure makeWindow.
    Only procedure headers (without the body) are put in the public section since to use a procedure
one needs only its name and parameter/return value types.
    Suppose variable Max is defined in module WinData and in StringHandling. Now an ambiguity
arises in the use of Max in the module Window procedures. It could refer to variable Max of both
modules. The language can consider an error to import two modules that have resources with the
same name. Or it may require the use of a module qualification before Max such as
     WinData.Max
or
     StringHandling.Max
The language may also require this syntax for all imported resources, not only the ambiguous ones.
  2
      Its Ada and Java names.
  3
      To be seen in a chapter about object-oriented programming.



                                                          23
module Window

import WinData, StringHandling;

public:
    { only procedure headers appear in the public section }
  proc makeWindow( w : WindowData;
                   name : String );
  const maxWidth = 1024,
        maxHeight = 800;
  proc closeAll();

private:

  const max = 16;
  var AllWindows : array[1..max] of WindowData;

  proc makeWindow( w : WindowData;
                   name : String )
      var i : integer;
    begin
    ...
    end

  proc closeAll()
      var i, k : integer;
    begin
    ...
    end


                             Figura 2.8: A module Window




                                         24
This would make typing difficult but the program clearer since the programmer would know immedi-
ately the module each resource comes from. However in general the programmer knows from where
each resource comes from, mainly in small programs and when using known libraries. In this case a
verbose syntax like “WinData.Max” is less legible than a lighter one like “Max”.
    Modules have several important features, described next.

   • When a module B imports module A, B can use only resources of the public section of A. That
     means any changes in the private part of A will not affect B in any way. In some language, B
     not even needs to be recompiled when the private part of A in changed. This feature is used to
     implement data abstraction, also called information hiding. Some languages (e.g. Ada) allow
     declarations like

      type Window is private;
      proc drawWindow( w : Window );

      in the public module section. Window is a type defined in the private section although declared
      as public. To define a variable is to give an order to the compiler generate code to allocate
      memory for the variable at run time. So definition requires all information about the variable to
      be specified. To declare a variable is to give its name and type so that type checking is possible.
      Modules importing module this module Window can declare variables of type Window but they
      cannot apply any operation on Window variables. For example, if Window is a record (C struct
      or C++ class), these modules cannot select a field of a variable of this type (like “win.icon”).

   • A program can (should) be divided in modules that can be separately compiled and understood.
     One may only know the public section of a module in order to use it. Then modules work as
     abstraction mechanisms reducing the program complexity. A well-designed module restricts the
     dependency from other modules to a minimum. Then it can largely be understood independently
     from the rest of the program.


2.4     Exceptions
Exceptions are constructs that allow the programmer to separate error signaling from error treatment,
thus making programs more readable. Usually there should be a test after each statement that can
result in error. This test should verify if the statement was successful. It it was not, code to try to
correct the error should be executed. If correction is not possible the program should be terminated.
    As an example, the code of Figure 2.9 builds a window from several dynamically allocated parts.
After each allocation there is a test to verify if it was successful. “A.new()” allocates memory for an
instance of type A.
    The code is populated with a lot of if’s to test the success of memory allocation. These if’s
do not belong to the functional part of the algorithm; that is, that part that fulfills the algorithm
specification. The if’s are a sort of auxiliary part that should best be kept separate from the main
algorithm body. This can be achieved with exceptions as shown by Figure 2.10 which presents another
implementation of procedure buildWindow. Each call to new either returns the required memory or
raises an exception. An exception is raised by a statement like
       raises Storage_Error:
that transfers the execution to a when clause put after keyword exception — see the example. In
other words, when there is not enough free memory procedure new, instead of returning nil (or NULL),
raises exception Storage_Error. The control is transferred to the line following

                                                  25
proc buildWindow() : Window
  var w : Window;
      op : Option;
      b : Button;
begin
w = Window.new();
if w == nil then return nil; endif
init( w, x1, y1, x2, y2 );
op = Option.new();
if op == nil then return nil; endif
init( op, Are you sure ?, xop, yop );
setOption(w, op);
b = Button.new();
if b == nil then return nil; endif
init(b, ...);
setButton(w, b);
b = Button.new();
if b == nil then return nil; endif
init(b, ...);
setButton(w, b);
b = Button.new();
if b == nil then return nil; endif
init(b, ...);
setButton(w, b);
end


                           Figura 2.9: Code to build a window




                                          26
proc buildWindow() : Window
  var w : Window;
      op : Option;
      b : Button;
begin
w = Window.new();
init( w, x1, y1, x2, y2 );
op = Option.new();
init( op, Are you sure ?, xop, yop );
setOption(w, op);
b = Button.new();
init(b, ...);
setButton(w, b);
b = Button.new();
init(b, ...);
setButton(w, b);
b = Button.new();
init(b, ...);
setButton(w, b);
exception
  when Storage_Error:
      { failure in memory allocations: not enough free memory }
    return nil;
  when Bad_Data:
      { invalid arguments to procedures }
    return nil;
end


              Figura 2.10: Use of exceptions to catch memory allocation errors




                                            27
when Storage_Error:
where the execution continues. The procedure then returns nil. Then to raise an exception is much
like to make an unconditional goto to a label given by the when clause. The difference from a goto
is that exceptions can be raised by a procedure and catch (or treated) by any other that is in the
procedure call stack.
     In the example of Figure 2.10, procedure buildWindow could also handle exception Bad_Data. This
exception may be raised by procedures init, setOption, setButton, or by any procedure called by
these ones.
     In the general case, a procedure P1 calls procedure P2 that calls P3 and so forth until Pn−1 calls
Pn . If Pn raises an exception Storage_Error, the run-time system (RTS) begins to search for a when
clause
        when Storage_Error:
in the procedures stacked, from Pn to P1 . If one of such clauses is found, the statements following it
are executed. If no such when clause is found, the program is terminated.
     Another example of exceptions is in Figure 2.11 in which main is the procedure where the program
execution begins. The second statement of main calls q that calls r. Depending on the value of n,
r raises exception Underflow or Overflow. If Underflow is raised, the control jumps to the “when
Underflow” clause of q. If Overflow is raised, r and q are skipped and the execution continues in the
“when Overflow” clause of main.
     The fourth statement of p calls r. If r raises Underflow, a run-time error is signaled and the
program is terminated. The stack of called procedures contains only main and r, and none of these
have a “when Underflow”clause. Note it does not matter q have this clause: it was not in the call
stack when the exception was raised.
     Then an exception can be raised and treated in some execution paths and not treated in others.
This results in unsafe programs that can terminate unexpectedly. There are other problems with
exceptions:

   • they make the program difficult to understand. An exception is like a goto to a label unknown
     at compile time. The label will only be known at run time and it may vary from execution to
     execution;

   • they require a closer cooperation among the modules of a program weakening their reusability.
     A module should be aware of internal details of other modules it uses because it needs to know
     which exceptions they can raise. That means a module should know something about the internal
     flow of execution of other modules breaking encapsulation;

   • they can leave the system in an inconsistent state because some procedures are not terminated.
     Then some data structures may have non-initialized pointers, files may not have been closed,
     and user interactions could have been suddenly interrupted.

   Although exceptions are criticized because of these problems, they are supported by major lan-
guages as Java, Ada, Eiffel, and C++. Exceptions have two main features:

   • they separate the functional part of the program (that fulfill its specification) from error handling.
     This can reduce the program complexity because parts that perform different functions are kept
     separately;

   • they save the programmer’s time that would be used to put error codes in return values of
     procedures and to test for procedure failure after each call.


                                                   28
proc r( n : integer )
  var a : A;
begin
if n  0
then
  raises Underflow;
endif
if n  Max
then
  raises Overflow;
endif
...
end { r }

proc q( n : integer )
begin
r(n);
...
exception
  when Underflow :
      { terminate the program }
    write(Underflow);
    exit(1);
end { q }

proc main()
  { program starts here }
  var n : integer;
begin
read(n);
q(n);
read(n);
r(n):
exception
  when Overflow :
      { terminate the program }
    write(Overflow);
    exit(1);
end { main }


                        Figura 2.11: An example with exceptions




                                          29
proc r( n : integer ) raises (Underflow, Overflow)
  var a : A;
begin
if n  0
then
  raises Underflow;
endif
if n  Max
then
  raises Overflow;
endif
    { no exception is raised in following statements of r
      represented by ... }
...
end { r }

proc q( n : integer ) raises (Overflow)
begin
r(n);
...
exception
  when Underflow :
      { terminate the program }
    write(Error: underflow);
    exit(1);
end { q }

proc main()
  { program starts here }
  var n : integer;
begin
read(n);
q(n);
read(n);
r(n):
exception
  when Overflow :
      { terminate the program }
    write(Error: overflow);
    exit(1);
end { main }


                       Figura 2.12: An example with safe exceptions




                                           30
Besides that, it may be faster to use exception than to test the return values by hand.
    Some languages support safe exceptions: the language guarantees at compile time that all excep-
tions raised at runtime will be caught by when clauses. These languages require that a procedure
either treats the exceptions it may raise or specifies these exceptions in its interface. For example,
Figure 2.12 shows the example of Figure 2.11 in a language with safe exceptions. After the header of
each procedure there should appear a declaration
     raises (E1, E2, ...)
with the exceptions E1, E2, ... that the procedure can raise. For example, r can raises exceptions
Underflow and Overflow. The exceptions a procedure can raise are those
   • the other procedures it calls can raise;
   • it can raise itself;
   • that are not treated by any when clause at its end.
    Procedure q calls r and therefore could raise Underflow and Overflow. Since q has a clause “when
Underflow”, it can only raise exception Overflow. Procedure main calls q and r and therefore it can
raise both Overflow and Underflow. Since main has only a when Overflow clause, it could raise an
Underflow exception that would not be catch by any when clause. As the language is exception-safe,
the compiler would issue an error in procedure main. To correct the program a “when Underflow”
clause should be added to this procedure.
    An alternative to using raise-when constructs is to use the try blocks as shown in the following
example.
try
  begin
  read(n);
  q(n);
  end
catch ( Underflow )
  begin
  write(Underflow !);
  exit(1);
  end
catch( Overflow )
  begin
  write(Overflow !);
  exit(1);
  end
The commands that can raise an exception are put in the try block delimited by begin-end. The
catch clauses are the equivalent of when clauses.
    When using when clauses, there is no way of knowing which procedure command raised the excep-
tion. try blocks are best fitted in this case since they allow to test for exceptions in several points of
the procedure.


2.5     Garbage Collection
Some languages do not allow the explicit deallocation of dynamic allocated memory by the program.
That is, there is no command or procedure like free of C, dispose of Pascal, or delete of C++. These

                                                   31
languages are said to support garbage collection. This concept applies to languages with explicit or
implicit dynamic memory allocation. Explicit allocation occurs in languages as Java, Ada, Smalltalk,
C++, Pascal, and Modula-2 that have commands/functions to allocate dynamic memory as new or
malloc. Implicit allocation occurs in Prolog or Lisp-like languages in which dynamic structures as
lists shrink and grow automatically when necessary. When a list grows, new memory is automatically
allocated to it by the run-time system.
     The procedure that follows illustrates the basic point of garbage collection.

proc main()
  { do nothing --- just an example }

  var p, t : ^integer;
begin
p = integer.new();   {     1   }
t = p;               {     2   }
p = nil;             {     3   }
t = nil;             {     4   }
end

    The type “^integer”is “pointer to integer” and memory for an integer value is allocated by
“integer.new()”. A memory block allocated by new() will only be freed by the garbage collector
when no pointer points to it. In this example, two pointers will point to the allocated memory after
executing statement 2. After statement 3, one pointer, t, will point to the memory. After statement
4, there is no reference to the allocated memory and therefore it will never be used by the program
again. From this point hereafter, the memory can be freed by the garbage collector.
    The garbage collector is called to free unused memory from time to time or when the free available
memory drops below a limit. A simple garbage collector (GC) works with the set of all global variables
and all local variables/parameters of the procedures of the call stack. We will call this set Live. It
contains all variables that can be used by the program at the point the GC is called. All memory
blocks referenced by the pointers in Live can be used by the program and should not be deallocated.
This memory may have pointers to other memory blocks and these should not be freed either because
they can be referenced indirectly by the variables in Live. Extending this reasoning, no memory
referenced direct or indirectly by variables in Live can be deallocated. This requirement suggests the
garbage collector could work as follows.

  1. First it finds and marks all memory blocks that can be reached from the set Live following
     pointers.

  2. Then it frees all unmarked blocks since these will never be used by the program.

   There are very strong reasons to use garbage collection. They become clear by examining the
problems, described in the following items, that occur when memory deallocation is explicitly made
by the programmer.

   • A module may free a memory block still in use by other modules. There could be two live pointers
     p and t pointing to the same memory block and the program may execute “dispose(p)” or
     “free(p)” to liberate the block pointed to by p. When the memory block is accessed using t,
     there may be a serious error. Either the block may have been allocated by another call to new
     or the dispose/free procedure may have damaged the memory block by using some parts of it
     as pointers to a linked list of free blocks.

                                                 32
• The program may have memory leaks. That is, there could be memory blocks that are not
     referenced by the program and that were not freed with dispose/delete/free. These blocks
     will only be freed at the end of the program by the operating system.

   • Complex data structures make it difficult to decide when a memory block can safely be deal-
     located. The programmer has to foresee all possible behaviors of the program at run time to
     decide when to deallocate a block. If a block is referenced by two pointers of the same data
     structure,4 the program should take care of not deallocating the block twice when deallocating
     the data structure. This induces the programmer to build her own garbage collector. Program-
     mer’s made GC are known to be unsafe and slow when compared with the garbage collectors
     provided by compilers.

   • Different program modules should closely cooperate in order to decide when deallocating memory
     [4]. This makes the modules tightly coupled thus reducing their reusability. Notice this problem
     only happens when dynamic memory is passed by procedure parameters from one module to
     another or when using global variables to refer to dynamic memory.
      This item says explicit memory deallocation breaks encapsulation. One module should know
      not only the interface of other modules but also how their data is structured and how their
      procedures work.

   • Different deallocation strategies may be used by the modules of a program or the libraries used
     by it [4]. For example, the operation deleteAll of a Queue data structure5 could remove all
     queue elements and free their memory. In another data structures such as Stack the operation
     clearAll similar to deleteAll of Queue could remove all stack elements but not free the memory
     allocated to them.
      The use of different strategies in the same program such as when to deallocate memory reduces
      the program legibility thus increasing errors caused by dynamic memory.

   • Polymorphism makes the execution flow difficult to foresee. In an object-oriented language the
     compiler or the programmer does not know which procedure m (called method) will be called at
     run time in a statement like
           a.m(b)
     There may be several methods called m in the program and which method m is called is determined
     only at run time according to the value of a. Then the programmer does not know if pointer b
     will be stored by m in some non-local variable or if m will delete it. In fact, the programmer that
     wrote this statement may not even know all the methods m that may be executed at run time.
      For short, with polymorphism it becomes difficult to understand the execution flow and therefore
      it becomes harder to decide when it is safe to deallocate a memory block [4].

    There are also arguments against garbage collection:

   • it is slow;

   • it causes long pauses during user interaction;

   • it cannot be used in real-time systems in which there should be possible to know at compile time
     how long it will take to execute a piece of code at run time;
   4
     Not necessaryly two pointers of the same struct or record. There may be dozen of structs/records with a lot of
pointers linking them in a single composit structure.
   5
     Queue could be a class of an object-oriented language or an abstract data type implemented with procedures.


                                                        33
• it makes difficult to use two languages in the same program. To understand this point, suppose
     a program was build using code of two languages: Eiffel that supports garbage collection and
     C++ that does not. A memory block allocated in the Eiffel code may be passed as parameter
     to the C++ code that may keep a pointer to it. If at some moment no pointer in the Eiffel code
     refers to this memory block, it may be freed even if the C++ pointer refer to it. There is no
     way in which the Eiffel garbage collector can know about the C++ pointer.

    All of these problems but the last have been solved or ameliorated. Garbage collectors are much
faster today than they were in the past. They usually spend 10 to 30% of the total program execution
time in object-oriented languages and from 20 to 40% in Lisp programs. When using complex data
structures, garbage collection can be even faster than manual deallocation.
    Research in garbage collection has produced collectors for a large variety of tastes. For example,
incremental GC do not produce long delays in the normal processing and there are even collectors
used in real-time systems.




                                                 34
Cap´
   ıtulo 3

Linguagens Orientadas a Objeto

3.1       Introdu¸˜o
                 ca
Orienta¸˜o a objetos utiliza classes como mecanismo b´sico de estrutura¸˜o de programas. Uma classe
         ca                                             a                 ca
´ um tipo composto de vari´veis (como records e structs) e procedimentos. Assim, uma classe ´ uma
e                            a                                                                      e
extens˜o de records/structs com a inclus˜o de comportamento representado por procedimentos. Um
       a                                  a
exemplo de declara¸˜o de classe est´ na Figura 3.1 que declara uma classe Store com procedimentos
                     ca              a
get e put e uma vari´vel n. Na terminologia de orienta¸˜o a objetos, get e put s˜o m´todos e n ´
                         a                                 ca                           a    e             e
uma vari´vel de instˆncia.
           a           a
    Uma vari´vel da classe Store, declarada como
               a
      var s : Store;
´ tratata como se fosse um ponteiro na linguagem S. Assim, deve ser alocada mem´ria para s com a
e                                                                                       o
instru¸˜o
      ca
      s = Store.new();
Esta mem´ria ´ um objeto da classe Store. Um objeto ´ o valor correspondente a uma classe assim
             o   e                                         e
como 3 ´ um valor do tipo int e “Alo !” ´ um valor do tipo string. Objetos s´ existem em execu¸˜o
         e                                 e                                       o                    ca
e classes s´ existem em tempo de compila¸˜o,
             o                               ca  1 pois s˜o tipos. Classes s˜o esqueletos dos quais s˜o
                                                         a                   a                           a
criados objetos e vari´veis referem-se a objetos. Ent˜o o objeto referenciado por s possui uma vari´vel
                        a                             a                                                a
n e dois m´todos.
             e
    Os campos de um record de Pascal ou struct de C s˜o manipulados usando-se “.” como em
                                                              a
“pessoa.nome” ou “produto.preco”. Objetos s˜o manipulados da mesma forma:
                                                   a
      s.put(5);
      i = s.get();
Contudo, fora da classe Store apenas os m´todos da parte p´blica s˜o vis´
                                              e                 u      a             ´
                                                                              ıveis. E ent˜o ilegal fazer
                                                                                          a
      s.n = 5;
j´ que n pertence ` parte privada da classe. A parte p´blica ´ composta por todos os m´todos e
 a                   a                                      u      e                             e
vari´veis de instˆncia que se seguem ` palavra public. A parte p´blica termina em uma declara¸˜o
    a              a                   a                             u                                  ca
“private:” ou no fim da classe (endclass). Em S, apenas m´todos podem estar na parte p´blica de
                                                                 e                               u
uma classe.
    Alocando dois objetos, como em

  var s, t : Store;
begin
s = Store.new();
t = Store.new();
  1
      Pelo menos em S.


                                                    35
class Store
  public:
    proc get() : integer
      begin
      return n;
      end
    proc put( pn : integer )
      begin
      n = pn;
      end
 private:
   var n : integer;
endclass


                                     Figura 3.1: Class Store

s.put(5);
t.put(12);
write( s.get(),  , t.get() );
end

s˜o alocados espa¸os para duas vari´veis de instˆncia n, uma para cada objeto. Em “s.put(5)”, o
 a                c                a            a
m´todo put ´ chamado e o uso de n na instru¸˜o
  e          e                               ca
 n = pn
de put refere-se a “s.n”. Um m´todo s´ ´ invocado por meio de um objeto. Assim, as referˆncias
                                e      oe                                                 e
a vari´veis de instˆncia em um m´todo referem-se `s vari´veis de instˆncia deste objeto. Afinal,
       a            a             e                 a      a          a
os m´todos s˜o feitos para manipular os dados do objeto, adicionando comportamento ao que seria
     e       a
uma estrutura composta apenas por dados. Na nomenclatura de orienta¸˜o a objetos, uma instru¸˜o
                                                                     ca                     ca
“s.put(5)” ´ o envio da mensagem “put(5)” ao objeto referenciado por s (ou objeto s para simpli-
             e
ficar).


3.2    Prote¸˜o de Informa¸˜o
            ca            ca
Prote¸˜o de informa¸˜o ´ um mecanismo que impede o acesso direto ` implementa¸˜o (estruturas de
      ca            ca e                                             a           ca
dados) de uma classe. As vari´veis de instˆncia s´ s˜o manipuladas por meio dos m´todos da classe.
                               a           a      o a                              e
Na linguagem S, todas as vari´veis de instˆncia devem ser declaradas como privadas, impedindo a sua
                              a           a
manipula¸˜o fora dos m´todos da classe. Para exemplificar este conceito, usaremos a classe Pilha da
         ca             e
Figura 3.2. Uma pilha ´ uma estrutura de dados onde o ultimo elemento inserido, com empilhe, ´
                        e                                 ´                                       e
sempre o primeiro a ser removido, com desempilhe. Esta estrutura espelha o que geralmente acontece
com uma pilha de objetos quaisquer.
   Esta pilha poderia ser utilizada como no programa abaixo.

proc main()
  var p, q : Pilha;
begin
p = Pilha.new();
p.crie();      // despreza o valor de retorno
p.empilhe(1);

                                                36
const Max = 100;

class Pilha
  private:
    var topo : integer;
           { vetor de inteiros }
         vet : array(integer)[Max];
  public:
    proc crie() : boolean
      begin
      topo = -1;
      return true;
      end
    proc empilhe( elem : integer ) : boolean
      begin
      if topo = Max - 1
      then
         return false;
      else
         topo = topo + 1;
         vet[topo] = elem;
         return true;
      endif
      end
    proc desempilhe() : integer
      begin
      if topo  0
      then
         return -1;
      else
         elem = vet[topo];
         topo = topo - 1;
         return elem;
      endif
      end
    proc vazia() : boolean
      begin
      return topo  0;
      end
endclass


                              Figura 3.2: Uma pilha em S




                                         37
p.empilhe(2);
p.empilhe(3);
while not p.vazia() do
  write( p.desempilhe() );

q = Pilha.new();
q.crie();
q.empilhe(10);
if not p.empilhe(20)
then
  erro();
endif
end
   O programador que usa Pilha s´ pode manipul´-la por meio de seus m´todos, sendo um erro de
                                    o               a                e
compila¸˜o o acesso direto `s suas vari´veis de instˆncia:
       ca                  a           a            a
     p.topo = p.topo + 1; { erro de compilacao }
     p.vet[p.topo] = 1;        { erro de compilacao }

   A prote¸˜o de informa¸˜o possui trˆs caracter´
          ca            ca           e          ısticas principais:
  1. torna mais f´cil a modifica¸˜o de representa¸˜o da classe, isto ´, a estrutura de dados usada
                  a            ca               ca                  e
     para a sua implementa¸˜o. No caso de Pilha, a implementa¸˜o ´ um vetor (vet) e um n´mero
                           ca                                  ca e                        u
     inteiro (topo).
     Suponha que o projetista de Pilha mude a estrutura de dados para uma lista encadeada, reti-
     rando o vetor vet e a vari´vel topo e resultando na classe da Figure 3.3.
                               a
     O que foi modificado foi o c´digo dos m´todos (veja acima), n˜o o prot´tipo deles. Assim, todo o
                                o          e                     a        o
     c´digo do procedimento main visto anteriormente n˜o ser´ afetado. Por outro lado, se o usu´rio
      o                                                 a    a                                  a
     tivesse declarado vet e topo como p´blicos e usado
                                         u
            p.topo = p.topo + 1;
            p.vet[p.topo] = 1
     para empilhar 1, haveria um erro de compila¸˜o com a nova representa¸˜o de Pilha, pois esta
                                                 ca                         ca
     n˜o possui vetor vet. E o campo topo ´ um ponteiro, n˜o mais um inteiro;
       a                                    e               a

  2. o acesso aos dados de Pilha (vet e topo) por m´todos tornam a programa¸˜o de mais alto
                                                       e                          ca
     n´
      ıvel, mais abstrata. Lembrando, abstra¸˜o ´ o processo de desprezar detalhes irrelevantes para
                                            ca e
     o nosso objetivo, concentrando-se apenas no que nos interessa.
     Nesse caso, a instru¸˜o
                         ca
           p.empilhe(1)
     ´ mais abstrata do que
     e
           p.topo = p.topo + 1;
           p.vet[p.topo] = 1;
     porque ela despreza detalhes irrelevantes para quem quer empilhar um n´mero (1), como que a
                                                                           u
     Pilha ´ representada como um vetor, que esse vetor ´ vet, que p.topo ´ o topo da pilha, que
            e                                             e                e
     p.topo ´ inicialmente -1, etc;
              e

  3. os m´todos usados para manipular os dados (crie, empilhe, desempilhe, vazia) conferem a
           e
     utiliza¸˜o adequada dos dados. Por exemplo, “p.empilhe(1)” confere se ainda h´ espa¸o na
             ca                                                                       a      c
     pilha, enquanto que em nas duas instru¸˜es alternativas mostradas acima o usu´rio se esqueceu
                                           co                                     a

                                                 38
class Pilha
  private:
    var topo : Elem;
  public:
    proc crie() : boolean
      begin topo = nil; return true; end
    proc empilhe( elem : integer ) : boolean
         var w : Elem;
      begin
      w = Elem.new();
      if w == nil then return false;
      else
         w.setNum(elem);
         w.setSuc(topo);
         topo = w;
         return true;
      endif
      end
    proc desempilhe() : integer
         var w : Elem;
             elem : integer;
      begin
      if topo == nil then return -1;
      else
         elem = topo.getNum();
         w = topo;
         topo = topo.getSuc();
         delete w;
         return elem;
      endif
      end
    proc vazia() : boolean
      begin
      return topo == nil;
      end
endclass


                 Figura 3.3: Uma classe Pilha implementada com uma lista




                                           39
class A
  public:
    proc put( pn : integer )
      begin
      n = pn;
      end
    proc get() : integer
      begin
      return n;
      end
  private:
    var n : integer;
endclass

class B subclassOf A
  public:
    proc imp()
      begin
      write( get() );
      end
endclass


                             Figura 3.4: A classe B herda da classe A

      disto. Resumindo, ´ mais seguro usar Prote¸˜o de Informa¸˜o porque os dados s˜o protegidos
                        e                       ca            ca                   a
      pelos m´todos.
              e


3.3    Heran¸a
            c
Heran¸a ´ um mecanismo que permite a uma classe B herdar os m´todos e vari´veis de instˆncia de
      c e                                                        e           a             a
uma classe A. Tudo se passa como se em B tivessem sido definidos os m´todos e vari´veis de instˆncia
                                                                    e            a            a
de A. A heran¸a de A por B ´ feita com a palavra chave subclassOf como mostrado na Figura 3.4.
              c            e
   A classe B possui todos os m´todos definidos em A mais aqueles definidos em seu corpo:
                                e
         proc put( pn : integer )
         proc get() : integer
         proc imp()
Assim, podemos utilizar todos estes m´todos com objetos de B:
                                      e

proc main()
  var b : B;
begin
b = B.new();
b.put(12);            // invoca A::put
b.imp();              // invoca B::imp
write( b.get() );     // invoca A::get
end

A::put ´ o m´todo put da classe A. A classe B ´ chamada de “subclasse de A” e A ´ a “superclasse de
       e    e                                 e                                 e

                                                40
Pessoa
                                                          
                                                                  k
                                                                  
                                                                  
                                                                    
                                                                        
                                                                         
                                                                             
                                                Trabalhador               Estudante
                                              
                                                          s
                                                          d
                                                           d
                                                            d
                                                                d
                                    FuncPublico               Professor

                                    Figura 3.5: Hierarquia da classe Pessoa


B”.2
    O m´todo B::imp possui uma chamada para um m´todo get. O m´todo invocado ser´ A::get.
         e                                               e             e                 a
Esta chamada poderia ser escrita como “self.get()” pois self, dentro de um m´todo, refere-se ao
                                                                                 e
objeto que recebeu a mensagem que causou a execu¸˜o do m´todo. Assim, o envio de mensagem
                                                       ca      e
“b.put(5)” causa a execu¸˜o do m´todo A::put e conseq¨entemente da atribui¸˜o “n = pn”. O “n”
                           ca      e                       u                  ca
refere-se a “b.n” e poder´
                         ıamos ter escrito esta atribui¸˜o como “self.n = pn”. self ´ o objeto que
                                                       ca                           e
recebeu a mensagem, b.
    A classe B pode redefinir m´todos herdados de A:
                              e

class B subclassOf A
  public:
    proc get() : integer
      begin
      return super.get() + 1;
      end
    proc imp()
      begin
      write( get() );
      end
endclass

    “super.get()” invoca o m´todo get da superclasse de B, que ´ A. Na chamada a get em imp, o
                                 e                                   e
m´todo get a ser usado ´ o mais pr´ximo poss´ na hierarquia de classes , que ´ B::get.
  e                      e           o           ıvel                             e
    Heran¸a ´ utilizada para expressar relacionamentos do tipo “´ um”. Por exemplo, um estudante ´
          c e                                                     e                                e
uma pessoa, um funcion´rio p´blico ´ um trabalhador, um professor ´ um trabalhador, um trabalhador
                        a      u     e                               e
´ uma pessoa. Estes relacionamentos s˜o mostrados na hierarquia da Figura 3.5, na qual a heran¸a de
e                                       a                                                        c
A por B ´ representada atrav´s de uma seta de B para A. A subclasse sempre aparecer´ mais embaixo
        e                     e                                                        a
nas figuras.
    Uma subclasse ´ sempre mais espec´
                    e                    ıfica do que a sua superclasse. Assim, um trabalhador ´ mais
                                                                                               e
espec´
     ıfico do que uma pessoa porque todo trabalhador ´ uma pessoa, mas o contr´rio nem sempre
                                                          e                          a
´ verdadeiro. Se tiv´ssemos feito Pessoa herdar Trabalhador, haveria um erro l´gico no programa,
e                     e                                                            o
mesmo se n˜o houvesse nenhum erro de compila¸˜o.
            a                                      ca
    Considere agora a hierarquia de classes representando figuras das Figuras 3.6 e 3.7. Se a classe
Circulo precisar utilizar as vari´veis x e y herdadas de Figura, ela dever´ chamar os m´todos getX()
                                  a                                       a              e
  2
      Na terminologia usualmente empregada em C++, A ´ a “classe base” e B a “classe derivada”.
                                                     e



                                                         41
class Figura
  public:
    proc set_xy( px, py : integer )
      begin
      x = px;
      y = py;
      end
    proc imp()
      begin
      write( Centro(, x, , , y, ) );
      end
    proc getX() : integer
      begin
      return x;
      end
    proc getY() : integer
      begin
      return y;
      end
  private:
    var x, y : integer;
endclass


                               Figura 3.6: Classe Figura




                                          42
class Circulo subclassOf Figura
  public:
    proc init( p_raio : real; x, y : integer )
      begin
      super.set_xy(x, y);
      raio = p_raio;
      end
    proc setRaio( p_raio : real )
      begin
      raio = p_raio;
      end
    proc getRaio() : real
      begin
      return raio;
      end
    proc imp()
      begin
      write( raio = , raio );
      super.imp();
      end
    proc getArea() : real
      begin
      return PI*raio*raio;
      end
  private:
    raio : real;
endclass


                              Figura 3.7: Classe Circulo




                                         43
e getY() desta classe. Uma subclasse n˜o pode manipular diretamente a parte privada da superclasse.
                                       a
Se isto fosse permitido, modifica¸˜es na representa¸˜o de uma classe poderiam invalidar as subclasses.
                                co                ca
    Algumas linguagens, como C++ e Eiffel, permitem que uma classe herde de mais de uma super-
classe. Esta facilidade causa uma ambig¨idade quando dois m´todos de mesmo nome s˜o herdados de
                                        u                   e                        a
duas superclasses diferentes. Por exemplo, suponha que uma classe JanelaTexto herde de Texto e
Janela e que ambas as superclasses definam um m´todo getNome. Que m´todo o envio de mensagem
                                                   e                     e
“jt.getNome()” dever´ invocar se o tipo de jt for JanelaTexto ? Em C++, h´ duas formas de se
                        a                                                       a
resolver esta ambig¨idade:
                     u
  1. a primeira ´ especificando-se qual superclasse se quer utilizar:
                e
           nome = jt.A::getNome()


  2. a segunda ´ definir um m´todo getNome em JanelaTexto.
               e            e
Em Eiffel o nome do m´todo getNome herdado de Texto ou Janela deve ser renomeado, evitando
                         e
assim a colis˜o de nomes.
              a
    Uma linguagem em que ´ permitido a uma classe herdar de mais de uma superclasse suporta
                               e
heran¸a m´ltipla. Este conceito, aparentemente muito util, n˜o ´ muito utilizado em sistemas reais
      c     u                                              ´    a e
e torna os programas mais lentos porque a implementa¸˜o de envio de mensagens ´ diferente do
                                                             ca                          e
que quando s´ h´ heran¸a simples. Heran¸a m´ltipla pode ser simulada, pelo menos parcialmente,
               o a        c                  c    u
declarando-se um objeto da superclasse na subclasse e redirecionando mensagens a este objeto — veja
Figura 3.8.
    H´ ainda outro problema com heran¸a m´ltipla. Considere que as classes B e C herdem da classe A
      a                                  c     u
e a classe D herde de B e C, formando um losango. Um objeto da classe D tamb´m ´ um objeto de A, B
                                                                                 e e
e C. Este objeto deve ter todas as vari´veis de A, B e C. Mas deve o objeto ter um ou dois conjuntos de
                                       a
dados de A ? Afinal, a classe D herda A por dois caminhos diferentes. Em alguns casos, seria melhor D
ter dois conjuntos de dados de A. Em outros, ´ melhor ter apenas um conjunto. Veja estes exemplos:
                                                e

      • a classe Pessoa ´ herdada por Estudante e Atleta, que s˜o herdadas por BolsistaAtleta.3
                        e                                         a
        Neste caso, deve-se ter um unico nome em objetos da classe BolsistaAtleta;
                                   ´

      • a classe Trabalhador ´ herdada por Professor e Gerente, que s˜o herdados por ProfessorGerente.4
                              e                                      a
        Neste caso, deve-se ter os dados do trabalhador, como tempo de servi¸o e nome do empregador,
                                                                            c
        duplicados em objetos de ProfessorGerente. Seria interessante que Trabalhador herdasse de
        Pessoa. Assim, um objeto de ProfessorGerente teria apenas uma vari´vel para nome e outros
                                                                              a
        dados b´sicos.
                 a

  Algumas linguagens optam por uma destas op¸˜es enquanto que outras permitem que se escolha
                                            co
uma delas no momento da heran¸a.
                             c


3.4       Polimorfismo
Se o tipo de uma vari´vel w for uma classe T, a atribui¸˜o
                      a                                ca
       w = nil;
estar´ correta qualquer que seja T. Isto ´ poss´
     a                                   e     ıvel porque nil ´ um valor polim´rfico: ele pode ser
                                                               e               o
usado onde se espera uma referˆncia para objetos de qualquer classe. Polimorfismo quer dizer faces
                                e
m´ltiplas e ´ o que acontece com nil, que possui infinitos tipos.
  u         e
  3
      O atleta ganha uma bolsa de estudos por ser atleta.
  4
      O professor trabalha em tempo parcial e tamb´m ´ um gerente.
                                                   e e


                                                         44
class B
  public:
    proc init()
      begin
      a = B.new();
      end
    proc get() : integer
      begin
      return a.get();
      end
    proc put( pn : integer )
      begin
      a.put(pn);
      end
    proc imp()
      begin
      write(get());
      end
    proc getA()
      begin
      return a;
      end
  private:
    var a : A;
endclass


                       Figura 3.8: Simula¸˜o de heran¸a de A por B
                                         ca          c




                                           45
Em S, uma vari´vel cujo tipo ´ uma classe pode referir-se a objetos de subclasses desta classe. O
                 a              e
c´digo
 o

proc main()
  var f : Figura;
      c : Circulo;
begin
c = Circulo.new();
c.init( 20.0, 30, 50 );
f = c;
f.imp();
end

est´ correto. A atribui¸˜o
    a                      ca
        f = c;
atribui uma referˆncia para Circulo a uma vari´vel de Figura.
                   e                              a
     S permite atribui¸˜es do tipo
                         co
        Classe = Subclasse
como a acima, que ´    e
        Figura = Circulo
Uma vari´vel cujo tipo ´ uma classe A sempre ser´ polim´rfica, pois ela poder´ apontar para objetos
           a                e                       a      o                     a
de A ou qualquer objeto de subclasses de A.
     Agora, qual m´todo
                    e
        f.imp()
ir´ invocar ? f referencia um objeto de Circulo e, portanto, seria natural que o m´todo invo-
  a                                                                                          e
cado fosse “Circulo::imp”. Contudo, o tipo de f ´ “Figura” e “f.imp()” tamb´m poderia invocar
                                                      e                              e
Figura::imp.
     O envio de mensagem “f.imp()” invocar´ o m´todo imp de Circulo. Ser´ feita uma busca em
                                               a      e                           a
tempo de execu¸˜o por m´todo imp na classe do objeto apontado por f. Se imp n˜o for encontrado
                 ca           e                                                        a
nesta classe, a busca continuar´ na superclasse, superclasse da superclasse e assim por diante. Quando
                                 a
o m´todo for encontrado, ele ser´ chamado. Sendo a busca feita em tempo de execu¸˜o, ser´ sem-
      e                            a                                                      ca      a
pre chamado o m´todo mais adequado ao objeto, isto ´, se f estiver apontando para um c´
                     e                                     e                                      ırculo,
ser´ chamado o m´todo imp de Circulo, se estiver apontando para um retˆngulo, ser´ chamado
    a                 e                                                           a          a
Retangulo::imp e assim por diante.
     A instru¸˜o “f.imp()” causar´ uma busca em tempo de compila¸˜o por m´todo imp na classe
             ca                      a                                  ca          e
declarada de f, que ´ Figura (f ´ declarado como “f : Figura”). Se imp n˜o fosse encontrado l´,
                         e         e                                             a                     a
a busca continuaria na superclasse de Figura (se existisse), superclasse da superclasse e assim por
diante. Se o compilador n˜o encontrar o m´todo imp, ele sinalizar´ um erro. Isto significa que uma
                               a             e                       a
instru¸˜o
        ca
        f.setRaio(10);
ser´ ilegal mesmo quando tivermos certeza de que f apontar´ em tempo de execu¸˜o para um objeto
    a                                                         a                      ca
de Circulo (que possui m´todo setRaio). A raz˜o para esta restri¸˜o ´ que o compilador n˜o pode
                               e                    a                 ca e                      a
garantir que f apontar´ para um objeto que possui m´todo setRaio. A inicializa¸˜o de f pode
                            a                              e                             ca
depender do fluxo de controle:

proc m( i : integer )
  var f, aFig : Figura;
      aCir : Circulo;
begin


                                                   46
aCir = Circulo.new();
aCir.init(20.0, 50, 30);
aFig = Figura.new();
aFig.set_xy( 30, 40 );
if i  0
then
  f = aFig;
else
  f = aCir;
endif
f.setRaio(10);
...
end

Se este procedimento fosse legal, f poderia ser inicializado com aFig. Em tempo de execu¸˜o, seria
                                                                                             ca
feita uma busca por m´todo setRaio na classe Figura e este m´todo n˜o seria encontrado, resultando
                         e                                     e       a
em um erro de tempo de execu¸˜o com o t´rmino do programa.
                                ca          e
     Como resultado da discuss˜o acima, temos que
                               a
       f.imp()
ser´ v´lido quando imp pertencer ` classe declarada de f ou suas superclasses (se existirem). J´ que f
    a a                            a                                                            a
pode apontar para um objeto de uma subclasse de Figura, podemos garantir que a classe deste objeto
possuir´ um m´todo imp em tempo de execu¸˜o ? A resposta ´ “sim”, pois f pode apontar apenas
        a       e                               ca               e
para objetos de Figura ou suas subclasses. O compilador garante, ao encontar
       f.imp()
que a classe declarada de f, Figura, possui m´todo imp e, como todas as subclasses herdam os m´todos
                                              e                                                  e
das superclasses, as subclasses de Figura possuir˜o pelo menos o m´todo imp herdado desta classe.
                                                   a                 e
Assim, f apontar´ para um objeto de Figura ou suas subclasses e este objeto certamente possuir´ um
                   a                                                                               a
m´todo imp.
  e
     Polimorfismo ´ fundamental para o reaproveitamento de software. Quando um m´todo aceitar
                    e                                                                     e
como parˆmetro um objeto de Figura, como
          a
       proc m( f : Figura )
podemos passar como parˆmetro objetos de qualquer subclasse desta classe. Isto porque uma chamada
                           a
       m(aCir);
envolve uma atribui¸˜o “f = aCir”, que ser´ correta se for da forma
                      ca                      a
       Classe = Subclasse
Ent˜o, podemos passar como parˆmetro a m objetos de Circulo, Retangulo, etc. N˜o ´ necess´rio
     a                             a                                                    a e          a
construir um m´todo m para objetos de cada uma das subclasses de Figura — um mesmo m´todo m
                 e                                                                             e
pode ser utilizado com objetos de todas as subclasses. O c´digo de m ´ reaproveitado por causa do
                                                             o           e
polimorfismo.
     Admitindo que as classes Retangulo e Triangulo existam e s˜o subclasses de Figura, o c´digo a
                                                                   a                            o
seguir mostra mais um exemplo de polimorfismo.

...

proc impVet( v : array(Figura)[]; n : integer )
  { Imprime cada elemento do vetor v de n elementos.
    v ´ um vetor de figuras. }
      e

  var i : integer;

                                                 47
begin
for i = 0 to n do
  v[i].imp();
end


proc main()
  var c1     : Circulo;
      r1, r2 : Retangulo;
      t1     : Triangulo;
      vetFig : array(Figura)[];
begin
c1 = Circulo.new();
r1 = Retangulo.new();
r2 = Retangulo.new();
t1 = Triangulo.new();
c1.init( 5.0, 80, 30 );
r1.init( 30, 50, 70, 60 );
r2.init( 20, 100, 80, 150 );
t1.init( 10, 18, 30, 20, 40, 25 );
  { atribui varios elementos a vetFig }
vetFig = ( r1, t1, r2, c1 );
impVet( vetFig, 4 );
end

A fun¸˜o impVet percorre o vetor v enviando a mensagem imp a cada um de seus elementos. O m´todo
      ca                                                                                    e
imp executado depender´ da classe do objeto apontado por “v[i]”.
                        a
   Existe uma outra forma de polimorfismo em C++ que ser´ mostrada acrescentando-se m´todos
                                                            a                              e
nas classes Figura e Circulo:

class Figura
  public:
    ...
    proc desenhe() begin end { vazio }
    proc apague() begin end
    proc mova( nx, ny : integer )
      begin
      apague();
      x = nx;
      y = ny;
      desenhe();
      end
  private:
    var x, y : integer;
endclass

class Circulo subclassOf Figura
  public:
      { Os ... abaixo sao os metodos de Circulo definidos anteriormente }

                                              48
...
    proc desenhe()
      begin
         { desenha um circulo }
      ...
      end
    proc apague()
      begin
         { apagua o circulo }
      ...
      end
  private:
    var raio : real;
endclass
   Os m´todos desenhe e apague de Figura n˜o fazem nada porque esta classe foi feita para ser
        e                                       a
herdada e n˜o para se criar objetos dela. O m´todo mova apaga o desenho anterior, move a figura e a
           a                                 e
desenha novamente. Como desenhe e apague s˜o vazios em Figura, mova s´ faz sentido se desenhe
                                               a                         o
e apague forem redefinidos em subclasses. Em
  var c : Circulo;
begin
c = Circulo.new();
c.init( 10.0, 50, 30 );
c.mova( 20, 80 );
...
end
o m´todo invocado em “c.mova(20, 80)” ser´ “Figura::mova”. Este m´todo possui um envio de
    e                                         a                            e
mensagem
      apague();
que ´ o mesmo que
    e
      self.apague();
que envia a mensagem apague ao objeto que recebeu a mensagem mova, que ´ “c”. Ent˜o, a busca por
                                                                            e        a
m´todo apague ser´ iniciada em Circulo (classe do objeto c), onde Circulo::apague ser´ encontrado
  e                 a                                                                   a
e executado. Da mesma forma, a instru¸˜oca
      desenhe()
em Figura::mova invocar´ Circulo::desenhe.
                           a
    Observando este exemplo, verificamos que n˜o foi necess´rio redefinir o m´todo mova em Circulo —
                                              a           a                e
o seu c´digo foi reaproveitado. Se tivermos uma classe Retangulo, subclasse de Figura, precisaremos
       o
de definir apenas desenhe e apague. O m´todo mova ser´ herdado de Figura e funcionar´ corretamente
                                         e             a                              a
com retˆngulos. Isto ´, o c´digo
        a             e     o
  var r : Retangulo;
begin
r = Retangulo.new();
r.init( 30, 50, 70, 20 );
r.mova( 100, 120 );
...
end

                                                49
invocar´ os m´todos desenhe e apague de Retangulo.
       a      e
   As redefini¸˜es de apague e desenhe em Circulo causaram altera¸˜es no m´todo mova herdado
               co                                                     co       e
de Figura, adaptando-o para trabalhar com c´
                                           ırculos. Ou seja, mova foi modificado pela redefini¸˜o de
                                                                                            ca
outros m´todos em uma subclasse. N˜o foi necess´rio redefinir mova em Circulo adaptando-o para
         e                          a            a
a nova situa¸˜o. Dizemos que o c´digo de mova foi reaproveitado em Circulo. O m´todo mova se
             ca                  o                                                  e
comportar´ de maneira diferente em cada subclasse, apesar de ser definido uma unica vez em Figura.
           a                                                                 ´


3.5     Modelos de Polimorfismo
Esta se¸˜o descreve quatro formas de suporte a polimorfismo empregado pelas linguagens Smalltalk,
       ca
POOL-I, Java e C++. Naturalmente, os modelos de polimorfismo descritos nas subse¸˜es seguintes
                                                                                     co
s˜o abstra¸˜es das linguagens reais e podem apresentar diferen¸as em rela¸˜o a elas.
 a        co                                                  c          ca

3.5.1   Smalltalk
Smalltalk [5] ´ uma linguagem tipada dinamicamente, o que quer dizer que na declara¸˜o de uma
              e                                                                         ca
vari´vel ou parˆmetro n˜o se coloca o tipo. Durante a execu¸˜o, uma vari´vel ir´ se referir a um
    a           a        a                                   ca            a      a
objeto e ter´ o tipo deste objeto. Conseq¨entemente, uma vari´vel pode se referir a objetos de tipos
            a                            u                   a
diferentes durante a sua existˆncia.
                              e
    No exemplo abaixo,

    ...
    var a, b;
begin
a = 1;
b = Janela.new();
b.init(a, 5, 20, 30);
a = b;
a.desenha();
...
end

se a instru¸˜o “a.desenha()” for colocada logo ap´s “a = 1”, haver´ o envio da mensagem desenha
           ca                                        o               a
a um n´mero inteiro. Como a classe dos inteiros n˜o possui um m´todo desenha, ocorrer´ um erro
        u                                             a             e                     a
de tipos e o programa ser´ abortado.
                         a
    Considere agora um m´todo
                          e
     proc m(y)
        begin
        y.desenha();
        y.move(10, 20);
        end
de uma classe A. Assuma que exista uma classe Janela em Smalltalk, que ´ aquela da Figura 3.9
                                                                             e
sem os tipos das declara¸˜es de vari´veis. Esta classe possui m´todos desenha e move, sendo que este
                        co          a                          e
ultimo n˜o causa erros de tipo se os seus dois parˆmetros s˜o n´meros inteiros.
´        a                                         a        a u
    Se um objeto de Janela for passado com parˆmetro a m, como em
                                                  a
       a = A.new();
       a.m( Janela.new() );
n˜o haver´ erros de tipo dentro deste m´todo. Se a m for passado um objeto de uma subclasse de
  a        a                              e
Janela, tamb´m n˜o haver´ erros de tipo. A raz˜o ´ que uma subclasse possui pelo menos todos os
              e    a        a                      a e

                                                50
class Janela
  private:
    var x, y : integer;
  public:
    proc desenha() begin ... end
    proc move( novo_x, novo_y : integer )
      begin
      self.x = novo_x;
      self.y = novo_y;
      self.desenha();
      end
    proc init( px, py : integer ); begin x = px; y = py; end
endclass

class JanelaTexto subclassOf Janela
  ...
  public:
    proc desenha() begin ... end
endclass


                             Figura 3.9: Classes Janela e JanelaTexto


m´todos da superclasse. Assim, se um objeto de Janela sabe responder a todas as mensagens enviadas
  e
a ele dentro de m, um objeto de uma subclasse tamb´m saber´ responder a todas estas mensagens.
                                                     e        a
Estamos admitindo que, se move for redefinido em uma subclasse, ele continuar´ aceitando dois inteiros
                                                                            a
como parˆmetros sem causar erros de tipo.
          a
    De fato, o m´todo m pode aceitar como parˆmetros objetos de qualquer classe que possua m´todos
                 e                            a                                               e
move e desenha tal que move aceita dois inteiros como parˆmetros e desenha n˜o possui parˆmetros.
                                                          a                   a            a
N˜o ´ necess´rio que esta classe herde de Janela. Este sistema de tipos, sem restri¸˜o nenhuma que
  a e         a                                                                    ca
n˜o seja a capacidade dos objetos de responder `s mensagens que lhe s˜o enviadas, possui o maior
 a                                               a                      a
grau poss´ de polimorfismo.
          ıvel
    Se m for codificado como

proc m(y, b)
  begin
  y.desenha();
  y.move(10, 20);
  if b
  then
    y.icon();
  endif
  end

o c´digo
   o
     a = A.new();
     a.m( Janela.new(), false );
n˜o causar´ erro de tipos em tempo de execu¸˜o, pois a mensagem icon n˜o ser´ enviada ao objeto
 a        a                                 ca                            a    a
de Janela em execu¸˜o. Se fosse enviada, haveria um erro j´ que a classe Janela n˜o possui m´todo
                    ca                                    a                      a          e


                                                 51
class JanelaProcesso
  ...
  public:
    proc desenha()                        begin   ...   end
    proc move( nx, ny : integer )         begin   ...   end
    proc init( px, py : integer )         begin   ...   end
    proc iniciaProcesso()                 begin   ...   end
    proc setProcesso( s : string )        begin   ...   end
endclass


                         Figura 3.10: Uma classe que ´ subtipo de Janela
                                                     e


icon.
    Em geral, o fluxo de execu¸˜o do programa, controlado por if’s, while’s e outras estruturas, de-
                              ca
termina quais mensagens s˜o enviadas para cada vari´vel. E este mesmo fluxo determina a capacidade
                          a                        a
de cada vari´vel de responder a mensagens. Para compreender melhor estes pontos, considere o c´digo
            a                                                                                 o

if b  0
then
  a = Janela.new();
else
  a = 1;
endif
if c  1
then
  a.desenha();
else
  a = a + 1;
endif

O primeiro if determina quais as mensagens a pode responder, que depende da classe do objeto a
que a se refere. O segundo if seleciona uma mensagem a ser enviada ` vari´vel a. Em Smalltalk, “+
                                                                   a     a
1” ´ considerado um envio de mensagem.
   e
    Ent˜o, o fluxo de execu¸˜o determina a corretude de tipos de um programa em Smalltalk, o que
       a                   ca
torna os programas muito inseguros. Alguns trechos de c´digo podem revelar um erro de tipos ap´s
                                                       o                                       o
meses de uso. Note que, como ´ imposs´ prever todos os caminhos de execu¸˜o de um programa em
                               e       ıvel                                ca
tempo de compila¸˜o, ´ tamb´m imposs´ garantir estaticamente que um programa em Smalltalk ´
                  ca e        e         ıvel                                                    e
corretamente tipado.

3.5.2   POOL-I
Esta se¸˜o descreve o modelo das linguagens POOL-I [1] e Green [8] [7]. Como o sistema de tipos
       ca
de Green foi parcialmente baseado no de POOL-I, este modelo ser´ chamado de modelo POOL-I.
                                                                  a
Green e POOL-I s˜o linguagens estaticamente tipada, pois todos os erros de tipo s˜o descobertos em
                  a                                                              a
compila¸˜o.
        ca
    Neste modelo, o tipo de uma classe ´ definido como o conjunto das interfaces (assinaturas ou
                                         e
signatures) de seus m´todos p´blicos. A interface de um m´todo ´ o seu nome, tipo do valor de
                      e        u                            e     e
retorno (se houver) e tipos de seus parˆmetros formais (o nome dos parˆmetros ´ desprezado). Por
                                       a                               a        e


                                                  52
exemplo, o tipo da classe Janela da Figura 3.9 ´  e
       { desenha(), move(integer, integer), init(integer, integer)}
sendo que { e } s˜o utilizados para delimitar os elementos de um conjunto, como em matem´tica. Um
                   a                                                                         a
tipo U ser´ subtipo de um tipo T se U possuir pelo menos as interfaces que T possui. Isto ´, T ⊂
           a                                                                                    e
U. Como exemplo, o tipo da classe JanelaProcesso da Figura 3.10 ´ um subtipo do tipo da classe
                                                                       e
Janela da Figura 3.9. Como abrevia¸˜o, dizemos que a classe JanelaProcesso ´ subtipo da classe
                                       ca                                          e
Janela.
    Quando uma classe B herdar de uma classe A, diremos que B ´ subclasse de A. Neste caso, B herdar´
                                                                 e                                    a
todos os m´todos p´blicos de A, implicando que B ´ subtipo de A.5 Observe que toda subclasse ´
             e       u                                 e                                              e
tamb´m subtipo, mas ´ poss´ existir subtipo que n˜o ´ subclasse — a classe JanelaProcesso da
     e                  e     ıvel                      a e
Figura 3.10 ´ subtipo mas n˜o subclasse de Janela.
              e              a
    Neste modelo, uma atribui¸˜oca
        t = s
estar´ correta se a classe declarada de s for subtipo da classe declarada de t. As atribui¸˜es do tipo
     a                                                                                    co
        Tipo = SubTipo;
s˜o v´lidas.
 a a
    Esta restri¸˜o permite a detec¸˜o de todos os erros de tipo em tempo de compila¸˜o, por duas
                ca                 ca                                                    ca
raz˜es:
   o
      • Em um envio de mensagem
              t.m(b1 , b2 , ... bn )
        o compilador confere se a classe com que t foi declarada possui um m´todo chamado m cujos
                                                                                  e
        parˆmetros formais possuem tipos T1 , T2 , ... Tn tal que o tipo de bi ´ subtipo de Ti , 1 ≤ i ≤ n.
           a                                                                   e
        A regra “Tipo = Subtipo” ´ obedecida tamb´m em passagem de parˆmetros.
                                   e                   e                        a

      • Ao executar este envio de mensagem, ´ poss´ que t n˜o se refira a um objeto de sua classe,
                                            e     ıvel        a
        mas de um subtipo do tipo da sua classe, por causa das atribui¸˜es do tipo Tipo = SubTipo,
                                                                      co
        como t = s. De qualquer forma, n˜o haver´ erro de execu¸˜o, pois tanto a sua classe quanto
                                         a        a              ca
        qualquer subtipo dela possuem o m´todo m com parˆmetros formais cujos tipos s˜o T1 , ... Tn .
                                         e               a                            a
    Em uma declara¸˜oca
        var a : A
a vari´vel a ´ associada ao tipo da classe A e n˜o ` classe A. Deste modo, a pode se referir a objetos de
      a      e                                  a a
classes que s˜o subtipos sem serem subclasses de A. Este ´ o motivo pelo qual a declara¸˜o da vari´vel
             a                                              e                            ca          a
a n˜o aloca mem´ria automaticamente para um objeto da classe A.
   a              o

3.5.3      C++
C++ [14] ´ uma linguagem estaticamente tipada em que todo subtipo ´ subclasse. Portanto, as
           e                                                      e
atribui¸˜es v´lidas possuem a forma
       co    a
           Classe = Subclasse

  Assume-se que a vari´vel do lado esquerdo da atribui¸˜o seja um ponteiro e que o lado direito seja
                       a                              ca
uma referˆncia para um objeto:
         e
Figura *p;
...
p = new Circulo(150, 200, 30);
  5
    A linguagem POOL-I, ao contr´rio deste modelo, permite subclasses que n˜o s˜o subtipos. Em Green, todas as
                                a                                          a a
subclasses s˜o subtipos.
            a


                                                     53
N˜o h´ polimorfismo em C++ quando n˜o se utiliza ponteiros. Se p fosse declarado como “Figura p;”,
  a a                                    a
ele poderia receber apenas objetos de Figura em atribui¸˜es. Neste modelo assume-se que n˜o h´
                                                           co                                 a a
vari´veis cujo tipo sejam classes, apenas ponteiros para classes.
    a
    O motivo pelo qual o modelo C++ exige que subtipo seja tamb´m subclasse ´ o desempenho. Uma
                                                                  e            e
chamada de m´todo ´ feita atrav´s de um vetor de ponteiros para fun¸˜es e ´ apenas duas ou trˆs
                e      e           e                                    co    e                    e
vezes mais lenta do que uma chamada de fun¸˜o normal.
                                              ca
    C++ suporta m´todos virtuais e n˜o virtuais, sendo que nestes ultimos a busca pelo m´todo ´ feita
                     e                 a                           ´                    e     e
em compila¸˜o — a liga¸˜o mensagem/m´todo ´ est´tica. Nesta subse¸˜o, consideramos que todos os
             ca          ca                e    e    a                ca
m´todos s˜o virtuais.
  e        a

3.5.4     Java
Java [11] [10] suporta apenas heran¸a simples. Contudo, a linguagem permite a declara¸˜o de interfaces
                                   c                                                 ca
que podem ser utilizados em muitos casos em que heran¸a m´ltipla deveria ser utilizada. Uma interface
                                                       c    u
declara assinaturas (signatures ou interfaces) de m´todos:
                                                   e

interface Printable {
   void print();
}

Uma assinatura de um m´todo ´ composto pelo tipo de retorno, o nome do m´todo e os parˆmetros
                         e     e                                        e             a
e seus tipos (sendo os nomes dos parˆmetros opcionais).
                                    a
    Uma classe pode implementar uma interface:

class Worker subclassOf Person implements Printable {
   ...
   real getSalary() { ... }
   void print() { ... }
}

Quando uma classe implementa uma interface, ela ´ obrigada a definir (com o corpo) os m´todos
                                                       e                                     e
que aparecem na interface. Se a classe Worker n˜o definisse o m´todo print, haveria um erro de
                                                    a                 e
compila¸˜o. Uma classe pode herdar de uma unica classe mas pode implementar v´rias interfaces
        ca                                       ´                                    a
diferentes.
    Este modelo considera as interfaces como classes de uma linguagem com heran¸a m´ltipla exceto
                                                                                  c     u
que as interfaces n˜o podem definir m´todos. Interfaces s˜o similares a classes abstratas6 e tudo se
                   a                    e                   a
passa como se o modelo admitisse heran¸a m´ltipla onde todas as classes herdadas s˜o completamente
                                         c    u                                   a
abstratas (sem nenhum corpo de m´todo) exceto possivelmente uma delas. Um tipo neste modelo ´
                                     e                                                            e
uma classe ou uma interface. Se uma classe A implementa uma interface I, ent˜o A ´ subtipo de I. E
                                                                              a     e
A ´ subtipo de sua superclasse, se existir. As atribui¸˜es v´lidas s˜o
  e                                                   co    a       a
      Tipo = subtipo

      Pode-se declarar uma vari´vel cujo tipo ´ uma interface. Como exemplo, o c´digo abaixo ´ v´lido.
                               a              e                                 o            e a

Printable p;
Person person;
person = new Worker();
p = person;
  6
    A diferen¸a ´ que classes abstratas podem declarar vari´veis de instˆncia e o corpo de alguns m´todos. E podem
             c e                                           a            a                          e
possuir m´todos privados. Em uma interface, todos os m´todos s˜o p´blicos.
         e                                             e        a u


                                                       54
proc Q( x : Janela; y : integer )
  begin
  x.desenha();
  if y  1
  then
    x.move(20, 5);
  endif
  end


                          Figura 3.11: Um procedimento no modelo C++


p.print();
p = new NightWorker(); // NightWorker ´ subclasse de Worker
                                      e

    Java ´ estaticamente tipada. Ent˜o, se o tipo de uma vari´vel ´ uma interface, apenas m´todos
          e                          a                         a    e                         e
com assinaturas declaradas na interface podem ser chamadas por meio da vari´vel. Por exemplo, por
                                                                             a
meio de p acima pode-se chamar apenas o m´todo print.
                                            e
    Interfaces em Java s˜o uma forma de adicionar os benef´
                        a                                 ıcios de heran¸a m´ltipla ` linguagem mas
                                                                        c   u       a
sem alguns dos problemas desta facilidade (como duplica¸˜o dos dados de objetos herdados por mais
                                                        ca
de um caminho — veja p´gina 44).
                          a
    Neste modelo Java, uma interface n˜o pode herdar de outra interface. Na linguagem Java isto ´
                                       a                                                          e
legal.

3.5.5   Compara¸˜o entre os Modelos de Polimorfismo e Sistema de Tipos
               ca
Agora podemos comparar o polimorfismo dos modelos de linguagens descritos acima. Considere o
m´todo Q da Figura 3.11 no modelo C++. Ele pode receber, como primeiro parˆmetro (x), objetos
   e                                                                               a
da classe Janela ou qualquer subclasse desta classe.
     Em Java, se Janela ´ uma interface, o primeiro parˆmetro passado a Q pode ser objeto de quaisquer
                        e                              a
classes que implementem esta interface ou que herdem das classes que implementam esta interface.
As classes que implementam uma interface geralmente n˜o tˆm nenhuma rela¸˜o de heran¸a entre
                                                          a e                   ca            c
si. Se quisermos passar um objeto de uma classe A para Q, basta fazer com que A implemente a
interface Janela. Isto ´, A deveria implementar os m´todos definidos em Janela e que possivelmente
                       e                              e
s˜o utilizados no corpo de Q.
 a
     Em uma linguagem com heran¸a simples e que n˜o suporte interfaces (como definidas em Java),
                                   c                  a
apenas objetos de Janela e suas subclasses poderiam ser passados como primeiro parˆmetro (assu-
                                                                                        a
mindo ent˜o que Janela ´ uma classe e n˜o um interface). Para passar objetos de uma classe A como
           a              e                a
parˆmetros, dever´
     a             ıamos fazer esta classe herdar de Janela, o que n˜o seria poss´ se A j´ herdasse
                                                                    a             ıvel      a
de uma outra classe.
     Se Janela for uma classe, poder˜o ser passados a Q, como primeiro parˆmetro, objetos da classe
                                     a                                      a
Janela ou qualquer subclasse desta classe, como em C++.
     Em POOL-I, os parˆmetros passados a Q podem ser de qualquer subtipo de Janela. Todas as
                         a
classes que herdam de Janela (subclasses) s˜o subtipos desta classe e h´ subtipos que n˜o s˜o sub-
                                              a                           a                a a
classes. Ou seja, o conjunto dos subtipos de Janela ´ potencialmente maior que o de subclasses de
                                                       e
Janela. Conseq¨entemente, em POOL-I o procedimento Q pode ser usado com mais classes do que em
                 u
C++, pois o conjunto de classes aceito como parˆmetro para Q nesta ultima linguagem (subclasses) ´
                                                  a                   ´                              e
potencialmente menor que o conjunto aceito por POOL-I (subtipos).



                                                 55
proc Q(x, y)
begin
x.desenha();
if y  1
then
  x.move(20, 5);
endif
end


                               Figura 3.12: Procedimento Q no modelo Smalltalk


    Em C++, Java e POOL-I, o compilador confere, na compila¸˜o de Q, se a classe/interface de x,
                                                                 ca
que ´ Janela, possui m´todos correspondentes `s mensagens enviadas estaticamente a x. Isto ´, o
     e                    e                         a                                             e
compilador confere se Janela possui m´todos desenha e move e se move admite dois inteiros como
                                          e
parˆmetros. Estaticamente ´ garantido que objetos de Janela podem ser passados a Q (como primeiro
    a                         e
parˆmetro) sem causar erros de tipo. Em tempo de execu¸˜o, objetos de subclasses ou subtipos de
    a                                                        ca
Janela ser˜o passados a Q, por causa de atribui¸˜es do tipo Tipo = SubTipo. Estes objetos saber˜o
           a                                        co                                              a
responder a todas as mensagens enviadas a eles dentro de Q, pois: a) eles possuem pelo menos todos
os m´todos que objetos da classe Janela possuem; b) objetos da classe Janela possuem m´todos para
      e                                                                                  e
responder a todas as mensagens enviadas ao parˆmetro x dentro de Q.
                                                    a
    Smalltalk dispensa tipos na declara¸˜o de vari´veis e, portanto, o procedimento Q neste modelo
                                          ca           a
seria aquele mostrado na Figura 3.12. Como nem x nem y possuem tipos, n˜o se exige que o objeto
                                                                            a
passado como primeiro parˆmetro real a Q possua m´todos desenha e move. De fato, na instru¸˜o
                             a                           e                                     ca
        Q(a,0)
´ enviada mensagem desenha ao objeto referenciado por x (e tamb´m por a), mas n˜o ´ enviada a
e                                                                    e                a e
mensagem move.
    Como conseq¨ˆncia, esta instru¸˜o pode ser executada com parˆmetros a de qualquer classe que
                 ue                  ca                             a
possua um m´todo desenha sem parˆmetros. Ao contr´rio de POOL-I, Java e C++, a classe do
              e                         a                  a
parˆmetro x de Q n˜o precisa possuir tamb´m o m´todo move. Logo, o m´todo Q pode ser usado com
    a               a                        e         e                 e
um conjunto de classes (para o parˆmetro x) potencialmente maior que o conjunto de classes usadas
                                     a
com o procedimento Q equivalente de POOL-I. Portanto, Smalltalk possui mais polimorfismo que
POOL-I.
    Em linguagens convencionais, uma atribui¸˜o a = b ser´ correta se os tipos de a e b forem iguais
                                                 ca          a
ou b puder ser convertido para o tipo de a (o que ocorre com perda de informa¸˜o se o tipo de b
                                                                                  ca
for mais abrangente do que o de a). Em POOL-I, a = b ser´ v´lido se a classe de b for subtipo da
                                                               a a
classe de a. Em C++, se a classe de b for subclasse da classe de a. Em Java, se a classe de b for
subclasse da classe de a (se o tipo de a for uma classe) ou implementar (direta ou indiretamente)
a interface que ´ o tipo de a (se o tipo de a for uma interface).7 Em Smalltalk, esta opera¸˜o ser´
                e                                                                            ca      a
sempre correta. Logo, as linguagens orientadas a objeto citadas estendem o significado da atribui¸˜o
                                                                                                  ca
permitindo um n´mero maior de tipos do seu lado direito. Como em passagem de parˆmetros existe
                  u                                                                   a
uma atribui¸˜o impl´
            ca       ıcita, procedimentos e m´todos podem aceitar parˆmetros reais de mais classes do
                                               e                       a
que normalmente aceitariam, o que ´ o motivo do reaproveitamento de c´digo. Concluindo, podemos
                                      e                                  o
afirmar que a mudan¸a do significado da atribui¸˜o ´ o motivo de todo o reaproveitamento de software
                      c                            ca e
causado pelo polimorfismo descrito neste artigo. Quando mais liberal (Smalltalk — nenhuma restri¸˜oca
ao lado direito de =) ´ a mudan¸a, maior o polimorfismo.
                        e          c
    Suponha que estejamos construindo um novo sistema de janelas e seja necess´rio construir uma
                                                                                 a
  7
      Aqui ignoramos o fato de que na linguagem Java uma interface pode herdar de outra interface.


                                                          56
classe Window que possua os mesmos m´todos que Janela. Contudo, Window possui uma aparˆncia
                                         e                                                      e
visual e uma implementa¸˜o bem diferentes de Janela. Certamente, ´ interessante poder passar
                          ca                                               e
objetos de Window onde se espera objetos de Janela. Todo o c´digo constru´ para manipular esta
                                                                  o          ıdo
ultima classe seria reusado pela classe Window.
´
    Em C++, Window deve herdar de Janela para que objetos de Window possam ser usados onde
se espera objetos de Janela. Como Window possui uma implementa¸˜o completamente diferente de
                                                                       ca
Janela, as vari´veis de instˆncia da superclasse Janela n˜o seriam usadas em objetos de Window.
                 a           a                               a
Ent˜o, heran¸a estaria sendo utilizada para expressar especifica¸˜o do problema e n˜o implementa¸˜o.
    a          c                                                 ca               a               ca
Especifica¸˜o ´ o que expressa a regra “um objeto de uma subclasse ´ um objeto de uma super-
           ca e                                                           e
classe”. Implementa¸˜o implica que uma subclasse possui pelos menos as vari´veis de instˆncia da
                      ca                                                        a           a
sua superclasse e herda algumas ou todas as implementa¸˜es dos m´todos.
                                                          co         e
    Em POOL-I, este problema n˜o existe, pois a especifica¸˜o e implementa¸˜o s˜o desempenhados
                                  a                          ca               ca a
por mecanismos diferentes. A saber, subtipagem e heran¸a. Em Java, o programador poderia fazer
                                                           c
Janela e Window herdarem de uma interface com todos os m´todos originais de Janela. Mas isto s´
                                                               e                                     o
ser´ poss´ se o c´digo fonte de Janela estiver dispon´
   a      ıvel      o                                   ıvel. Em POOL-I, isto n˜o ´ necess´rio.
                                                                                a e       a
    Existe um problema ainda maior em C++ por causa da liga¸˜o subtipo-subclasse. Considere que
                                                                  ca
a classe Janela possua um m´todo
                              e
proc n( outra : Janela )
  begin
  ...
  w = outra.x;
  ...
  end
Dentro deste m´todo ´ feito um acesso ` vari´vel x do parˆmetro outra. Esta vari´vel de instˆncia
                e      e                a      a            a                       a          a
foi, naturalmente, declarada em Janela. Se este parˆmetro refere-se a um objeto de Window, subclasse
                                                    a
de Janela, ent˜o outra.x n˜o foi inicializado. A raz˜o ´ que a classe Window n˜o utiliza as vari´veis
               a             a                        a e                      a                a
herdadas de Janela e, portanto, n˜o as inicializa. Note que o mesmo problema ocorre com a linguagem
                                 a
Java.


3.6       Classes parametrizadas
A classe Store da Figura 3.13, em Smalltalk, permite o armazenamento de objetos de qualquer tipo.
Um objeto de Store guarda um outro objeto atrav´s do m´todo put e retorna o objeto armazenado
                                                     e         e
atrav´s de get.
      e
    A classe Store em POOL-I ´ mostrada na Figura 3.14 com o nome de Store_Int. Como cada
                                  e
vari´vel possui um tipo nesta linguagem, a classe Store se torna restrita — s´ pode armazenar inteiros.
    a                                                                          o
Se quisermos armazenar objetos de outros tipos, teremos que construir outras classes semelhantes a
Store — uma classe para cada tipo, como Store boolean, Store real, etc.
    Em Smalltalk, a classe Store ´ utilizada para todos os tipos, causando reaproveitamento de c´digo.
                                   e                                                                 o
Nesta linguagem, podemos ter uma ´rvore bin´ria ou lista encadeada gen´rica, que permite armazenar
                                      a        a                            e
objetos de qualquer tipo. Os m´todos para a manipula¸˜o de cada uma destas estruturas de dados ´
                                 e                        ca                                               e
constru´ uma unica vez. Como n˜o h´ conferˆncia de tipos, ´ poss´ inserir objetos de diferentes
        ıdo       ´                   a a        e                e      ıvel
classes na lista encadeada, criando uma lista heterogˆnia.
                                                       e
    A linguagem POOL-I possui uma constru¸˜o que oferece um pouco da flexibilidade de Smalltalk,
                                              ca
chamada de classes parametrizadas.8 Uma classe parametrizada possui um ou mais parˆmetros, quea
s˜o tipos ou constantes. Estes parˆmetros s˜o substitu´
 a                                  a       a            ıdos por tipos e valores reais ao se usar a classe.
  8
      O modelo apresentado abaixo difere daquele da linguagem real. Ele serve apenas como exemplo.


                                                         57
class Store
  private:
    var n;
  public:
    proc put( i )
      begin
      n = i;
      end
    proc get()
      begin
      return n;
      end
endclass


          Figura 3.13: Classe para armazenar objetos de qualquer tipo, em Smalltalk
class Store_Int
  private:
    var n : integer;
  public:
    proc put( i : integer )
      begin
      n = i;
      end
    proc get() : integer
      begin
      return n;
      end
endclass


                    Figura 3.14: Classe que armazena inteiros, em POOL-I

class Store[ type T ]
  private:
    var n : T;
  public:
    proc put( i : T )
      begin
      n = i;
      end
    proc get() : T
      begin
      return n;
      end
endclass


                    Figura 3.15: Classe parametrizada Store em POOL-I


                                             58
A classe Store da Figura 3.15 ´ parametrizada e possui um tipo com parˆmetro, como indicado
                                  e                                            a
pela palavra-chave type. Na declara¸˜o de uma vari´vel desta classe, deve ser especificado o parˆmetro
                                   ca             a                                            a
T:

var
       v_int : Store[integer];
       v_bool : Store[boolean];

      Quando o compilador encontrar a declara¸˜o de v int, ele executar´ os seguintes passos:
                                             ca                        a

      • copiar´ o texto de Store para uma ´rea de mem´ria. Observe que ´ o texto e n˜o um poss´
              a                           a           o                e            a         ıvel
        c´digo compilado correspondente aos m´todos desta classe;
         o                                    e

      • o identificador T ser´ substitu´ por integer (que ´ o parˆmetro real da classe) em toda a
                             a         ıdo                  e       a
        c´pia. Isto criar´ uma nova classe, Store[integer]. O texto desta classe ser´ exatamente igual
         o               a                                                          a
        a Store Int da Figura 3.14;

      • o texto produzido no item anterior ser´ compilado.
                                              a

    Observe que Store n˜o ´ uma classe, ´ apenas uma m´scara (ou esqueleto — template em
                         a e                e                 a
Inglˆs) para a cria¸˜o de classes. Os exemplos (instˆncias) constru´
    e              ca                                  a              ıdos a partir de Store, como
Store[integer], s˜o realmente classes. Eles podem ser usados como tipo de vari´veis, em heran¸a,
                   a                                                              a              c
etc. A substitui¸˜o dos parˆmetros de uma classe parametrizada (no caso, T) por valores reais (no
                ca          a
caso, integer) ´ chamada de instancia¸˜o da classe.
               e                      ca
    Cada instancia¸˜o da classe com tipos diferentes causa a cria¸˜o de um novo texto. Portanto, h´
                  ca                                             ca                                a
duplica¸˜o de c´digo — na declara¸˜o de v int e v bool, haveria a cria¸˜o de dois m´todos put e
        ca     o                   ca                                    ca            e
dois get.
    Considere agora que um novo m´todo foi acrescentado ` classe Store da Figura 3.15:
                                   e                      a

proc sum( k : Store[T] )
  begin
  n = n.add( k.get() );
  end

Agora, uma mensagem add ´ enviada ao objeto n, o que implica que o tipo de n, T, deve possuir
                               e
m´todo add com parˆmetro do tipo T (o tipo de k.get() ´ T).
  e                   a                                     e
    Esta restri¸˜o torna ilegal as declara¸˜es de v int e v bool, pois nem integer nem boolean pos-
                 ca                       co
suem m´todo add. Estas declara¸˜es causariam erro de compila¸˜o, que seria sinalizado na an´lise
         e                          co                             ca                           a
do m´todo sum de Store[integer] e Store[boolean]. No caso geral, estes errros s´ s˜o detectados
      e                                                                             o a
na compila¸˜o da classe instanciada. Isto implica em que o c´digo fonte (o texto) dos m´todos da
             ca                                                 o                         e
classe parametrizada devem ser do conhecimento do compilador na instancia¸˜o da classe. Como con-
                                                                             ca
seq¨ˆncia, em geral classes parametrizadas n˜o podem ser compiladas e colocadas em uma biblioteca
    ue                                          a
                        ´
de arquivos objetos.9 E necess´rio que o c´digo delas seja conhecido para que possam ser utilizadas.
                                 a           o
    Suponha que uma classe A possua o m´todo add que tome objeto de A como parˆmetro. Ent˜o
                                              e                                      a            a
n˜o haver´ erro na declara¸˜o
 a         a                ca
       var v_A : Store[A];
Na manuten¸˜o do software, ´ comum reescrever m´todos para alcan¸ar diferentes objetivos. Por
              ca                 e                     e                 c
exemplo, o m´todo add poderia ser substitu´ por m´todo soma em sum de Store, resultando em
                e                              ıdo      e
  9
    Algumas linguagens (ex: Eiffel) n˜o duplicam o c´digo de algumas classes parametrizadas. Ent˜o esta afirma¸˜o
                                      a            o                                           a            ca
n˜o ´ verdadeira para estas linguagens.
 a e


                                                      59
class Heap[ type T, const Max : integer ]
  private:
    var v    : array(T)[Max];
         Num : integer;
  public:
    proc ins( elem : T ) : boolean
      ...
endclass

proc main()
    { procedimento onde se inicia a execucao do programa }
  var h : Heap[ integer, 200 ];
...


                 Figura 3.16: Classe parametrizada com parˆmetro que ´ constante
                                                          a          e

proc sum( k : Store[T] )
  begin
  n = n.soma( k.get() );
  end
Esta modifica¸˜o ´ interna a sum. Ela n˜o deveria alterar os usu´rios deste m´todo. Afinal, proced-
              ca e                     a                         a             e
imentos e m´todos s˜o mecanismos de abstra¸˜o — para utiliz´-los, basta ler a sua documenta¸˜o e
            e        a                      ca                 a                                ca
conhecer seus parˆmetros. Como ele funciona e que algoritmos utiliza s˜o abstra´
                  a                                                   a        ıdos dos seus usu´rios.
                                                                                                a
   Contudo, na troca de add por soma causa erro na declara¸˜o de v A, admitindo que a classe A n˜o
                                                            ca                                     a
possui m´todo soma. Ou seja, temos um c´digo compilando corretamente (declara¸˜o de v A) e ele
         e                                o                                         ca
torna-se inv´lido por causa de mudan¸as internas a um m´todo (sum), mudan¸as que n˜o alteram a
            a                        c                    e                    c         a
semˆntica (ou a documenta¸˜o) deste m´todo. Por este motivo, deve-se especificar, com coment´rios,
    a                      ca          e                                                        a
que m´todos os parˆmetros que s˜o tipos devem possuir. Exemplo:
      e             a           a
class Store[ type T ]
  { O tipo T deve possuir o metodo
      add(T) : T
    }
  private:
  ...
endclass
Uma vez que a classe parametrizada ´ liberada para uso, n˜o devem ser acrescentados novos m´todos
                                    e                    a                                 e
na lista de m´todos que cada tipo que ´ parˆmetro deve suportar.
             e                        e    a
    Parˆmetros de classes parametrizadas tamb´m podem ser constantes, como mostrado no exemplo
        a                                     e
da Figura 3.16.


3.7     Outros T´picos sobre Orienta¸˜o a Objetos
                o                   ca
3.7.1   Identidade de Objetos
Identidade ´ a propriedade de um objeto que o distingue de todos os outros. Identidade n˜o depende
           e                                                                            a
do conte´do ou endere¸o do objeto. Identidade ´ necess´ria em modelagens do mundo real, onde cada
        u             c                       e       a

                                                 60
¨           ¨
                                       1
                                      ©           1
                                                   ©
                                      ! u
                                      ¡ e           T
                                     ¡   e
                                    ¡      e
                                  ¡         e
                                 a          c      b

                        Figura 3.17: Vari´veis referindo-se a n´meros diferentes
                                         a                     u

objeto ´ unico. Por exemplo, as transforma¸˜es que uma pessoa sofre ao longo de sua existˆncia, da
       e´                                    co                                          e
infˆncia ` velhice, n˜o modificam a sua identidade. Wegner [15] apresenta uma conversa entre duas
   a     a           a
pessoas que ilustra a distin¸˜o entre valor e identidade:
                            ca
     – Smith, vocˆ mudou. Vocˆ era alto e agora est´ baixo. Vocˆ era magro e agora est´ gordo.
                 e             e                   a           e                      a
        Vocˆ tinha olhos azuis e agora tem olhos verdes.
            e
     – Mas meu nome n˜o ´ Smith.
                       a e
     – Oh, ent˜o vocˆ mudou seu nome tamb´m !
              a     e                        e

   Considere o c´digo
                o
proc main()
  var a, b, c :    integer;
begin
a = 1;
b = 1;
c = a;
if equal(a, b)     then   write(’A’);      endif
if a == b          then   write(’B’);      endif
if equal(a, c)     then   write(’C’);      endif
if a == c          then   write(’D’);      endif
end.
onde equal(a, b) testa se a e b possuem os mesmos valores e a == b se a e b referenciam o mesmo
objeto.
   a e b s˜o associados ao n´mero 1, mas cada associa¸˜o cria um objeto diferente cujo valor ´ 1.
          a                 u                           ca                                   e
Assim, a e b referenciam objetos diferentes com o mesmo valor, fazendo o c´digo acima imprimir
                                                                           o
ACD. A representa¸˜o gr´fica dos objetos e vari´veis est´ na Figura 3.17.
                    ca   a                      a        a

3.7.2   Persistˆncia
               e
Persistˆncia ´ a habilidade de certos valores persistirem entre ativa¸˜es de um programa. Os valores
        e     e                                                      co
persistentes devem ser armazenados em um arquivo e posteriormente recuperados.
    A motiva¸˜o para o suporte ` persistˆncia ´ que o mapeamento entre as estruturas de dados usadas
              ca                a       e     e
no programa (listas, vetores, tabelas hash, etc) e as estruturas dos arquivos ou base de dados n˜o ´
                                                                                                a e
trivial. Este mapeamento utiliza tipicamente 30% do c´digo de um sistema. E permite que os dados
                                                         o
sejam armazenados com um tipo e recuperados com outro (erro de tipos — arquivos n˜o possuem tipo
                                                                                     a
!).

                                                   61
¨
                                a        E
                                           ©
                                           ¡ e
                                           !
                                         ¡¡   e
                                        ¡
                                        ¡       e
                                       ¨
                                      ¡
                                                e ¨
                                                 
                                                 …
                          c         E
                                      ©
                                              E
                                                 ©
                                                   '         b



                                    Figura 3.18: Um objeto complexo


    Na linguagem S, um objeto a ´ feito persistente pelo comando
                                  e
       a.store(NomeArq);
onde store ´ o nome de um m´todo que todas as classes possuem automaticamente (n˜o ´ declarada
             e                 e                                                      a e
pelo usu´rio). NomeArq ´ uma string com o nome do arquivo que conter´ os dados.
         a               e                                             a
    O m´todo store grava n˜o s´ as vari´veis de instˆncia de a como tamb´m os objetos referenciados
         e                   a o        a            a                    e
indiretamente por elas. Os tipos de cada objeto tamb´m s˜o armazenados.
                                                       e  a
    A estrutura de dados armazenada em um arquivo ´ recuperada com m´todo retrieve:
                                                       e                   e
       a.retrieve(NomeArq);
A tentativa de recuperar dados com tipos diferentes daqueles armazenados causa um erro de execu¸˜o.
                                                                                                ca
Por exemplo, haveria erro se o tipo de a fosse A e o armazenado em NomeArq fosse do tipo B que n˜oa
´ subclasse de A.
e
    Os m´todos store e retrieve manipulam adequadamente qualquer grafo de referˆncias que o
           e                                                                           e
objeto possua. Por exemplo, o objeto da Figura 3.18 seria recuperado do disco com as mesmas
referˆncias que ele possuia quando estava em mem´ria, incluindo as referˆncias simultˆneas entre a e
     e                                             o                     e           a
c e as duas referˆncias ao objeto apontado por b.
                 e

3.7.3   Iteradores
Iteradores s˜o constru¸˜es que permitem percorrer um conjunto tomando elemento a elemento. Um
            a          co
iterador pode ser uma constru¸˜o especial da linguagem ou feito com m´todos normais, como exem-
                               ca                                          e
plificado na Figura 3.19.
    O m´todo reset inicializa o iterador. O m´todo next retorna o pr´ximo n´ da lista. O procedi-
         e                                     e                         o       o
mento next retorna nil se o fim da lista foi atingido. Um exemplo de uso de iterador ´ percorrer um
                                                                                        e
conjunto imprimindo todos os seus elementos como mostrado na Figura 3.20.
    Observe a simplicidade do uso de iteradores. O c´digo correspondente sem eles seria muito mais
                                                      o
complicado e necessitaria de mais vari´veis, em geral. Al´m disto, utilizaria m´todos espec´
                                       a                   e                      e            ıficos da
classe List (estamos admitindo que todos os iteradores s˜o formados por m´todos com nomes reset
                                                          a                   e
e next).
    Iteradores s˜o mecanismos de abstra¸˜o — eles eliminam detalhes que n˜o precisamos saber para
                a                        ca                                   a
percorrer um conjunto e obter elemento a elemento. Eles podem ser usados com uma variada gama
de tipos abstratos e estruturas de dados, como listas, ´rvores bin´rias, tabelas hash, heap, etc.
                                                       a          a


3.8     Discuss˜o Sobre Orienta¸˜o a Objetos
               a               ca
Dentre todos os paradigmas de linguagens, o orientado a objetos ´ o que possui melhores mecanismos
                                                                e
para representa¸˜o do mundo real em programas. Os elementos da realidade e as estruturas de dados
               ca


                                                  62
class List
  private:
      { head aponta para o inicio da lista. O tipo de cada no da lista
        e
        ´ ListNo. }

    var head : ListNo;
         current : ListNo;
  public:
    ...
    proc reset()
      begin
      current = head;
      end
    proc next() : ListNo
         var elem : ListNo;
      begin
      elem = current;
      current = current.suc;
      return elem;
      end
endclass


                 Figura 3.19: Exemplo de Iterador constru´ com m´todos
                                                         ıdo    e




proc main()
  var
     s : List;
     elem : ListNo;
    ...
begin
...
s.reset();
while (elem = s.next())  nil do
  elem.write();
end.


                        Figura 3.20: Uso de iterador de uma lista




                                           63
s˜o representados claramente no programa por meio de classes. Elementos como Pessoa, Governo,
 a
Empresa, Balan¸o de Pagamentos, Texto, Janela, ´
                 c                                Icone, Rel´gio, Carro, Trabalhador, Pilha, Lista, e
                                                            o
Fila s˜o representados diretamente por meio de classes. O mapeamento claro entre o mundo real e
      a
programas torna mais f´cil a compreens˜o e a manuten¸˜o do c´digo. N˜o s´ os programas espelham
                        a               a              ca      o        a o
o mundo como ´ relativamente f´cil descobrir o que deve ser modificado no c´digo quando h´ alguma
                e                a                                          o              a
altera¸˜o no mundo real.
      ca
    Heran¸a permite reaproveitar elegantemente c´digo de superclasses. Uma subclasse define apenas
          c                                      o
os m´todos que devem ser diferentes da superclasse. Hierarquias de heran¸a s˜o criadas incremental-
     e                                                                    c a
mente com o tempo. As novas subclasses acrescentam funcionalidades ao c´digo existente exigindo
                                                                            o
poucas ou nenhuma modifica¸˜o deste.
                              ca
    Polimorfismo ´ o motivo da alta taxa de reaproveitamento de c´digo encontrada em sistemas
                   e                                                  o
orientados a objeto. C´digo existente pode passar a trabalhar com subclasses sem necessidade de
                         o
nenhuma altera¸˜o. Prote¸˜o de informa¸˜o, estimulada ou mesmo requerida por muitas linguagens
                ca         ca             ca
orientadas a objeto, impede que modifica¸˜es nas estruturas de dados de uma classe invalidem outras
                                          co
classes. Este conceito ´ fundamental para a constru¸˜o de sistemas, aumentando substancialmente a
                       e                            ca
sua manutenabilidade. De fato, prote¸˜o de informa¸˜o ´ considerada mais importante do que heran¸a
                                     ca            ca e                                           c
pela comunidade de orienta¸˜o a objetos.
                            ca




                                                 64
Cap´
   ıtulo 4

Linguagens Funcionais

4.1    Introdu¸˜o
              ca
Linguagens funcionais consideram o programa como uma fun¸˜o matem´tica. Todas as computa¸˜es
                                                             ca        a                       co
s˜o feitas por fun¸˜es que tomam como parˆmetros outras fun¸˜es. N˜o existe o conceito de vari´vel
 a                co                        a                 co     a                         a
onde um valor pode ser armazenado por meio da atribui¸˜o e utilizado posteriormente. Para com-
                                                          ca
preendermos o paradigma funcional precisamos estudar primeiro o paradigma imperativo, descrito a
seguir.
    Uma linguagem ´ chamada imperativa se ela baseia-se no comando de atribui¸˜o e, conseq¨ente-
                     e                                                          ca          u
mente, em uma mem´ria que pode ser modificada. No paradigma imperativo, vari´veis s˜o associadas
                      o                                                         a     a
a posi¸˜es de mem´ria que podem ser modificadas in´meras vezes durante a execu¸˜o do programa
       co           o                                 u                            ca
atrav´s do comando de atribui¸˜o. Isto ´, dada uma vari´vel x, um comando
      e                        ca       e                a
       x = expressao
pode ser executado v´rias vezes durante o tempo de vida da vari´vel x.
                       a                                         a
    O comando de atribui¸˜o desempenha um papel central em linguagens imperativas. Tipicamente
                           ca
40% dos comandos s˜o atribui¸˜es. Todos os outros comandos s˜o apenas auxiliares. Nestas lingua-
                      a        co                                a
gens, o estado da computa¸˜o ´ determinado pelo conte´do das vari´veis (que podem ser de tipos
                             ca e                         u          a
b´sicos, vetores ou dinˆmicas) que ´, por sua vez, determinado pelo fluxo de execu¸˜o do programa.
 a                       a         e                                              ca
    Para compreender este ponto, considere um procedimento p que possui, no seu corpo, v´rias a
atribui¸˜es:
        co

proc p( a, b : integer )
    var i, j, k : integer;
begin
i = 1;
j = a*b;
...
while k  j and j  b do
  begin
  if a  i + j
  then
     j = j + 1;
  else
     k = a + b;
  endif
  ...


                                               65
end { while }
a = k - a;
...
end { p }

Para compreendermos o estado da computa¸˜o ap´s o while, temos que imaginar todo o fluxo de
                                                       ca      o
execu¸˜o do algoritmo, que depende das altera¸˜es que s˜o feitas nas vari´veis vis´
       ca                                                 co           a                  a         ıveis dentro de p.
Isto ´ dif´ de compreender — os seres humanos n˜o conseguem raciocinar corretamente neste caso
      e ıcil                                                   a
porque a execu¸˜o do programa ´ dinˆmica e depende de muitos fatores (valores das vari´veis).
                   ca                 e       a                                                            a
                                           e                            ca ´
    O ponto central deste problema ´ o comando de atribui¸˜o. E ele que permite a altera¸˜o do valor       ca
das vari´veis. O comando
          a
    x = x + 1
´ um absurdo se considerada a sua interpreta¸˜o matem´tica, mas ´ v´lido em linguagens imperativas.
e                                                     ca            a        e a
´ v´lido porque o x do lado esquerdo se refere a uma posi¸˜o de mem´ria de um tempo futuro em
E a                                                                      ca          o
rela¸˜o ao x ` direita de “=”. Se o x da direita existir no tempo t, o x da esquerda existir´ em t
    ca           a                                                                                              a
+ ∆t. Logo, a atribui¸˜o introduz o efeito tempo no programa, o que causa o seu comportamento
                             ca
dinˆmico que dificulta a sua compreens˜o.
    a                                            a
    As linguagens imperativas foram projetadas tendo em vista as m´quinas em que elas seriam usadas.
                                                                               a
Isto ´, elas espelham a arquitetura da maioria dos computadores atuais, que possuem a chamada
      e
arquitetura de Von Neumann. Uma das caracter´                 ısticas destas m´quinas ´ a manipula¸˜o de uma
                                                                               a          e                ca
palavra de mem´ria por vez. N˜o ´ poss´
                    o                 a e          ıvel trabalhar com um vetor inteiro ao mesmo tempo, por
exemplo. A restri¸˜o “uma palavra de mem´ria por vez”´ o gargalo das m´quinas Von Neumann. E
                      ca                              o               e                   a                            ´
um dos fatores (o principal) que impede a sua eficiˆncia. Este tipo de m´quina realiza computa¸˜es
                                                               e                       a                            co
alterando posi¸˜es de mem´ria, fazendo desvios e testes. As linguagens imperativas, que espelham
                  co              o
computadores Von Neumann, seguem esta filosofia. Como conseq¨ˆncia, o estado da computa¸˜o em
                                                                             ue                                 ca
um certo ponto depende dos valores das vari´veis, que dependem do fluxo de execu¸˜o, que depende
                                                       a                                            ca
dos valores das vari´veis e assim por diante.
                        a
    A atribui¸˜oca
        a = b
liga o significado de a ao de b. Ap´s v´rias atribui¸˜es, temos um emaranhado de liga¸˜es entre
                                             o a                  co                                         co
vari´veis (ou entre vari´veis e express˜es) cuja semˆntica torna-se dif´ de entender.
     a                       a                o               a                  ıcil
    A solu¸˜o para eliminar caracter´
             ca                           ısticas dinˆmicas dos algoritmos ´ eliminar o comando de atribui¸˜o.
                                                       a                       e                                     ca
Eliminando-se este comando, devem ser eliminados os comandos de repeti¸˜o como for, while,   ca
do-while, repeat-until. Eles dependem da altera¸˜o de alguma vari´vel para que possam parar.
                                                               ca                  a
    Passagem de parˆmetros por referˆncia tamb´m deixa de ter sentido, pois uma vari´vel deste tipo
                         a                    e            e                                           a
deve ser alterada dentro da rotina, o que n˜o pode ser conseguido sem atribui¸˜o. Vari´veis globais
                                                     a                                        ca          a
n˜o podem existir, uma vez que elas n˜o podem ser alteradas. Mas podem existir constantes globais
  a                                             a
e locais.
    Em passagem de parˆmetros, o valor dos parˆmetros reais ´ copiado nos parˆmetros formais.
                                a                             a              e                       a
Isto ´ chamado de inicializa¸˜o e ´ diferente de atribui¸˜o. Inicializa¸˜o ´ a cria¸˜o de uma posi¸˜o
      e                            ca   e                           ca            ca e           ca                  ca
de mem´ria e a coloca¸˜o de um valor nesta posi¸˜o imediatamente ap´s a sua cria¸˜o. Ap´s a
           o                  ca                                ca                      o                ca        o
inicializa¸˜o, o valor armazenado nesta mem´ria n˜o pode ser modificado (em linguagens funcionais).
            ca                                        o      a
    Linguagens que n˜o possuem comando de atribui¸˜o s˜o chamadas de linguagens declarativas.
                           a                                       ca a
Linguagens funcionais s˜o linguagens declarativas em que o programa ´ considerado uma fun¸˜o
                               a                                                        e                            ca
matem´tica. Na maioria das linguagens funcionais o mecanismo de repeti¸˜o de trechos de c´digo
         a                                                                                 ca                     o
´ a recurs˜o.
e            a
    Uma compara¸˜o entre programa¸˜o funcional (recurs˜o, sem atribui¸˜o) e imperativa (repeti¸˜o,
                      ca                     ca                       a               ca                            ca
atribui¸˜o) ´ feita abaixo utilizando-se a linguagem S.
         ca e


                                                          66
{ fatorial imperativo }
proc fat( n : integer ) : integer
  var i, p : integer;
begin
p = 1;
for i = 1 to n do
  p = i*p;
return p;
end

  { fatorial funcional }
proc fat( n : integer ) : integer
begin
if n == 0
then
    return 1;
else
  return n*fat(n-1);
endif
end

    A primeira fun¸˜o fat possui duas vari´veis locais e trˆs atribui¸˜es. Como j´ foi escrito, vari´veis
                   ca                       a               e         co            a               a
e atribui¸˜es dificultam o entendimento do programa. Esta rotina possui tamb´m uma itera¸˜o (for)
         co                                                                       e            ca
e precisamos executar mentalmente os passos desta itera¸˜o para assegurar a corre¸˜o do algoritmo. A
                                                         ca                           ca
segunda fun¸˜o fat n˜o possui nenhuma vari´vel local nem atribui¸˜o. N˜o h´ comando de repeti¸˜o.
            ca         a                      a                      ca    a a                       ca
Como conseq¨ˆncia, o significado do algoritmo ´ dado estaticamente. N˜o precisamos imaginar o
              ue                                    e                         a
programa funcionando para compreendˆ-lo. Tamb´m n˜o ´ necess´rio “desenrolar”as chamadas re-
                                          e           e   a e          a
cursivas.
    Esta ´ a diferen¸a entre linguagens imperativas e declarativas. As primeiras possuem significado
          e          c
que depende da dinˆmica do programa e as segundas possuem significado est´tico. As linguagens
                      a                                                             a
declarativas aproveitam toda a nossa habilidade matem´tica j´ que esta disciplina ´ baseada princi-
                                                          a      a                       e
palmente em rela¸˜es est´ticas.
                  co      a
    Linguagens funcionais puras (LF) n˜o possuem comandos de atribui¸˜o, comandos de repeti¸˜o,
                                          a                                ca                        ca
passagem de parˆmetros por referˆncia, vari´veis globais, seq¨ˆncia de instru¸˜es (colocada entre
                  a                  e          a                 ue                 co
begin-end em S), vari´veis locais, ponteiros. LF expressam algoritmos por meio de formas funcionais,
                        a
que s˜o mecanismos de combina¸˜o de fun¸˜es para a cria¸˜o de outras fun¸˜es. Na maioria das LF,
      a                           ca        co               ca                co
a unica forma funcional ´ a composi¸˜o de fun¸˜es:
  ´                       e           ca         co
       h(x) = f◦g(x) = f(g(x))
Por exemplo, podemos construir a fun¸˜o que ´ combina¸˜o de n elementos tomados i a i, chamada
                                         ca      e         ca
de comb(n,i), a partir da fun¸˜o fatorial:
                               ca

proc comb( n, i : integer ) : integer
begin
  return fat(n) div ( fat(n-i)*fat(i) );
end

Os operadores aritm´ticos (*, +, /, -, etc) tamb´m s˜o fun¸˜es no sentido funcional do termo.
                     e                          e   a     co
    Em uma express˜o formada apenas por vari´veis, em uma linguagem imperativa, n˜o h´ efeitos
                     a                           a                                      a a
colaterais e n˜o precisamos nos preocupar onde o computador armazena os resultados intermedi´rios
              a                                                                               a


                                                   67
do c´lculo. Por n˜o existir efeitos colaterais, uma fun¸˜o em uma LF retorna sempre o mesmo valor
      a            a                                      ca
se forem passados os mesmos parˆmetros. Assim, ´ poss´ avaliar em paralelo as fun¸˜es presentes
                                    a                 e      ıvel                              co
em uma express˜o. Em
                 a
... f( g(x), h(x) ) + p(y);
pode-se calcular g(x), h(x) e p(y) ao mesmo tempo (ou f(...) e p(y)) alocando um processador
para calcular cada fun¸˜o. Observe que, se houvesse passagem por referˆncia ou vari´veis globais, a
                         ca                                                   e              a
ordem de chamada destas fun¸˜es poderia influenciar o resultado.
                               co
     Uma fun¸˜o cujo valor de retorno depende apenas dos valores dos parˆmetros possui transparˆncia
             ca                                                              a                           e
referencial (TR). Isto ´, dados os mesmos parˆmetros, os valores de retorno s˜o idˆnticos. Linguagens
                        e                       a                                 a      e
com transparˆncia referencial s˜o aquelas onde todas as fun¸˜es apresentam esta caracter´
               e                 a                                 co                              ıstica (ex:
linguagens funcionais puras). Uma consequˆncia deste fato ´ a eleva¸˜o do n´ de abstra¸˜o — h´
                                              e                   e       ca        ıvel           ca        a
menos detalhes para serem compreendidos. Por exemplo, ´ mais f´cil entender como uma fun¸˜o
                                                                  e      a                                 ca
funciona porque as suas partes, compostas por express˜es, s˜o independentes entre si. O resultado de
                                                          o     a
um trecho n˜o afetar´ de modo algum outro trecho, a menos que o primeiro trecho seja uma express˜o
             a        a                                                                                     a
passada como parˆmetro ao segundo. Em linguagens com atribui¸˜o, o resultado de um peda¸o de
                    a                                                  ca                               c
c´digo altera necessariamente outros segmentos do programa e de uma forma que depende do fluxo
  o
de execu¸˜o.
          ca
     Em uma LF, tudo s˜o fun¸˜es, inclusive o comando if de sele¸˜o, que possui a seguinte forma:
                          a    co                                     ca
        if exp then exp1 else exp2
que seria equivalente a uma fun¸˜o de forma expl´
                                 ca                 ıcita
        if (exp, exp1, exp2)
N˜o h´ necessidade de endif pois ap´s o then ou o else existe exatamente uma express˜o. O ponto
   a a                                o                                                          a
e v´ırgula ap´s a express˜o tamb´m ´ desnecess´rio pois ele separa instru¸˜es que n˜o existem aqui.
             o             a       e e            a                            co          a
Utilizando este if, a fun¸˜o fatorial ficaria
                            ca

proc fat( n : integer ) : integer
  is
     if n == 0
     then
       1
     else
       n*fat(n-1);

Utilizaremos esta sintaxe no restante deste cap´
                                               ıtulo. O corpo da fun¸˜o ´ colocado ap´s is e ´ formado
                                                                    ca e             o       e
por uma unica express˜o.
         ´             a
   Uma conseq¨ˆncia da transparˆncia referencial ´ a regra da reescrita: cada chamada de fun¸˜o
                ue                  e                 e                                            ca
pode ser substitu´ pelo pr´prio corpo da fun¸˜o. Assim, para calcular fat(2), podemos fazer:
                 ıda         o                  ca

  fat(2) = if 2 == 0 then 1 else 2*fat(1) =
           if 2 == 0 then 1 else 2*
              (if 1 == 0 then 1 else 1*fat(0)) =
           if 2 == 0 then 1
           else
             2*(if 1 == 0
                then
                  1
                else
                  1*(if 0 == 0 then 1 else 0*fat(-1)) )



                                                     68
(def membro (lambda(x L)
    (cond ( (null L)             nil)
          ( (eq x (car L))       T)
          ( T                    (membro x (cdr L)) )
      )
   ))


                                Figura 4.1: Fun¸˜o membro em Lisp
                                               ca
proc membro( x, L )
  is
    if L == nil
    then
      false
    else
      if x == car(L)
      then
         true
      else
         membro( x, cdr(L) );


                           Figura 4.2: Fun¸˜o membro com a sintaxe de S
                                          ca


Avaliando, temos
   fat(2) = 2*1*1 = 2
    O processo acima ´ chamado de redu¸˜o e ´ o meio empregado para executar programas em
                       e                  ca    e
linguagens funcionais, pelo menos conceitualmente.


4.2    Lisp
Esta se¸˜o apresenta algumas das caracter´
        ca                                 ısticas da linguagem Lisp, a primeira linguagem funcional.
Nesta linguagem tudo ´ representado por listas: o pr´prio programa, suas fun¸˜es e os dados que ele
                         e                             o                       co
utiliza. Listas nesta linguagem s˜o delimitadas por ( e ):
                                 a

      (3 carro 2.7)
      ( (3 azul) -5)

A Figura 4.1 mostra uma fun¸˜o membro que toma uma lista L e um valor x como parˆmetros que
                              ca                                                           a
retorna true (T) se o valor est´ na lista e nil (false em Lisp) caso contr´rio. Em Lisp, cond ´ um
                               a                                              a                  e
if estendido para manipular v´rias express˜es. Neste caso, h´ trˆs express˜es, “(null L)”, “(eq x
                               a             o                   a e            o
(car L))” e “T”. Se a primeira express˜o for verdadeira, a instru¸˜o cond returnar´ nil. A fun¸˜o
                                         a                           ca                a           ca
car retorna o primeiro elemento da lista e cdr retorna a lista retirando o primeiro elemento. Exemplo:
      (car ’(1 2 3)) −→ 1
      (cdr ’(1 2 3)) −→ (2 3)
Ap´s a seta ´ mostrado o resultado da avalia¸˜o da express˜o. A compara¸˜o de igualdade ´ feita com
   o        e                                ca             a               ca               e
eq, sendo que “(eq x (car L))” compara x com (car L). Uma fun¸˜o em S equivalente ` fun¸˜o da
                                                                       ca                   a   ca
Figura 4.1 em Lisp est´ na Figura 4.2.
                      a


                                                 69
Tudo o que vem ap´s ( ´ considerado uma aplica¸˜o de fun¸˜o, a menos que ’ preceda o (.
                        o    e                           ca         ca
Exemplo:
      ’(a b c) −→ (a b c)
      (membro a ’(b c a)) −→ T
      (+ 2 (* 3 5)) −→ 17
      (comb 5 3) −→ 10
(comb 5 3) chama a fun¸˜o comb com 5 e 3 como parˆmetros.
                          ca                           a
   N˜o h´ especifica¸˜o de tipos na declara¸˜o de vari´veis — a linguagem ´ dinamicamente tipada.
     a a             ca                    ca           a                    e
Logo, todas as fun¸˜es s˜o polim´rficas e podem ocorrer erros de tipo em execu¸˜o. A fun¸˜o membro,
                  co    a       o                                              ca      ca
por exemplo, pode ser usada em listas de inteiros, reais, s´
                                                           ımbolos, etc. Exemplo:
      (membro 3 ’(12 98 1 3)) −→ T
      (membro azul ’(3 verde 3.14 amarelo)) −→ nil

    Um erro de execu¸˜o ocorre na chamada
                    ca
    (delta azul verde 5)
da fun¸˜o delta:
      ca

      (def delta (lambda (a b c)
         (- (* b b) (* 4 a c))
         )
      )

Os parˆmetros a e b recebem azul e verde sobre os quais as opera¸˜es aritm´ticas n˜o est˜o definidas.
       a                                                        co         e      a     a
    Lisp utiliza a mesma representa¸˜o para programas e dados — listas. Isto permite a um programa
                                   ca
construir listas que s˜o executadas em tempo de execu¸˜o pela fun¸˜o Eval:
                      a                              ca          ca
      (Eval L)
A fun¸˜o Eval tratar´ L como uma fun¸˜o e a executar´.
      ca              a                ca             a
    Um grande n´mero de dialetos foi produzido a partir de Lisp, tornando praticamente imposs´
                   u                                                                            ıvel
transportar programas de um compilador para outro. Para contornar este problema, foi criada a
linguagem Common Lisp que incorpora facilidades encontradas em v´rios dialetos de Lisp. A inclus˜o
                                                                   a                             a
de orienta¸˜o a objetos em Common Lips resultou na linguagem Common Lisp Object System, CLOS.
          ca


4.3     A Linguagem FP
Outro exemplo de linguagem funcional ´ FP, projetada por John Backus, o principal projetista de
                                         e
Fortran. FP ´ puramente funcional, n˜o possui vari´veis e oferece muitas possibilidades de combinar
             e                        a           a
fun¸˜es al´m da composi¸˜o.
   co     e             ca
   Uma seq¨ˆncia de elementos em FP ´ denotada por  a1 , a2 , . . . an  e a aplica¸˜o de uma fun¸˜o
            ue                          e                                           ca            ca
f ao parˆmetro x (que pode ser uma seq¨ˆncia) ´ denotada por f:x. A fun¸˜o FIRST extrai o primeiro
        a                               ue    e                            ca
elemento de uma seq¨ˆncia e TAIL retorna a seq¨ˆncia exceto pelo primeiro elemento:
                     ue                        ue
FIRT :  3, 7, 9, 21  −→ 3
TAIL :  3, 7, 9, 21  −→  7, 9, 21 

   A unica forma funcional (mecanismo de combinar fun¸˜es) na maioria das LF ´ a composi¸˜o. Em
      ´                                                co                      e        ca
FP, existem outras formas funcionais al´m desta, sendo algumas delas citadas abaixo.
                                       e

  1. Composi¸˜o.
            ca
     (f◦g) : x ≡ f:(g:x)
     Exemplo:

                                                 70
DEF quarta ≡ (SQR◦SQR):x


  2. Constru¸˜o.
              ca
     [f1 , f2 , ... fn ]:x ≡ f1 :x, ..., fn :x
     Exemplo:
     [MIN, MAX]:  0, 1, 2  ≡ MIN: 0, 1, 2 , MAX: 0, 1, 2  ≡  0, 2 


  3. Aplique a todos
     α f:x ≡
       if x == nil then nil
       else if x eh a sequencia x1 , x2 , ...xn 
              then
                 f:x1 , ..., f:xn 
     nil ´ a lista vazia.
         e
     Exemplo:
     α SQR: 3, 5, 7  ≡ SQR:3, SQR:5, SQR:7  ≡  9, 25, 49 


  4. Condi¸˜o
           ca
     (IF p f g):x ≡ if p:x == T then f:x else g:x
     T ´ um ´tomo que representa true.
       e     a
     Exemplo
      (IF PRIMO SOMA1 SUB2):29


  5. Enquanto
      (WHILE p f):x ≡ if p:x == T then (WHILE p f): (f:x) else x

      Esta forma funcional aplica f em x enquanto a aplica¸˜o de p em x for verdadeira (T).
                                                          ca


4.4    SML - Standard ML
SML ´ uma linguagem funcional fortemente tipada e com um alto grau de polimorfismo. Este polimor-
       e
fismo ´ semelhante ao de classes parametrizadas e determinado automaticamente pelo compilador, que
       e
analisa cada fun¸˜o e determina a forma mais gen´rica que ela pode ter. Antes de estudar esta fun-
                  ca                               e
cionalidade, veremos alguns t´picos b´sicos desta linguagem.
                               o     a
    Al´m de tipos b´sicos (integer, boolean, string, etc.), SML suporta listas de forma semelhante
       e              a
a LISP. Uma lista com os trˆs primeiros n´meros ´
                             e            u       e
        [1, 2, 3]
e a lista vazia ´ []. Sendo SML fortemente tipada, listas heterogˆneas (elementos de v´rios tipos) s˜o
                e                                                e                    a             a
ilegais.
    Os parˆmetros de uma fun¸˜o podem estar declarados sem tipo:
            a                   ca

proc succ(n)
  is
    n + 1;



                                                 71
O compilador descobre que n deve ser inteiro, pois a opera¸˜o + (em SML), exige que os seus
                                                              ca
operandos sejam do mesmo tipo. Como 1 ´ do tipo integer, n deve ser integer e o resultado
                                           e
tamb´m.
     e
   O compilador produz o seguinte cabe¸alho para succ:
                                       c
      proc succ(n : integer) : integer
O tipo desta fun¸˜o n˜o envolve o nome, sendo representado como
                ca a
      integer −→ integer

    O tipo de uma fun¸˜o
                       ca
proc f( x1 : T1 ; x2 : T2 ; ...xn : Tn ) : R
´ expresso como
e
      T1 ×T2 × ...Tn −→ R
O tipo da fun¸˜o ´ o tipo dos parˆmetros e do valor de retorno, sendo os primeiros separados por ×.
              ca e               a
    Veja outros dois exemplos dados a seguir.

a)     proc calcula(a, b)
         is
            if b  a
            then
              1
            else
              b

       Para que a fun¸˜o possua tipos corretos, as express˜es que se seguem ao then e ao else devem
                     ca                                   o
       possuir o mesmo tipo. Assim, b (else) possui o mesmo tipo que 1 (do then — integer). As
       opera¸˜es de compara¸˜o (ex: ) s´ se aplicam a valores do mesmo tipo. Logo, a ´ do mesmo
             co             ca           o                                               e
       tipo que b. O tipo final de Calcula ´:
                                           e
       integer × integer −→ integer


b)     proc inutil(a, b)
         is
            if a  1
            then
              inutil(b-1, a)
            else
              a
       Por “a  1”, a ´ inteiro. Por “inutil(b-1, a)”, b tamb´m deve ser inteiro por dois motivos:
                      e                                      e

         • Est´ em uma subtra¸˜o com um inteiro (“b-1”).
              a              ca
         • a ´ passado como parˆmetro real onde o parˆmetro formal ´ b, e a ´ inteiro. O tipo do
             e                    a                  a             e        e
           valor de retorno ´ igual ao tipo de a.
                            e

       O tipo de inutil ´:
                        e
       integer × integer −→ integer


     Algumas vezes o compilador n˜o consegue deduzir os tipos e h´ erro, como em
                                 a                               a


                                                 72
proc soma(a, b)
  is
    a + b;

Considerando que o operador + pode ser aplicado tanto a reais como inteiros, o tipo de soma poderia
ser qualquer um dos abaixo
      integer × integer −→ integer
      real × real −→ real
e, portanto, h´ ambig¨idade, que ´ resolvida colocando-se pelo menos um dos tipos (de a, b ou do
              a       u          e
valor de retorno). Exemplo:
      proc soma(a : integer; b) is ...
      proc soma(a, b) : integer is ...

    Se a express˜o do then e do else de um if pudessem ser de tipos diferentes, poderia haver erros
                a
de tipo em execu¸˜o, como o abaixo.
                  ca

proc f(a)
  is
    if a  1
    then
      1
    else
      Eu sou um erro

proc g
  is
    f(0) + 1;

f(0) retorna uma string ` qual tenta-se somar um inteiro. Por causa das restri¸˜es impostas pelo
                           a                                                  co
sistema de tipos, erros de execu¸˜o como este nunca ocorrem em programas SML.
                                ca
    A fun¸˜o
         ca

proc id(x)
  is
    x;

pode ser usada com valores de qualquer tipo, e ´ v´lida na linguagem. O seu tipo ´
                                               e a                               e
’a −→ ’a
onde ’a significa um tipo qualquer. Se houvesse mais um parˆmetro e este pudesse ser de um outro
                                                              a
tipo qualquer, este seria chamado de ’b.
    Um outro exemplo ´ a fun¸˜o
                        e     ca

proc nada(x, y)
  is
     x;

cujo tipo ´
          e
’a × ’b −→ ’a
Outra dedu¸˜o de tipo ´ apresentada abaixo
            ca        e



                                                73
proc escolhe(i, a, b)
  is
    if i  0
    then
      a
    else
      b
O tipo de escolhe ´:
                  e
integer × ’a × ’a −→ ’a

    A dedu¸˜o dos tipos corretos para as vari´veis ´ feito por um algoritmo que tamb´m determina se
            ca                               a     e                                   e
h´ ambig¨idade ou n˜o. Este ´ um fato importante: a defini¸˜o de SML utiliza n˜o apenas defini¸˜es
 a        u           a       e                               ca                   a                co
est´ticas mas tamb´m dinˆmicas (algoritmos). Ist ´ uma qualidade, pois aumenta o polimorfismo,
   a                e      a                         e
mas tamb´m um problema. Como algoritmos s˜o mais dif´
          e                                    a           ıceis de entender do que rela¸˜es est´ticas, o
                                                                                        co      a
programador necessita de um esfor¸o mais para decidir se o c´digo que ele produziu em SML ´ v´lido
                                   c                           o                                e a
ou n˜o.
     a
    Listas s˜o delimitadas por [ e ], como [1, 2, 3], e possuem um tipo que termina sempre com a
            a
palavra list. Alguns exemplos de tipos de listas est˜o na tabela seguinte.
                                                      a


                            Lista                       Tipo
                            [1, 2, 3]                   integer list
                            [a, azul, b]          string list
                            [ [1, 2], [3], [4] ]        integer list list


   O construtor :: constr´i uma lista a partir de um elemento e de outra lista. Exemplos:
                         o
1::[2,3] resulta em [1, 2, 3].
   A aplica¸˜o da fun¸˜o
           ca        ca
proc ins(a : integer; L : integer list) : integer list
  is
     a::L;
sobre 1 e [2, 3] resulta em [1, 2, 3]. Isto ´, ins(1, [2, 3]) −→ [1, 2, 3].
                                            e
   O tamanho de uma lista pode ser calculado pela fun¸˜o len:
                                                     ca
proc     len([])   is   0
       | len(h::t) is   1 + len(t);
    A fun¸˜o len possui, na verdade, duas defini¸˜es. Uma para a lista vazia e outra para listas com
          ca                                   co
pelo menos um elemento. As defini¸˜es s˜o separadas por |. Em uma chamada
                                  co    a
       len([1, 2, 3])
´ feito o emparelhamento do parˆmetro [1, 2, 3] com a segunda defini¸˜o de len, resultando nas
e                               a                                       ca
seguintes inicializa¸˜es:
                    co
       h = 1
       t = [2, 3]
Ent˜o a express˜o 1 + len([2, 3]) ´ calculada e retornada.
    a            a                   e
    De um modo geral, em uma chamada
       len(L)

                                                   74
´ utilizada uma das defini¸˜es de len de acordo com o parˆmetro L. A presen¸a de um if em len,
e                         co                             a                  c
como
       if L == [] then 0 else ...
torna-se desnecess´ria. A programa¸˜o com emparelhamento ´ ligeiramente mais abstrata (alto n´
                  a               ca                       e                                 ıvel)
do que com if.
   Admitindo que todos os tipos suportam a opera¸˜o de igualdade, uma fun¸˜o que testa a presen¸a
                                                ca                       ca                    c
de x em uma lista ´:
                   e

proc     membro(x, [])
           is false
       | membro(x, h::t)
           is
             if x == h
             then
               true
             else
               membro(x,t);

E o seu tipo ´
             e
’a × ’a list −→ boolean



4.5     Listas Infinitas e Avalia¸˜o Pregui¸osa
                                ca        c
Linguagens funcionais freq¨entemente suportam estruturas de dados potencialmente infinitas. Por
                            u
exemplo,
     ones = 1 : ones
´ uma lista infinita de 1’s em Haskell. A fun¸˜o
e                                           ca


proc numsFrom( n : integer )
  is
     [n : numsFrom(n + 1)]

retorna uma lista infinita de n´meros naturais come¸ando em n. Naturalmente, um programa n˜o usa
                              u                    c                                       a
uma lista infinita j´ que ele termina em um tempo finito. Estas listas s˜o constru´
                   a                                                  a         ıdas ` medida que
                                                                                     a
os seus elementos v˜o sendo requisitados, em uma maneira pregui¸osa (lazy evaluation).
                    a                                          c
    Este mecanismo ´ usado para facilitar a implementa¸˜o de algoritmos e mesmo para aumentar a
                     e                                ca
eficiˆncia da linguagem. Por exemplo, a fun¸˜o [17]
    e                                      ca

proc cmpTree( tree1, tree2 )
  is
     cmpLists( treeToList(tree1), treeToList(tree2) );

compara duas ´rvores pela compara¸˜o dos n´s das ´rvores colocados em forma de lista. A fun¸˜o
               a                       ca        o       a                                             ca
treeToList converte a ´rvore para lista de maneira pregui¸osa. Assim, se o primeiro elemento das duas
                         a                                   c
a
´rvores forem diferentes, cmpLists retornar´ false, terminando a fun¸˜o cmpTree. Sem constru¸˜o
                                               a                           ca                          ca
pregui¸osa da lista, seria necess´rio construir as duas listas totalmente antes de come¸ar a fazer o teste
      c                          a                                                     c
e descobrir que as listas s˜o diferentes logo no primeiro elemento.
                           a


                                                   75
4.6     Fun¸˜es de Ordem Mais Alta
           co
A maioria das linguagens modernas permitem que fun¸˜es sejam passadas como parˆmetros. Isto
                                                   co                           a
permite a constru¸˜o de rotinas gen´ricas. Por exemplo, pode-se construir uma fun¸˜o max que
                  ca               e                                              ca
retorna o maior elemento de um vetor qualquer. A opera¸˜o de compara¸˜o entre dois elementos ´
                                                      ca             ca                      e
passada a max como uma fun¸˜o. Em uma linguagem sem tipos, max seria:
                           ca
proc max( v, n, gt )
  var maior, i;
begin
maior = v[1];
for i = 2 to n do
  if gt(v[i], maior)
  then
    maior = v[i];
  endif
return maior;
end
   O c´digo de max pode ser utilizado com vetores de qualquer tipo T, desde que se defina uma fun¸˜o
      o                                                                                         ca
de compara¸˜o para o tipo T. Exemplo:
          ca
proc gt_real(a, b)        { para numeros reais }
begin
return a  b;
end
...
m = max( VetReal, gt_real );
m1 = max( VetNomes, gt_string );
Fun¸˜es que admitem fun¸˜es como parˆmetros s˜o chamadas fun¸˜es de mais alta ordem (“higher
    co                   co         a        a                co
order functions”).
   Uma fun¸˜o map em SML que aplica uma fun¸˜o f a todos os elementos de uma lista, produzindo
            ca                              ca
uma lista como resultado, seria:

proc     map( proc f(’a) : ’b; []   ) is []
       | map( proc f(’a) : ’b; h::t )
         is
           f(h)::map(t);

seu tipo ´:
         e
(’a −→ ’b) × ’a list −→ ’b list
   Observe que fun¸˜es como parˆmetro s˜o completamente desnecess´rias em linguagens orientadas a
                   co            a       a                        a
objeto pois cada objeto ´ associado a um conjunto de m´todos. Quando um objeto for passado como
                        e                             e
parˆmetro, teremos o efeito de passar tamb´m todos os seus m´todos como parˆmetro simulando
   a                                        e                  e               a
fun¸˜es de ordem mais alta.
   co


4.7     Discuss˜o Sobre Linguagens Funcionais
               a
A necessidade de eficiˆncia fez com que na maioria das linguagens funcionais fossem acrescentadas
                     e
duas constru¸˜es imperativas, a saber, seq¨ˆncia e atribui¸˜o. Seq¨ˆncia permite que as instru¸˜es de
            co                            ue              ca      ue                          co

                                                 76
uma lista sejam executadas sequencialmente, introduzindo a no¸˜o de tempo. No exemplo seguinte,
                                                             ca
esta lista est´ delimitada por begin-end.
              a

begin
a = a + 1;
if a  b
then
  return f(a, b)
else
  return f(b, a)
end

     Obviamente, seq¨ˆncia s´ tem sentido na presen¸a de atribui¸˜o ou entrada/sa´ de dados, pois
                       ue      o                        c              ca                  ıda
de outro modo o resultado de cada instru¸˜o da seq¨ˆncia seria uma express˜o cujo resultado seria
                                            ca           ue                         a
perdido ap´s a sua avalia¸˜o.
            o              ca
     Programadores produzem aproximadamente a mesma quantidade de linhas de c´digo por ano,    o
independente da linguagem. Assim, quanto mais alto n´ a linguagem ´, mais problemas podem ser
                                                           ıvel              e
resolvidos na mesma unidade de tempo. Uma linguagem ´ de mais alto n´ que outra por possuir
                                                               e                ıvel
menos detalhes, o que implica em ser mais compacta (necessita de menos constru¸˜es/instru¸˜es para
                                                                                        co           co
fazer a mesma coisa que outra). Como linguagens funcionais s˜o de mais alto n´ que a maioria das
                                                                    a                 ıvel
outras, elas implicam em maior produtividade para o programador.
     V´rios fatores tornam linguagens funcionais de alto n´
       a                                                        ıvel, como o uso de recurs˜o ao inv´s de
                                                                                               a         e
itera¸˜o, ausˆncia de atribui¸˜o e aloca¸˜o e desaloca¸˜o autom´tica de mem´ria. Este ultimo item
      ca      e                ca        ca               ca           a            o             ´
´ particularmente importante. N˜o s´ o programador n˜o precisa desalocar a mem´ria dinˆmica (h´
e                                  a o                      a                               o       a          a
coleta de lixo) mas ele tamb´m n˜o precisa aloc´-la explicitamente. As listas utilizadas por linguagens
                              e    a             a
funcionais aumentam e diminuem automaticamente, poupando ao programador o trabalho de gerenci´-               a
las.
     ´
     E mais f´cil definir uma linguagem funcional formalmente do que linguagens de outros paradigmas,
             a
assim como programas funcionais s˜o adequados para an´lise formal. A raz˜o ´ que as linguagens
                                     a                         a                   a e
deste paradigma possuem um parentesco proximo com a matem´tica, facilitando o mapeamento da
                                                                        a
linguagem ou programa para modelos matem´ticos.a
     H´ dois problemas principais com linguagens funcionais. Primeiro, um sistema real ´ mapeado em
       a                                                                                        e
um programa que ´ uma fun¸˜o matem´tica composta por outras fun¸˜es. Logo, n˜o h´ o conceito
                     e          ca       a                                 co                 a a
de estado do programa dado pelas vari´veis globais, dificultando a implementa¸˜o de muitos sistemas
                                       a                                             ca
que exigem que o programa tenha um estado. Estes sistemas n˜o s˜o facilmente mapeados em fun¸˜es
                                                                    a a                                    co
matem´ticas. De fato, o paradigma que representa melhor o mundo real ´ o orientado a objetos. O
         a                                                                     e
conceito de objeto ´ justamente um bloco de mem´ria (que guarda um estado) modificado por meio
                      e                               o
de envio de mensagens.
     Uma outra face deste problema ´ entrada e sa´ de dados em linguagens funcionais. Fun¸˜es que
                                     e              ıda                                               co
fazem entrada e sa´ n˜o suportam transparˆncia referencial. Por exemplo, uma fun¸˜o getchar()
                     ıda a                     e                                               ca
que retorna o pr´ximo car´ter da entrada padr˜o provavelmente retornar´ dois valores diferentes se
                   o        a                     a                            a
for chamada duas vezes.
     O segundo problema com linguagens funcionais ´ a eficiˆncia. Elas s˜o lentas por n˜o permitirem
                                                       e         e          a                   a
atribui¸˜es. Se, por exemplo, for necess´rio modificar um unico elemento de uma lista, toda a lista
         co                               a                      ´
dever´ ser duplicada. Este tipo de opera¸˜o pode ser otimizada em alguns casos1 pelo compilador
       a                                    ca
ou o programador pode encontrar maneiras alternativas de expressar o algoritmo. Neste ultimo caso,´
  1
      Este t´pico n˜o ser´ discutido aqui.
            o      a     a



                                                      77
´ prov´vel que o modo alternativo de codifica¸˜o seja dif´ de entender por n˜o ser o mais simples
e       a                                       ca         ıcil                   a
poss´ıvel.
    M´quinas paralelas podem aumentar enormemente a eficiˆncia de programas funcionais. Contudo,
       a                                                       e
esta tecnologia ainda n˜o est´ suficientemente madura para concluirmos que linguagens funcionais s˜o
                         a     a                                                                    a
t˜o eficientes quanto linguagens imperativas.
 a
    O uso de atribui¸˜o em um programa n˜o elimina todos os benef´
                      ca                      a                        ıcios da programa¸˜o funcional.
                                                                                          ca
Um bom programador limita as atribui¸˜es ao m´
                                        co        ınimo necess´rio ` eficiˆncia, fazendo com que grande
                                                                a a      e
parte do programa seja realmente funcional. Assim, pelo menos esta parte do c´digo ser´ legivel e
                                                                                    o        a
f´cil de ser paralelizada e otimizada, que s˜o as qualidades associadas ` programa¸˜o funcional.
 a                                          a                            a           ca




                                                 78
Cap´
   ıtulo 5

Prolog — Programming in Logic

5.1    Introdu¸˜o
              ca
Prolog ´ uma linguagem l´gica. Ela permite a definic˜o de fatos e de relacionamentos entre objetos.
        e                 o                          a
Nesta linguagem objeto ´ designa valores de qualquer tipo. Um programa em Prolog consiste de fatos
                        e
e regras. Um fato ´ uma afirma¸˜o sempre verdadeira. Uma regra ´ uma afirma¸˜o cuja veracidade
                  e             ca                                   e            ca
depende de outras regras ou fatos. Para exemplificar estes conceitos, utilizaremos o seguinte programa
em Prolog:

homem(jose).
homem(joao).
homem(pedro).
homem(paulo).
mulher(maria).
mulher(ana).
pais(pedro, joao, maria).
pais(paulo, joao, maria).
pais(maria, jose, ana).

Neste c´digo s´ h´ fatos e cada um deles possui um significado. “homem(X)” afirma que X ´ homem
       o      o a                                                                     e
e pais(F, H, M) significa que F ´ filho de pai H e m˜e M. Este programa representa uma fam´ na
                                 e                 a                                     ılia
qual

   • Jos´ e Ana s˜o pais de Maria;
        e        a

   • Jo˜o e Maria s˜o pais de Pedro e Paulo
       a           a

   As informa¸˜es sobre a fam´ podem ser estendidas por novos fatos ou regras, como pela regra
             co              ılia

irmao(X, Y) :-
   homem(X),
   pais(X, H, M),
   pais(Y, H, M).

A regra acima ser´ verdadeira se as regras que se seguem a :- (que funciona como um if) forem
                   a
verdadeiras. Isto ´, X ser´ irm˜o de Y se X for homem e possuir os mesmos pais H e M de Y. A v´
                  e       a    a                                                              ırgula
funciona como um and l´gico.
                         o



                                                 79
Prolog admite que todos os identificadores que se iniciam com letras mai´sculas (como X, Y, H e M)
                                                                          u
s˜o nomes de vari´veis. Nomes iniciados em min´scula s˜o s´
 a                a                             u        a ımbolos. N´meros (1, 52, 3) e s´
                                                                       u                   ımbolos
s˜o tipos de dados b´sicos da linguagem e s˜o chamados de ´tomos.
 a                  a                      a               a
   Prolog ´ uma linguagem interativa que permite a formula¸˜o de perguntas atrav´s de goals. Um
           e                                                 ca                    e
goal ´ uma meta que desejamos saber se ´ verdadeira ou falsa e em que situa¸˜es. Por exemplo, se
     e                                   e                                   co
quisermos saber se pedro ´ homem, colocamos
                          e

?- homem(pedro).

e o sistema responder´
                     a
       yes
sendo que “homem(pedro)” ´ o goal do qual queremos saber a veracidade.
                             e
    Fatos, regras e goals s˜o exemplos de cl´usulas. Um predicado ´ um conjunto de fatos e/ou
                           a                a                      e
regras com o mesmo nome e n´mero de argumentos. O programa exemplo definido anteriormente
                                u
possui os predicados homem, mulher, pais e irmao. Veremos adiante que predicado ´ o equivalente
                                                                                 e
a procedimento em outras linguagens. O conjunto de todos os predicados forma a base de dados do
programa.
    Um goal pode envolver vari´veis:
                               a

?- mulher(M).

O objetivo desta quest˜o ´ encontrar os nomes das mulheres armazenados na base de dados. O sistema
                       a e
de tempo de execu¸˜o de Prolog tenta encontrar os valores de M que fazem esta cl´usula verdadeira.
                    ca                                                          a
                                                                                 ´
Ele rastreia todo o programa em busca da primeira cl´usula com nome “mulher”. E encontrado
                                                     a
       mulher(maria)
e ´ feita a associa¸˜o
  e                ca
       M = maria
Neste ponto, um valor de M que torna mulher(M) verdadeiro ´ encontrado e o sistema escreve a
                                                               e
resposta:

?- mulher(M).
M = maria

   Se o usu´rio digitar ; (ponto-e-v´
           a                        ırgula), Prolog retornar´ `s cl´usulas do programa e:
                                                            aa a

   • tornar´ inv´lida a associa¸˜o de M com maria. Ent˜o M volta a n˜o estar instanciada — n˜o
           a     a             ca                         a               a                  a
     tem valor. A associa¸˜o entre uma vari´vel e um valor ´ chamado de instancia¸˜o. Antes
                            ca                 a                 e                  ca
     de uma instancia¸˜o, a vari´vel ´ chamada de livre e n˜o est´ associada a nada. Ap´s uma
                       ca         a    e                       a     a                  o
     instancia¸˜o, uma vari´vel n˜o pode ser instanciada novamente, exceto em backtracking (que
              ca             a     a
     ser´ visto adiante), quando a instancia¸˜o anterior deixa de existir.
        a                                   ca

   • continuar´ a procurar por cl´usula que emparelhe com “mulher(M)” tornando esta cl´usula
              a                  a                                                           a
     verdadeira. Neste processo M ser´ instanciada. Esta procura se iniciar´ na cl´usula seguinte `
                                     a                                     a      a               a
     ultima encontrada. A ultima foi “mulher(maria)” e a seguinte ser´ “mulher(ana)”.
     ´                    ´                                           a

Ent˜o, a busca por mulher continuar´ em mulher(ana) e M ser´ associado a ana:
   a                               a                       a

?- mulher(M).
M = maria;
M = ana



                                                 80
Digitando ; a busca continuar´ a partir de
                             a
      pais(pedro, joao, maria)
e n˜o ser´ encontrada nenhuma cl´usula mulher, no que o sistema responder´ “no”:
   a     a                       a                                       a

?- mulher(M).
M = maria;
M = ana;
no

   O algoritmo que executa a busca, por toda a base de dados, por cl´usula que emparelha dado
                                                                       a
goal ´ chamado de algoritmo de unifica¸˜o. Dizemos que um fato (ou regra) emparelha (match) um
     e                                ca
goal se ´ poss´ existir uma correspondˆncia entre os dois. Os itens a seguir exemplificam algumas
        e     ıvel                     e
tentativas de emparelhamento. Utilizamos a sintaxe
       mulher(maria) = mulher(X)
para a tentativa de emparelhamento do goal “mulher(X)” com a cl´usula “mulher(maria)”.
                                                                 a

a) mulher(maria) = mulher(X)
     h´ emparelhamento e X ´ instanciado com maria, que indicaremos como X = maria.
      a                    e

b) mulher(maria) = mulher(ana)
     n˜o h´ emparelhamento, pois maria = ana
      a a

c)   mulher(Y) = mulher(X)
       h´ emparelhamento e X = Y. Observe que nenhuma das duas vari´veis, X ou Y, est´ instanciada.
        a                                                          a                 a

d) pais(pedro, X, maria) = pais(Y, joao, Z)
     h´ emparelhamento e Y = pedro, X = joao e Z = maria
      a

   Uma cl´usula composta ser´ verdadeira se o forem todos os seus fatos e regras. Por exemplo,
           a                a
considere a meta
?- irmao(pedro, paulo).
que produz um emparelhamento com irmao(X, Y), fazendo X = pedro, Y = paulo e a gera¸˜o dos
                                                                                       ca
subgoals

homem(pedro),
pais(pedro, H, M),
pais(paulo, H, M).

     O primeiro subgoal, homem(pedro), ´ verdadeiro (pelos fatos) e pode ser eliminado, restando
                                       e

pais(pedro, H, M),
pais(paulo, H, M).

    O subgoal pais(pedro, H, M) emparelha com pais(pedro, joao, maria), produzindo as asso-
cia¸˜es H = joao e M = maria. Um emparelhamento sempre ´ verdadeiro e, portanto, o subgoal
   co                                                  e
       pais(pedro, joao, maria)
´ eliminado e o goal inicial ´ reduzido a
e                            e
       pais(paulo, joao, maria).
Que ´ provado por um dos fatos.
     e
    Portanto, a meta
       irmao(pedro, paulo)
´ verdadeira.
e

                                                  81
Podemos perguntar quest˜es como
                           o
      ?- irmao(X, Y).
que ´ substitu´ pelos subgoals
    e         ıda

homem(X),
pais(X, H, M),
pais(Y, H, M).

   O primeiro subgoal emparelha com homem(jose), fazendo X = jose e produzindo
     pais(jose, H, M)
     pais(Y, H, M).

    O primeiro subgoal (pais(jose, H, M)) n˜o pode ser emparelhado com ningu´m e falha. Esta
                                               a                                    e
falha causa um retrocesso (backtracking) ao subgoal anterior, homem(X). A instancia¸˜o de X com jose
                                                                                   ca
´ destru´
e       ıda, tornando X uma vari´vel livre. A busca por cl´usula que emparelha com este goal continua
                                a                         a
em homem(joao), que tamb´m causa falha em
                            e
      pais(joao, H, M),
      pais(Y, H, M).
H´ retrocesso para homem(X) e matching com homem(pedro), fazendo X = pedro, e resultando em
  a
      pais(pedro, H, M),
      pais(Y, H, M).

    O primeiro subgoal emparelha com
      pais(pedro, joao, maria)
fazendo
      H = joao, M = maria
e resultando em
      pais(Y, joao, maria).
    Sempre que um novo subgoal dever ser satisfeito, a busca por cl´usula para emparelhamento comeca
                                                                   a
na primeira cl´usula do programa, independente de onde parou a busca do subgoal anterior (que ´
              a                                                                                    e
pais(pedro, H, M)).
    O emparelhamento de pais(Y, joao, maria) ´ feito com pais(pedro, joao, maria). O resul-
                                                    e
tado final ´
          e
      X = pedro
      Y = pedro

   Pela nossa defini¸˜o, pedro ´ irm˜o dele mesmo. Digitando ; ´ feito um retrocesso e a busca por
                   ca         e    a                          e
emparelhamento para
      pais(Y, joao, maria)
continua em pais(paulo, joao, maria), que sucede e produz
      X = pedro
      Y = paulo

    Observe que a ordem das cl´usulas no programa ´ importante porque ela diz a ordem dos retro-
                               a                      e
cessos. Outras regras que seriam uteis para rela¸˜es familiares s˜o dadas abaixo.
                                 ´              co               a

pai(P, F) :-                   /* P ´ pai de F */
                                    e
  pais(F, P, M).



                                                 82
mae(M, F) :-               /* M ´ mae de F */
                                e
  pais(F, P, M).

avo(A, N) :-               /* A ´ avo (homem) de N. A = avo, N = neto */
                                e
  homem(A),
  pai(A, F),
  pai(F, N).

avo(A, N) :-               /* A ´ avo (homem) de N. A = avo, N = neto */
                                e
  homem(A),
  pai(A, F),
  mae(F, N).

tio(T, S) :-               /* T ´ tio de S. T = tio, S = sobrinho */
                                e
  irmao(T, P),
  pai(P, S).

tio(T, S) :-               /* T ´ tio de S. T = tio, S = sobrinho */
                                e
  irmao(T, P),
  mae(P, S).

filho(F, P) :-             /* F ´ filho de P */
                                e
  homem(F),
  pai(P, F).

filho(F, M) :-             /* F ´ filho de M */
                                e
  homem(F),
  mae(M, F).

paimaeDe(A, D) :-          /* A ´ pai ou mae de D */
                                e
  pai(A, D).

paimaeDe(A, D) :-
  mae(A, D).

ancestral(A, D) :-       /* A ´ ancestral de D */
                              e
  paimaeDe(A, D).

ancestral(A, D) :-
  paimaeDe(A, Y),
  ancestral(Y, D).

   Uma estrutura cumpre um papel semelhante a um registro (record ou struct) em linguagens
imperativas. Uma estrutura para representar um curso da universidade teria a forma
     curso( nome, professor, numVagas, departamento )
e poderia ser utilizada em cl´usulas da mesma forma que vari´veis e n´meros:
                             a                              a        u

professor( Nome, curso(_, Nome, _, _) ).


                                           83
haVagas( curso(_, _, N, _) ) :-
  N  0.

   Uma estrutura pode ser atribu´ a uma vari´vel e emparelhada:
                                ıda         a

?- ED = curso( estruturasDeDados, joao, 30, dc ), professor(Nome, ED),
    haVagas(ED).
Nome = joao
yes

?- curso(icc, P, 60, Depart) = curso(C, maria, N, dc).
C = icc
P = maria
N = 60
Depart = dc
yes

    O sinal = ´ utilizado tanto para instanciar vari´veis como para comparar valores. Assim, se X n˜o
              e                                     a                                              a
estiver instanciado,
      X = 2
instanciar´ X com 2. Se X tiver sido instanciado com o valor 3, “X = 2” ser´ avaliado como falso.
           a                                                                  a
Estude os exemplos abaixo.

cmp(A, B) :-
  A = B.

?- cmp(X, 2).
X = 2
yes

?- cmp(3, 2).
 no

?- X = 2, cmp(X, Y).
X = 2
Y = 2
yes

?- cmp(X, Y).
X = _1
Y = _1
yes

   _1 ´ o nome de uma vari´vel criada pelo Prolog. Obviamente ela n˜o est´ inicializada.
      e                    a                                          a    a
   Prolog n˜o avalia opera¸˜es aritm´ticas ` direita ou esquerda de =. Observe os exemplos a seguir.
           a              co        e      a

?- 6 = 2*3.
no

?- X = 2*3.


                                                 84
X = 2*3
yes

?- 5 + 1 = 2*3.
no

O que seria a express˜o “2*3” ´ tratada como a estrutura “*(2,3)”. Se a avalia¸˜o da express˜o for
                      a          e                                              ca             a
necess´ria, deve-se utilizar o operador is. Para resolver “X is exp” o sistema avalia exp e ent˜o:
      a                                                                                        a

   • compara o resultado com X se este estiver instanciado ou;

   • instancia X com o resultado de exp se X n˜o estiver instanciado.
                                              a

   Observe que exp ´ avaliado e portanto n˜o pode ter nenhuma vari´vel livre. Pode-se colocar valores
                     e                    a                         a
ou vari´veis do lado esquerdo de is. Veja alguns exemplos a seguir.
       a

?- X is 2*3.
X = 6
yes

?- 6 is 2*3.
yes

?- 5 + 1 is 2*3.
no

?- X is 2*3, X is 6.
X = 6
yes

?- X is 2*3, X is 3.
no

?- 6 is 2*X.
no

Note que o ultimo goal falha pois X est´ livre.
           ´                           a
   La¸os do tipo for podem ser simulados [17] utilizando-se o operador is:
     c

for(0).
for(N) :-
  write(N),
  NewN is N - 1,
  for(NewN).

Este la¸o seria equivalente a
       c
      for i = N downto 1 do
         write(i);
em S onde downto indica la¸o decrescente; isto ´, N = 1.
                             c                  e
    Listas s˜o as principais estruturas de dados de Prolog. Uma lista ´ um conjunto de valores entre
            a                                                         e
colchetes:

                                                 85
[1, 2, 3]
     [jose, joao, pedro]
     [1, joao]

   Uma lista tamb´m ´ representada por
                   e e
     [Head | Tail]
onde Head ´ o seu primeiro elemento e Tail ´ a sublista restante. Assim, os exemplos de listas acima
          e                                e
poderiam ser escritos como

         [1 | [2, 3]]
         [jose | [joao, pedro]]
         [1 | [joao]]

O emparelhamento de [1, 2, 3] com [H | T] produz

H = 1
T = [2, 3]

    Para emparelhar com [H | T], uma lista deve possuir pelo menos um elemento, H, pois T pode ser
instanciado com a lista vazia, [].
    Com estas informa¸˜es, podemos construir um predicado que calcula o tamanho de uma lista:
                      co

length([], 0).
length([H | T], N) :-
   length(T, M),
   N is M + 1.

   Este goal retornar´ em N o tamanho da lista L ou ir´ comparar N com o tamanho da lista. Exemplo:
                     a                                a

?- length([1, 2, 3], N).
N = 3
yes

?- length([], 0)
yes

   Pode-se tamb´m especificar mais de um elemento cabe¸a para uma lista:
               e                                     c

semestre( [1, 2, 3, 4] ).
?- semestre( [X, _, Y | T] ).
X = 1
Y = 3
T = [4]
yes

   A seguir mostramos alguns outros exemplos de predicados que manipulam listas.
   Um predicado concat(A, B, C) que concatena as listas A e B produzindo C seria

concat([], [], []).
concat([], [H|T], [H|T]).
concat([X|Y], B, [X|D]) :-
  concat(Y, B, D).

                                                86
Um predicado pertence(X, L) que sucede se X pertencer ` lista L seria
                                                         a

pertence(X, [X|_]).
pertence(X, [_|T]) :-
  pertence(X, T).

    O predicado numTotalVagas utiliza a estrutura curso descrita anteriormente e calcula o n´mero
                                                                                            u
total de vagas de uma lista de cursos.

  /* numTotalVagas(N, L) significa que N ´ o numero total de vagas
                                         e
     nos cursos da lista L */

numTotalVagas( 0, [] ).
numTotalVagas( Total, [ curso(_, _, N, _) | T ] ) :-
  numTotalVagas(TotalParcial, T),
  Total is TotalParcial + N.

   O predicado del(X, Big, Small) elimina o elemento X da lista Big produzindo a lista Small.

del(X, [X|L], L).
del(X, [_|Big], Small) :-
  del(X, Big, Small).


5.2    Cut e fail
Cut ´ o fato ! que sempre sucede. Em uma meta
    e
      ?- pA(X), pB(X), !, pC(X).
o cut (!) impede que haja retrocesso de pC(X) para !.
   Considerando a base de dados

pA(joao).
pA(pedro).
pB(pedro).
pC(joao).

a tentativa de satisfa¸˜o do goal acima resulta no seguinte: ´ encontrado matching para pA(X) com
                      ca                                     e
X = joao. O goal pB(joao) falha e h´ retrocesso para pA(X). A busca por matching por pA(X)
                                         a
continua, resultando em X = pedro. O goal pB(pedro) sucede, como tamb´m !. O goal pC(pedro)
                                                                            e
falha e ´ tentado retrocesso para !, que ´ proibido, causando a falha de todo o goal.
        e                                e
    Se o cut estiver dentro de um predicado, como em

pD(X) :- pA(X), !, pB(X).
pD(X) :- pA(X), pC(X).

a tentativa de retrocesso atrav´s do ! causar´ a falha de todo o predicado. Por exemplo, a meta
                                e            a
      ?- pD(joao).
emparelhar´ com pD(X), resultando na meta
            a
      pA(joao), !, pB(joao)
pA(joao) sucede e pB(joao) falha. A tentativa de retrocesso para ! causar´ a falha de todo o
                                                                                a
predicado pD, isto ´, do goal pD(joao). Se apenas o subgoal pA(X) da primeira cl´usula do predicado
                    e                                                            a
pD falhasse, seria tentado a segunda,

                                                87
pD(X) :- pA(X), pC(X)
que seria bem sucedida, j´ que pA(joao) e pC(joao) sucedem.
                         a
   O cut ´ utilizado para eliminar algumas possibilidades da ´rvore de busca. Eliminar um retrocesso
          e                                                  a
para um predicado pA significa que algumas possibilidades de pA n˜o foram utilizadas, poupando
                                                                      a
tempo.
   O operador fail sempre falha e ´ utilizado para for¸ar o retrocesso para o goal anterior. Por
                                      e                   c
exemplo, o goal

?- homem(X), write(X), write(’ ’), fail.
jose joao pedro
no

for¸a o retrocesso por todas as cl´usulas que emparelham “homem(X)”. Este operador pode ser utilizado
   c                              a
[19] para implementar um comando while em Prolog:

while :-
  pertence( X, [1, 2, 3, 4, 5] ),
  body(X),
  fail.

body(X) :-
  write(X),
  write(’ ’).

?- while.
1 2 3 4 5
no

O operador fail com o cut pode ser utilizado para invalidar todo um predicado:

fat(N, P) :-             /* fatorial de N ´ P */
                                          e
  N  0, !, fail.
fat(0, 1).
fat(N, P) :-
  N  0,
  N1 is N - 1,
  fat(N1, P1),
  P is N*P1.

Assim, o goal
       ?- fat(-5, P).
       no
falha logo na primeira cl´usula. Sem o cut/fail, todas as outras regras do predicado seriam testadas.
                         a
    Com o cut podemos expressar o fato de que algumas regras de um predicado s˜o mutualmente
                                                                                    a
exclusivas. Isto ´, se uma sucede, obrigatoriamente as outras falham. Por exemplo, fat poderia ser
                 e
codificado como

fat(0, 1) :- !.
fat(N, P) :-               /* fatorial de N ´ P */
                                            e
  N  0,
  N1 is N - 1,

                                                 88
fat(N1, P1),
  P is N*P1.

Assim, em

?- fat(0, P).
P = 1;
no

seria feito um emparelhamento apenas com a primeira cl´usula, “fat(0, 1)”. Sem o cut nesta
                                                          a
cl´usula, o “;” que se segue a “P = 1” causaria um novo emparelhamento como “fat(N, P)”, a
  a
segunda cl´usula do predicado, que falharia.
           a
    Observe que o cut foi introduzido apenas por uma quest˜o de eficiˆncia. Ele n˜o altera em nada
                                                           a        e           a
o significado do predicado. Este tipo de cut ´ chamado de cut verde.
                                             e
    Um cut ´ vermelho quando a sua remo¸˜o altera o significado do predicado. Como exemplo temos
             e                           ca

  /* max(A, B, C) significa que C ´ o maximo entre A e B */
                                  e

max(X, Y, X) :-
  X = Y,
  !.
max(X, Y, Y).

pertence(X, [X|_]) :-
  !.
pertence(X, [_|T]) :-
  pertence(X, T).

?- max(5, 2, M).
M = 5;
no

?- pertence(X, [1, 2, 3]).
X = 1;
no

Retirando o cut dos predicados, ter´
                                   ıamos

  /* max(A, B, C) significa que C ´ o maximo entre A e B */
                                  e

max(X, Y, X) :-
  X = Y.
max(X, Y, Y).

pertence(X, [X|_]).
pertence(X, [_|T]) :-
  pertence(X, T).

?- max(5, 2, M).
M = 5;


                                               89
M = 2;
no

?- pertence(X, [1, 2, 3]).
X = 1;
X = 2;
X = 3;
no

O cut pode tanto melhorar a eficiˆncia (verdes, vermelhos) e o poder expressivo da linguagem (verdes)
                                 e
como tornar o c´digo dif´ de entender (vermelhos). Freq¨entemente o cut vermelho remove a bidi-
                o        ıcil                             u
recionalidade dos argumentos de um predicado, como no caso de pertence. Sem o cut, este predicado
poderia tanto ser utilizado para recuperar os elementos da lista, um a um, como para testar se um
elemento pertence ` lista. Com o cut, pertence permite recuperarmos apenas o primeiro elemento da
                   a
lista.


5.3      Erros em Prolog
Prolog ´ uma linguagem dinamicamente tipada e, conseq¨entemente, podem ocorrer erros de tipo em
       e                                             u
tempo de execu¸˜o. Por exemplo, em
               ca

add(X, Y) :-
   Y is X + 1.

?- add ( [a, b], Y).

tenta-se somar 1 a uma lista.
    Contudo, a maior parte dos que seriam erros de tipo simplesmente fazem as opera¸˜es de empar-
                                                                                   co
elhamento falhar, sem causar erros em execu¸˜o. Exemplo:
                                           ca

dias(jan, 31).
...
dias(dez, 31).

?- dias(N, jan).
no

   Os compiladores Prolog geralmente n˜o avisam se um predicado n˜o definido ´ utilizado:
                                      a                          a          e

while :-
  peretnce(X, [1, 2, 3]),
  write(X),
  write(’ ’),
  fail.

?- while.
no

Neste caso, pertence foi digitado incorretamente como peretnce que nunca suceder´.
                                                                                a



                                                90
5.4    Reaproveitamento de C´digo
                            o
Prolog ´ dinamicamente tipada e portanto suporta o polimorfismo causado por esta caracter´
       e                                                                                ıstica. Os
parametros reais passados a um predicado podem ser de qualquer tipo, o que torna todo predicado
potencialmente polimorfico. Por exemplo, para o predicado
length([], 0).
length([_ | L], N) :-
   length(L, NL), N is NL + 1.
podem ser passadas como parˆmetro listas de qualquer tipo, reaproveitamento o predicado:
                           a
?- length([greem, red], NumCores).
NumCores = 2
yes
?- lenght([1, -5, 12], NumElem).
NumElem = 3
yes
   Em Prolog, n˜o h´ defini¸˜o de quais parˆmetros s˜o de entrada e quais s˜o de sa´ de um
                 a    a   ca              a        a                      a        ıda
predicado. De fato, um parˆmetro pode ser de entrada em uma chamada e de sa´ em outra.
                          a                                                    ıda
Utilizando o predicado

pertence(X, [X | _]).
pertence(X, [_ | C]) :-
   pertence(X, C).

podemos perguntar se um elemento pertence a uma lista:
     ?- pertence(a, [b, e, f, a, g]).
e tamb´m que elementos pertencem ` lista:
      e                          a
?- pertence(E, [b, e, f, a, g]).
E = b;
E = e;
E = f;
E = a;
E = g;
no
No primeiro caso, o parˆmetro formal X ´ de entrada (por valor — a) e no segundo (E), de sa´
                        a                e                                                   ıda.
   A conseq¨ˆncia do rac´
             ue            ıocionio acima ´ que temos duas fun¸˜es diferentes utilizando um unico
                                           e                     co                             ´
predicado. Logo, existe reaproveitamento de c´digo por n˜o ser fixo o tipo de passagem de parˆmetros
                                              o         a                                    a
em Prolog. Outras linguagens exigiriam a constru¸˜o de dois procedimentos, um para cada fun¸˜o de
                                                  ca                                           ca
pertence.
   Podemos comparar a caracter´   ıstica acima de Prolog com l´gica : dada uma f´rmula do c´lculo
                                                               o                   o           a
proposicional, como ((a∧b)∨c), e valores de algumas da vari´veis e/ou resultado da express˜o, pode-
                                                            a                              a
mos obter o valor das vari´veis restantes. Por exemplo
                          a

   • se ((a∧b)∨c) = true e a = true, c = false, ent˜o b dever´ ser true.
                                                   a         a


   • se ((a∧b)∨c) = true e a = true, c = true, b poder´ ser true ou false.
                                                      a

                                                91
No predicado pertence h´ uma constru¸˜o semelhante:
                          a            ca

   • se pertence(E, [b, e, f, a, g]) = true, ent˜o E = b ou E = e, ... ou E = g.
                                                a

    E pode assumir diversos valores para fazer pertence(E, [b, e, f, a, g]) true, da mesma forma
que, na ultima f´rmula, b pode assumir dois valores (true ou false) para fazer a equa¸˜o verdadeira.
        ´       o                                                                    ca
Esta forma de reaproveitamento de c´digo ´ exclusiva das linguagens l´gicas e n˜o se relaciona a
                                       o       e                        o          a
polimorfismo — s˜o conceitos diferentes.
                  a


5.5    Manipula¸˜o da Base de Dados
               ca
O predicado assert sempre sucede e introduz um novo fato ou regra na base de dados:

?- otimo( zagalo ).
no

?- assert( otimo(zagalo) ).
yes

?- otimo(zagalo).
yes

O predicado retract remove um fato ou regra da base de dados:

?- retract( otimo(zagalo) ).
yes

?- otimo(zagalo).
no

?- assert( otimo(luxemburgo) ).
yes

   assert pode fazer um programa em Prolog “aprender” durante a sua execu¸˜o. O que ele
                                                                                  ca
aprende pode ser gravado em disco e recuperado posteriormente. Por exemplo, considere um predicado
fatorial que calcula o fatorial de um n´mero e armazena os valores j´ calculados na base de dados.
                                        u                            a

   /* fat(N, P) significa que o fatorial de N ´ P */
                                              e
fat(0, 1).

   /* fatorial(N, P) significa que o fatorial de N ´ P */
                                                   e
fatorial(N, P) :-
  fat(N, P).
fatorial(N, P) :-
  N1 is N - 1,
  fatorial(N1, P1),
  P is P1*N,
  assert( fat(N, P) ).




                                                92
Inicialmente, h´ apenas um fato para o predicado fat. Quando fatorial for invocado, como em
                a
       ?- fatorial(3, P).
       P = 6
ser˜o introduzidos na base de dados fatos da forma fat(N, P). Neste caso, a base de dados conter´
   a                                                                                            a
os seguintes fatos de fat:

fat(0,   1).
fat(1,   1).
fat(2,   2).
fat(3,   6).

Agora, quando o fatorial de 3 for novamente requisitado, ele ser´ tomado da base de dados, o que ´
                                                                a                                e
muito mais r´pido do que calcul´-lo novamente por sucessivas multiplica¸˜es.
             a                  a                                      co
    assert pode tamb´m incluir regras na base de dados:
                      e
       ?- assert( (otimo(X) :- not (X = zagalo)) ).
A regra deve vir dentro de parˆnteses.
                              e
    Nos exemplos anteriores, admitimos que os fatos e regras introduzidos na base de dados por
assert s˜o sempre colocados no fim da base. Se for necess´rio introduzi-los no in´
          a                                                   a                      ıcio, podemos
utilizar asserta. Se quisermos explicitar que os fatos ou regras s˜o introduzidos no final da base,
                                                                   a
podemos utilizar assertz.


5.6      Aspectos N˜o L´gicos de Prolog
                   a   o
Algumas constru¸˜es de Prolog e o pr´prio algoritmo de unifica¸˜o fazem com que esta linguagem n˜o
                 co                  o                         ca                              a
seja completamente l´gica. Em l´gica, uma express˜o “A and B” ´ idˆntica a “B and A” e “A or B”
                     o          o                   a              e e
´ idˆntica a “B or A”. Em Prolog, isto n˜o ´ sempre verdadeiro. O “and” aparece em regras como
e e                                      a e
      R(X) :- A(X), B(X)
em que R(X) ser´ verdadeiro se A(X) e B(X) forem verdadeiros. Em Prolog, a invers˜o de A e B na
                 a                                                                 a
regra, resultando em
      R(X) :- B(X), A(X).
pode produzir resultados diferentes da regra anterior, violando a l´gica.
                                                                   o
    O “or” aparece quando h´ mais de uma regra para um mesmo predicado ou quando usamos “;”:
                             a

R(X) :- A(X) ; B(X).

S(X) :- A(X).
S(X) :- B(X).

R(X) (ou S(X)) ser´ verdadeiro se A(X) ou B(X) o forem. Novamente, os dois predicados acima podem
                   a
apresentar resultados diferentes se reescritos como

R(X) :- B(X) ; A(X).

S(X) :- B(X).
S(X) :- A(X).

   Em l´gica matem´tica, dada uma express˜o qualquer como “A and B” e os valores das vari´veis,
        o            a                       a                                                   a
podemos calcular o resultado da express˜o. E dado o valor da express˜o e de todas as vari´veis, exceto
                                       a                            a                    a
uma delas, podemos calcular o valor ou valores desta vari´vel. Assim, se “A and B” for falso e A for
                                                         a
verdadeiro, saberemos que B ´ falso. Em Prolog esta regra nem sempre ´ verdadeira. Quando n˜o for,
                            e                                         e                         a

                                                 93
diremos que n˜o h´ bidirecionalidade entre os argumentos de entrada e sa´
                a a                                                        ıda. Em uma linguagem
l´gica pura, qualquer argumento pode ser de entrada ou de sa´
 o                                                             ıda.
    Idealmente, um programador de Prolog deveria se preocupar apenas em especificar as rela¸˜es  co
l´gicas entre os parˆmetros de cada predicado. O programador n˜o deveria pensar em como o algoritmo
 o                  a                                            a
de unifica¸˜o trabalha para satisfazer as rela¸˜es l´gicas especificadas pelo programa. Desta forma,
           ca                                   co  o
o programador estaria utilizando rela¸˜es l´gicas est´ticas, bastante abstratas, ao inv´s de pensar
                                        co    o        a                               e
em rela¸˜es dinˆmicas que s˜o dif´
         co       a           a     ıceis de entender. Contudo, para tornar Prolog eficiente v´rias
                                                                                               a
constru¸˜es n˜o l´gicas, citadas a seguir, s˜o suportadas pela linguagem.
        co     a o                          a

   • O is for¸a uma express˜o a ser avaliada quebrando a simetria exigida pela l´gica. Isto ´, um
              c               a                                                    o           e
     goal
            6 is 2*X
     n˜o ser´ v´lido se X n˜o estiver instanciado. Por este motivo, a ordem dos goals no corpo de um
      a     a a            a
     predicado ´ importante. O predicado length (tamanho de uma lista) n˜o pode ser implementado
                e                                                          a
     como

     length([], 0).
     length([_|L], N) :-
       N is N1 + 1,
       length(L, N1).

     pois N n˜o estaria instanciado no goal “N1 is N + 1” em uma pergunta
             a
           ?- length([1, 2], X).


   • O cut tamb´m for¸a os goals a uma determinada ordem dentro de um predicado. Mudando-se a
                 e    c
     ordem, muda-se o significado do predicado. A ordem em que as cl´usulas s˜o colocadas podem
                                                                   a        a
     se tornar importantes por causa do cut. Assim, o predicado

     max(X, Y, X) :-
       X = Y,
       !.
     max(X, Y, Y).

     n˜o poderia ser escrito como
      a

     max(X, Y, Y).
     max(X, Y, X) :-
       X = Y,
       !.

     O cut remove a bidirecionalidade da entrada e sa´ como no caso do predicado pertence. Se
                                                     ıda
     este for definido como

     pertence(X, [X | _]) :-
       !.
     pertence(X, [_ | C]) :-
       pertence(X, C).


                                                94
o goal
        ?- pertence(X, [1, 2, 3]).
  n˜o serve para obter, por meio do X, todos os elementos da lista. Isto ´ o mesmo que dizer que,
   a                                                                     e
  dado que A ´ verdadeiro e o resultado de A and B ´ falso, o sistema n˜o consegue dizer o valor
              e                                     e                    a
  de B.

• A ordem com que Prolog faz a unifica¸˜o altera o significado dos predicados. Por exemplo,
                                       ca
  suponha que o predicado ancestral fosse definido como

  ancestral(A, D) :-            /* A ´ ancestral de D */
                                     e
    ancestral(Y, D),
    paimaeDe(A, Y).
  ancestral(A, D) :-
    paimaeDe(A, D).

  Agora o goal
         ?- ancestral(jose, pedro).
  faz o sistema entrar em um la¸o infinito, apesar do goal ser verdadeiro.
                               c

• O not pode ser definido como

  not(P) :-
    P, !, fail.
  not(P).

  Para satisfazer um goal not(P), Prolog tenta provar que P ´ verdadeiro. Se for, not(P) falha.
                                                              e
  Esta forma de avalia¸˜o pode fazer a ordem dos goals de um predicado importante. Um exemplo,
                      ca
  tomado de [18], ´:
                  e

  r(a).
  q(b).
  p(X) :- not r(X).

  ?- q(X), p(X).
  X = b

  Mas, invertendo o goal,

  ?- p(X), q(X).
  no

  o resultado ´ diferente.
              e

• As rotinas assert e retract de manipula¸˜o da base de dados podem conduzir aos mesmos
                                            ca
  problemas que o cut e o not. Por exemplo,

  chuva :- assert(molhado).
  ?- molhado, chuva.
  no


                                             95
?- chuva, molhado.
      yes

      ?- molhado, chuva.
      yes

   • rela¸˜es l´gicas n˜o podem representar entrada e sa´ de dados, que possuem problemas semel-
         co o          a                                ıda
     hantes a assert e retract. Por exemplo, um predicado que lˆ um arquivo pode retornar em
                                                                  e
     um dos seus parˆmetros valores diferentes em diferentes chamadas. Ent˜o o conceito de estado,
                      a                                                   a
     estranho ` l´gica, ´ introduzido na linguagem.
               a o       e


5.7    Discuss˜o Sobre Prolog
              a
Um programa em Prolog ´ formado pela base de dados (BD) (cl´usulas) e pelo algoritmo de unifica¸˜o
                        e                                     a                                  ca
(AU). A BD cont´m as rela¸˜es l´gicas entre objetos e o AU ´ o meio de descobrir se dado goal ´
                  e         co   o                             e                                    e
verdadeiro de acordo com a BD. Assim,
          programa = BD + AU
   O programa em Prolog possui instru¸˜es que est˜o impl´
                                      co         a      ıcitas no algoritmo de unifica¸˜o e, portanto,
                                                                                     ca
n˜o precisam ser colocadas na base de dados. Por exemplo, usando o BD
 a
dias(jan, 31).
dias(fev, 28).
...
dias(dez, 31).
podemos saber o n´mero de dias do mˆs de Setembro sem precisar escrever nenhum algoritmo:
                   u                  e
      ?- dias(set, N).
a busca por N ´ feita pelo AU. No caso geral, parte das repeti¸˜es (la¸os, incluindo recurs˜o) e testes
              e                                               co      c                    a
est˜o na BD e parte no AU. No caso acima, a BD n˜o contribui com nenhuma repeti¸˜o ou teste.
   a                                                a                                  ca
    Um outro exemplo ´ um predicado para verificar se elemento X pertence a uma lista:
                       e
membro(X, [X | L]).
membro(X, [Y | L]) :-
   membro(X, L).
A fun¸˜o equivalente utilizando a sintaxe de S ´ mostrada na Figura 5.1.
      ca                                         e
    A fun¸˜o em S possui muito mais detalhes que a de Prolog. Contudo, o predicado em Prolog
           ca
possui opera¸˜es equivalentes `quelas da fun¸˜o em S. Algumas destas opera¸˜es est˜o impl´
               co              a                ca                                co     a      ıcitas
no algoritmo de unifica¸˜o e outras expl´
                         ca                ıcitas no programa. No predicado membro, as opera¸˜es  co
(p == nil) e (p.elem == x) est˜o impl´
                                 a      ıcitas no AU, pois estes testes s˜o feitos no emparelhamento
                                                                          a
com as cl´usulas de membro. A opera¸˜o p == nil ´ equivalente a n˜o obter emparelhamento, j´
           a                           ca               e                a                           a
que a lista ´ vazia, e que resulta em falha do predicado. E p.elem == x ´ equivalente a obter
              e                                                                  e
emparelhamento com a primeira cl´usula, membro(X, [X|L]).
                                   a
    A obten¸˜o do elemento da frente da lista ´ feito com . em S e pela conven¸˜o de separar uma lista
             ca                                e                              ca
em cabe¸a e cauda ([H | T]) em Prolog. No predicado membro, a unica opera¸˜o realmente expl´
         c                                                          ´           ca               ıcita
´ a recurs˜o na segunda cl´usula que aparece disfar¸ada de uma defini¸˜o de cl´usula.
e          a                a                         c                 ca       a
    A fun¸˜o e o predicado membro demonstram que nem todas as opera¸˜es precisam estar explicitadas
          ca                                                           co
nas cl´usulas (BD), pois as opera¸˜es contidas no AU fazem parte do programa final.
      a                           co
    A linguagem Prolog ´ adequada justamente para aquele problemas cujas solu¸˜es algoritmicas
                          e                                                          co
possuem semelhan¸a com o algoritmo de unifica¸˜o. Sendo semelhantes, a maior parte da solu¸˜o
                   c                               ca                                              ca

                                                  96
proc membro( p, x )
begin
  if p == nil
  then
     return false;
  else
     if p.elem == x
     then
       return true;
     else
       return membro(p.suc, x);
     endif
  endif
end;


                    Figura 5.1: Fun¸˜o para descobrir se x ´ membro da lista p
                                   ca                      e


pode ser deixada para o AU, tornando a BD muito mais f´cil de se fazer (mais abstrata). A parte
                                                           a
n˜o semelhante ao AU deve ser codificada nas regras, como a chamada recursiva a membro na segunda
  a
cl´usula do exemplo anterior.
  a
    De um modo geral, uma linguagem ´ boa para resolver determinados problemas se estes s˜o
                                          e                                                       a
facilmente mapeados nela. Neste caso, a linguagem possui, implicitamente, os algoritmos e estruturas
de dados mais comumente usados para resolver estes problemas.




                                                97
Cap´
   ıtulo 6

Linguagens Baseadas em Fluxo de
Dados

Linguagens baseadas em fluxo de dados (data flow) obt´m concorrˆncia executando qualquer instru¸˜o
                                                        e          e                              ca
t˜o logo os dados que ela utiliza estejam dispon´
 a                                               ıveis. Assim, uma chamada de fun¸˜o f(x) (ou uma
                                                                                    ca
atribui¸˜o “y = x”) ser´ executada t˜o logo a vari´vel x tenha recebido um valor em alguma outra
       ca                a             a             a
parte do programa — ent˜o vari´veis podem estar em um de dois estados: inicializadas ou n˜o. N˜o
                           a     a                                                           a     a
interessa onde f(x) (ou y = x) est´ no programa ou se as instru¸˜es que o precedem textualmente no
                                   a                              co
c´digo fonte j´ tenham sido executadas: f(x) (ou y = x) ser´ executada t˜o logo a vari´vel x tenha
 o             a                                               a            a            a
um valor. Ent˜o a execu¸˜o do programa obedece `s dependˆncias entre os dados e n˜o a ordem
                 a         ca                          a         e                         a
textual das instru¸˜es no c´digo fonte.
                   co       o
    A execu¸˜o de um programa em uma linguagem data flow utiliza um grafo de fluxo de dados (GFD)
            ca
no qual os v´rtices representam instru¸˜es e as arestas as dependˆncias entre elas. Haver´ uma aresta
             e                         co                          e                     a
(v, w) no grafo se w depender dos dados produzidos em v. Como exemplo, considere as atribui¸˜es  co
do procedimento

proc m(b, d, e)
     { declara e ja   inicializa as variaveis }
  var
     a = b + 1,       {   1   }
     c = d/2 + e,     {   2   }
     i = a + 1,       {   3   }
     f = 2*i + c,     {   4   }
     h = b + c,       {   5   }
     k = f + c;       {   6   }
  is
     return a + c +   i + f + h + k;

Ent˜o h´ uma aresta (representada por uma seta) de 1 para 3. O GFD das instru¸˜es acima est´ na
   a a                                                                         co          a
Figura 6.
   As inicializa¸˜es do procedimento m podem ser executadas em v´rias ordens poss´
                co                                              a                ıveis:

1 2 3 4 5 6
2 5 1 3 4 6
2 1 3 4 6 5
...


                                                 98
Figura 6.1: Um grafo do fluxo de dados


   Se n˜o h´ caminho ligando a instru¸˜o V a U, ent˜o estas instru¸˜es s˜o independentes entre si e
       a a                           ca            a              co    a
podem ser executadas concorrentemente. Por exemplo, podem ser executadas em paralelo as instru¸˜es
                                                                                              co

1 e   2
3 e   5
4 e   5
5 e   6
...

    Um programa em uma linguagem data flow (LDF) ´ transladado em um GDF que ´ executado
                                                       e                                e
por uma m´quina data flow.1 Esta m´quina tenta executar tantas intru¸˜es em paralelo quanto ´
            a                          a                                 co                        e
poss´
    ıvel, respeitando as dependˆncias entre elas. Conseq¨entemente, a ordem de execu¸˜o n˜o ´
                                 e                        u                               ca    a e
necessariamente a ordem textual das instru¸˜es no programa. Por exemplo, a instru¸˜o 5 poderia ser
                                           co                                      ca
executada antes da 3 ou da 4.
    Podemos imaginar a execu¸˜o de um programa data flow como valores fluindo entre os n´s do
                                ca                                                             o
GDF. A instru¸˜o de um dado n´ poder´ ser executada se houver valores dispon´
               ca                 o      a                                      ıveis nos n´s de que
                                                                                           o
ela depende. Um n´ representando uma vari´vel possui valor dispon´
                    o                         a                     ıvel ap´s ela ser usada do lado
                                                                            o
esquerdo de uma atribui¸˜o:
                         ca
      a = exp
Por exemplo, a instru¸˜o 4 s´ poder´ ser executada se h´ valores em 2 e 3 (valores de c e i).
                      ca     o      a                  a
    Uma conseq¨ˆncia das regras da dependˆncia ´ que cada vari´vel deve ser inicializada uma unica
                ue                          e    e              a                              ´
vez. Caso contr´rio, haveria n˜o determinismo nos programas. Por exemplo, em
                a              a

proc p() : integer
  var
     a = 1,   { 1 }
     b = 2*a, { 2 }
     a = 5;   { 3 }
  is
     a + b;
  1
     Obviamente, qualquer computador poderia executar este programa, mas sup˜e-se que um computador data flow
                                                                            o
seria mais eficiente.


                                                    99
Figura 6.2: Um grafo do fluxo de dados de um comando if




                  Figura 6.3: Um grafo do fluxo de dados de um comando while

o valor final de b poderia ser 2 ou 10, pois a seq¨ˆncia de execu¸˜o das atribui¸˜es poderia ser
                                                 ue               ca           co
      1 2 3
ou
      3 2 1
entre outras. A exigˆncia de uma unica inicializa¸˜o ´ chamada de regra da atribui¸˜o unica.
                    e              ´              ca e                             ca ´
   Um comando if em uma linguagem data flow t´       ıpica ´ da forma
                                                          e
      if exp then exp1 else exp2
como em linguagens funcionais. A express˜o exp1 s´ ser´ avaliada se a express˜o exp do if for
                                             a          o    a                      a
true. Como a ordem de execu¸˜o s´ depende da disponibilidade de dados, a express˜o exp1 ´ feita
                                ca o                                                  a         e
dependente de exp da seguinte forma: se exp resultar em true, exp1 recebe um token que habilita a
sua avalia¸˜o. Sem este token, exp1 n˜o ser´ avaliada mesmo que todos os outros valores de que ela
          ca                           a     a
depende estejam dispon´ ıveis. O grafo de fluxo de dados do if
if a  b
then
  c + 1
else
  d - 1
est´ na Figura 6.
   a
    Comandos while funcionam de forma semelhante aos if’s. Os comandos do corpo do while s˜o  a
dependentes de um valor true resultante da avalia¸˜o da express˜o condicional. O GFD do while do
                                                 ca            a
c´digo
 o
i = 1;

                                               100
p = 1;
while i = N do
  begin
  p = p*i;
  i = i + 1;
  end

est´ na Figura 6.
   a
    Este c´digo permite a modifica¸˜o de vari´veis de controle dentro do la¸o, violando a regra de
          o                           ca       a                              c
atribui¸˜o unica. H´ duas atribui¸˜es para i e duas para p. Este problema ´ contornado permitindo
       ca ´         a               co                                       e
a cria¸˜o de uma nova vari´vel i (e p) a cada itera¸˜o do la¸o. Assim, temos um stream de valores
      ca                    a                        ca      c
para i e outro para p. As m´quinas data flow associam tags aos valores de i e p de tal forma que os
                              a
valores de um passo do la¸o n˜o s˜o confundidos com valores de outros passos.
                          c a a
    Considere que a multiplica¸˜o p*i seja muito mais lenta que a atribui¸˜o i = i + 1 e o teste
                                ca                                           ca
i = N, de tal forma que o la¸o avan¸a rapidamente na instru¸˜o i = i + 1 e lentamente em p = p*i.
                             c        c                     ca
Isto ´, poder´
     e        ıamos ter a situa¸˜o em que i = 12 (considerando N = 15) mas p ainda est´ sendo
                                 ca                                                         a
multiplicado por i = 3. Haveria diversos valores de i esperando para serem multiplicados por p.
Estes valores n˜o se confundem. O valor de p da k-´sima itera¸˜o ´ sempre multiplicado pelo valor de
                a                                  e          ca e
i da k-´sima itera¸˜o para resultar no valor de p da (k + 1)-´sima itera¸˜o.
        e         ca                                         e          ca
    Em uma chamada de fun¸˜o, alguns parˆmetros podem ser passados antes dos outros e podem
                               ca            a
causar a execu¸˜o de instru¸˜es dentro da fun¸˜o.
               ca           co                ca
    Considere a fun¸˜o
                    ca

proc p(x,      y : integer) : integer
  var
     z = x     + 3,          { 1 }
     t = x     + 1;          { 2 }
  is
     z + t     + 2 * y;        { 3 }

e a chamada de fun¸˜o p
                  ca

proc q()
  var
     a = 1,
     b = f(2),
     k = p(a, b);
  is
     ...

onde a j´ recebeu um valor, mas o valor de b ainda est´ sendo calculado. Admita que o c´lculo de
        a                                              a                                   a
f(2) ´ demorado. O parˆmetro a ´ passado a p e causa a execu¸˜o das instru¸˜es 1 e 2. A instru¸˜o
      e                  a         e                          ca             co                   ca
3 ´ executada t˜o logo o valor de b esteja dispon´ e seja passado a p.
  e             a                                 ıvel
    Linguagens data flow utilizam granuralidade muito fina de paralelismo gerando uma quantidade
enorme de tarefas2 executadas paralelamente. Os recursos necess´rios para gerenciar este paralelismo
                                                               a
s˜o gigantescos e exigem necessariamente uma m´quina data flow. Se o c´digo for compilado para uma
 a                                               a                     o
m´quina n˜o data flow, mesmo com v´rios processadores, haver´ uma enorme perda de desempenho.
  a        a                          a                       a
Uma linguagem contendo apenas os conceitos expostos neste cap´  ıtulo deixa de aproveitar as maiores
  2
      Tarefas referem-se a trechos de c´digo e n˜o a processos do Sistema Operacional.
                                       o        a



                                                          101
oportunidades de paralelismo encontradas em programas reais, que s˜o: a manipula¸˜o de vetores
                                                                      a              ca
inteiros de uma vez pela m´quina sobre os quais podem ser aplicadas as opera¸˜es aritm´ticas. Uma
                           a                                                  co        e
granuralidade mais alta de paralelismo ´ tamb´m interessante pois a quantidade de comunica¸˜o entre
                                       e     e                                            ca
os processos em paralelo ´ minimizada. Contudo a linguagem que definimos n˜o permite a defini¸˜o
                         e                                                    a                 ca
de processos com execu¸˜o interna seq¨encial mas executando em paralelo com outros processos.
                       ca             u




                                               102
Referˆncias Bibliogr´ficas
     e              a

[1] America, Pierre; Linden, Frank van der. A Parallel Object-Oriented Language with Inheritance
    and Subtyping, SIGPLAN Notices, Vol. 25, No. 10, October 1990. ECOOP/OOPSLA 90.

[2] Lippman, Stanley B. C++ Primer. Addison-Wesley, 1991.

[3] Deitel, H.M. e Deitel P.J. C++ How to Program. Prentice-Hall, 1994.

[4] GC FAQ — draft. Available at
    http://www.centerline.com/people/chase/GC/GC-faq.html

[5] Goldberg, Adele; Robson, David. Smalltalk-80: the Language and its Implementation. Addison-
    Wesley, 1983.

[6] Hoare, Charles A. R. The Emperor’s Old Clothes. CACM, Vol. 24, No. 2, February 1981.

[7] Guimar˜es, Jos´ de Oliveira. The Green Language home page. Available at
           a      e
    http://www.dc.ufscar.br/~jose/green.

[8] Guimar˜es, Jos´ de Oliveira. The Green Language. Computer Languages, Systems  Structures,
            a       e
    Vol. 32, Issue 4, pages 203-215, December 2006.

[9] Guimar˜es, Jos´ de Oliveira. The Object Oriented Model and Its Advantages. OOPS Messenger,
            a       e
    Vol. 6, No. 1, January 1995.

[10] Kjell,   Bradley. Introduction to Computer               Science   using   Java.   Available   at
    http://chortle.ccsu.edu/CS151/cs151java.html.

[11] Niemeyer, P. and Peck, J. (1997) Exploring Java. O’Reilly  Associates, Sebastopol.

[12] Rojas, Ra´l, et al. (2000). Plankalk¨l: The First High-Level Programming Language
               u                           u
    and its Implementation. Institut f¨r Informatik, Freie Universit¨t Berlin, Technical Report
                                      u                             a
    B-3/2000. Available at http://www.zib.de/zuse/Inhalt/Programme/Plankalkuel/Plankalkuel-
    Report/Plankalkuel-Report.htm.

[13] Slater, Robert. Portraits in Silicon. MIT Press, 1989.

[14] Stroustrup, Bjarne. The C++ Programming Language. Second Edition, Addison-Wesley, 1991.

[15] Wegner, Peter. Research Directions in Object-Oriented Programming, chapter The Object-
    Oriented Classification Paradigm, pp. 479–559. MIT Press, 1987.

[16] Weinberg, Gerald M. The Psychology of Computer Programming, Van Nostrand Reinhold, 1971.

[17] Ben-Ari, M. Understanding Programming Languages. John Wiley  Sons, 1996.

                                                 103
[18] Bratko, Ivan. Prolog Programming for Artificial Intelligence. International Computer Science
    Series, 1986.

[19] Finkel, Raphael. Advanced Programming Language Design. Addison-Wesley, 1996.




                                              104

Mais conteúdo relacionado

PDF
Programacao Orientada A Objetos (Java)
PDF
Scilab programacao
PDF
Introdução à programação em R
PDF
Curso estatistica descritiva no r
PDF
Aprenda computaocompython
PDF
Estrutura de dados
PDF
Matematica fcul
PDF
Caelum ruby-on-rails-rr71
Programacao Orientada A Objetos (Java)
Scilab programacao
Introdução à programação em R
Curso estatistica descritiva no r
Aprenda computaocompython
Estrutura de dados
Matematica fcul
Caelum ruby-on-rails-rr71

Mais procurados (17)

PDF
Aprenda computação com python 3.0 (1)
PDF
Apostila r gen melhor
PDF
Livro eduardo
PDF
Publicado ruby on-rails-rr71
PDF
Caelum java-testes-xml-design-patterns-fj16
PDF
K19 k03-sql-e-modelo-relacional
PDF
Curso de robotica movel
PDF
Apostila ruby-completa
PDF
Estrutura de dados 2
PDF
Apostila latex
PDF
Linux basico
PDF
K19 k22-desenvolvimento-web-avancado-com-jsf2-ejb3.1-e-cdi
PDF
Apostila c# iniciantes
PDF
Ncl e Lua - desenvolvendo aplicações interativas para tv digital
PDF
Introdução às redes
PDF
K19 k11-orientacao-a-objetos-em-java
PDF
K19 k12-desenvolvimento-web-com-jsf2-e-jpa2
Aprenda computação com python 3.0 (1)
Apostila r gen melhor
Livro eduardo
Publicado ruby on-rails-rr71
Caelum java-testes-xml-design-patterns-fj16
K19 k03-sql-e-modelo-relacional
Curso de robotica movel
Apostila ruby-completa
Estrutura de dados 2
Apostila latex
Linux basico
K19 k22-desenvolvimento-web-avancado-com-jsf2-ejb3.1-e-cdi
Apostila c# iniciantes
Ncl e Lua - desenvolvendo aplicações interativas para tv digital
Introdução às redes
K19 k11-orientacao-a-objetos-em-java
K19 k12-desenvolvimento-web-com-jsf2-e-jpa2
Anúncio

Destaque (20)

DOC
French and english five little speckled frogs
PDF
Adlandpro coffee lovers slide share
PPTX
Os animais (6)
PDF
Antropologia Ciborgue: a onipresença da tecnologia e a condição humana no cib...
PDF
Projeto alinhamento das estratégias de operações
PDF
2013版中国常规天然气管道项目名单(英文)
PPTX
"I AM APPRECIATED" - PTR. ALVIN GUTIERREZ - 10AM MORNING SERVICE
PDF
CLOSED-CHEST CARDIAC MASSAGE
PDF
éTica é um ramo da filosofia que estuda o comportamento moral do ser humano
PDF
DIREITO CIVIL
PPTX
If You Build It, Will They Come? - How to Increase Learning Adoption
PDF
FGV - RAE Revista de Administração de Empresas, 2015. Volume 55, Número 4
PDF
Reflexões a cerca das dificuldades de aprendizagens em leitura e escrita uma...
PDF
Tuga IT - Power BI for Developers
DOCX
Fases do desenvolvimento_motor
DOC
Conceitue contas
PDF
REQB® - Foundation Level Requirements Manager
PDF
Estatistica regular 1
 
DOCX
Performance appraisal problems and solutions
PPT
Aprendizagem complexa
French and english five little speckled frogs
Adlandpro coffee lovers slide share
Os animais (6)
Antropologia Ciborgue: a onipresença da tecnologia e a condição humana no cib...
Projeto alinhamento das estratégias de operações
2013版中国常规天然气管道项目名单(英文)
"I AM APPRECIATED" - PTR. ALVIN GUTIERREZ - 10AM MORNING SERVICE
CLOSED-CHEST CARDIAC MASSAGE
éTica é um ramo da filosofia que estuda o comportamento moral do ser humano
DIREITO CIVIL
If You Build It, Will They Come? - How to Increase Learning Adoption
FGV - RAE Revista de Administração de Empresas, 2015. Volume 55, Número 4
Reflexões a cerca das dificuldades de aprendizagens em leitura e escrita uma...
Tuga IT - Power BI for Developers
Fases do desenvolvimento_motor
Conceitue contas
REQB® - Foundation Level Requirements Manager
Estatistica regular 1
 
Performance appraisal problems and solutions
Aprendizagem complexa
Anúncio

Semelhante a Programming Languages Paradigms (20)

PDF
Poojava
PDF
Apostila de Java: Orientação a Objetos
PDF
Tutorial ruby eustaquio taq rangel
PDF
Documentação de uma linguagem de progração
PDF
Tutorial ruby
PDF
caelum-java-objetos-fj11.pdf
PDF
Programacao cpp
PDF
K19 k31-csharp-e-orientacao-a-objetos
PDF
Python
PDF
Java basico
PDF
Linguagem c
PDF
O_Fantastico_Mundo_da_Linguagem_C em pdf.pdf
PDF
O mundo-da-linguagem-c
PDF
O fantc3a1stico-mundo-da-linguagem-c
PDF
Estruturas dados
PDF
Estruturas dados
PDF
Algoritmos
PDF
Programação Orientada a Objetos com Java
PDF
Poojava
Apostila de Java: Orientação a Objetos
Tutorial ruby eustaquio taq rangel
Documentação de uma linguagem de progração
Tutorial ruby
caelum-java-objetos-fj11.pdf
Programacao cpp
K19 k31-csharp-e-orientacao-a-objetos
Python
Java basico
Linguagem c
O_Fantastico_Mundo_da_Linguagem_C em pdf.pdf
O mundo-da-linguagem-c
O fantc3a1stico-mundo-da-linguagem-c
Estruturas dados
Estruturas dados
Algoritmos
Programação Orientada a Objetos com Java

Último (19)

PDF
Fundamentos de gerenciamento de ordens e planejamento no SAP TransportationMa...
PDF
20250805_ServiceNow e a Arquitetura Orientada a Serviços (SOA) A Base para Ap...
PDF
Custos e liquidação no SAP Transportation Management, TM130 Col18
PPTX
Informática Aplicada Informática Aplicada Plano de Ensino - estudo de caso NR...
PDF
Gestão de transportes básica no SAP S/4HANA, S4611 Col20
PPTX
Como-se-implementa-um-softwareeeeeeeeeeeeeeeeeeeeeeeee.pptx
PPTX
Programação - Linguagem C - Variáveis, Palavras Reservadas, tipos de dados, c...
PPTX
Aula16ManipulaçãoDadosssssssssssssssssssssssssssss
PDF
Processos na gestão de transportes, TM100 Col18
PPTX
Gestao-de-Bugs-em-Software-Introducao.pptxxxxxxxx
PPTX
BANCO DE DADOS - AULAS INICIAIS-sgbd.pptx
PDF
Custos e faturamento no SAP S/4HANA Transportation Management, S4TM3 Col26
PDF
Apple Pippin Uma breve introdução. - David Glotz
PDF
Mergulho profundo técnico para gestão de transportes no SAP S/4HANA, S4TM6 Col14
PDF
COBITxITIL-Entenda as diferença em uso governança TI
PDF
Fullfilment AI - Forum ecommerce 2025 // Distrito e Total Express
PPTX
Aula 18 - Manipulacao De Arquivos python
PDF
Aula04-Academia Heri- Tecnologia Geral 2025
PDF
Otimizador de planejamento e execução no SAP Transportation Management, TM120...
Fundamentos de gerenciamento de ordens e planejamento no SAP TransportationMa...
20250805_ServiceNow e a Arquitetura Orientada a Serviços (SOA) A Base para Ap...
Custos e liquidação no SAP Transportation Management, TM130 Col18
Informática Aplicada Informática Aplicada Plano de Ensino - estudo de caso NR...
Gestão de transportes básica no SAP S/4HANA, S4611 Col20
Como-se-implementa-um-softwareeeeeeeeeeeeeeeeeeeeeeeee.pptx
Programação - Linguagem C - Variáveis, Palavras Reservadas, tipos de dados, c...
Aula16ManipulaçãoDadosssssssssssssssssssssssssssss
Processos na gestão de transportes, TM100 Col18
Gestao-de-Bugs-em-Software-Introducao.pptxxxxxxxx
BANCO DE DADOS - AULAS INICIAIS-sgbd.pptx
Custos e faturamento no SAP S/4HANA Transportation Management, S4TM3 Col26
Apple Pippin Uma breve introdução. - David Glotz
Mergulho profundo técnico para gestão de transportes no SAP S/4HANA, S4TM6 Col14
COBITxITIL-Entenda as diferença em uso governança TI
Fullfilment AI - Forum ecommerce 2025 // Distrito e Total Express
Aula 18 - Manipulacao De Arquivos python
Aula04-Academia Heri- Tecnologia Geral 2025
Otimizador de planejamento e execução no SAP Transportation Management, TM120...

Programming Languages Paradigms

  • 1. Programming Languages Paradigms Jos´ de Oliveira Guimar˜es e a Computer Science Department UFSCar S˜o Carlos, SP a Brazil e-mail: jose@dc.ufscar.br 16 de mar¸o de 2007 c
  • 2. Sum´rio a 1 Introduction 4 1.1 Basic Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2 History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Reasons to Study Programming Languages . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 What Characterizes a Good Programming Language ? . . . . . . . . . . . . . . . . . . 6 1.5 Compilers and Linkers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.6 Run-Time System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.7 Interpreters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.8 Equivalence of Programming Languages . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2 Basic Concepts 15 2.1 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.1.1 Static and Dynamic Type Binding . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.1.2 Strong and Static Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.2 Block Structure and Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.4 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.5 Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3 Linguagens Orientadas a Objeto 35 3.1 Introdu¸˜o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ca . . . . . . . . . . . 35 3.2 Prote¸˜o de Informa¸˜o . . . . . . . . . . . . . . . . . . . . . . . . ca ca . . . . . . . . . . . 36 3.3 Heran¸a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . c . . . . . . . . . . . 40 3.4 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.5 Modelos de Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.5.1 Smalltalk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.5.2 POOL-I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.5.3 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.5.4 Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 3.5.5 Compara¸˜o entre os Modelos de Polimorfismo e Sistema de ca Tipos . . . . . . . 55 3.6 Classes parametrizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.7 Outros T´picos sobre Orienta¸˜o a Objetos . . . . . . . . . . . . . o ca . . . . . . . . . . . 60 3.7.1 Identidade de Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.7.2 Persistˆncia . . . . . . . . . . . . . . . . . . . . . . . . . . . e . . . . . . . . . . . 61 3.7.3 Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.8 Discuss˜o Sobre Orienta¸˜o a Objetos . . . . . . . . . . . . . . . . a ca . . . . . . . . . . . 62 1
  • 3. 4 Linguagens Funcionais 65 4.1 Introdu¸˜o . . . . . . . . . . . . . . . . . ca . . . . . . . . . . . . . . . . . . . . . . . . . . 65 4.2 Lisp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.3 A Linguagem FP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.4 SML - Standard ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.5 Listas Infinitas e Avalia¸˜o Pregui¸osa . ca c . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.6 Fun¸˜es de Ordem Mais Alta . . . . . . co . . . . . . . . . . . . . . . . . . . . . . . . . . 76 4.7 Discuss˜o Sobre Linguagens Funcionais a . . . . . . . . . . . . . . . . . . . . . . . . . . 76 5 Prolog — Programming in Logic 79 5.1 Introdu¸˜o . . . . . . . . . . . . . ca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 5.2 Cut e fail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.3 Erros em Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 5.4 Reaproveitamento de C´digo . . o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 5.5 Manipula¸˜o da Base de Dados . ca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 5.6 Aspectos N˜o L´gicos de Prolog . a o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.7 Discuss˜o Sobre Prolog . . . . . . a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 6 Linguagens Baseadas em Fluxo de Dados 98 2
  • 4. Preface This book is about programming languages paradigms. A language paradigm is a way of thinking about a problem, restricting the ways we can build a program to specific patterns that are better enforced by a language supporting that paradigm. Then, the object-oriented paradigm forces one to divide a program into classes and objects while the functional paradigm requires the program to be split into mathematical functions. Programming languages books usually explain programming language paradigms through several representative languages in addition to the main concepts of the field. There is, in general, a great em- phasis on real languages which blurs the main points of the paradigms/concepts with minor languages particularities. We intend to overcome these difficulties by presenting all concepts in a Pascal-like syntax and by explaining only the fundamental concepts. Everything not important was left out. This idea has been proven successful in many programming language courses taught at the Federal University of S˜o Carlos, Brazil. a This book is organized as follows. Chapter 1 covers a miscellany of topics like programming language definition, history of the field, characteristics of good languages, and some discussion on compilers and computer theory. Basic programming language concepts are presented in Chapter 2. The other chapters discuss several paradigms like object oriented, functional, and logic. 3
  • 5. Cap´ ıtulo 1 Introduction 1.1 Basic Questions A programming language is a set of syntactic and semantic rules that enables one to describe any program. What is a program will be better described at the end of this chapter. For a moment, consider a program any set of steps that can be mechanically carried out. A language is characterized by its syntax and semantics. The syntax is easily expressed through a grammar, generally a context-free grammar. The semantics specifies the meaning of each statement or construct of the language. The semantics is very difficult to formalize and in general is expressed in a natural language as English. As an example the syntax for the while statement in C++ is while-stat ::= while ( expression ) statement and its semantics is “while the expression evaluates to a value different from 0, keep executing state- ment”. Semantics is a very trick matter and difficult to express in any way. In particular, natural languages are very ambiguous and not adequate to express all the subtleties of programming lan- guages. For example, in the semantics of while just described it is not clear what should happen if expression evaluates to 0 the first time it is calculated. Should statement be executed one time or none ? There are formal methods to define a language semantics such as Denotational Semantics and Operational Semantics but they are difficult to understand and not intend to be used by the common language programmer. As a consequence, almost every language has an obscure point that gets different interpretations by different compiler writers. Then a program that works when compiled by one compiler may not work when compiled by other compilers. 1.2 History The first programming language was designed in 1945 by Konrad Zuse, who build the first general purpose digital computer in 1941 [13]. The language was called plankalk¨l and only recently it has u been implemented [12]. Fortran was the first high level programming language to be implemented. Its design began in 1953 and a compiler was released in 1957 [13]. Fortran, which means FORmula TRANslation, was designed to scientific and engineering computations. The language has gone through a series of modifications through the years and is far from retiring. Algol (ALGOrithm Language) was also designed in the 1950’s. The first version appeared in 1958 and a revision was presented in a 1960 report. This language was extensively used mainly in Europe. 4
  • 6. Algol was one of the most (or the most) influential language already designed. Several languages that have been largely used as C, C++, Pascal, Simula, Ada, Java and Modula-2 are its descendents. Algol introduced begin-end blocks, recursion, strong typing, call by name and by value, structured iteration commands as while, and dynamic arrays whose size is determmined at run time. COBOL, which stands for COmmon Business Oriented Language, was designed in the late 1950’s for business data processing. This language was adequate for this job at that time but today it is obsolete. It has not left any (important) descendent and is being replaced by newer languages. Lisp ( LISt Processing) was designed in 1960 by John MacCarthy. The basic data structure of the language is the list. Everything is a list element or a list, including the program itself. Lisp had greatly influenced programming language design since its releasing. It introduced garbage collection and was the first functional language. Simula-67 was the first object-oriented language. It was designed in 1967 by a research team in Norway. Simula-67 descends from Simula which is an Algol extension. Simula was largely used to simulation in Europe. Alan Kay began the design of Smalltalk in the beginning of 1970’s. The language was later refined by a group of people in XEROX PARC resulting in Smalltalk-76 and Smalltalk-80, which is the current standard. Smalltalk influenced almost every object-oriented language designed in the last two decades. The first computers were programmed by given as input to them the bytes representing the in- structions (machine code). Then, to add 10 to register R0 one would have to give as input a number representing the machine instruction “mov R0, 10”. These primitive machine languages are called “first generation languages”. Then the first assembly languages appeared. They allowed to write instructions as mov R0, 10 add R0, R1 that were late translated to machine code by a compiler. The assembly languages are second generation languages. The third generation was born with Plankalk¨l1 and encompasses languages as Fortran, Algol, u Cobol, PL/I, Pascal, C, and Ada. These languages were the first to be called “high-level languages”. Fourth generation languages have specific purposes as to handle data bases. They are used in narrow domains and are very high level. These languages are not usually discussed in programming language books because they lack interesting and general concepts. There is no precise definition of language generation and this topic is usually not discussed in research articles about programming languages. The fact a language belongs to the fourth or fifth generation does not make it better than a third or even a second generation language. It may only be a different language adequate to its domain. 1.3 Reasons to Study Programming Languages Why should one take a programming language course ? Everyone in Computer Science will need to choose a programming language to work since algorithms permeate almost every field of Computer Science and are expressed in languages. Then, it is important to know how to identify the best language for each job. Although more than eight thousand languages have been designed from a dozen different paradigms, only a few have achieved widespread use. That makes it easier to identify the best language for a given programming project. It is even easier to first identify the best paradigm for the job since there are a few of them and then to identify a language belonging to that paradigm. Besides this, there are several motives to study programming languages. 1 It seems the third generation was born before the second ! 5
  • 7. • It helps one to program the language she is using. The programmer becomes open to new ways of structuring her program/data and of doing computation. For example, she may simulate object-oriented constructs in a non-object oriented language. That would make the program clearer and easier to maintain. By studying functional languages in which recursion is the main tool for executing several times a piece of code, she may learn how to use better this feature and when to use it. In fact, the several paradigms teach us a lot about alternative ways of seeing the world which includes alternative ways of structuring data, designing algorithms, and maintaining programs. • It helps to understand some aspects of one’s favorite language. Programming language books (and this in particular) concentrates in concepts rather than in particularities of real languages. Then the reader can understand the paradigm/language characteristics better than if she learns how to program a real language. In fact, it is pretty common a programmer ignore important conceptual aspects of a language she has heavily used. • It helps to learn new languages. The concepts employed in a programming language paradigm are applied to all languages supporting that paradigm. Therefore after learning the concepts of a paradigm it becomes easier to learn a language of that paradigm. Besides that, the basic features of programming languages such as garbage collection, block structure, and exceptions are common to several paradigms and one needs to learn them just one time. 1.4 What Characterizes a Good Programming Language ? General purpose programming languages are intended to be used in several fields such as commercial data processing, scientific/engineering computations, user interface, and system software. Special purpose languages are designed to a specialized field and are awkward to use everywhere. Smalltalk, Lisp, Java and C++ are general purpose languages whereas Prolog, Fortran, and Cobol are special ones. Of course, a general purpose language does not need to be suitable for all fields. For example, current implementations of Smalltalk makes this language too slow to be used for scientific/engineering computations. Now we can return to the question “What characterizes a good programming language ?”. There are several aspects to consider, explained next. • The language may have been designed to a particular field and therefore it contains features that make it easy to build programs in that field. For example, AWK is a language to handle data organized in lines of text, each line with a list of fields. A one-line program in AWK may be equivalent to a 1000-line program in C++. • Clear syntax. Although syntax is considered a minor issue, an obscure syntax makes source code difficult to understand. For example, in C/C++ the statement *f()[++i] = 0["ABC" + 1]; is legal although unclear. • Orthogonality of concepts. Two concepts are orthogonal if the use of one of them does not prevent the use of the other. For example, in C there are the concept of types (int, float, user defined structs2 ) and parameter passing to functions. In the first versions of this language, all types could be passed to functions by value (copying) except structs. One should always pass a pointer to the struct instead of the structure itself. 2 Structs are the equivalent of records in other languages as Pascal. 6
  • 8. Lack of orthogonality makes the programmer use only part of the language. If she has doubts about the legality of some code, she usually will not even try to use that code [16]. Besides being underused, a non-orthogonal language is more difficult to learn since the programmer has to know if two concepts are valid together or not. In fact, non-orthogonality is an obstacle to learning greater than the language size. A big and orthogonal language may be easier to learn than a median-size and non-orthogonal language. On the other side, full orthogonal languages may support features that are not frequently used or even not used at all. For example, in some languages values of any type can be passed to procedures by reference and by value. Then it is allowed to pass an array to a procedure by value. This is completly unnecessary3 and maked the language harder to implement. • Size of the language. Since a big language has too many constructs, there is a high probability it will not be orthogonal. Since it is difficult for a programmer to master all the peculiarities of a big language, she will get more difficult-to-fix compiler errors and therefore she will tend to use only part of the language. In fact, different people will use different language subsets, a situation that could be avoided by designing a main language with several specialized languages based in it [6]. It is hard to implement a big language not only because of the sheer number of its constructs but mainly because these constructs interact with each other in many ways. For example, in Algol 68 a procedure can return values of any type. To the programmer, to declare a procedure returning a type is as easy as to declare it returning other type. However the compiler may need to treat each type separately when doing semantic analysis and generating code. Two types may need two completely different code generation schemes. Then the compiler has to worry about the iteration between “return value” and “type”. The complexity of this iteration is hidden from the programmer. Because of problems as described above, big language compilers frequently are slow, expensive or flawed. Languages are troublesome to specify unambiguously and having a big language make things worse. The result may be different compilers implementing the same language constructs in different ways. On the other side, small languages tend to lack important features such as support to separate compilation of modules. This stimulates each compiler writer to introduce this feature by herself, resulting in dozen of language dialects incompatible to each other. When selecting a language to use, one should also consider factors external to the languages such as the ones described below. • Availability of good compilers, debuggers, and tools for the language. This may be the determi- nant factor in choosing a language and it often is. Several good languages are not largely used because they lack good tools. One of the reasons Fortran has been successful is the existence of very good optimized compilers. • Portability, which is the ability to move the source code of a program from one compiler/machine to another without having to rewrite part of it. Portability involves a series of factors such as the language itself, the language libraries, the machine, and the compiler. We will briefly explain each of these topics. Badly specified languages free compiler writers to implement ambiguously defined constructs in different ways. A library furnished with a compiler may not be available with another one, even 3 The author of this book has never seen a single situation in which this is required. 7
  • 9. A.c B.c void f() ... void g() ... ... ... f(); f(); ... ... g(); g(); Figura 1.1: Two files of a C program in the same machine. Differences in machines such as byte ordering of integers or error signaling may introduce errors when porting a program from one machine to another. For example, the code while ( w->value != x && w != NULL ) w = w->suc; in C would work properly in old micro computers. If w is NULL, w->value would not cause a core dump since old micro computers do not support memory protection. Finally, different compilers may introduce language constructs by themselves. If a program uses these constructs, it will hardly be portable to other compilers. • Good libraries. The availability of good libraries can be the major factor when choosing a language for a programming project. The use of suitable libraries can drastically reduce the development time and the cost of a system. 1.5 Compilers and Linkers A compiler is a program that reads a program written in one language L1 and translates it to another language L2 . Usually, L1 is a high language language as C++ or Prolog and L2 is assembly or machine language. However, C as been used as L2 . Using C as the target language makes the compiled code portable to any machine that has a C compiler. If a compiler produces assembler code as output, its use is restricted to a specific architecture. When a compiler translates a L1 file to machine language it will produce an output file called “object code” (usually with extension “.o” or “.obj”). In the general case, a executable program is produced by combining several object codes. This task is made by the linker as in the following example. Suppose files “A.c” and “B.c” were compiled to “A.obj” and “B.obj”. File “A.c” defines a proce- dure f and calls procedure g defined in “B.c”. File “B.c” defines a procedure g and calls procedure f. There is a call to f in “A.c” and a call to g in “B.c”. This configuration is shown in Figure 1.1. The compiler compiles “A.c” and “B.c” producing “A.obj” and “B.obj”, shown in Figure 1.2. Each file is represented by a rectangle with three parts. The upper part contains the machine code corresponding to the C file . In this code, we use call 000 for any procedure call since we do not know the exact address of the procedure in the executable file. This address will be determined by the linker and will replace 000. The middle part contains the names and addresses of the procedures defined in this “.obj” file. Then, the file “A.obj” defines a procedure called f whose address is 200. That is, the address of the first f machine instruction is 8
  • 10. B.obj 0 A.obj 0 100 g 200 f 300 call 000 400 call 000 600 call 000 700 call 000 800 1000 f 200 g 100 400 f 300 f 600 g 700 g ... ... Figura 1.2: Object file configurations .exe 0 200 f 400 call 200 600 call 900 800 900 g 1100 call 200 1500 call 900 1800 Figura 1.3: Executable file 200 in “A.obj”. The lower rectangle part of “A.obj” contains the names of the procedures called in “A.obj” together with the call addresses. Then, procedure f is called at address 400 and g is called in address 600. To calculate the previous numbers (200, 400, 600) we assume the first byte of “A.obj” has address 0. To build the executable program, the linker groups “A.obj” and “B.obj” in a single file shown in Figure 1.3. As “B.obj” was put after “A.obj”, the linker adds to the addresses of “B.obj” the size of “A.obj”, which is 800. So, the definition of procedure g was in address 100 and now it is in address 900 (100 + 800). Since all procedures are in their definitive addresses, the linker adjusted the procedure calls using the addresses of f and g (200 and 900). File “A.c” calls procedures f and g as shown in Figure 1.1. The compiler generated for these calls the code ... call 000 /* call to f. This is a comment */ 9
  • 11. ... call 000 /* call to g */ ... in which 000 was employed because the compiler did not know the address of f and g. After calculating the definitive addresses of these procedures, the linker modifies these calls to ... call 200 /* call to f */ ... call 900 /* call to g */ ... To execute a program, the operating system loads it to memory and adjusts a register that keeps where the program code begins. Then a call to address 200 means in fact a call to this number plus the value of this register. 1.6 Run-Time System In any executable program there are machine instructions that were not directly specified in the program. These instructions compose the run-time system of the program and are necessary to support the program execution. The higher level the language, the bigger its run-time system since the language nees to perform a lot of computations that were concealed from the programmers to make programming easier. For example, some languages support garbage collection that reduces errors associated to dynamic memory handling by not allowing explicit memory deallocation by the program (free of C, delete or dispose of other languages). The program becomes bigger because of the garbage collector but the programming is at a higher level when compared with languages with explicit memory deallocation. Some responsibilities of run-time system are enumerated below. • When a procedure is called, the RTS allocates memory to its local variables. When a procedure returns, the RTS frees this memory. • The RTS manages the stack of called procedures. It keeps the return addresses of each procedure in known stack positions. • When the program begins, the command line with which the program was called is passed to the program.4 This command line is furnished by the operating system and passed to the program by the run-time system. • Casting of values from one type to another can be made by the run-time system or the compiler. • In object-oriented languages, it is the RTS that does the search for a method in message sends. • When an exception is raised, the RTS looks for a “when” or “catch” clause in the stack of called procedures to treat the exception. • In C++, the RTS calls the constructor of a class when an object is created and calls the destructor when the object is destroyed. In Java, the RTS one calls the constructor when the object is created. 4 In C++ the command line is handled by the arguments argc and argv of function main. In Java, static method main of the main class has an array parameter with the arguments. 10
  • 12. • The RTS does the garbage collecting. Part of the run-time system is composed by compiled libraries and part is added by the compiler to the compiled code. In the first case is the algorithm for garbage collection and the code that gets the command line from the operating system. In the second case are all other items cited above. 1.7 Interpreters A compiler generates machine code linked by the linker and executed directly by the computer. An interpreter generates bytecodes that are interpreted by the interpreter itself. Bytecodes are instructions for a virtual machine. In general, the bytecodes are closely related to the language the interpreter works with. There are several tradeoffs in using compilers/linkers or interpreters, discussed in the following items. 1. It is easy to control the execution of a program if it is being interpreted. The interpreter can confer the legality of each bytecode before executing it. Then the interpreter can easily prevent illegal memory access, out-of-range array indeces, and dangerous file system manipulations; 2. It is easier to build a debugger for a interpreted program since the interpreter is in control of the execution of each bytecode and the virtual machine is much simpler than a real computer. 3. Interpreters are easier to build than compilers. First they translate the source code to bytecodes and compilers produce machine code. Bytecodes are in general much simpler than machine instructions. Second, interpreters do not need a linker. The linking is made dynamically during the interpretation. Third the run-time system is inside the interpreter itself, written in a high- level language. At least part of the run-time system of a compiled program needs to be in machine language. 4. The sequence Edit-Compile to bytecodes-Interpret is faster than the sequence Edit-Compile- Link-Execute required by compilers. Interpreters do not need linking and changes in one of the files that compose the program do not demand the recompilation of other files. To understand this point consider a program is composed by several files already compiled to bytecodes. Suppose the programmer does a small change in one of the files and asks the interpreter to execute the program. The interpreter will translate the changed file to bytecodes and will interpret the program. Only the modified file should be recompiled. If compilation is used, a small change in a file may require recompilation of several files that depend on this. For example, if the programmer changes the value of a constant declared as const Max = 100; in C++ or other languages, the compiler should recompile all files that use this constant.5 After compiling all the files of a program, they are linked to produce the executable file. Com- pared with the interpretation environments, compilation takes more programmer’s time. Inter- preters are fast to respond to any programmer’s change in the program and are heavily used for rapid prototyping. 5. Compilers produce a much more faster code than interpreters since they produce code that is executed directly by the machine. Usually the compiled code is 10 to 20 times faster than the interpreted equivalent code. 5 In C++, the compiler should recompile all files that include the header file that defines this constant. 11
  • 13. From the previous comparison we conclude that interpreters are best during the program’s devel- opment phase. In this step it is important to make the program run as fast as possible after changing the source code and it is important to discover as many run-time errors as possible. After the program is ready it should run fast and therefore it should be compiled. 1.8 Equivalence of Programming Languages There are thousands of programming languages employing hundreds of different concepts such as parallel constructs, objects, exception, logical restrictions, functions as parameters, generic types, and so forth. So one should wonder if there is a problem that can be solved by one language and not by another. The answer is no. All languages are equivalent in their algorithm power although it is easier to implement some algorithms in some languages than in others. There are languages • supporting parallel constructs. Two or more pieces of the same program can be executed in two or more machine processors; • without the assignment statement; • without variables; • without iterations statements such as while’s and for’s: the only way to execute a piece of code more than one time is through recursion; • without iterations statements or recursion. There are pre-defined ways to operate with data structures as arrays that make while/for/recursion unnecessary. Albeit these differences, all languages are equivalent. Every parallel program can be transformed in a sequential one. The assignment statement and variables may be discarded if all expressions are used as input to other expressions and there are alternative ways to iterative statements/recursion in handling arrays and other data structures that require the scanning of their elements. Every program that uses recursion can be transformed in another without it. This generally requires the use of a explicit stack to handle what was once a recursive call. In general there is no doubt if a given programming language is really a language. If it has a iteration statement as while’s (or recursion) and some kind of if statement, it is a real language. However, if it is not clear whether a definition L is a language, one can build an interpreter of a known language K in L. Now all algorithms that can be implemented in K can also be implemented in L. To see that, first implement an algorithm in K resulting in a program P. Then give P to the interpreter written in L. We can consider that the interpreter is executing the algorithm. Therefore the algorithm was implemented in L. Based in this reasoning we can consider interpreters as programs capable of solving every kind of problem. It is only necessary to give them the appropriate input. The mother of all computeres is the Turing Machine, devised in 1936. In fact, computability has been defined in terms of it by the Church-Turing thesis: Any computable function can be computed by the Turing Machine or, alternatively, Any mechanical computation can be made in a Turing Machine 12
  • 14. In fact, this “thesis” is an unproven hypothesis. However, there has never been found any algorithm that cannot be implemented by a Turing machine or any programming language. There are theoretic limitations in what a language can do. It is impossible, for example, to devise an algorithm P that takes the text of another algorithm Q as input and guarantees Q will finish. To prove that, suppose P exists and P(text of A) returns true if A finish its execution, false otherwise. If Q is defined as proc Q() begin while P(text of Q) do ; end what will happen ? The ; after the while is the null statement. If P(text of Q) returns true that means P says Q does stop. But in this case Q will not stop because the while statement will be equivalent to while true do ; If P(text of Q) returns false that means P says Q will never stop. But in this case Q will stop. The above reasoning leads to nonsense conclusions. Then we conclude P does not exist since all the other logical reasonings are correct. If Q takes one or more parameters as input, the reasoning above has to be slightly modified. First, all of the parameters should be represented as a single integer number — this can be done although we will no show how. Assume that the language permits integers of any length. Procedures are also represented as integer numbers — to each procedure is associated a single integer. Now suppose function P(n, d) returns true if the procedure associated to number n halts6 when taking the integer d as parameter. P(n, d) returns false otherwise. If Q is defined as proc Q(d) begin while P(d, d) do ; end and called as Q(number of Q), there is a paradox. If Q(number of Q) halts, P(d, d) returns true and the while statement above will never stop. Therefore Q(number of Q) would not halt. If Q(number of Q) does not halt, P(d, d) returns false and the while statement above will be executed a single time. This means Q will soon halts. Contradiction, P cannot exists. In the general case, an algorithm cannot deduce what other one does. So, an algorithm cannot tell another will print “3” as output or produce 52 as result. It is also not possible for an algorithm to decide whether two programs or algorithms are equivalent; that is, whether they always produce the same output when given the same input. All these restrictions to algorithm power apply to algorithms and not to programs implemented in a computer. The former assumes memory is infinite and in computers memory is always limited. It is therefore possible to devise a program S that takes a program P as input and discover if P will stop when executed in a given machine. Program S considers all bits of the machine memory (including registers, 6 Finish its execution at some time, it does not matter when. 13
  • 15. internal flags, I/O ports) as as single number describing the current memory state. Almost every P statement modifies this number even if only the program counter or some internal flag.7 Program S can simulate P execution and record the numbers resulted after the execution of each statement. This would result in a list n1 , n2 , n3 , ... nk If a number is equal to same previous number (e.g. nk == n2 ) then program P has an infinite loop. The sequence n2 , n3 , ... nk will be repeated forever. This happen because computers are deterministic: each computer state (number) is followed by exactly one other state. If this sequence reaches a number corresponding to the last program statement, the program stops. Assume all input was put in the computer memory before the execution started. 7 In fact, the only statement we found that does not modify anything is “L: goto L” 14
  • 16. Cap´ ıtulo 2 Basic Concepts This chapter explains general concepts found in several programming languages paradigms such as strong and dynamic typing, modules, garbage collection, scope, and block structure. In the following sections and in the remaining chapters of this book we will explain concepts and languages using a Pascal-like syntax as shown in Figure 2.1. To explain each concept or language we will add new syntax to the basic language that will be called S. We will describe only a few constructs of S since most of them can be understood easily by any programmer. • A procedures in S is declared with the keyword proc and it may have return value type as procedure fat in the example. In this book, the word procedure is used for subroutine, routine, function, and subprogram. • Keyword return is used to return the value of a procedure with return value type (in fact, a function). After the execution of this statement the procedure returns to the callee. • begin and end delimits a group of statements inside a procedure, a command for, or while. The if statement does not need begin-end since it finishes with endif. • The assignment statement is “=”. Language S uses “==” for equality comparison. • The execution of a program begins in a procedure called main. • There are four basic types in S: integer, boolean, real, and string. 2.1 Types A type is a set of values together with the operations that can be applied on them. Then, type integer is the set of values -32768, ... 0, 1, ... 32767 plus the operations + - * / % and or in which % is the remainder of division and and and or are applied bit by bit. 2.1.1 Static and Dynamic Type Binding A variable can be associated to a type at compile or run time. The first case occurs in languages requiring the type in variable declarations as C++, Java and Pascal: var i : integer; These languages are said to support static type binding. 15
  • 17. { global variable } const max = 100; proc fat( n : integer ) : integer { return the factorial of n } begin if n <= 1 then return 1; else return n*fat(n-1); endif end proc print( n : integer; s : String ) var i : integer; begin for i = 1 to n do write(s); end proc main() { program execution starts here } var j, n : integer; begin write("Type a number"); read(n); if n == 0 or n > max then n = fat(1); endif j = 1; while j < n do begin print(j, "Hello"); write("*****"); j = j + 1; end end Figura 2.1: A program in language S 16
  • 18. Dynamically typed languages do not allow types to be specified in variable declarations. A variable type will only be know at run time when the variable refers to some number/string/struct or object. That is, the binding variable/type is dynamic. A variable can refer to values of any type since the variable itself is untyped. See the example below in which b receives initially a string and then a number or array. var a, b; begin a = ?; b = "Hello Guy"; if a <= 0 then b = 3; else { array of three heterogeneous elements } b = #( 1.0, false, "here" ); if a == 0 then b = b + 1; else write( b[1], " ", b[3] ); { 1 } end. In this example, if the ? were replaced by a) -1, there would be a run-time error; b) 0, there would be no error; c) 1, there would not be any error and the program would print 1.0 here Since there is no type, the compiler cannot enforce the operations used with a variable are valid. For example, if ? is replaced by -1, the program tries to apply operation [] on an integer number — b receives 3 and the program tries to evaluate b[1] and b[3]. The result would be a run-time error or, more specifically, a run-time type error. A type error occurs whenever one applies an operation on a value whose type does not support that operation. Static type binding allows the compiler to discovers some or all type errors. For example, in var s : string; begin s = s + 1; ... end the compiler would sign an error in the first statement since type string does not support operation “+” with an integer value. It is easier for a compiler to generate efficient code to a static type binding language than to a dynamically typed one. In the first case, a statement a = b + c; 17
  • 19. proc search(v, n, x) { searchs for x in v from v[0] to v[n-1]. Returns the first i such that v[i] == x or -1 if x is not found in v. } var i; begin i = 0; while i < n do if v[i] == x then return i; else i = i + 1; endif return -1; end Figura 2.2: A polymorphic procedure results in a few machine statements like mov a, b add a, c In a dynamically typed language the compiler would generate code to: 1. test if b and c types support operation +; 2. select the appropriate operation to execute. There are at least three different operations that can be used in this example: + between integers, reals, and strings (to concatenation). 3. execute the operation. Clearly the first generated code is much faster than the second one. Although dynamically typed languages are unsafe and produce code difficult to optimize (generally very slow), people continue to use them. Why ? First these languages free the programmer from the burden of having to specify types, which makes programming lighter. On may think more abstractly since one of the programming concepts (types at compile time) has been eliminated.1 Other consequence of this more abstract programming is polymorphism. A polymorphic variable can assume more than one type during its lifetime. So, any untyped variable is polymorphic. Poly- morphic variables lead to polymorphic procedures which have at least one polymorphic parameter. A polymorphic value has more than one type, as NULL in C++, null in Java or nil in other languages. Polymorphic procedures are important because their code can be reused with parameters of more than one type. For example, procedure search of Figure 2.2 can be used with arrays of several types as integer, boolean, string, or any other that support the operation “==”. search can even be used with heterogeneous arrays in which the array elements have different types. Then the code of search was made just one time and reused by several types. In dynamic typed languages polymorphism comes naturally without any further effort from the programmer to create reusable code since any variable is polymorphic. 1 Although it is pretty common programmers to introduce types in their variable names as aPerson, aList, and anArray. 18
  • 20. 2.1.2 Strong and Static Typing A strongly typed language prevents any run-time type error from happening. Then a type error is caught by the compiler or at run time before it happens. For example in a language with static type binding, the compiler would sign an error in a = p*3; if p is a pointer. In a dynamically typed language this error would be pointed at run time before it happens. Languages with dynamic typing are naturally strongly typed since the appropriate operation to be used is chosen at run time and the run-time system may not find this operations resulting in a run-time type error. In a non-strongly typed language the statement above would be executed producing a nonsense result. Some authors consider a language strongly typed only if type errors are caught at compile time. This appear not to be the dominant opinion among programming language people. Therefore we will consider in this book that a language is strongly typed if it requires type errors to be discovered at compile or run time. A language is statically typed if all type errors can be caught at compile time. Then all statically typed languages are also strongly typed. In general languages in which variables are declared with a type (Pascal, C++, Java) are statically typed. The type error in the code var i : integer; begin i = "hello"; ... end would be discovered at compile time. The definitions of static and strong typing are not employed rigorously. For example, C++ is considered a statically typed language although a C++ program may have type errors that are not discovered even at run time. For example, the code int *p; char *s = "Hi"; p = (int *) s; *p = *p*3; declares a pointer p to int and a pointer s to char, assigns s to p through a type conversion, and multiply by 3 a memory area reserved to a string. In this last statement there is a type error since a memory area is used with an operation that does not belong to its type (string or char *). 2.2 Block Structure and Scope Algol-like languages (like Pascal, Modula, and Ada) support the declaration of procedures inside other procedures as shown in Figure 2.3 in which Q is inside P. Block structure was devised to restrict the scope of procedures. Scope of an identifier is the program region in which it is defined. That is, the region in which it can potentially be used. In general, the scope of a global variable such as max of Figure 2.3 extends from the point of declaration to the end of the file. Then max can be used in P and Q. The scope of a local variable of a procedure X is the point where it is declared to the end of X. This same rule applies to local procedures. Then, the scope of variable i of P is from 2 to 6 and the scope of Q is from 3 to 6. Variable k can be used only inside Q. 19
  • 21. var max : integer; { 1 } proc P( n : integer ) var i : integer; { 2 } proc Q() { 3 } var k : integer; { 4 } begin { Q } ... end { Q } { 5 } begin { P } ... end { P } { 6 } ... { 7 - end of file } Figura 2.3: An example of procedure nesting A procedure may declare a local variable or procedure with name equal to an outer identifier. For example, one could change the name of variable k of Q to max. Inside Q, max would mean the local variable max instead of the global max. Nearest declared identifiers have precedence over outer scope ones. Visibility of an identifier is the program region where it is visible. In the example above with k changed to max, the scope of the global max is from 1 to 7. However, max is not visible inside Q. Lifetime of a variable is the time interval during the program execution in which memory was allocated to the variable. Then, the lifetime of a global variable is all program execution whereas a variable local to a procedure is created when the procedure is called and destroyed when it returns. According to the definitions above, scope and visibility are determined at compile time and lifetime at run time. However, in some languages the scope of a variable varies dynamically at run time. When a procedure is called, its local variables are created in a stack. If the procedure calls another ones, these can access its local variables since they continue to be in the stack. The local variables become invisible only when the procedure returns. This strange concept is called dynamic scope. An example of it is shown in Figure 2.4. The program begins its execution in the main procedure where Q is called after statement “max = 5”. At the end of Q, after the if statement, procedure P is called resulting in the call stack of Figure 2.5 (a). Inside P the variables max, n, and i are visible. Then it is fine p use n in the for statement. After P returns and Q returns the execution continues in the main procedure and P is called at point { 1 }, resulting in the call stack of Figure 2.5 (b). Now P tries to use the undefined variable n resulting in a run-time error. Note that could have been an error even if n existed at that point because p might have tried to use n as if it had a different type as string. Dynamic scope is intrinsically unsafe as shown in the previous example. It is dangerous to use a variable that is not in the static scope such as n in P. So this should never be done. But then dynamic scope is unuseful since it degenerates into static scope ! Why then do people use it ? By our knowledge it is used only because it makes it easier to build interpreters to the language: variables are only checked at run time using an already existing stack of local variables. Now we return to block structures. An example is in Figure 2.6 whose correspondent tree is in Figure 2.7. In this tree, an arrow from A to B means B is inside A. The variables visible in a procedure X are the global variables plus all local variables of the procedures in the path from X to the root of 20
  • 22. proc P() var i : integer; begin for i = 1 to n do write(i); end procedure Q() var n : integer; begin if max < 10 then n = max; else n = 10; endif P(); end; proc main() { main procedure. Program execution starts here } var max : integer; begin max = 5; Q(); P(); { 1 } end. Figura 2.4: Example of dynamic scope P i Q n P i main max main max (a) (b) Figura 2.5: Call stack 21
  • 23. var max : integer; proc A() var min : integer; proc B() : char var g : integer; proc C() var ok : boolean; begin { C } ... end { C } begin { B } ... end { B } proc D() var j : integer; begin { D } ... end { D } begin { A } ... end { A } Figura 2.6: Nesting of procedures ¨ A © t t ¨) t ¨ ” B © D © ¨ c C© Figura 2.7: Nesting tree 22
  • 24. the tree. Then, the variables visible in C are the global ones plus those of C itself, B, and A. The objective of block structure is to create abstraction levels by hinding procedures from the rest of the program. Albeit this noble goal, there are several problems with block structures: • it requires extra work to find the correct procedure nesting; • it becomes difficult to read the source code since the procedure header with the local variables and formal parameters is kept far from the procedure body; • the lowest level procedures are, in general, the deepest nested ones. After all they are the building blocks for the procedures higher in the tree. This is a problem because low level procedures are, in general, the most generic ones. Probably the deepest nested procedures will be needed in other parts of the program. And it may be not easy to move them to outside all nestings because they may use local variables of the procedures higher in the tree. For example, procedure C in Figure 2.6 may use variable g of B and min of A. When moving C outside all nestings, variables g and min must be introduced as parameters. This requires modifying all calls to C in B. • it becomes confusing to discover the visible variables of a procedure. These variables are clearly identified with the help of a tree like that of Figure 2.7 but difficult to identify by reading the source code. 2.3 Modules A module or package2 is a set of resources such as procedures, types, constants, classes3 , and variables. Each resource of a module can be public or private. Public resources can be used by other modules that import this one through a special command discussed next. Private resources are only visible inside the module itself. Modules are declared as shown in Figure 2.8 which presents module Window that: • imports modules WinData and StringHandling; • defines in the public section two procedures (makeWindow and closeAll) and two constants (maxWidth and maxHeight). By importing module StringHandling, all resources defined in the public section of this module become visible in module Window. Then type String defined in the public section of StringHandling can be used in the header of procedure makeWindow. Only procedure headers (without the body) are put in the public section since to use a procedure one needs only its name and parameter/return value types. Suppose variable Max is defined in module WinData and in StringHandling. Now an ambiguity arises in the use of Max in the module Window procedures. It could refer to variable Max of both modules. The language can consider an error to import two modules that have resources with the same name. Or it may require the use of a module qualification before Max such as WinData.Max or StringHandling.Max The language may also require this syntax for all imported resources, not only the ambiguous ones. 2 Its Ada and Java names. 3 To be seen in a chapter about object-oriented programming. 23
  • 25. module Window import WinData, StringHandling; public: { only procedure headers appear in the public section } proc makeWindow( w : WindowData; name : String ); const maxWidth = 1024, maxHeight = 800; proc closeAll(); private: const max = 16; var AllWindows : array[1..max] of WindowData; proc makeWindow( w : WindowData; name : String ) var i : integer; begin ... end proc closeAll() var i, k : integer; begin ... end Figura 2.8: A module Window 24
  • 26. This would make typing difficult but the program clearer since the programmer would know immedi- ately the module each resource comes from. However in general the programmer knows from where each resource comes from, mainly in small programs and when using known libraries. In this case a verbose syntax like “WinData.Max” is less legible than a lighter one like “Max”. Modules have several important features, described next. • When a module B imports module A, B can use only resources of the public section of A. That means any changes in the private part of A will not affect B in any way. In some language, B not even needs to be recompiled when the private part of A in changed. This feature is used to implement data abstraction, also called information hiding. Some languages (e.g. Ada) allow declarations like type Window is private; proc drawWindow( w : Window ); in the public module section. Window is a type defined in the private section although declared as public. To define a variable is to give an order to the compiler generate code to allocate memory for the variable at run time. So definition requires all information about the variable to be specified. To declare a variable is to give its name and type so that type checking is possible. Modules importing module this module Window can declare variables of type Window but they cannot apply any operation on Window variables. For example, if Window is a record (C struct or C++ class), these modules cannot select a field of a variable of this type (like “win.icon”). • A program can (should) be divided in modules that can be separately compiled and understood. One may only know the public section of a module in order to use it. Then modules work as abstraction mechanisms reducing the program complexity. A well-designed module restricts the dependency from other modules to a minimum. Then it can largely be understood independently from the rest of the program. 2.4 Exceptions Exceptions are constructs that allow the programmer to separate error signaling from error treatment, thus making programs more readable. Usually there should be a test after each statement that can result in error. This test should verify if the statement was successful. It it was not, code to try to correct the error should be executed. If correction is not possible the program should be terminated. As an example, the code of Figure 2.9 builds a window from several dynamically allocated parts. After each allocation there is a test to verify if it was successful. “A.new()” allocates memory for an instance of type A. The code is populated with a lot of if’s to test the success of memory allocation. These if’s do not belong to the functional part of the algorithm; that is, that part that fulfills the algorithm specification. The if’s are a sort of auxiliary part that should best be kept separate from the main algorithm body. This can be achieved with exceptions as shown by Figure 2.10 which presents another implementation of procedure buildWindow. Each call to new either returns the required memory or raises an exception. An exception is raised by a statement like raises Storage_Error: that transfers the execution to a when clause put after keyword exception — see the example. In other words, when there is not enough free memory procedure new, instead of returning nil (or NULL), raises exception Storage_Error. The control is transferred to the line following 25
  • 27. proc buildWindow() : Window var w : Window; op : Option; b : Button; begin w = Window.new(); if w == nil then return nil; endif init( w, x1, y1, x2, y2 ); op = Option.new(); if op == nil then return nil; endif init( op, Are you sure ?, xop, yop ); setOption(w, op); b = Button.new(); if b == nil then return nil; endif init(b, ...); setButton(w, b); b = Button.new(); if b == nil then return nil; endif init(b, ...); setButton(w, b); b = Button.new(); if b == nil then return nil; endif init(b, ...); setButton(w, b); end Figura 2.9: Code to build a window 26
  • 28. proc buildWindow() : Window var w : Window; op : Option; b : Button; begin w = Window.new(); init( w, x1, y1, x2, y2 ); op = Option.new(); init( op, Are you sure ?, xop, yop ); setOption(w, op); b = Button.new(); init(b, ...); setButton(w, b); b = Button.new(); init(b, ...); setButton(w, b); b = Button.new(); init(b, ...); setButton(w, b); exception when Storage_Error: { failure in memory allocations: not enough free memory } return nil; when Bad_Data: { invalid arguments to procedures } return nil; end Figura 2.10: Use of exceptions to catch memory allocation errors 27
  • 29. when Storage_Error: where the execution continues. The procedure then returns nil. Then to raise an exception is much like to make an unconditional goto to a label given by the when clause. The difference from a goto is that exceptions can be raised by a procedure and catch (or treated) by any other that is in the procedure call stack. In the example of Figure 2.10, procedure buildWindow could also handle exception Bad_Data. This exception may be raised by procedures init, setOption, setButton, or by any procedure called by these ones. In the general case, a procedure P1 calls procedure P2 that calls P3 and so forth until Pn−1 calls Pn . If Pn raises an exception Storage_Error, the run-time system (RTS) begins to search for a when clause when Storage_Error: in the procedures stacked, from Pn to P1 . If one of such clauses is found, the statements following it are executed. If no such when clause is found, the program is terminated. Another example of exceptions is in Figure 2.11 in which main is the procedure where the program execution begins. The second statement of main calls q that calls r. Depending on the value of n, r raises exception Underflow or Overflow. If Underflow is raised, the control jumps to the “when Underflow” clause of q. If Overflow is raised, r and q are skipped and the execution continues in the “when Overflow” clause of main. The fourth statement of p calls r. If r raises Underflow, a run-time error is signaled and the program is terminated. The stack of called procedures contains only main and r, and none of these have a “when Underflow”clause. Note it does not matter q have this clause: it was not in the call stack when the exception was raised. Then an exception can be raised and treated in some execution paths and not treated in others. This results in unsafe programs that can terminate unexpectedly. There are other problems with exceptions: • they make the program difficult to understand. An exception is like a goto to a label unknown at compile time. The label will only be known at run time and it may vary from execution to execution; • they require a closer cooperation among the modules of a program weakening their reusability. A module should be aware of internal details of other modules it uses because it needs to know which exceptions they can raise. That means a module should know something about the internal flow of execution of other modules breaking encapsulation; • they can leave the system in an inconsistent state because some procedures are not terminated. Then some data structures may have non-initialized pointers, files may not have been closed, and user interactions could have been suddenly interrupted. Although exceptions are criticized because of these problems, they are supported by major lan- guages as Java, Ada, Eiffel, and C++. Exceptions have two main features: • they separate the functional part of the program (that fulfill its specification) from error handling. This can reduce the program complexity because parts that perform different functions are kept separately; • they save the programmer’s time that would be used to put error codes in return values of procedures and to test for procedure failure after each call. 28
  • 30. proc r( n : integer ) var a : A; begin if n 0 then raises Underflow; endif if n Max then raises Overflow; endif ... end { r } proc q( n : integer ) begin r(n); ... exception when Underflow : { terminate the program } write(Underflow); exit(1); end { q } proc main() { program starts here } var n : integer; begin read(n); q(n); read(n); r(n): exception when Overflow : { terminate the program } write(Overflow); exit(1); end { main } Figura 2.11: An example with exceptions 29
  • 31. proc r( n : integer ) raises (Underflow, Overflow) var a : A; begin if n 0 then raises Underflow; endif if n Max then raises Overflow; endif { no exception is raised in following statements of r represented by ... } ... end { r } proc q( n : integer ) raises (Overflow) begin r(n); ... exception when Underflow : { terminate the program } write(Error: underflow); exit(1); end { q } proc main() { program starts here } var n : integer; begin read(n); q(n); read(n); r(n): exception when Overflow : { terminate the program } write(Error: overflow); exit(1); end { main } Figura 2.12: An example with safe exceptions 30
  • 32. Besides that, it may be faster to use exception than to test the return values by hand. Some languages support safe exceptions: the language guarantees at compile time that all excep- tions raised at runtime will be caught by when clauses. These languages require that a procedure either treats the exceptions it may raise or specifies these exceptions in its interface. For example, Figure 2.12 shows the example of Figure 2.11 in a language with safe exceptions. After the header of each procedure there should appear a declaration raises (E1, E2, ...) with the exceptions E1, E2, ... that the procedure can raise. For example, r can raises exceptions Underflow and Overflow. The exceptions a procedure can raise are those • the other procedures it calls can raise; • it can raise itself; • that are not treated by any when clause at its end. Procedure q calls r and therefore could raise Underflow and Overflow. Since q has a clause “when Underflow”, it can only raise exception Overflow. Procedure main calls q and r and therefore it can raise both Overflow and Underflow. Since main has only a when Overflow clause, it could raise an Underflow exception that would not be catch by any when clause. As the language is exception-safe, the compiler would issue an error in procedure main. To correct the program a “when Underflow” clause should be added to this procedure. An alternative to using raise-when constructs is to use the try blocks as shown in the following example. try begin read(n); q(n); end catch ( Underflow ) begin write(Underflow !); exit(1); end catch( Overflow ) begin write(Overflow !); exit(1); end The commands that can raise an exception are put in the try block delimited by begin-end. The catch clauses are the equivalent of when clauses. When using when clauses, there is no way of knowing which procedure command raised the excep- tion. try blocks are best fitted in this case since they allow to test for exceptions in several points of the procedure. 2.5 Garbage Collection Some languages do not allow the explicit deallocation of dynamic allocated memory by the program. That is, there is no command or procedure like free of C, dispose of Pascal, or delete of C++. These 31
  • 33. languages are said to support garbage collection. This concept applies to languages with explicit or implicit dynamic memory allocation. Explicit allocation occurs in languages as Java, Ada, Smalltalk, C++, Pascal, and Modula-2 that have commands/functions to allocate dynamic memory as new or malloc. Implicit allocation occurs in Prolog or Lisp-like languages in which dynamic structures as lists shrink and grow automatically when necessary. When a list grows, new memory is automatically allocated to it by the run-time system. The procedure that follows illustrates the basic point of garbage collection. proc main() { do nothing --- just an example } var p, t : ^integer; begin p = integer.new(); { 1 } t = p; { 2 } p = nil; { 3 } t = nil; { 4 } end The type “^integer”is “pointer to integer” and memory for an integer value is allocated by “integer.new()”. A memory block allocated by new() will only be freed by the garbage collector when no pointer points to it. In this example, two pointers will point to the allocated memory after executing statement 2. After statement 3, one pointer, t, will point to the memory. After statement 4, there is no reference to the allocated memory and therefore it will never be used by the program again. From this point hereafter, the memory can be freed by the garbage collector. The garbage collector is called to free unused memory from time to time or when the free available memory drops below a limit. A simple garbage collector (GC) works with the set of all global variables and all local variables/parameters of the procedures of the call stack. We will call this set Live. It contains all variables that can be used by the program at the point the GC is called. All memory blocks referenced by the pointers in Live can be used by the program and should not be deallocated. This memory may have pointers to other memory blocks and these should not be freed either because they can be referenced indirectly by the variables in Live. Extending this reasoning, no memory referenced direct or indirectly by variables in Live can be deallocated. This requirement suggests the garbage collector could work as follows. 1. First it finds and marks all memory blocks that can be reached from the set Live following pointers. 2. Then it frees all unmarked blocks since these will never be used by the program. There are very strong reasons to use garbage collection. They become clear by examining the problems, described in the following items, that occur when memory deallocation is explicitly made by the programmer. • A module may free a memory block still in use by other modules. There could be two live pointers p and t pointing to the same memory block and the program may execute “dispose(p)” or “free(p)” to liberate the block pointed to by p. When the memory block is accessed using t, there may be a serious error. Either the block may have been allocated by another call to new or the dispose/free procedure may have damaged the memory block by using some parts of it as pointers to a linked list of free blocks. 32
  • 34. • The program may have memory leaks. That is, there could be memory blocks that are not referenced by the program and that were not freed with dispose/delete/free. These blocks will only be freed at the end of the program by the operating system. • Complex data structures make it difficult to decide when a memory block can safely be deal- located. The programmer has to foresee all possible behaviors of the program at run time to decide when to deallocate a block. If a block is referenced by two pointers of the same data structure,4 the program should take care of not deallocating the block twice when deallocating the data structure. This induces the programmer to build her own garbage collector. Program- mer’s made GC are known to be unsafe and slow when compared with the garbage collectors provided by compilers. • Different program modules should closely cooperate in order to decide when deallocating memory [4]. This makes the modules tightly coupled thus reducing their reusability. Notice this problem only happens when dynamic memory is passed by procedure parameters from one module to another or when using global variables to refer to dynamic memory. This item says explicit memory deallocation breaks encapsulation. One module should know not only the interface of other modules but also how their data is structured and how their procedures work. • Different deallocation strategies may be used by the modules of a program or the libraries used by it [4]. For example, the operation deleteAll of a Queue data structure5 could remove all queue elements and free their memory. In another data structures such as Stack the operation clearAll similar to deleteAll of Queue could remove all stack elements but not free the memory allocated to them. The use of different strategies in the same program such as when to deallocate memory reduces the program legibility thus increasing errors caused by dynamic memory. • Polymorphism makes the execution flow difficult to foresee. In an object-oriented language the compiler or the programmer does not know which procedure m (called method) will be called at run time in a statement like a.m(b) There may be several methods called m in the program and which method m is called is determined only at run time according to the value of a. Then the programmer does not know if pointer b will be stored by m in some non-local variable or if m will delete it. In fact, the programmer that wrote this statement may not even know all the methods m that may be executed at run time. For short, with polymorphism it becomes difficult to understand the execution flow and therefore it becomes harder to decide when it is safe to deallocate a memory block [4]. There are also arguments against garbage collection: • it is slow; • it causes long pauses during user interaction; • it cannot be used in real-time systems in which there should be possible to know at compile time how long it will take to execute a piece of code at run time; 4 Not necessaryly two pointers of the same struct or record. There may be dozen of structs/records with a lot of pointers linking them in a single composit structure. 5 Queue could be a class of an object-oriented language or an abstract data type implemented with procedures. 33
  • 35. • it makes difficult to use two languages in the same program. To understand this point, suppose a program was build using code of two languages: Eiffel that supports garbage collection and C++ that does not. A memory block allocated in the Eiffel code may be passed as parameter to the C++ code that may keep a pointer to it. If at some moment no pointer in the Eiffel code refers to this memory block, it may be freed even if the C++ pointer refer to it. There is no way in which the Eiffel garbage collector can know about the C++ pointer. All of these problems but the last have been solved or ameliorated. Garbage collectors are much faster today than they were in the past. They usually spend 10 to 30% of the total program execution time in object-oriented languages and from 20 to 40% in Lisp programs. When using complex data structures, garbage collection can be even faster than manual deallocation. Research in garbage collection has produced collectors for a large variety of tastes. For example, incremental GC do not produce long delays in the normal processing and there are even collectors used in real-time systems. 34
  • 36. Cap´ ıtulo 3 Linguagens Orientadas a Objeto 3.1 Introdu¸˜o ca Orienta¸˜o a objetos utiliza classes como mecanismo b´sico de estrutura¸˜o de programas. Uma classe ca a ca ´ um tipo composto de vari´veis (como records e structs) e procedimentos. Assim, uma classe ´ uma e a e extens˜o de records/structs com a inclus˜o de comportamento representado por procedimentos. Um a a exemplo de declara¸˜o de classe est´ na Figura 3.1 que declara uma classe Store com procedimentos ca a get e put e uma vari´vel n. Na terminologia de orienta¸˜o a objetos, get e put s˜o m´todos e n ´ a ca a e e uma vari´vel de instˆncia. a a Uma vari´vel da classe Store, declarada como a var s : Store; ´ tratata como se fosse um ponteiro na linguagem S. Assim, deve ser alocada mem´ria para s com a e o instru¸˜o ca s = Store.new(); Esta mem´ria ´ um objeto da classe Store. Um objeto ´ o valor correspondente a uma classe assim o e e como 3 ´ um valor do tipo int e “Alo !” ´ um valor do tipo string. Objetos s´ existem em execu¸˜o e e o ca e classes s´ existem em tempo de compila¸˜o, o ca 1 pois s˜o tipos. Classes s˜o esqueletos dos quais s˜o a a a criados objetos e vari´veis referem-se a objetos. Ent˜o o objeto referenciado por s possui uma vari´vel a a a n e dois m´todos. e Os campos de um record de Pascal ou struct de C s˜o manipulados usando-se “.” como em a “pessoa.nome” ou “produto.preco”. Objetos s˜o manipulados da mesma forma: a s.put(5); i = s.get(); Contudo, fora da classe Store apenas os m´todos da parte p´blica s˜o vis´ e u a ´ ıveis. E ent˜o ilegal fazer a s.n = 5; j´ que n pertence ` parte privada da classe. A parte p´blica ´ composta por todos os m´todos e a a u e e vari´veis de instˆncia que se seguem ` palavra public. A parte p´blica termina em uma declara¸˜o a a a u ca “private:” ou no fim da classe (endclass). Em S, apenas m´todos podem estar na parte p´blica de e u uma classe. Alocando dois objetos, como em var s, t : Store; begin s = Store.new(); t = Store.new(); 1 Pelo menos em S. 35
  • 37. class Store public: proc get() : integer begin return n; end proc put( pn : integer ) begin n = pn; end private: var n : integer; endclass Figura 3.1: Class Store s.put(5); t.put(12); write( s.get(), , t.get() ); end s˜o alocados espa¸os para duas vari´veis de instˆncia n, uma para cada objeto. Em “s.put(5)”, o a c a a m´todo put ´ chamado e o uso de n na instru¸˜o e e ca n = pn de put refere-se a “s.n”. Um m´todo s´ ´ invocado por meio de um objeto. Assim, as referˆncias e oe e a vari´veis de instˆncia em um m´todo referem-se `s vari´veis de instˆncia deste objeto. Afinal, a a e a a a os m´todos s˜o feitos para manipular os dados do objeto, adicionando comportamento ao que seria e a uma estrutura composta apenas por dados. Na nomenclatura de orienta¸˜o a objetos, uma instru¸˜o ca ca “s.put(5)” ´ o envio da mensagem “put(5)” ao objeto referenciado por s (ou objeto s para simpli- e ficar). 3.2 Prote¸˜o de Informa¸˜o ca ca Prote¸˜o de informa¸˜o ´ um mecanismo que impede o acesso direto ` implementa¸˜o (estruturas de ca ca e a ca dados) de uma classe. As vari´veis de instˆncia s´ s˜o manipuladas por meio dos m´todos da classe. a a o a e Na linguagem S, todas as vari´veis de instˆncia devem ser declaradas como privadas, impedindo a sua a a manipula¸˜o fora dos m´todos da classe. Para exemplificar este conceito, usaremos a classe Pilha da ca e Figura 3.2. Uma pilha ´ uma estrutura de dados onde o ultimo elemento inserido, com empilhe, ´ e ´ e sempre o primeiro a ser removido, com desempilhe. Esta estrutura espelha o que geralmente acontece com uma pilha de objetos quaisquer. Esta pilha poderia ser utilizada como no programa abaixo. proc main() var p, q : Pilha; begin p = Pilha.new(); p.crie(); // despreza o valor de retorno p.empilhe(1); 36
  • 38. const Max = 100; class Pilha private: var topo : integer; { vetor de inteiros } vet : array(integer)[Max]; public: proc crie() : boolean begin topo = -1; return true; end proc empilhe( elem : integer ) : boolean begin if topo = Max - 1 then return false; else topo = topo + 1; vet[topo] = elem; return true; endif end proc desempilhe() : integer begin if topo 0 then return -1; else elem = vet[topo]; topo = topo - 1; return elem; endif end proc vazia() : boolean begin return topo 0; end endclass Figura 3.2: Uma pilha em S 37
  • 39. p.empilhe(2); p.empilhe(3); while not p.vazia() do write( p.desempilhe() ); q = Pilha.new(); q.crie(); q.empilhe(10); if not p.empilhe(20) then erro(); endif end O programador que usa Pilha s´ pode manipul´-la por meio de seus m´todos, sendo um erro de o a e compila¸˜o o acesso direto `s suas vari´veis de instˆncia: ca a a a p.topo = p.topo + 1; { erro de compilacao } p.vet[p.topo] = 1; { erro de compilacao } A prote¸˜o de informa¸˜o possui trˆs caracter´ ca ca e ısticas principais: 1. torna mais f´cil a modifica¸˜o de representa¸˜o da classe, isto ´, a estrutura de dados usada a ca ca e para a sua implementa¸˜o. No caso de Pilha, a implementa¸˜o ´ um vetor (vet) e um n´mero ca ca e u inteiro (topo). Suponha que o projetista de Pilha mude a estrutura de dados para uma lista encadeada, reti- rando o vetor vet e a vari´vel topo e resultando na classe da Figure 3.3. a O que foi modificado foi o c´digo dos m´todos (veja acima), n˜o o prot´tipo deles. Assim, todo o o e a o c´digo do procedimento main visto anteriormente n˜o ser´ afetado. Por outro lado, se o usu´rio o a a a tivesse declarado vet e topo como p´blicos e usado u p.topo = p.topo + 1; p.vet[p.topo] = 1 para empilhar 1, haveria um erro de compila¸˜o com a nova representa¸˜o de Pilha, pois esta ca ca n˜o possui vetor vet. E o campo topo ´ um ponteiro, n˜o mais um inteiro; a e a 2. o acesso aos dados de Pilha (vet e topo) por m´todos tornam a programa¸˜o de mais alto e ca n´ ıvel, mais abstrata. Lembrando, abstra¸˜o ´ o processo de desprezar detalhes irrelevantes para ca e o nosso objetivo, concentrando-se apenas no que nos interessa. Nesse caso, a instru¸˜o ca p.empilhe(1) ´ mais abstrata do que e p.topo = p.topo + 1; p.vet[p.topo] = 1; porque ela despreza detalhes irrelevantes para quem quer empilhar um n´mero (1), como que a u Pilha ´ representada como um vetor, que esse vetor ´ vet, que p.topo ´ o topo da pilha, que e e e p.topo ´ inicialmente -1, etc; e 3. os m´todos usados para manipular os dados (crie, empilhe, desempilhe, vazia) conferem a e utiliza¸˜o adequada dos dados. Por exemplo, “p.empilhe(1)” confere se ainda h´ espa¸o na ca a c pilha, enquanto que em nas duas instru¸˜es alternativas mostradas acima o usu´rio se esqueceu co a 38
  • 40. class Pilha private: var topo : Elem; public: proc crie() : boolean begin topo = nil; return true; end proc empilhe( elem : integer ) : boolean var w : Elem; begin w = Elem.new(); if w == nil then return false; else w.setNum(elem); w.setSuc(topo); topo = w; return true; endif end proc desempilhe() : integer var w : Elem; elem : integer; begin if topo == nil then return -1; else elem = topo.getNum(); w = topo; topo = topo.getSuc(); delete w; return elem; endif end proc vazia() : boolean begin return topo == nil; end endclass Figura 3.3: Uma classe Pilha implementada com uma lista 39
  • 41. class A public: proc put( pn : integer ) begin n = pn; end proc get() : integer begin return n; end private: var n : integer; endclass class B subclassOf A public: proc imp() begin write( get() ); end endclass Figura 3.4: A classe B herda da classe A disto. Resumindo, ´ mais seguro usar Prote¸˜o de Informa¸˜o porque os dados s˜o protegidos e ca ca a pelos m´todos. e 3.3 Heran¸a c Heran¸a ´ um mecanismo que permite a uma classe B herdar os m´todos e vari´veis de instˆncia de c e e a a uma classe A. Tudo se passa como se em B tivessem sido definidos os m´todos e vari´veis de instˆncia e a a de A. A heran¸a de A por B ´ feita com a palavra chave subclassOf como mostrado na Figura 3.4. c e A classe B possui todos os m´todos definidos em A mais aqueles definidos em seu corpo: e proc put( pn : integer ) proc get() : integer proc imp() Assim, podemos utilizar todos estes m´todos com objetos de B: e proc main() var b : B; begin b = B.new(); b.put(12); // invoca A::put b.imp(); // invoca B::imp write( b.get() ); // invoca A::get end A::put ´ o m´todo put da classe A. A classe B ´ chamada de “subclasse de A” e A ´ a “superclasse de e e e e 40
  • 42. Pessoa   k             Trabalhador Estudante   s d   d   d   d FuncPublico Professor Figura 3.5: Hierarquia da classe Pessoa B”.2 O m´todo B::imp possui uma chamada para um m´todo get. O m´todo invocado ser´ A::get. e e e a Esta chamada poderia ser escrita como “self.get()” pois self, dentro de um m´todo, refere-se ao e objeto que recebeu a mensagem que causou a execu¸˜o do m´todo. Assim, o envio de mensagem ca e “b.put(5)” causa a execu¸˜o do m´todo A::put e conseq¨entemente da atribui¸˜o “n = pn”. O “n” ca e u ca refere-se a “b.n” e poder´ ıamos ter escrito esta atribui¸˜o como “self.n = pn”. self ´ o objeto que ca e recebeu a mensagem, b. A classe B pode redefinir m´todos herdados de A: e class B subclassOf A public: proc get() : integer begin return super.get() + 1; end proc imp() begin write( get() ); end endclass “super.get()” invoca o m´todo get da superclasse de B, que ´ A. Na chamada a get em imp, o e e m´todo get a ser usado ´ o mais pr´ximo poss´ na hierarquia de classes , que ´ B::get. e e o ıvel e Heran¸a ´ utilizada para expressar relacionamentos do tipo “´ um”. Por exemplo, um estudante ´ c e e e uma pessoa, um funcion´rio p´blico ´ um trabalhador, um professor ´ um trabalhador, um trabalhador a u e e ´ uma pessoa. Estes relacionamentos s˜o mostrados na hierarquia da Figura 3.5, na qual a heran¸a de e a c A por B ´ representada atrav´s de uma seta de B para A. A subclasse sempre aparecer´ mais embaixo e e a nas figuras. Uma subclasse ´ sempre mais espec´ e ıfica do que a sua superclasse. Assim, um trabalhador ´ mais e espec´ ıfico do que uma pessoa porque todo trabalhador ´ uma pessoa, mas o contr´rio nem sempre e a ´ verdadeiro. Se tiv´ssemos feito Pessoa herdar Trabalhador, haveria um erro l´gico no programa, e e o mesmo se n˜o houvesse nenhum erro de compila¸˜o. a ca Considere agora a hierarquia de classes representando figuras das Figuras 3.6 e 3.7. Se a classe Circulo precisar utilizar as vari´veis x e y herdadas de Figura, ela dever´ chamar os m´todos getX() a a e 2 Na terminologia usualmente empregada em C++, A ´ a “classe base” e B a “classe derivada”. e 41
  • 43. class Figura public: proc set_xy( px, py : integer ) begin x = px; y = py; end proc imp() begin write( Centro(, x, , , y, ) ); end proc getX() : integer begin return x; end proc getY() : integer begin return y; end private: var x, y : integer; endclass Figura 3.6: Classe Figura 42
  • 44. class Circulo subclassOf Figura public: proc init( p_raio : real; x, y : integer ) begin super.set_xy(x, y); raio = p_raio; end proc setRaio( p_raio : real ) begin raio = p_raio; end proc getRaio() : real begin return raio; end proc imp() begin write( raio = , raio ); super.imp(); end proc getArea() : real begin return PI*raio*raio; end private: raio : real; endclass Figura 3.7: Classe Circulo 43
  • 45. e getY() desta classe. Uma subclasse n˜o pode manipular diretamente a parte privada da superclasse. a Se isto fosse permitido, modifica¸˜es na representa¸˜o de uma classe poderiam invalidar as subclasses. co ca Algumas linguagens, como C++ e Eiffel, permitem que uma classe herde de mais de uma super- classe. Esta facilidade causa uma ambig¨idade quando dois m´todos de mesmo nome s˜o herdados de u e a duas superclasses diferentes. Por exemplo, suponha que uma classe JanelaTexto herde de Texto e Janela e que ambas as superclasses definam um m´todo getNome. Que m´todo o envio de mensagem e e “jt.getNome()” dever´ invocar se o tipo de jt for JanelaTexto ? Em C++, h´ duas formas de se a a resolver esta ambig¨idade: u 1. a primeira ´ especificando-se qual superclasse se quer utilizar: e nome = jt.A::getNome() 2. a segunda ´ definir um m´todo getNome em JanelaTexto. e e Em Eiffel o nome do m´todo getNome herdado de Texto ou Janela deve ser renomeado, evitando e assim a colis˜o de nomes. a Uma linguagem em que ´ permitido a uma classe herdar de mais de uma superclasse suporta e heran¸a m´ltipla. Este conceito, aparentemente muito util, n˜o ´ muito utilizado em sistemas reais c u ´ a e e torna os programas mais lentos porque a implementa¸˜o de envio de mensagens ´ diferente do ca e que quando s´ h´ heran¸a simples. Heran¸a m´ltipla pode ser simulada, pelo menos parcialmente, o a c c u declarando-se um objeto da superclasse na subclasse e redirecionando mensagens a este objeto — veja Figura 3.8. H´ ainda outro problema com heran¸a m´ltipla. Considere que as classes B e C herdem da classe A a c u e a classe D herde de B e C, formando um losango. Um objeto da classe D tamb´m ´ um objeto de A, B e e e C. Este objeto deve ter todas as vari´veis de A, B e C. Mas deve o objeto ter um ou dois conjuntos de a dados de A ? Afinal, a classe D herda A por dois caminhos diferentes. Em alguns casos, seria melhor D ter dois conjuntos de dados de A. Em outros, ´ melhor ter apenas um conjunto. Veja estes exemplos: e • a classe Pessoa ´ herdada por Estudante e Atleta, que s˜o herdadas por BolsistaAtleta.3 e a Neste caso, deve-se ter um unico nome em objetos da classe BolsistaAtleta; ´ • a classe Trabalhador ´ herdada por Professor e Gerente, que s˜o herdados por ProfessorGerente.4 e a Neste caso, deve-se ter os dados do trabalhador, como tempo de servi¸o e nome do empregador, c duplicados em objetos de ProfessorGerente. Seria interessante que Trabalhador herdasse de Pessoa. Assim, um objeto de ProfessorGerente teria apenas uma vari´vel para nome e outros a dados b´sicos. a Algumas linguagens optam por uma destas op¸˜es enquanto que outras permitem que se escolha co uma delas no momento da heran¸a. c 3.4 Polimorfismo Se o tipo de uma vari´vel w for uma classe T, a atribui¸˜o a ca w = nil; estar´ correta qualquer que seja T. Isto ´ poss´ a e ıvel porque nil ´ um valor polim´rfico: ele pode ser e o usado onde se espera uma referˆncia para objetos de qualquer classe. Polimorfismo quer dizer faces e m´ltiplas e ´ o que acontece com nil, que possui infinitos tipos. u e 3 O atleta ganha uma bolsa de estudos por ser atleta. 4 O professor trabalha em tempo parcial e tamb´m ´ um gerente. e e 44
  • 46. class B public: proc init() begin a = B.new(); end proc get() : integer begin return a.get(); end proc put( pn : integer ) begin a.put(pn); end proc imp() begin write(get()); end proc getA() begin return a; end private: var a : A; endclass Figura 3.8: Simula¸˜o de heran¸a de A por B ca c 45
  • 47. Em S, uma vari´vel cujo tipo ´ uma classe pode referir-se a objetos de subclasses desta classe. O a e c´digo o proc main() var f : Figura; c : Circulo; begin c = Circulo.new(); c.init( 20.0, 30, 50 ); f = c; f.imp(); end est´ correto. A atribui¸˜o a ca f = c; atribui uma referˆncia para Circulo a uma vari´vel de Figura. e a S permite atribui¸˜es do tipo co Classe = Subclasse como a acima, que ´ e Figura = Circulo Uma vari´vel cujo tipo ´ uma classe A sempre ser´ polim´rfica, pois ela poder´ apontar para objetos a e a o a de A ou qualquer objeto de subclasses de A. Agora, qual m´todo e f.imp() ir´ invocar ? f referencia um objeto de Circulo e, portanto, seria natural que o m´todo invo- a e cado fosse “Circulo::imp”. Contudo, o tipo de f ´ “Figura” e “f.imp()” tamb´m poderia invocar e e Figura::imp. O envio de mensagem “f.imp()” invocar´ o m´todo imp de Circulo. Ser´ feita uma busca em a e a tempo de execu¸˜o por m´todo imp na classe do objeto apontado por f. Se imp n˜o for encontrado ca e a nesta classe, a busca continuar´ na superclasse, superclasse da superclasse e assim por diante. Quando a o m´todo for encontrado, ele ser´ chamado. Sendo a busca feita em tempo de execu¸˜o, ser´ sem- e a ca a pre chamado o m´todo mais adequado ao objeto, isto ´, se f estiver apontando para um c´ e e ırculo, ser´ chamado o m´todo imp de Circulo, se estiver apontando para um retˆngulo, ser´ chamado a e a a Retangulo::imp e assim por diante. A instru¸˜o “f.imp()” causar´ uma busca em tempo de compila¸˜o por m´todo imp na classe ca a ca e declarada de f, que ´ Figura (f ´ declarado como “f : Figura”). Se imp n˜o fosse encontrado l´, e e a a a busca continuaria na superclasse de Figura (se existisse), superclasse da superclasse e assim por diante. Se o compilador n˜o encontrar o m´todo imp, ele sinalizar´ um erro. Isto significa que uma a e a instru¸˜o ca f.setRaio(10); ser´ ilegal mesmo quando tivermos certeza de que f apontar´ em tempo de execu¸˜o para um objeto a a ca de Circulo (que possui m´todo setRaio). A raz˜o para esta restri¸˜o ´ que o compilador n˜o pode e a ca e a garantir que f apontar´ para um objeto que possui m´todo setRaio. A inicializa¸˜o de f pode a e ca depender do fluxo de controle: proc m( i : integer ) var f, aFig : Figura; aCir : Circulo; begin 46
  • 48. aCir = Circulo.new(); aCir.init(20.0, 50, 30); aFig = Figura.new(); aFig.set_xy( 30, 40 ); if i 0 then f = aFig; else f = aCir; endif f.setRaio(10); ... end Se este procedimento fosse legal, f poderia ser inicializado com aFig. Em tempo de execu¸˜o, seria ca feita uma busca por m´todo setRaio na classe Figura e este m´todo n˜o seria encontrado, resultando e e a em um erro de tempo de execu¸˜o com o t´rmino do programa. ca e Como resultado da discuss˜o acima, temos que a f.imp() ser´ v´lido quando imp pertencer ` classe declarada de f ou suas superclasses (se existirem). J´ que f a a a a pode apontar para um objeto de uma subclasse de Figura, podemos garantir que a classe deste objeto possuir´ um m´todo imp em tempo de execu¸˜o ? A resposta ´ “sim”, pois f pode apontar apenas a e ca e para objetos de Figura ou suas subclasses. O compilador garante, ao encontar f.imp() que a classe declarada de f, Figura, possui m´todo imp e, como todas as subclasses herdam os m´todos e e das superclasses, as subclasses de Figura possuir˜o pelo menos o m´todo imp herdado desta classe. a e Assim, f apontar´ para um objeto de Figura ou suas subclasses e este objeto certamente possuir´ um a a m´todo imp. e Polimorfismo ´ fundamental para o reaproveitamento de software. Quando um m´todo aceitar e e como parˆmetro um objeto de Figura, como a proc m( f : Figura ) podemos passar como parˆmetro objetos de qualquer subclasse desta classe. Isto porque uma chamada a m(aCir); envolve uma atribui¸˜o “f = aCir”, que ser´ correta se for da forma ca a Classe = Subclasse Ent˜o, podemos passar como parˆmetro a m objetos de Circulo, Retangulo, etc. N˜o ´ necess´rio a a a e a construir um m´todo m para objetos de cada uma das subclasses de Figura — um mesmo m´todo m e e pode ser utilizado com objetos de todas as subclasses. O c´digo de m ´ reaproveitado por causa do o e polimorfismo. Admitindo que as classes Retangulo e Triangulo existam e s˜o subclasses de Figura, o c´digo a a o seguir mostra mais um exemplo de polimorfismo. ... proc impVet( v : array(Figura)[]; n : integer ) { Imprime cada elemento do vetor v de n elementos. v ´ um vetor de figuras. } e var i : integer; 47
  • 49. begin for i = 0 to n do v[i].imp(); end proc main() var c1 : Circulo; r1, r2 : Retangulo; t1 : Triangulo; vetFig : array(Figura)[]; begin c1 = Circulo.new(); r1 = Retangulo.new(); r2 = Retangulo.new(); t1 = Triangulo.new(); c1.init( 5.0, 80, 30 ); r1.init( 30, 50, 70, 60 ); r2.init( 20, 100, 80, 150 ); t1.init( 10, 18, 30, 20, 40, 25 ); { atribui varios elementos a vetFig } vetFig = ( r1, t1, r2, c1 ); impVet( vetFig, 4 ); end A fun¸˜o impVet percorre o vetor v enviando a mensagem imp a cada um de seus elementos. O m´todo ca e imp executado depender´ da classe do objeto apontado por “v[i]”. a Existe uma outra forma de polimorfismo em C++ que ser´ mostrada acrescentando-se m´todos a e nas classes Figura e Circulo: class Figura public: ... proc desenhe() begin end { vazio } proc apague() begin end proc mova( nx, ny : integer ) begin apague(); x = nx; y = ny; desenhe(); end private: var x, y : integer; endclass class Circulo subclassOf Figura public: { Os ... abaixo sao os metodos de Circulo definidos anteriormente } 48
  • 50. ... proc desenhe() begin { desenha um circulo } ... end proc apague() begin { apagua o circulo } ... end private: var raio : real; endclass Os m´todos desenhe e apague de Figura n˜o fazem nada porque esta classe foi feita para ser e a herdada e n˜o para se criar objetos dela. O m´todo mova apaga o desenho anterior, move a figura e a a e desenha novamente. Como desenhe e apague s˜o vazios em Figura, mova s´ faz sentido se desenhe a o e apague forem redefinidos em subclasses. Em var c : Circulo; begin c = Circulo.new(); c.init( 10.0, 50, 30 ); c.mova( 20, 80 ); ... end o m´todo invocado em “c.mova(20, 80)” ser´ “Figura::mova”. Este m´todo possui um envio de e a e mensagem apague(); que ´ o mesmo que e self.apague(); que envia a mensagem apague ao objeto que recebeu a mensagem mova, que ´ “c”. Ent˜o, a busca por e a m´todo apague ser´ iniciada em Circulo (classe do objeto c), onde Circulo::apague ser´ encontrado e a a e executado. Da mesma forma, a instru¸˜oca desenhe() em Figura::mova invocar´ Circulo::desenhe. a Observando este exemplo, verificamos que n˜o foi necess´rio redefinir o m´todo mova em Circulo — a a e o seu c´digo foi reaproveitado. Se tivermos uma classe Retangulo, subclasse de Figura, precisaremos o de definir apenas desenhe e apague. O m´todo mova ser´ herdado de Figura e funcionar´ corretamente e a a com retˆngulos. Isto ´, o c´digo a e o var r : Retangulo; begin r = Retangulo.new(); r.init( 30, 50, 70, 20 ); r.mova( 100, 120 ); ... end 49
  • 51. invocar´ os m´todos desenhe e apague de Retangulo. a e As redefini¸˜es de apague e desenhe em Circulo causaram altera¸˜es no m´todo mova herdado co co e de Figura, adaptando-o para trabalhar com c´ ırculos. Ou seja, mova foi modificado pela redefini¸˜o de ca outros m´todos em uma subclasse. N˜o foi necess´rio redefinir mova em Circulo adaptando-o para e a a a nova situa¸˜o. Dizemos que o c´digo de mova foi reaproveitado em Circulo. O m´todo mova se ca o e comportar´ de maneira diferente em cada subclasse, apesar de ser definido uma unica vez em Figura. a ´ 3.5 Modelos de Polimorfismo Esta se¸˜o descreve quatro formas de suporte a polimorfismo empregado pelas linguagens Smalltalk, ca POOL-I, Java e C++. Naturalmente, os modelos de polimorfismo descritos nas subse¸˜es seguintes co s˜o abstra¸˜es das linguagens reais e podem apresentar diferen¸as em rela¸˜o a elas. a co c ca 3.5.1 Smalltalk Smalltalk [5] ´ uma linguagem tipada dinamicamente, o que quer dizer que na declara¸˜o de uma e ca vari´vel ou parˆmetro n˜o se coloca o tipo. Durante a execu¸˜o, uma vari´vel ir´ se referir a um a a a ca a a objeto e ter´ o tipo deste objeto. Conseq¨entemente, uma vari´vel pode se referir a objetos de tipos a u a diferentes durante a sua existˆncia. e No exemplo abaixo, ... var a, b; begin a = 1; b = Janela.new(); b.init(a, 5, 20, 30); a = b; a.desenha(); ... end se a instru¸˜o “a.desenha()” for colocada logo ap´s “a = 1”, haver´ o envio da mensagem desenha ca o a a um n´mero inteiro. Como a classe dos inteiros n˜o possui um m´todo desenha, ocorrer´ um erro u a e a de tipos e o programa ser´ abortado. a Considere agora um m´todo e proc m(y) begin y.desenha(); y.move(10, 20); end de uma classe A. Assuma que exista uma classe Janela em Smalltalk, que ´ aquela da Figura 3.9 e sem os tipos das declara¸˜es de vari´veis. Esta classe possui m´todos desenha e move, sendo que este co a e ultimo n˜o causa erros de tipo se os seus dois parˆmetros s˜o n´meros inteiros. ´ a a a u Se um objeto de Janela for passado com parˆmetro a m, como em a a = A.new(); a.m( Janela.new() ); n˜o haver´ erros de tipo dentro deste m´todo. Se a m for passado um objeto de uma subclasse de a a e Janela, tamb´m n˜o haver´ erros de tipo. A raz˜o ´ que uma subclasse possui pelo menos todos os e a a a e 50
  • 52. class Janela private: var x, y : integer; public: proc desenha() begin ... end proc move( novo_x, novo_y : integer ) begin self.x = novo_x; self.y = novo_y; self.desenha(); end proc init( px, py : integer ); begin x = px; y = py; end endclass class JanelaTexto subclassOf Janela ... public: proc desenha() begin ... end endclass Figura 3.9: Classes Janela e JanelaTexto m´todos da superclasse. Assim, se um objeto de Janela sabe responder a todas as mensagens enviadas e a ele dentro de m, um objeto de uma subclasse tamb´m saber´ responder a todas estas mensagens. e a Estamos admitindo que, se move for redefinido em uma subclasse, ele continuar´ aceitando dois inteiros a como parˆmetros sem causar erros de tipo. a De fato, o m´todo m pode aceitar como parˆmetros objetos de qualquer classe que possua m´todos e a e move e desenha tal que move aceita dois inteiros como parˆmetros e desenha n˜o possui parˆmetros. a a a N˜o ´ necess´rio que esta classe herde de Janela. Este sistema de tipos, sem restri¸˜o nenhuma que a e a ca n˜o seja a capacidade dos objetos de responder `s mensagens que lhe s˜o enviadas, possui o maior a a a grau poss´ de polimorfismo. ıvel Se m for codificado como proc m(y, b) begin y.desenha(); y.move(10, 20); if b then y.icon(); endif end o c´digo o a = A.new(); a.m( Janela.new(), false ); n˜o causar´ erro de tipos em tempo de execu¸˜o, pois a mensagem icon n˜o ser´ enviada ao objeto a a ca a a de Janela em execu¸˜o. Se fosse enviada, haveria um erro j´ que a classe Janela n˜o possui m´todo ca a a e 51
  • 53. class JanelaProcesso ... public: proc desenha() begin ... end proc move( nx, ny : integer ) begin ... end proc init( px, py : integer ) begin ... end proc iniciaProcesso() begin ... end proc setProcesso( s : string ) begin ... end endclass Figura 3.10: Uma classe que ´ subtipo de Janela e icon. Em geral, o fluxo de execu¸˜o do programa, controlado por if’s, while’s e outras estruturas, de- ca termina quais mensagens s˜o enviadas para cada vari´vel. E este mesmo fluxo determina a capacidade a a de cada vari´vel de responder a mensagens. Para compreender melhor estes pontos, considere o c´digo a o if b 0 then a = Janela.new(); else a = 1; endif if c 1 then a.desenha(); else a = a + 1; endif O primeiro if determina quais as mensagens a pode responder, que depende da classe do objeto a que a se refere. O segundo if seleciona uma mensagem a ser enviada ` vari´vel a. Em Smalltalk, “+ a a 1” ´ considerado um envio de mensagem. e Ent˜o, o fluxo de execu¸˜o determina a corretude de tipos de um programa em Smalltalk, o que a ca torna os programas muito inseguros. Alguns trechos de c´digo podem revelar um erro de tipos ap´s o o meses de uso. Note que, como ´ imposs´ prever todos os caminhos de execu¸˜o de um programa em e ıvel ca tempo de compila¸˜o, ´ tamb´m imposs´ garantir estaticamente que um programa em Smalltalk ´ ca e e ıvel e corretamente tipado. 3.5.2 POOL-I Esta se¸˜o descreve o modelo das linguagens POOL-I [1] e Green [8] [7]. Como o sistema de tipos ca de Green foi parcialmente baseado no de POOL-I, este modelo ser´ chamado de modelo POOL-I. a Green e POOL-I s˜o linguagens estaticamente tipada, pois todos os erros de tipo s˜o descobertos em a a compila¸˜o. ca Neste modelo, o tipo de uma classe ´ definido como o conjunto das interfaces (assinaturas ou e signatures) de seus m´todos p´blicos. A interface de um m´todo ´ o seu nome, tipo do valor de e u e e retorno (se houver) e tipos de seus parˆmetros formais (o nome dos parˆmetros ´ desprezado). Por a a e 52
  • 54. exemplo, o tipo da classe Janela da Figura 3.9 ´ e { desenha(), move(integer, integer), init(integer, integer)} sendo que { e } s˜o utilizados para delimitar os elementos de um conjunto, como em matem´tica. Um a a tipo U ser´ subtipo de um tipo T se U possuir pelo menos as interfaces que T possui. Isto ´, T ⊂ a e U. Como exemplo, o tipo da classe JanelaProcesso da Figura 3.10 ´ um subtipo do tipo da classe e Janela da Figura 3.9. Como abrevia¸˜o, dizemos que a classe JanelaProcesso ´ subtipo da classe ca e Janela. Quando uma classe B herdar de uma classe A, diremos que B ´ subclasse de A. Neste caso, B herdar´ e a todos os m´todos p´blicos de A, implicando que B ´ subtipo de A.5 Observe que toda subclasse ´ e u e e tamb´m subtipo, mas ´ poss´ existir subtipo que n˜o ´ subclasse — a classe JanelaProcesso da e e ıvel a e Figura 3.10 ´ subtipo mas n˜o subclasse de Janela. e a Neste modelo, uma atribui¸˜oca t = s estar´ correta se a classe declarada de s for subtipo da classe declarada de t. As atribui¸˜es do tipo a co Tipo = SubTipo; s˜o v´lidas. a a Esta restri¸˜o permite a detec¸˜o de todos os erros de tipo em tempo de compila¸˜o, por duas ca ca ca raz˜es: o • Em um envio de mensagem t.m(b1 , b2 , ... bn ) o compilador confere se a classe com que t foi declarada possui um m´todo chamado m cujos e parˆmetros formais possuem tipos T1 , T2 , ... Tn tal que o tipo de bi ´ subtipo de Ti , 1 ≤ i ≤ n. a e A regra “Tipo = Subtipo” ´ obedecida tamb´m em passagem de parˆmetros. e e a • Ao executar este envio de mensagem, ´ poss´ que t n˜o se refira a um objeto de sua classe, e ıvel a mas de um subtipo do tipo da sua classe, por causa das atribui¸˜es do tipo Tipo = SubTipo, co como t = s. De qualquer forma, n˜o haver´ erro de execu¸˜o, pois tanto a sua classe quanto a a ca qualquer subtipo dela possuem o m´todo m com parˆmetros formais cujos tipos s˜o T1 , ... Tn . e a a Em uma declara¸˜oca var a : A a vari´vel a ´ associada ao tipo da classe A e n˜o ` classe A. Deste modo, a pode se referir a objetos de a e a a classes que s˜o subtipos sem serem subclasses de A. Este ´ o motivo pelo qual a declara¸˜o da vari´vel a e ca a a n˜o aloca mem´ria automaticamente para um objeto da classe A. a o 3.5.3 C++ C++ [14] ´ uma linguagem estaticamente tipada em que todo subtipo ´ subclasse. Portanto, as e e atribui¸˜es v´lidas possuem a forma co a Classe = Subclasse Assume-se que a vari´vel do lado esquerdo da atribui¸˜o seja um ponteiro e que o lado direito seja a ca uma referˆncia para um objeto: e Figura *p; ... p = new Circulo(150, 200, 30); 5 A linguagem POOL-I, ao contr´rio deste modelo, permite subclasses que n˜o s˜o subtipos. Em Green, todas as a a a subclasses s˜o subtipos. a 53
  • 55. N˜o h´ polimorfismo em C++ quando n˜o se utiliza ponteiros. Se p fosse declarado como “Figura p;”, a a a ele poderia receber apenas objetos de Figura em atribui¸˜es. Neste modelo assume-se que n˜o h´ co a a vari´veis cujo tipo sejam classes, apenas ponteiros para classes. a O motivo pelo qual o modelo C++ exige que subtipo seja tamb´m subclasse ´ o desempenho. Uma e e chamada de m´todo ´ feita atrav´s de um vetor de ponteiros para fun¸˜es e ´ apenas duas ou trˆs e e e co e e vezes mais lenta do que uma chamada de fun¸˜o normal. ca C++ suporta m´todos virtuais e n˜o virtuais, sendo que nestes ultimos a busca pelo m´todo ´ feita e a ´ e e em compila¸˜o — a liga¸˜o mensagem/m´todo ´ est´tica. Nesta subse¸˜o, consideramos que todos os ca ca e e a ca m´todos s˜o virtuais. e a 3.5.4 Java Java [11] [10] suporta apenas heran¸a simples. Contudo, a linguagem permite a declara¸˜o de interfaces c ca que podem ser utilizados em muitos casos em que heran¸a m´ltipla deveria ser utilizada. Uma interface c u declara assinaturas (signatures ou interfaces) de m´todos: e interface Printable { void print(); } Uma assinatura de um m´todo ´ composto pelo tipo de retorno, o nome do m´todo e os parˆmetros e e e a e seus tipos (sendo os nomes dos parˆmetros opcionais). a Uma classe pode implementar uma interface: class Worker subclassOf Person implements Printable { ... real getSalary() { ... } void print() { ... } } Quando uma classe implementa uma interface, ela ´ obrigada a definir (com o corpo) os m´todos e e que aparecem na interface. Se a classe Worker n˜o definisse o m´todo print, haveria um erro de a e compila¸˜o. Uma classe pode herdar de uma unica classe mas pode implementar v´rias interfaces ca ´ a diferentes. Este modelo considera as interfaces como classes de uma linguagem com heran¸a m´ltipla exceto c u que as interfaces n˜o podem definir m´todos. Interfaces s˜o similares a classes abstratas6 e tudo se a e a passa como se o modelo admitisse heran¸a m´ltipla onde todas as classes herdadas s˜o completamente c u a abstratas (sem nenhum corpo de m´todo) exceto possivelmente uma delas. Um tipo neste modelo ´ e e uma classe ou uma interface. Se uma classe A implementa uma interface I, ent˜o A ´ subtipo de I. E a e A ´ subtipo de sua superclasse, se existir. As atribui¸˜es v´lidas s˜o e co a a Tipo = subtipo Pode-se declarar uma vari´vel cujo tipo ´ uma interface. Como exemplo, o c´digo abaixo ´ v´lido. a e o e a Printable p; Person person; person = new Worker(); p = person; 6 A diferen¸a ´ que classes abstratas podem declarar vari´veis de instˆncia e o corpo de alguns m´todos. E podem c e a a e possuir m´todos privados. Em uma interface, todos os m´todos s˜o p´blicos. e e a u 54
  • 56. proc Q( x : Janela; y : integer ) begin x.desenha(); if y 1 then x.move(20, 5); endif end Figura 3.11: Um procedimento no modelo C++ p.print(); p = new NightWorker(); // NightWorker ´ subclasse de Worker e Java ´ estaticamente tipada. Ent˜o, se o tipo de uma vari´vel ´ uma interface, apenas m´todos e a a e e com assinaturas declaradas na interface podem ser chamadas por meio da vari´vel. Por exemplo, por a meio de p acima pode-se chamar apenas o m´todo print. e Interfaces em Java s˜o uma forma de adicionar os benef´ a ıcios de heran¸a m´ltipla ` linguagem mas c u a sem alguns dos problemas desta facilidade (como duplica¸˜o dos dados de objetos herdados por mais ca de um caminho — veja p´gina 44). a Neste modelo Java, uma interface n˜o pode herdar de outra interface. Na linguagem Java isto ´ a e legal. 3.5.5 Compara¸˜o entre os Modelos de Polimorfismo e Sistema de Tipos ca Agora podemos comparar o polimorfismo dos modelos de linguagens descritos acima. Considere o m´todo Q da Figura 3.11 no modelo C++. Ele pode receber, como primeiro parˆmetro (x), objetos e a da classe Janela ou qualquer subclasse desta classe. Em Java, se Janela ´ uma interface, o primeiro parˆmetro passado a Q pode ser objeto de quaisquer e a classes que implementem esta interface ou que herdem das classes que implementam esta interface. As classes que implementam uma interface geralmente n˜o tˆm nenhuma rela¸˜o de heran¸a entre a e ca c si. Se quisermos passar um objeto de uma classe A para Q, basta fazer com que A implemente a interface Janela. Isto ´, A deveria implementar os m´todos definidos em Janela e que possivelmente e e s˜o utilizados no corpo de Q. a Em uma linguagem com heran¸a simples e que n˜o suporte interfaces (como definidas em Java), c a apenas objetos de Janela e suas subclasses poderiam ser passados como primeiro parˆmetro (assu- a mindo ent˜o que Janela ´ uma classe e n˜o um interface). Para passar objetos de uma classe A como a e a parˆmetros, dever´ a ıamos fazer esta classe herdar de Janela, o que n˜o seria poss´ se A j´ herdasse a ıvel a de uma outra classe. Se Janela for uma classe, poder˜o ser passados a Q, como primeiro parˆmetro, objetos da classe a a Janela ou qualquer subclasse desta classe, como em C++. Em POOL-I, os parˆmetros passados a Q podem ser de qualquer subtipo de Janela. Todas as a classes que herdam de Janela (subclasses) s˜o subtipos desta classe e h´ subtipos que n˜o s˜o sub- a a a a classes. Ou seja, o conjunto dos subtipos de Janela ´ potencialmente maior que o de subclasses de e Janela. Conseq¨entemente, em POOL-I o procedimento Q pode ser usado com mais classes do que em u C++, pois o conjunto de classes aceito como parˆmetro para Q nesta ultima linguagem (subclasses) ´ a ´ e potencialmente menor que o conjunto aceito por POOL-I (subtipos). 55
  • 57. proc Q(x, y) begin x.desenha(); if y 1 then x.move(20, 5); endif end Figura 3.12: Procedimento Q no modelo Smalltalk Em C++, Java e POOL-I, o compilador confere, na compila¸˜o de Q, se a classe/interface de x, ca que ´ Janela, possui m´todos correspondentes `s mensagens enviadas estaticamente a x. Isto ´, o e e a e compilador confere se Janela possui m´todos desenha e move e se move admite dois inteiros como e parˆmetros. Estaticamente ´ garantido que objetos de Janela podem ser passados a Q (como primeiro a e parˆmetro) sem causar erros de tipo. Em tempo de execu¸˜o, objetos de subclasses ou subtipos de a ca Janela ser˜o passados a Q, por causa de atribui¸˜es do tipo Tipo = SubTipo. Estes objetos saber˜o a co a responder a todas as mensagens enviadas a eles dentro de Q, pois: a) eles possuem pelo menos todos os m´todos que objetos da classe Janela possuem; b) objetos da classe Janela possuem m´todos para e e responder a todas as mensagens enviadas ao parˆmetro x dentro de Q. a Smalltalk dispensa tipos na declara¸˜o de vari´veis e, portanto, o procedimento Q neste modelo ca a seria aquele mostrado na Figura 3.12. Como nem x nem y possuem tipos, n˜o se exige que o objeto a passado como primeiro parˆmetro real a Q possua m´todos desenha e move. De fato, na instru¸˜o a e ca Q(a,0) ´ enviada mensagem desenha ao objeto referenciado por x (e tamb´m por a), mas n˜o ´ enviada a e e a e mensagem move. Como conseq¨ˆncia, esta instru¸˜o pode ser executada com parˆmetros a de qualquer classe que ue ca a possua um m´todo desenha sem parˆmetros. Ao contr´rio de POOL-I, Java e C++, a classe do e a a parˆmetro x de Q n˜o precisa possuir tamb´m o m´todo move. Logo, o m´todo Q pode ser usado com a a e e e um conjunto de classes (para o parˆmetro x) potencialmente maior que o conjunto de classes usadas a com o procedimento Q equivalente de POOL-I. Portanto, Smalltalk possui mais polimorfismo que POOL-I. Em linguagens convencionais, uma atribui¸˜o a = b ser´ correta se os tipos de a e b forem iguais ca a ou b puder ser convertido para o tipo de a (o que ocorre com perda de informa¸˜o se o tipo de b ca for mais abrangente do que o de a). Em POOL-I, a = b ser´ v´lido se a classe de b for subtipo da a a classe de a. Em C++, se a classe de b for subclasse da classe de a. Em Java, se a classe de b for subclasse da classe de a (se o tipo de a for uma classe) ou implementar (direta ou indiretamente) a interface que ´ o tipo de a (se o tipo de a for uma interface).7 Em Smalltalk, esta opera¸˜o ser´ e ca a sempre correta. Logo, as linguagens orientadas a objeto citadas estendem o significado da atribui¸˜o ca permitindo um n´mero maior de tipos do seu lado direito. Como em passagem de parˆmetros existe u a uma atribui¸˜o impl´ ca ıcita, procedimentos e m´todos podem aceitar parˆmetros reais de mais classes do e a que normalmente aceitariam, o que ´ o motivo do reaproveitamento de c´digo. Concluindo, podemos e o afirmar que a mudan¸a do significado da atribui¸˜o ´ o motivo de todo o reaproveitamento de software c ca e causado pelo polimorfismo descrito neste artigo. Quando mais liberal (Smalltalk — nenhuma restri¸˜oca ao lado direito de =) ´ a mudan¸a, maior o polimorfismo. e c Suponha que estejamos construindo um novo sistema de janelas e seja necess´rio construir uma a 7 Aqui ignoramos o fato de que na linguagem Java uma interface pode herdar de outra interface. 56
  • 58. classe Window que possua os mesmos m´todos que Janela. Contudo, Window possui uma aparˆncia e e visual e uma implementa¸˜o bem diferentes de Janela. Certamente, ´ interessante poder passar ca e objetos de Window onde se espera objetos de Janela. Todo o c´digo constru´ para manipular esta o ıdo ultima classe seria reusado pela classe Window. ´ Em C++, Window deve herdar de Janela para que objetos de Window possam ser usados onde se espera objetos de Janela. Como Window possui uma implementa¸˜o completamente diferente de ca Janela, as vari´veis de instˆncia da superclasse Janela n˜o seriam usadas em objetos de Window. a a a Ent˜o, heran¸a estaria sendo utilizada para expressar especifica¸˜o do problema e n˜o implementa¸˜o. a c ca a ca Especifica¸˜o ´ o que expressa a regra “um objeto de uma subclasse ´ um objeto de uma super- ca e e classe”. Implementa¸˜o implica que uma subclasse possui pelos menos as vari´veis de instˆncia da ca a a sua superclasse e herda algumas ou todas as implementa¸˜es dos m´todos. co e Em POOL-I, este problema n˜o existe, pois a especifica¸˜o e implementa¸˜o s˜o desempenhados a ca ca a por mecanismos diferentes. A saber, subtipagem e heran¸a. Em Java, o programador poderia fazer c Janela e Window herdarem de uma interface com todos os m´todos originais de Janela. Mas isto s´ e o ser´ poss´ se o c´digo fonte de Janela estiver dispon´ a ıvel o ıvel. Em POOL-I, isto n˜o ´ necess´rio. a e a Existe um problema ainda maior em C++ por causa da liga¸˜o subtipo-subclasse. Considere que ca a classe Janela possua um m´todo e proc n( outra : Janela ) begin ... w = outra.x; ... end Dentro deste m´todo ´ feito um acesso ` vari´vel x do parˆmetro outra. Esta vari´vel de instˆncia e e a a a a a foi, naturalmente, declarada em Janela. Se este parˆmetro refere-se a um objeto de Window, subclasse a de Janela, ent˜o outra.x n˜o foi inicializado. A raz˜o ´ que a classe Window n˜o utiliza as vari´veis a a a e a a herdadas de Janela e, portanto, n˜o as inicializa. Note que o mesmo problema ocorre com a linguagem a Java. 3.6 Classes parametrizadas A classe Store da Figura 3.13, em Smalltalk, permite o armazenamento de objetos de qualquer tipo. Um objeto de Store guarda um outro objeto atrav´s do m´todo put e retorna o objeto armazenado e e atrav´s de get. e A classe Store em POOL-I ´ mostrada na Figura 3.14 com o nome de Store_Int. Como cada e vari´vel possui um tipo nesta linguagem, a classe Store se torna restrita — s´ pode armazenar inteiros. a o Se quisermos armazenar objetos de outros tipos, teremos que construir outras classes semelhantes a Store — uma classe para cada tipo, como Store boolean, Store real, etc. Em Smalltalk, a classe Store ´ utilizada para todos os tipos, causando reaproveitamento de c´digo. e o Nesta linguagem, podemos ter uma ´rvore bin´ria ou lista encadeada gen´rica, que permite armazenar a a e objetos de qualquer tipo. Os m´todos para a manipula¸˜o de cada uma destas estruturas de dados ´ e ca e constru´ uma unica vez. Como n˜o h´ conferˆncia de tipos, ´ poss´ inserir objetos de diferentes ıdo ´ a a e e ıvel classes na lista encadeada, criando uma lista heterogˆnia. e A linguagem POOL-I possui uma constru¸˜o que oferece um pouco da flexibilidade de Smalltalk, ca chamada de classes parametrizadas.8 Uma classe parametrizada possui um ou mais parˆmetros, quea s˜o tipos ou constantes. Estes parˆmetros s˜o substitu´ a a a ıdos por tipos e valores reais ao se usar a classe. 8 O modelo apresentado abaixo difere daquele da linguagem real. Ele serve apenas como exemplo. 57
  • 59. class Store private: var n; public: proc put( i ) begin n = i; end proc get() begin return n; end endclass Figura 3.13: Classe para armazenar objetos de qualquer tipo, em Smalltalk class Store_Int private: var n : integer; public: proc put( i : integer ) begin n = i; end proc get() : integer begin return n; end endclass Figura 3.14: Classe que armazena inteiros, em POOL-I class Store[ type T ] private: var n : T; public: proc put( i : T ) begin n = i; end proc get() : T begin return n; end endclass Figura 3.15: Classe parametrizada Store em POOL-I 58
  • 60. A classe Store da Figura 3.15 ´ parametrizada e possui um tipo com parˆmetro, como indicado e a pela palavra-chave type. Na declara¸˜o de uma vari´vel desta classe, deve ser especificado o parˆmetro ca a a T: var v_int : Store[integer]; v_bool : Store[boolean]; Quando o compilador encontrar a declara¸˜o de v int, ele executar´ os seguintes passos: ca a • copiar´ o texto de Store para uma ´rea de mem´ria. Observe que ´ o texto e n˜o um poss´ a a o e a ıvel c´digo compilado correspondente aos m´todos desta classe; o e • o identificador T ser´ substitu´ por integer (que ´ o parˆmetro real da classe) em toda a a ıdo e a c´pia. Isto criar´ uma nova classe, Store[integer]. O texto desta classe ser´ exatamente igual o a a a Store Int da Figura 3.14; • o texto produzido no item anterior ser´ compilado. a Observe que Store n˜o ´ uma classe, ´ apenas uma m´scara (ou esqueleto — template em a e e a Inglˆs) para a cria¸˜o de classes. Os exemplos (instˆncias) constru´ e ca a ıdos a partir de Store, como Store[integer], s˜o realmente classes. Eles podem ser usados como tipo de vari´veis, em heran¸a, a a c etc. A substitui¸˜o dos parˆmetros de uma classe parametrizada (no caso, T) por valores reais (no ca a caso, integer) ´ chamada de instancia¸˜o da classe. e ca Cada instancia¸˜o da classe com tipos diferentes causa a cria¸˜o de um novo texto. Portanto, h´ ca ca a duplica¸˜o de c´digo — na declara¸˜o de v int e v bool, haveria a cria¸˜o de dois m´todos put e ca o ca ca e dois get. Considere agora que um novo m´todo foi acrescentado ` classe Store da Figura 3.15: e a proc sum( k : Store[T] ) begin n = n.add( k.get() ); end Agora, uma mensagem add ´ enviada ao objeto n, o que implica que o tipo de n, T, deve possuir e m´todo add com parˆmetro do tipo T (o tipo de k.get() ´ T). e a e Esta restri¸˜o torna ilegal as declara¸˜es de v int e v bool, pois nem integer nem boolean pos- ca co suem m´todo add. Estas declara¸˜es causariam erro de compila¸˜o, que seria sinalizado na an´lise e co ca a do m´todo sum de Store[integer] e Store[boolean]. No caso geral, estes errros s´ s˜o detectados e o a na compila¸˜o da classe instanciada. Isto implica em que o c´digo fonte (o texto) dos m´todos da ca o e classe parametrizada devem ser do conhecimento do compilador na instancia¸˜o da classe. Como con- ca seq¨ˆncia, em geral classes parametrizadas n˜o podem ser compiladas e colocadas em uma biblioteca ue a ´ de arquivos objetos.9 E necess´rio que o c´digo delas seja conhecido para que possam ser utilizadas. a o Suponha que uma classe A possua o m´todo add que tome objeto de A como parˆmetro. Ent˜o e a a n˜o haver´ erro na declara¸˜o a a ca var v_A : Store[A]; Na manuten¸˜o do software, ´ comum reescrever m´todos para alcan¸ar diferentes objetivos. Por ca e e c exemplo, o m´todo add poderia ser substitu´ por m´todo soma em sum de Store, resultando em e ıdo e 9 Algumas linguagens (ex: Eiffel) n˜o duplicam o c´digo de algumas classes parametrizadas. Ent˜o esta afirma¸˜o a o a ca n˜o ´ verdadeira para estas linguagens. a e 59
  • 61. class Heap[ type T, const Max : integer ] private: var v : array(T)[Max]; Num : integer; public: proc ins( elem : T ) : boolean ... endclass proc main() { procedimento onde se inicia a execucao do programa } var h : Heap[ integer, 200 ]; ... Figura 3.16: Classe parametrizada com parˆmetro que ´ constante a e proc sum( k : Store[T] ) begin n = n.soma( k.get() ); end Esta modifica¸˜o ´ interna a sum. Ela n˜o deveria alterar os usu´rios deste m´todo. Afinal, proced- ca e a a e imentos e m´todos s˜o mecanismos de abstra¸˜o — para utiliz´-los, basta ler a sua documenta¸˜o e e a ca a ca conhecer seus parˆmetros. Como ele funciona e que algoritmos utiliza s˜o abstra´ a a ıdos dos seus usu´rios. a Contudo, na troca de add por soma causa erro na declara¸˜o de v A, admitindo que a classe A n˜o ca a possui m´todo soma. Ou seja, temos um c´digo compilando corretamente (declara¸˜o de v A) e ele e o ca torna-se inv´lido por causa de mudan¸as internas a um m´todo (sum), mudan¸as que n˜o alteram a a c e c a semˆntica (ou a documenta¸˜o) deste m´todo. Por este motivo, deve-se especificar, com coment´rios, a ca e a que m´todos os parˆmetros que s˜o tipos devem possuir. Exemplo: e a a class Store[ type T ] { O tipo T deve possuir o metodo add(T) : T } private: ... endclass Uma vez que a classe parametrizada ´ liberada para uso, n˜o devem ser acrescentados novos m´todos e a e na lista de m´todos que cada tipo que ´ parˆmetro deve suportar. e e a Parˆmetros de classes parametrizadas tamb´m podem ser constantes, como mostrado no exemplo a e da Figura 3.16. 3.7 Outros T´picos sobre Orienta¸˜o a Objetos o ca 3.7.1 Identidade de Objetos Identidade ´ a propriedade de um objeto que o distingue de todos os outros. Identidade n˜o depende e a do conte´do ou endere¸o do objeto. Identidade ´ necess´ria em modelagens do mundo real, onde cada u c e a 60
  • 62. ¨ ¨ 1 © 1 © ! u ¡ e T ¡ e ¡ e ¡ e a c b Figura 3.17: Vari´veis referindo-se a n´meros diferentes a u objeto ´ unico. Por exemplo, as transforma¸˜es que uma pessoa sofre ao longo de sua existˆncia, da e´ co e infˆncia ` velhice, n˜o modificam a sua identidade. Wegner [15] apresenta uma conversa entre duas a a a pessoas que ilustra a distin¸˜o entre valor e identidade: ca – Smith, vocˆ mudou. Vocˆ era alto e agora est´ baixo. Vocˆ era magro e agora est´ gordo. e e a e a Vocˆ tinha olhos azuis e agora tem olhos verdes. e – Mas meu nome n˜o ´ Smith. a e – Oh, ent˜o vocˆ mudou seu nome tamb´m ! a e e Considere o c´digo o proc main() var a, b, c : integer; begin a = 1; b = 1; c = a; if equal(a, b) then write(’A’); endif if a == b then write(’B’); endif if equal(a, c) then write(’C’); endif if a == c then write(’D’); endif end. onde equal(a, b) testa se a e b possuem os mesmos valores e a == b se a e b referenciam o mesmo objeto. a e b s˜o associados ao n´mero 1, mas cada associa¸˜o cria um objeto diferente cujo valor ´ 1. a u ca e Assim, a e b referenciam objetos diferentes com o mesmo valor, fazendo o c´digo acima imprimir o ACD. A representa¸˜o gr´fica dos objetos e vari´veis est´ na Figura 3.17. ca a a a 3.7.2 Persistˆncia e Persistˆncia ´ a habilidade de certos valores persistirem entre ativa¸˜es de um programa. Os valores e e co persistentes devem ser armazenados em um arquivo e posteriormente recuperados. A motiva¸˜o para o suporte ` persistˆncia ´ que o mapeamento entre as estruturas de dados usadas ca a e e no programa (listas, vetores, tabelas hash, etc) e as estruturas dos arquivos ou base de dados n˜o ´ a e trivial. Este mapeamento utiliza tipicamente 30% do c´digo de um sistema. E permite que os dados o sejam armazenados com um tipo e recuperados com outro (erro de tipos — arquivos n˜o possuem tipo a !). 61
  • 63. ¨ a E © ¡ e ! ¡¡ e ¡ ¡ e ¨ ¡ e ¨ … c E © E © ' b Figura 3.18: Um objeto complexo Na linguagem S, um objeto a ´ feito persistente pelo comando e a.store(NomeArq); onde store ´ o nome de um m´todo que todas as classes possuem automaticamente (n˜o ´ declarada e e a e pelo usu´rio). NomeArq ´ uma string com o nome do arquivo que conter´ os dados. a e a O m´todo store grava n˜o s´ as vari´veis de instˆncia de a como tamb´m os objetos referenciados e a o a a e indiretamente por elas. Os tipos de cada objeto tamb´m s˜o armazenados. e a A estrutura de dados armazenada em um arquivo ´ recuperada com m´todo retrieve: e e a.retrieve(NomeArq); A tentativa de recuperar dados com tipos diferentes daqueles armazenados causa um erro de execu¸˜o. ca Por exemplo, haveria erro se o tipo de a fosse A e o armazenado em NomeArq fosse do tipo B que n˜oa ´ subclasse de A. e Os m´todos store e retrieve manipulam adequadamente qualquer grafo de referˆncias que o e e objeto possua. Por exemplo, o objeto da Figura 3.18 seria recuperado do disco com as mesmas referˆncias que ele possuia quando estava em mem´ria, incluindo as referˆncias simultˆneas entre a e e o e a c e as duas referˆncias ao objeto apontado por b. e 3.7.3 Iteradores Iteradores s˜o constru¸˜es que permitem percorrer um conjunto tomando elemento a elemento. Um a co iterador pode ser uma constru¸˜o especial da linguagem ou feito com m´todos normais, como exem- ca e plificado na Figura 3.19. O m´todo reset inicializa o iterador. O m´todo next retorna o pr´ximo n´ da lista. O procedi- e e o o mento next retorna nil se o fim da lista foi atingido. Um exemplo de uso de iterador ´ percorrer um e conjunto imprimindo todos os seus elementos como mostrado na Figura 3.20. Observe a simplicidade do uso de iteradores. O c´digo correspondente sem eles seria muito mais o complicado e necessitaria de mais vari´veis, em geral. Al´m disto, utilizaria m´todos espec´ a e e ıficos da classe List (estamos admitindo que todos os iteradores s˜o formados por m´todos com nomes reset a e e next). Iteradores s˜o mecanismos de abstra¸˜o — eles eliminam detalhes que n˜o precisamos saber para a ca a percorrer um conjunto e obter elemento a elemento. Eles podem ser usados com uma variada gama de tipos abstratos e estruturas de dados, como listas, ´rvores bin´rias, tabelas hash, heap, etc. a a 3.8 Discuss˜o Sobre Orienta¸˜o a Objetos a ca Dentre todos os paradigmas de linguagens, o orientado a objetos ´ o que possui melhores mecanismos e para representa¸˜o do mundo real em programas. Os elementos da realidade e as estruturas de dados ca 62
  • 64. class List private: { head aponta para o inicio da lista. O tipo de cada no da lista e ´ ListNo. } var head : ListNo; current : ListNo; public: ... proc reset() begin current = head; end proc next() : ListNo var elem : ListNo; begin elem = current; current = current.suc; return elem; end endclass Figura 3.19: Exemplo de Iterador constru´ com m´todos ıdo e proc main() var s : List; elem : ListNo; ... begin ... s.reset(); while (elem = s.next()) nil do elem.write(); end. Figura 3.20: Uso de iterador de uma lista 63
  • 65. s˜o representados claramente no programa por meio de classes. Elementos como Pessoa, Governo, a Empresa, Balan¸o de Pagamentos, Texto, Janela, ´ c Icone, Rel´gio, Carro, Trabalhador, Pilha, Lista, e o Fila s˜o representados diretamente por meio de classes. O mapeamento claro entre o mundo real e a programas torna mais f´cil a compreens˜o e a manuten¸˜o do c´digo. N˜o s´ os programas espelham a a ca o a o o mundo como ´ relativamente f´cil descobrir o que deve ser modificado no c´digo quando h´ alguma e a o a altera¸˜o no mundo real. ca Heran¸a permite reaproveitar elegantemente c´digo de superclasses. Uma subclasse define apenas c o os m´todos que devem ser diferentes da superclasse. Hierarquias de heran¸a s˜o criadas incremental- e c a mente com o tempo. As novas subclasses acrescentam funcionalidades ao c´digo existente exigindo o poucas ou nenhuma modifica¸˜o deste. ca Polimorfismo ´ o motivo da alta taxa de reaproveitamento de c´digo encontrada em sistemas e o orientados a objeto. C´digo existente pode passar a trabalhar com subclasses sem necessidade de o nenhuma altera¸˜o. Prote¸˜o de informa¸˜o, estimulada ou mesmo requerida por muitas linguagens ca ca ca orientadas a objeto, impede que modifica¸˜es nas estruturas de dados de uma classe invalidem outras co classes. Este conceito ´ fundamental para a constru¸˜o de sistemas, aumentando substancialmente a e ca sua manutenabilidade. De fato, prote¸˜o de informa¸˜o ´ considerada mais importante do que heran¸a ca ca e c pela comunidade de orienta¸˜o a objetos. ca 64
  • 66. Cap´ ıtulo 4 Linguagens Funcionais 4.1 Introdu¸˜o ca Linguagens funcionais consideram o programa como uma fun¸˜o matem´tica. Todas as computa¸˜es ca a co s˜o feitas por fun¸˜es que tomam como parˆmetros outras fun¸˜es. N˜o existe o conceito de vari´vel a co a co a a onde um valor pode ser armazenado por meio da atribui¸˜o e utilizado posteriormente. Para com- ca preendermos o paradigma funcional precisamos estudar primeiro o paradigma imperativo, descrito a seguir. Uma linguagem ´ chamada imperativa se ela baseia-se no comando de atribui¸˜o e, conseq¨ente- e ca u mente, em uma mem´ria que pode ser modificada. No paradigma imperativo, vari´veis s˜o associadas o a a a posi¸˜es de mem´ria que podem ser modificadas in´meras vezes durante a execu¸˜o do programa co o u ca atrav´s do comando de atribui¸˜o. Isto ´, dada uma vari´vel x, um comando e ca e a x = expressao pode ser executado v´rias vezes durante o tempo de vida da vari´vel x. a a O comando de atribui¸˜o desempenha um papel central em linguagens imperativas. Tipicamente ca 40% dos comandos s˜o atribui¸˜es. Todos os outros comandos s˜o apenas auxiliares. Nestas lingua- a co a gens, o estado da computa¸˜o ´ determinado pelo conte´do das vari´veis (que podem ser de tipos ca e u a b´sicos, vetores ou dinˆmicas) que ´, por sua vez, determinado pelo fluxo de execu¸˜o do programa. a a e ca Para compreender este ponto, considere um procedimento p que possui, no seu corpo, v´rias a atribui¸˜es: co proc p( a, b : integer ) var i, j, k : integer; begin i = 1; j = a*b; ... while k j and j b do begin if a i + j then j = j + 1; else k = a + b; endif ... 65
  • 67. end { while } a = k - a; ... end { p } Para compreendermos o estado da computa¸˜o ap´s o while, temos que imaginar todo o fluxo de ca o execu¸˜o do algoritmo, que depende das altera¸˜es que s˜o feitas nas vari´veis vis´ ca co a a ıveis dentro de p. Isto ´ dif´ de compreender — os seres humanos n˜o conseguem raciocinar corretamente neste caso e ıcil a porque a execu¸˜o do programa ´ dinˆmica e depende de muitos fatores (valores das vari´veis). ca e a a e ca ´ O ponto central deste problema ´ o comando de atribui¸˜o. E ele que permite a altera¸˜o do valor ca das vari´veis. O comando a x = x + 1 ´ um absurdo se considerada a sua interpreta¸˜o matem´tica, mas ´ v´lido em linguagens imperativas. e ca a e a ´ v´lido porque o x do lado esquerdo se refere a uma posi¸˜o de mem´ria de um tempo futuro em E a ca o rela¸˜o ao x ` direita de “=”. Se o x da direita existir no tempo t, o x da esquerda existir´ em t ca a a + ∆t. Logo, a atribui¸˜o introduz o efeito tempo no programa, o que causa o seu comportamento ca dinˆmico que dificulta a sua compreens˜o. a a As linguagens imperativas foram projetadas tendo em vista as m´quinas em que elas seriam usadas. a Isto ´, elas espelham a arquitetura da maioria dos computadores atuais, que possuem a chamada e arquitetura de Von Neumann. Uma das caracter´ ısticas destas m´quinas ´ a manipula¸˜o de uma a e ca palavra de mem´ria por vez. N˜o ´ poss´ o a e ıvel trabalhar com um vetor inteiro ao mesmo tempo, por exemplo. A restri¸˜o “uma palavra de mem´ria por vez”´ o gargalo das m´quinas Von Neumann. E ca o e a ´ um dos fatores (o principal) que impede a sua eficiˆncia. Este tipo de m´quina realiza computa¸˜es e a co alterando posi¸˜es de mem´ria, fazendo desvios e testes. As linguagens imperativas, que espelham co o computadores Von Neumann, seguem esta filosofia. Como conseq¨ˆncia, o estado da computa¸˜o em ue ca um certo ponto depende dos valores das vari´veis, que dependem do fluxo de execu¸˜o, que depende a ca dos valores das vari´veis e assim por diante. a A atribui¸˜oca a = b liga o significado de a ao de b. Ap´s v´rias atribui¸˜es, temos um emaranhado de liga¸˜es entre o a co co vari´veis (ou entre vari´veis e express˜es) cuja semˆntica torna-se dif´ de entender. a a o a ıcil A solu¸˜o para eliminar caracter´ ca ısticas dinˆmicas dos algoritmos ´ eliminar o comando de atribui¸˜o. a e ca Eliminando-se este comando, devem ser eliminados os comandos de repeti¸˜o como for, while, ca do-while, repeat-until. Eles dependem da altera¸˜o de alguma vari´vel para que possam parar. ca a Passagem de parˆmetros por referˆncia tamb´m deixa de ter sentido, pois uma vari´vel deste tipo a e e a deve ser alterada dentro da rotina, o que n˜o pode ser conseguido sem atribui¸˜o. Vari´veis globais a ca a n˜o podem existir, uma vez que elas n˜o podem ser alteradas. Mas podem existir constantes globais a a e locais. Em passagem de parˆmetros, o valor dos parˆmetros reais ´ copiado nos parˆmetros formais. a a e a Isto ´ chamado de inicializa¸˜o e ´ diferente de atribui¸˜o. Inicializa¸˜o ´ a cria¸˜o de uma posi¸˜o e ca e ca ca e ca ca de mem´ria e a coloca¸˜o de um valor nesta posi¸˜o imediatamente ap´s a sua cria¸˜o. Ap´s a o ca ca o ca o inicializa¸˜o, o valor armazenado nesta mem´ria n˜o pode ser modificado (em linguagens funcionais). ca o a Linguagens que n˜o possuem comando de atribui¸˜o s˜o chamadas de linguagens declarativas. a ca a Linguagens funcionais s˜o linguagens declarativas em que o programa ´ considerado uma fun¸˜o a e ca matem´tica. Na maioria das linguagens funcionais o mecanismo de repeti¸˜o de trechos de c´digo a ca o ´ a recurs˜o. e a Uma compara¸˜o entre programa¸˜o funcional (recurs˜o, sem atribui¸˜o) e imperativa (repeti¸˜o, ca ca a ca ca atribui¸˜o) ´ feita abaixo utilizando-se a linguagem S. ca e 66
  • 68. { fatorial imperativo } proc fat( n : integer ) : integer var i, p : integer; begin p = 1; for i = 1 to n do p = i*p; return p; end { fatorial funcional } proc fat( n : integer ) : integer begin if n == 0 then return 1; else return n*fat(n-1); endif end A primeira fun¸˜o fat possui duas vari´veis locais e trˆs atribui¸˜es. Como j´ foi escrito, vari´veis ca a e co a a e atribui¸˜es dificultam o entendimento do programa. Esta rotina possui tamb´m uma itera¸˜o (for) co e ca e precisamos executar mentalmente os passos desta itera¸˜o para assegurar a corre¸˜o do algoritmo. A ca ca segunda fun¸˜o fat n˜o possui nenhuma vari´vel local nem atribui¸˜o. N˜o h´ comando de repeti¸˜o. ca a a ca a a ca Como conseq¨ˆncia, o significado do algoritmo ´ dado estaticamente. N˜o precisamos imaginar o ue e a programa funcionando para compreendˆ-lo. Tamb´m n˜o ´ necess´rio “desenrolar”as chamadas re- e e a e a cursivas. Esta ´ a diferen¸a entre linguagens imperativas e declarativas. As primeiras possuem significado e c que depende da dinˆmica do programa e as segundas possuem significado est´tico. As linguagens a a declarativas aproveitam toda a nossa habilidade matem´tica j´ que esta disciplina ´ baseada princi- a a e palmente em rela¸˜es est´ticas. co a Linguagens funcionais puras (LF) n˜o possuem comandos de atribui¸˜o, comandos de repeti¸˜o, a ca ca passagem de parˆmetros por referˆncia, vari´veis globais, seq¨ˆncia de instru¸˜es (colocada entre a e a ue co begin-end em S), vari´veis locais, ponteiros. LF expressam algoritmos por meio de formas funcionais, a que s˜o mecanismos de combina¸˜o de fun¸˜es para a cria¸˜o de outras fun¸˜es. Na maioria das LF, a ca co ca co a unica forma funcional ´ a composi¸˜o de fun¸˜es: ´ e ca co h(x) = f◦g(x) = f(g(x)) Por exemplo, podemos construir a fun¸˜o que ´ combina¸˜o de n elementos tomados i a i, chamada ca e ca de comb(n,i), a partir da fun¸˜o fatorial: ca proc comb( n, i : integer ) : integer begin return fat(n) div ( fat(n-i)*fat(i) ); end Os operadores aritm´ticos (*, +, /, -, etc) tamb´m s˜o fun¸˜es no sentido funcional do termo. e e a co Em uma express˜o formada apenas por vari´veis, em uma linguagem imperativa, n˜o h´ efeitos a a a a colaterais e n˜o precisamos nos preocupar onde o computador armazena os resultados intermedi´rios a a 67
  • 69. do c´lculo. Por n˜o existir efeitos colaterais, uma fun¸˜o em uma LF retorna sempre o mesmo valor a a ca se forem passados os mesmos parˆmetros. Assim, ´ poss´ avaliar em paralelo as fun¸˜es presentes a e ıvel co em uma express˜o. Em a ... f( g(x), h(x) ) + p(y); pode-se calcular g(x), h(x) e p(y) ao mesmo tempo (ou f(...) e p(y)) alocando um processador para calcular cada fun¸˜o. Observe que, se houvesse passagem por referˆncia ou vari´veis globais, a ca e a ordem de chamada destas fun¸˜es poderia influenciar o resultado. co Uma fun¸˜o cujo valor de retorno depende apenas dos valores dos parˆmetros possui transparˆncia ca a e referencial (TR). Isto ´, dados os mesmos parˆmetros, os valores de retorno s˜o idˆnticos. Linguagens e a a e com transparˆncia referencial s˜o aquelas onde todas as fun¸˜es apresentam esta caracter´ e a co ıstica (ex: linguagens funcionais puras). Uma consequˆncia deste fato ´ a eleva¸˜o do n´ de abstra¸˜o — h´ e e ca ıvel ca a menos detalhes para serem compreendidos. Por exemplo, ´ mais f´cil entender como uma fun¸˜o e a ca funciona porque as suas partes, compostas por express˜es, s˜o independentes entre si. O resultado de o a um trecho n˜o afetar´ de modo algum outro trecho, a menos que o primeiro trecho seja uma express˜o a a a passada como parˆmetro ao segundo. Em linguagens com atribui¸˜o, o resultado de um peda¸o de a ca c c´digo altera necessariamente outros segmentos do programa e de uma forma que depende do fluxo o de execu¸˜o. ca Em uma LF, tudo s˜o fun¸˜es, inclusive o comando if de sele¸˜o, que possui a seguinte forma: a co ca if exp then exp1 else exp2 que seria equivalente a uma fun¸˜o de forma expl´ ca ıcita if (exp, exp1, exp2) N˜o h´ necessidade de endif pois ap´s o then ou o else existe exatamente uma express˜o. O ponto a a o a e v´ırgula ap´s a express˜o tamb´m ´ desnecess´rio pois ele separa instru¸˜es que n˜o existem aqui. o a e e a co a Utilizando este if, a fun¸˜o fatorial ficaria ca proc fat( n : integer ) : integer is if n == 0 then 1 else n*fat(n-1); Utilizaremos esta sintaxe no restante deste cap´ ıtulo. O corpo da fun¸˜o ´ colocado ap´s is e ´ formado ca e o e por uma unica express˜o. ´ a Uma conseq¨ˆncia da transparˆncia referencial ´ a regra da reescrita: cada chamada de fun¸˜o ue e e ca pode ser substitu´ pelo pr´prio corpo da fun¸˜o. Assim, para calcular fat(2), podemos fazer: ıda o ca fat(2) = if 2 == 0 then 1 else 2*fat(1) = if 2 == 0 then 1 else 2* (if 1 == 0 then 1 else 1*fat(0)) = if 2 == 0 then 1 else 2*(if 1 == 0 then 1 else 1*(if 0 == 0 then 1 else 0*fat(-1)) ) 68
  • 70. (def membro (lambda(x L) (cond ( (null L) nil) ( (eq x (car L)) T) ( T (membro x (cdr L)) ) ) )) Figura 4.1: Fun¸˜o membro em Lisp ca proc membro( x, L ) is if L == nil then false else if x == car(L) then true else membro( x, cdr(L) ); Figura 4.2: Fun¸˜o membro com a sintaxe de S ca Avaliando, temos fat(2) = 2*1*1 = 2 O processo acima ´ chamado de redu¸˜o e ´ o meio empregado para executar programas em e ca e linguagens funcionais, pelo menos conceitualmente. 4.2 Lisp Esta se¸˜o apresenta algumas das caracter´ ca ısticas da linguagem Lisp, a primeira linguagem funcional. Nesta linguagem tudo ´ representado por listas: o pr´prio programa, suas fun¸˜es e os dados que ele e o co utiliza. Listas nesta linguagem s˜o delimitadas por ( e ): a (3 carro 2.7) ( (3 azul) -5) A Figura 4.1 mostra uma fun¸˜o membro que toma uma lista L e um valor x como parˆmetros que ca a retorna true (T) se o valor est´ na lista e nil (false em Lisp) caso contr´rio. Em Lisp, cond ´ um a a e if estendido para manipular v´rias express˜es. Neste caso, h´ trˆs express˜es, “(null L)”, “(eq x a o a e o (car L))” e “T”. Se a primeira express˜o for verdadeira, a instru¸˜o cond returnar´ nil. A fun¸˜o a ca a ca car retorna o primeiro elemento da lista e cdr retorna a lista retirando o primeiro elemento. Exemplo: (car ’(1 2 3)) −→ 1 (cdr ’(1 2 3)) −→ (2 3) Ap´s a seta ´ mostrado o resultado da avalia¸˜o da express˜o. A compara¸˜o de igualdade ´ feita com o e ca a ca e eq, sendo que “(eq x (car L))” compara x com (car L). Uma fun¸˜o em S equivalente ` fun¸˜o da ca a ca Figura 4.1 em Lisp est´ na Figura 4.2. a 69
  • 71. Tudo o que vem ap´s ( ´ considerado uma aplica¸˜o de fun¸˜o, a menos que ’ preceda o (. o e ca ca Exemplo: ’(a b c) −→ (a b c) (membro a ’(b c a)) −→ T (+ 2 (* 3 5)) −→ 17 (comb 5 3) −→ 10 (comb 5 3) chama a fun¸˜o comb com 5 e 3 como parˆmetros. ca a N˜o h´ especifica¸˜o de tipos na declara¸˜o de vari´veis — a linguagem ´ dinamicamente tipada. a a ca ca a e Logo, todas as fun¸˜es s˜o polim´rficas e podem ocorrer erros de tipo em execu¸˜o. A fun¸˜o membro, co a o ca ca por exemplo, pode ser usada em listas de inteiros, reais, s´ ımbolos, etc. Exemplo: (membro 3 ’(12 98 1 3)) −→ T (membro azul ’(3 verde 3.14 amarelo)) −→ nil Um erro de execu¸˜o ocorre na chamada ca (delta azul verde 5) da fun¸˜o delta: ca (def delta (lambda (a b c) (- (* b b) (* 4 a c)) ) ) Os parˆmetros a e b recebem azul e verde sobre os quais as opera¸˜es aritm´ticas n˜o est˜o definidas. a co e a a Lisp utiliza a mesma representa¸˜o para programas e dados — listas. Isto permite a um programa ca construir listas que s˜o executadas em tempo de execu¸˜o pela fun¸˜o Eval: a ca ca (Eval L) A fun¸˜o Eval tratar´ L como uma fun¸˜o e a executar´. ca a ca a Um grande n´mero de dialetos foi produzido a partir de Lisp, tornando praticamente imposs´ u ıvel transportar programas de um compilador para outro. Para contornar este problema, foi criada a linguagem Common Lisp que incorpora facilidades encontradas em v´rios dialetos de Lisp. A inclus˜o a a de orienta¸˜o a objetos em Common Lips resultou na linguagem Common Lisp Object System, CLOS. ca 4.3 A Linguagem FP Outro exemplo de linguagem funcional ´ FP, projetada por John Backus, o principal projetista de e Fortran. FP ´ puramente funcional, n˜o possui vari´veis e oferece muitas possibilidades de combinar e a a fun¸˜es al´m da composi¸˜o. co e ca Uma seq¨ˆncia de elementos em FP ´ denotada por a1 , a2 , . . . an e a aplica¸˜o de uma fun¸˜o ue e ca ca f ao parˆmetro x (que pode ser uma seq¨ˆncia) ´ denotada por f:x. A fun¸˜o FIRST extrai o primeiro a ue e ca elemento de uma seq¨ˆncia e TAIL retorna a seq¨ˆncia exceto pelo primeiro elemento: ue ue FIRT : 3, 7, 9, 21 −→ 3 TAIL : 3, 7, 9, 21 −→ 7, 9, 21 A unica forma funcional (mecanismo de combinar fun¸˜es) na maioria das LF ´ a composi¸˜o. Em ´ co e ca FP, existem outras formas funcionais al´m desta, sendo algumas delas citadas abaixo. e 1. Composi¸˜o. ca (f◦g) : x ≡ f:(g:x) Exemplo: 70
  • 72. DEF quarta ≡ (SQR◦SQR):x 2. Constru¸˜o. ca [f1 , f2 , ... fn ]:x ≡ f1 :x, ..., fn :x Exemplo: [MIN, MAX]: 0, 1, 2 ≡ MIN: 0, 1, 2 , MAX: 0, 1, 2 ≡ 0, 2 3. Aplique a todos α f:x ≡ if x == nil then nil else if x eh a sequencia x1 , x2 , ...xn then f:x1 , ..., f:xn nil ´ a lista vazia. e Exemplo: α SQR: 3, 5, 7 ≡ SQR:3, SQR:5, SQR:7 ≡ 9, 25, 49 4. Condi¸˜o ca (IF p f g):x ≡ if p:x == T then f:x else g:x T ´ um ´tomo que representa true. e a Exemplo (IF PRIMO SOMA1 SUB2):29 5. Enquanto (WHILE p f):x ≡ if p:x == T then (WHILE p f): (f:x) else x Esta forma funcional aplica f em x enquanto a aplica¸˜o de p em x for verdadeira (T). ca 4.4 SML - Standard ML SML ´ uma linguagem funcional fortemente tipada e com um alto grau de polimorfismo. Este polimor- e fismo ´ semelhante ao de classes parametrizadas e determinado automaticamente pelo compilador, que e analisa cada fun¸˜o e determina a forma mais gen´rica que ela pode ter. Antes de estudar esta fun- ca e cionalidade, veremos alguns t´picos b´sicos desta linguagem. o a Al´m de tipos b´sicos (integer, boolean, string, etc.), SML suporta listas de forma semelhante e a a LISP. Uma lista com os trˆs primeiros n´meros ´ e u e [1, 2, 3] e a lista vazia ´ []. Sendo SML fortemente tipada, listas heterogˆneas (elementos de v´rios tipos) s˜o e e a a ilegais. Os parˆmetros de uma fun¸˜o podem estar declarados sem tipo: a ca proc succ(n) is n + 1; 71
  • 73. O compilador descobre que n deve ser inteiro, pois a opera¸˜o + (em SML), exige que os seus ca operandos sejam do mesmo tipo. Como 1 ´ do tipo integer, n deve ser integer e o resultado e tamb´m. e O compilador produz o seguinte cabe¸alho para succ: c proc succ(n : integer) : integer O tipo desta fun¸˜o n˜o envolve o nome, sendo representado como ca a integer −→ integer O tipo de uma fun¸˜o ca proc f( x1 : T1 ; x2 : T2 ; ...xn : Tn ) : R ´ expresso como e T1 ×T2 × ...Tn −→ R O tipo da fun¸˜o ´ o tipo dos parˆmetros e do valor de retorno, sendo os primeiros separados por ×. ca e a Veja outros dois exemplos dados a seguir. a) proc calcula(a, b) is if b a then 1 else b Para que a fun¸˜o possua tipos corretos, as express˜es que se seguem ao then e ao else devem ca o possuir o mesmo tipo. Assim, b (else) possui o mesmo tipo que 1 (do then — integer). As opera¸˜es de compara¸˜o (ex: ) s´ se aplicam a valores do mesmo tipo. Logo, a ´ do mesmo co ca o e tipo que b. O tipo final de Calcula ´: e integer × integer −→ integer b) proc inutil(a, b) is if a 1 then inutil(b-1, a) else a Por “a 1”, a ´ inteiro. Por “inutil(b-1, a)”, b tamb´m deve ser inteiro por dois motivos: e e • Est´ em uma subtra¸˜o com um inteiro (“b-1”). a ca • a ´ passado como parˆmetro real onde o parˆmetro formal ´ b, e a ´ inteiro. O tipo do e a a e e valor de retorno ´ igual ao tipo de a. e O tipo de inutil ´: e integer × integer −→ integer Algumas vezes o compilador n˜o consegue deduzir os tipos e h´ erro, como em a a 72
  • 74. proc soma(a, b) is a + b; Considerando que o operador + pode ser aplicado tanto a reais como inteiros, o tipo de soma poderia ser qualquer um dos abaixo integer × integer −→ integer real × real −→ real e, portanto, h´ ambig¨idade, que ´ resolvida colocando-se pelo menos um dos tipos (de a, b ou do a u e valor de retorno). Exemplo: proc soma(a : integer; b) is ... proc soma(a, b) : integer is ... Se a express˜o do then e do else de um if pudessem ser de tipos diferentes, poderia haver erros a de tipo em execu¸˜o, como o abaixo. ca proc f(a) is if a 1 then 1 else Eu sou um erro proc g is f(0) + 1; f(0) retorna uma string ` qual tenta-se somar um inteiro. Por causa das restri¸˜es impostas pelo a co sistema de tipos, erros de execu¸˜o como este nunca ocorrem em programas SML. ca A fun¸˜o ca proc id(x) is x; pode ser usada com valores de qualquer tipo, e ´ v´lida na linguagem. O seu tipo ´ e a e ’a −→ ’a onde ’a significa um tipo qualquer. Se houvesse mais um parˆmetro e este pudesse ser de um outro a tipo qualquer, este seria chamado de ’b. Um outro exemplo ´ a fun¸˜o e ca proc nada(x, y) is x; cujo tipo ´ e ’a × ’b −→ ’a Outra dedu¸˜o de tipo ´ apresentada abaixo ca e 73
  • 75. proc escolhe(i, a, b) is if i 0 then a else b O tipo de escolhe ´: e integer × ’a × ’a −→ ’a A dedu¸˜o dos tipos corretos para as vari´veis ´ feito por um algoritmo que tamb´m determina se ca a e e h´ ambig¨idade ou n˜o. Este ´ um fato importante: a defini¸˜o de SML utiliza n˜o apenas defini¸˜es a u a e ca a co est´ticas mas tamb´m dinˆmicas (algoritmos). Ist ´ uma qualidade, pois aumenta o polimorfismo, a e a e mas tamb´m um problema. Como algoritmos s˜o mais dif´ e a ıceis de entender do que rela¸˜es est´ticas, o co a programador necessita de um esfor¸o mais para decidir se o c´digo que ele produziu em SML ´ v´lido c o e a ou n˜o. a Listas s˜o delimitadas por [ e ], como [1, 2, 3], e possuem um tipo que termina sempre com a a palavra list. Alguns exemplos de tipos de listas est˜o na tabela seguinte. a Lista Tipo [1, 2, 3] integer list [a, azul, b] string list [ [1, 2], [3], [4] ] integer list list O construtor :: constr´i uma lista a partir de um elemento e de outra lista. Exemplos: o 1::[2,3] resulta em [1, 2, 3]. A aplica¸˜o da fun¸˜o ca ca proc ins(a : integer; L : integer list) : integer list is a::L; sobre 1 e [2, 3] resulta em [1, 2, 3]. Isto ´, ins(1, [2, 3]) −→ [1, 2, 3]. e O tamanho de uma lista pode ser calculado pela fun¸˜o len: ca proc len([]) is 0 | len(h::t) is 1 + len(t); A fun¸˜o len possui, na verdade, duas defini¸˜es. Uma para a lista vazia e outra para listas com ca co pelo menos um elemento. As defini¸˜es s˜o separadas por |. Em uma chamada co a len([1, 2, 3]) ´ feito o emparelhamento do parˆmetro [1, 2, 3] com a segunda defini¸˜o de len, resultando nas e a ca seguintes inicializa¸˜es: co h = 1 t = [2, 3] Ent˜o a express˜o 1 + len([2, 3]) ´ calculada e retornada. a a e De um modo geral, em uma chamada len(L) 74
  • 76. ´ utilizada uma das defini¸˜es de len de acordo com o parˆmetro L. A presen¸a de um if em len, e co a c como if L == [] then 0 else ... torna-se desnecess´ria. A programa¸˜o com emparelhamento ´ ligeiramente mais abstrata (alto n´ a ca e ıvel) do que com if. Admitindo que todos os tipos suportam a opera¸˜o de igualdade, uma fun¸˜o que testa a presen¸a ca ca c de x em uma lista ´: e proc membro(x, []) is false | membro(x, h::t) is if x == h then true else membro(x,t); E o seu tipo ´ e ’a × ’a list −→ boolean 4.5 Listas Infinitas e Avalia¸˜o Pregui¸osa ca c Linguagens funcionais freq¨entemente suportam estruturas de dados potencialmente infinitas. Por u exemplo, ones = 1 : ones ´ uma lista infinita de 1’s em Haskell. A fun¸˜o e ca proc numsFrom( n : integer ) is [n : numsFrom(n + 1)] retorna uma lista infinita de n´meros naturais come¸ando em n. Naturalmente, um programa n˜o usa u c a uma lista infinita j´ que ele termina em um tempo finito. Estas listas s˜o constru´ a a ıdas ` medida que a os seus elementos v˜o sendo requisitados, em uma maneira pregui¸osa (lazy evaluation). a c Este mecanismo ´ usado para facilitar a implementa¸˜o de algoritmos e mesmo para aumentar a e ca eficiˆncia da linguagem. Por exemplo, a fun¸˜o [17] e ca proc cmpTree( tree1, tree2 ) is cmpLists( treeToList(tree1), treeToList(tree2) ); compara duas ´rvores pela compara¸˜o dos n´s das ´rvores colocados em forma de lista. A fun¸˜o a ca o a ca treeToList converte a ´rvore para lista de maneira pregui¸osa. Assim, se o primeiro elemento das duas a c a ´rvores forem diferentes, cmpLists retornar´ false, terminando a fun¸˜o cmpTree. Sem constru¸˜o a ca ca pregui¸osa da lista, seria necess´rio construir as duas listas totalmente antes de come¸ar a fazer o teste c a c e descobrir que as listas s˜o diferentes logo no primeiro elemento. a 75
  • 77. 4.6 Fun¸˜es de Ordem Mais Alta co A maioria das linguagens modernas permitem que fun¸˜es sejam passadas como parˆmetros. Isto co a permite a constru¸˜o de rotinas gen´ricas. Por exemplo, pode-se construir uma fun¸˜o max que ca e ca retorna o maior elemento de um vetor qualquer. A opera¸˜o de compara¸˜o entre dois elementos ´ ca ca e passada a max como uma fun¸˜o. Em uma linguagem sem tipos, max seria: ca proc max( v, n, gt ) var maior, i; begin maior = v[1]; for i = 2 to n do if gt(v[i], maior) then maior = v[i]; endif return maior; end O c´digo de max pode ser utilizado com vetores de qualquer tipo T, desde que se defina uma fun¸˜o o ca de compara¸˜o para o tipo T. Exemplo: ca proc gt_real(a, b) { para numeros reais } begin return a b; end ... m = max( VetReal, gt_real ); m1 = max( VetNomes, gt_string ); Fun¸˜es que admitem fun¸˜es como parˆmetros s˜o chamadas fun¸˜es de mais alta ordem (“higher co co a a co order functions”). Uma fun¸˜o map em SML que aplica uma fun¸˜o f a todos os elementos de uma lista, produzindo ca ca uma lista como resultado, seria: proc map( proc f(’a) : ’b; [] ) is [] | map( proc f(’a) : ’b; h::t ) is f(h)::map(t); seu tipo ´: e (’a −→ ’b) × ’a list −→ ’b list Observe que fun¸˜es como parˆmetro s˜o completamente desnecess´rias em linguagens orientadas a co a a a objeto pois cada objeto ´ associado a um conjunto de m´todos. Quando um objeto for passado como e e parˆmetro, teremos o efeito de passar tamb´m todos os seus m´todos como parˆmetro simulando a e e a fun¸˜es de ordem mais alta. co 4.7 Discuss˜o Sobre Linguagens Funcionais a A necessidade de eficiˆncia fez com que na maioria das linguagens funcionais fossem acrescentadas e duas constru¸˜es imperativas, a saber, seq¨ˆncia e atribui¸˜o. Seq¨ˆncia permite que as instru¸˜es de co ue ca ue co 76
  • 78. uma lista sejam executadas sequencialmente, introduzindo a no¸˜o de tempo. No exemplo seguinte, ca esta lista est´ delimitada por begin-end. a begin a = a + 1; if a b then return f(a, b) else return f(b, a) end Obviamente, seq¨ˆncia s´ tem sentido na presen¸a de atribui¸˜o ou entrada/sa´ de dados, pois ue o c ca ıda de outro modo o resultado de cada instru¸˜o da seq¨ˆncia seria uma express˜o cujo resultado seria ca ue a perdido ap´s a sua avalia¸˜o. o ca Programadores produzem aproximadamente a mesma quantidade de linhas de c´digo por ano, o independente da linguagem. Assim, quanto mais alto n´ a linguagem ´, mais problemas podem ser ıvel e resolvidos na mesma unidade de tempo. Uma linguagem ´ de mais alto n´ que outra por possuir e ıvel menos detalhes, o que implica em ser mais compacta (necessita de menos constru¸˜es/instru¸˜es para co co fazer a mesma coisa que outra). Como linguagens funcionais s˜o de mais alto n´ que a maioria das a ıvel outras, elas implicam em maior produtividade para o programador. V´rios fatores tornam linguagens funcionais de alto n´ a ıvel, como o uso de recurs˜o ao inv´s de a e itera¸˜o, ausˆncia de atribui¸˜o e aloca¸˜o e desaloca¸˜o autom´tica de mem´ria. Este ultimo item ca e ca ca ca a o ´ ´ particularmente importante. N˜o s´ o programador n˜o precisa desalocar a mem´ria dinˆmica (h´ e a o a o a a coleta de lixo) mas ele tamb´m n˜o precisa aloc´-la explicitamente. As listas utilizadas por linguagens e a a funcionais aumentam e diminuem automaticamente, poupando ao programador o trabalho de gerenci´- a las. ´ E mais f´cil definir uma linguagem funcional formalmente do que linguagens de outros paradigmas, a assim como programas funcionais s˜o adequados para an´lise formal. A raz˜o ´ que as linguagens a a a e deste paradigma possuem um parentesco proximo com a matem´tica, facilitando o mapeamento da a linguagem ou programa para modelos matem´ticos.a H´ dois problemas principais com linguagens funcionais. Primeiro, um sistema real ´ mapeado em a e um programa que ´ uma fun¸˜o matem´tica composta por outras fun¸˜es. Logo, n˜o h´ o conceito e ca a co a a de estado do programa dado pelas vari´veis globais, dificultando a implementa¸˜o de muitos sistemas a ca que exigem que o programa tenha um estado. Estes sistemas n˜o s˜o facilmente mapeados em fun¸˜es a a co matem´ticas. De fato, o paradigma que representa melhor o mundo real ´ o orientado a objetos. O a e conceito de objeto ´ justamente um bloco de mem´ria (que guarda um estado) modificado por meio e o de envio de mensagens. Uma outra face deste problema ´ entrada e sa´ de dados em linguagens funcionais. Fun¸˜es que e ıda co fazem entrada e sa´ n˜o suportam transparˆncia referencial. Por exemplo, uma fun¸˜o getchar() ıda a e ca que retorna o pr´ximo car´ter da entrada padr˜o provavelmente retornar´ dois valores diferentes se o a a a for chamada duas vezes. O segundo problema com linguagens funcionais ´ a eficiˆncia. Elas s˜o lentas por n˜o permitirem e e a a atribui¸˜es. Se, por exemplo, for necess´rio modificar um unico elemento de uma lista, toda a lista co a ´ dever´ ser duplicada. Este tipo de opera¸˜o pode ser otimizada em alguns casos1 pelo compilador a ca ou o programador pode encontrar maneiras alternativas de expressar o algoritmo. Neste ultimo caso,´ 1 Este t´pico n˜o ser´ discutido aqui. o a a 77
  • 79. ´ prov´vel que o modo alternativo de codifica¸˜o seja dif´ de entender por n˜o ser o mais simples e a ca ıcil a poss´ıvel. M´quinas paralelas podem aumentar enormemente a eficiˆncia de programas funcionais. Contudo, a e esta tecnologia ainda n˜o est´ suficientemente madura para concluirmos que linguagens funcionais s˜o a a a t˜o eficientes quanto linguagens imperativas. a O uso de atribui¸˜o em um programa n˜o elimina todos os benef´ ca a ıcios da programa¸˜o funcional. ca Um bom programador limita as atribui¸˜es ao m´ co ınimo necess´rio ` eficiˆncia, fazendo com que grande a a e parte do programa seja realmente funcional. Assim, pelo menos esta parte do c´digo ser´ legivel e o a f´cil de ser paralelizada e otimizada, que s˜o as qualidades associadas ` programa¸˜o funcional. a a a ca 78
  • 80. Cap´ ıtulo 5 Prolog — Programming in Logic 5.1 Introdu¸˜o ca Prolog ´ uma linguagem l´gica. Ela permite a definic˜o de fatos e de relacionamentos entre objetos. e o a Nesta linguagem objeto ´ designa valores de qualquer tipo. Um programa em Prolog consiste de fatos e e regras. Um fato ´ uma afirma¸˜o sempre verdadeira. Uma regra ´ uma afirma¸˜o cuja veracidade e ca e ca depende de outras regras ou fatos. Para exemplificar estes conceitos, utilizaremos o seguinte programa em Prolog: homem(jose). homem(joao). homem(pedro). homem(paulo). mulher(maria). mulher(ana). pais(pedro, joao, maria). pais(paulo, joao, maria). pais(maria, jose, ana). Neste c´digo s´ h´ fatos e cada um deles possui um significado. “homem(X)” afirma que X ´ homem o o a e e pais(F, H, M) significa que F ´ filho de pai H e m˜e M. Este programa representa uma fam´ na e a ılia qual • Jos´ e Ana s˜o pais de Maria; e a • Jo˜o e Maria s˜o pais de Pedro e Paulo a a As informa¸˜es sobre a fam´ podem ser estendidas por novos fatos ou regras, como pela regra co ılia irmao(X, Y) :- homem(X), pais(X, H, M), pais(Y, H, M). A regra acima ser´ verdadeira se as regras que se seguem a :- (que funciona como um if) forem a verdadeiras. Isto ´, X ser´ irm˜o de Y se X for homem e possuir os mesmos pais H e M de Y. A v´ e a a ırgula funciona como um and l´gico. o 79
  • 81. Prolog admite que todos os identificadores que se iniciam com letras mai´sculas (como X, Y, H e M) u s˜o nomes de vari´veis. Nomes iniciados em min´scula s˜o s´ a a u a ımbolos. N´meros (1, 52, 3) e s´ u ımbolos s˜o tipos de dados b´sicos da linguagem e s˜o chamados de ´tomos. a a a a Prolog ´ uma linguagem interativa que permite a formula¸˜o de perguntas atrav´s de goals. Um e ca e goal ´ uma meta que desejamos saber se ´ verdadeira ou falsa e em que situa¸˜es. Por exemplo, se e e co quisermos saber se pedro ´ homem, colocamos e ?- homem(pedro). e o sistema responder´ a yes sendo que “homem(pedro)” ´ o goal do qual queremos saber a veracidade. e Fatos, regras e goals s˜o exemplos de cl´usulas. Um predicado ´ um conjunto de fatos e/ou a a e regras com o mesmo nome e n´mero de argumentos. O programa exemplo definido anteriormente u possui os predicados homem, mulher, pais e irmao. Veremos adiante que predicado ´ o equivalente e a procedimento em outras linguagens. O conjunto de todos os predicados forma a base de dados do programa. Um goal pode envolver vari´veis: a ?- mulher(M). O objetivo desta quest˜o ´ encontrar os nomes das mulheres armazenados na base de dados. O sistema a e de tempo de execu¸˜o de Prolog tenta encontrar os valores de M que fazem esta cl´usula verdadeira. ca a ´ Ele rastreia todo o programa em busca da primeira cl´usula com nome “mulher”. E encontrado a mulher(maria) e ´ feita a associa¸˜o e ca M = maria Neste ponto, um valor de M que torna mulher(M) verdadeiro ´ encontrado e o sistema escreve a e resposta: ?- mulher(M). M = maria Se o usu´rio digitar ; (ponto-e-v´ a ırgula), Prolog retornar´ `s cl´usulas do programa e: aa a • tornar´ inv´lida a associa¸˜o de M com maria. Ent˜o M volta a n˜o estar instanciada — n˜o a a ca a a a tem valor. A associa¸˜o entre uma vari´vel e um valor ´ chamado de instancia¸˜o. Antes ca a e ca de uma instancia¸˜o, a vari´vel ´ chamada de livre e n˜o est´ associada a nada. Ap´s uma ca a e a a o instancia¸˜o, uma vari´vel n˜o pode ser instanciada novamente, exceto em backtracking (que ca a a ser´ visto adiante), quando a instancia¸˜o anterior deixa de existir. a ca • continuar´ a procurar por cl´usula que emparelhe com “mulher(M)” tornando esta cl´usula a a a verdadeira. Neste processo M ser´ instanciada. Esta procura se iniciar´ na cl´usula seguinte ` a a a a ultima encontrada. A ultima foi “mulher(maria)” e a seguinte ser´ “mulher(ana)”. ´ ´ a Ent˜o, a busca por mulher continuar´ em mulher(ana) e M ser´ associado a ana: a a a ?- mulher(M). M = maria; M = ana 80
  • 82. Digitando ; a busca continuar´ a partir de a pais(pedro, joao, maria) e n˜o ser´ encontrada nenhuma cl´usula mulher, no que o sistema responder´ “no”: a a a a ?- mulher(M). M = maria; M = ana; no O algoritmo que executa a busca, por toda a base de dados, por cl´usula que emparelha dado a goal ´ chamado de algoritmo de unifica¸˜o. Dizemos que um fato (ou regra) emparelha (match) um e ca goal se ´ poss´ existir uma correspondˆncia entre os dois. Os itens a seguir exemplificam algumas e ıvel e tentativas de emparelhamento. Utilizamos a sintaxe mulher(maria) = mulher(X) para a tentativa de emparelhamento do goal “mulher(X)” com a cl´usula “mulher(maria)”. a a) mulher(maria) = mulher(X) h´ emparelhamento e X ´ instanciado com maria, que indicaremos como X = maria. a e b) mulher(maria) = mulher(ana) n˜o h´ emparelhamento, pois maria = ana a a c) mulher(Y) = mulher(X) h´ emparelhamento e X = Y. Observe que nenhuma das duas vari´veis, X ou Y, est´ instanciada. a a a d) pais(pedro, X, maria) = pais(Y, joao, Z) h´ emparelhamento e Y = pedro, X = joao e Z = maria a Uma cl´usula composta ser´ verdadeira se o forem todos os seus fatos e regras. Por exemplo, a a considere a meta ?- irmao(pedro, paulo). que produz um emparelhamento com irmao(X, Y), fazendo X = pedro, Y = paulo e a gera¸˜o dos ca subgoals homem(pedro), pais(pedro, H, M), pais(paulo, H, M). O primeiro subgoal, homem(pedro), ´ verdadeiro (pelos fatos) e pode ser eliminado, restando e pais(pedro, H, M), pais(paulo, H, M). O subgoal pais(pedro, H, M) emparelha com pais(pedro, joao, maria), produzindo as asso- cia¸˜es H = joao e M = maria. Um emparelhamento sempre ´ verdadeiro e, portanto, o subgoal co e pais(pedro, joao, maria) ´ eliminado e o goal inicial ´ reduzido a e e pais(paulo, joao, maria). Que ´ provado por um dos fatos. e Portanto, a meta irmao(pedro, paulo) ´ verdadeira. e 81
  • 83. Podemos perguntar quest˜es como o ?- irmao(X, Y). que ´ substitu´ pelos subgoals e ıda homem(X), pais(X, H, M), pais(Y, H, M). O primeiro subgoal emparelha com homem(jose), fazendo X = jose e produzindo pais(jose, H, M) pais(Y, H, M). O primeiro subgoal (pais(jose, H, M)) n˜o pode ser emparelhado com ningu´m e falha. Esta a e falha causa um retrocesso (backtracking) ao subgoal anterior, homem(X). A instancia¸˜o de X com jose ca ´ destru´ e ıda, tornando X uma vari´vel livre. A busca por cl´usula que emparelha com este goal continua a a em homem(joao), que tamb´m causa falha em e pais(joao, H, M), pais(Y, H, M). H´ retrocesso para homem(X) e matching com homem(pedro), fazendo X = pedro, e resultando em a pais(pedro, H, M), pais(Y, H, M). O primeiro subgoal emparelha com pais(pedro, joao, maria) fazendo H = joao, M = maria e resultando em pais(Y, joao, maria). Sempre que um novo subgoal dever ser satisfeito, a busca por cl´usula para emparelhamento comeca a na primeira cl´usula do programa, independente de onde parou a busca do subgoal anterior (que ´ a e pais(pedro, H, M)). O emparelhamento de pais(Y, joao, maria) ´ feito com pais(pedro, joao, maria). O resul- e tado final ´ e X = pedro Y = pedro Pela nossa defini¸˜o, pedro ´ irm˜o dele mesmo. Digitando ; ´ feito um retrocesso e a busca por ca e a e emparelhamento para pais(Y, joao, maria) continua em pais(paulo, joao, maria), que sucede e produz X = pedro Y = paulo Observe que a ordem das cl´usulas no programa ´ importante porque ela diz a ordem dos retro- a e cessos. Outras regras que seriam uteis para rela¸˜es familiares s˜o dadas abaixo. ´ co a pai(P, F) :- /* P ´ pai de F */ e pais(F, P, M). 82
  • 84. mae(M, F) :- /* M ´ mae de F */ e pais(F, P, M). avo(A, N) :- /* A ´ avo (homem) de N. A = avo, N = neto */ e homem(A), pai(A, F), pai(F, N). avo(A, N) :- /* A ´ avo (homem) de N. A = avo, N = neto */ e homem(A), pai(A, F), mae(F, N). tio(T, S) :- /* T ´ tio de S. T = tio, S = sobrinho */ e irmao(T, P), pai(P, S). tio(T, S) :- /* T ´ tio de S. T = tio, S = sobrinho */ e irmao(T, P), mae(P, S). filho(F, P) :- /* F ´ filho de P */ e homem(F), pai(P, F). filho(F, M) :- /* F ´ filho de M */ e homem(F), mae(M, F). paimaeDe(A, D) :- /* A ´ pai ou mae de D */ e pai(A, D). paimaeDe(A, D) :- mae(A, D). ancestral(A, D) :- /* A ´ ancestral de D */ e paimaeDe(A, D). ancestral(A, D) :- paimaeDe(A, Y), ancestral(Y, D). Uma estrutura cumpre um papel semelhante a um registro (record ou struct) em linguagens imperativas. Uma estrutura para representar um curso da universidade teria a forma curso( nome, professor, numVagas, departamento ) e poderia ser utilizada em cl´usulas da mesma forma que vari´veis e n´meros: a a u professor( Nome, curso(_, Nome, _, _) ). 83
  • 85. haVagas( curso(_, _, N, _) ) :- N 0. Uma estrutura pode ser atribu´ a uma vari´vel e emparelhada: ıda a ?- ED = curso( estruturasDeDados, joao, 30, dc ), professor(Nome, ED), haVagas(ED). Nome = joao yes ?- curso(icc, P, 60, Depart) = curso(C, maria, N, dc). C = icc P = maria N = 60 Depart = dc yes O sinal = ´ utilizado tanto para instanciar vari´veis como para comparar valores. Assim, se X n˜o e a a estiver instanciado, X = 2 instanciar´ X com 2. Se X tiver sido instanciado com o valor 3, “X = 2” ser´ avaliado como falso. a a Estude os exemplos abaixo. cmp(A, B) :- A = B. ?- cmp(X, 2). X = 2 yes ?- cmp(3, 2). no ?- X = 2, cmp(X, Y). X = 2 Y = 2 yes ?- cmp(X, Y). X = _1 Y = _1 yes _1 ´ o nome de uma vari´vel criada pelo Prolog. Obviamente ela n˜o est´ inicializada. e a a a Prolog n˜o avalia opera¸˜es aritm´ticas ` direita ou esquerda de =. Observe os exemplos a seguir. a co e a ?- 6 = 2*3. no ?- X = 2*3. 84
  • 86. X = 2*3 yes ?- 5 + 1 = 2*3. no O que seria a express˜o “2*3” ´ tratada como a estrutura “*(2,3)”. Se a avalia¸˜o da express˜o for a e ca a necess´ria, deve-se utilizar o operador is. Para resolver “X is exp” o sistema avalia exp e ent˜o: a a • compara o resultado com X se este estiver instanciado ou; • instancia X com o resultado de exp se X n˜o estiver instanciado. a Observe que exp ´ avaliado e portanto n˜o pode ter nenhuma vari´vel livre. Pode-se colocar valores e a a ou vari´veis do lado esquerdo de is. Veja alguns exemplos a seguir. a ?- X is 2*3. X = 6 yes ?- 6 is 2*3. yes ?- 5 + 1 is 2*3. no ?- X is 2*3, X is 6. X = 6 yes ?- X is 2*3, X is 3. no ?- 6 is 2*X. no Note que o ultimo goal falha pois X est´ livre. ´ a La¸os do tipo for podem ser simulados [17] utilizando-se o operador is: c for(0). for(N) :- write(N), NewN is N - 1, for(NewN). Este la¸o seria equivalente a c for i = N downto 1 do write(i); em S onde downto indica la¸o decrescente; isto ´, N = 1. c e Listas s˜o as principais estruturas de dados de Prolog. Uma lista ´ um conjunto de valores entre a e colchetes: 85
  • 87. [1, 2, 3] [jose, joao, pedro] [1, joao] Uma lista tamb´m ´ representada por e e [Head | Tail] onde Head ´ o seu primeiro elemento e Tail ´ a sublista restante. Assim, os exemplos de listas acima e e poderiam ser escritos como [1 | [2, 3]] [jose | [joao, pedro]] [1 | [joao]] O emparelhamento de [1, 2, 3] com [H | T] produz H = 1 T = [2, 3] Para emparelhar com [H | T], uma lista deve possuir pelo menos um elemento, H, pois T pode ser instanciado com a lista vazia, []. Com estas informa¸˜es, podemos construir um predicado que calcula o tamanho de uma lista: co length([], 0). length([H | T], N) :- length(T, M), N is M + 1. Este goal retornar´ em N o tamanho da lista L ou ir´ comparar N com o tamanho da lista. Exemplo: a a ?- length([1, 2, 3], N). N = 3 yes ?- length([], 0) yes Pode-se tamb´m especificar mais de um elemento cabe¸a para uma lista: e c semestre( [1, 2, 3, 4] ). ?- semestre( [X, _, Y | T] ). X = 1 Y = 3 T = [4] yes A seguir mostramos alguns outros exemplos de predicados que manipulam listas. Um predicado concat(A, B, C) que concatena as listas A e B produzindo C seria concat([], [], []). concat([], [H|T], [H|T]). concat([X|Y], B, [X|D]) :- concat(Y, B, D). 86
  • 88. Um predicado pertence(X, L) que sucede se X pertencer ` lista L seria a pertence(X, [X|_]). pertence(X, [_|T]) :- pertence(X, T). O predicado numTotalVagas utiliza a estrutura curso descrita anteriormente e calcula o n´mero u total de vagas de uma lista de cursos. /* numTotalVagas(N, L) significa que N ´ o numero total de vagas e nos cursos da lista L */ numTotalVagas( 0, [] ). numTotalVagas( Total, [ curso(_, _, N, _) | T ] ) :- numTotalVagas(TotalParcial, T), Total is TotalParcial + N. O predicado del(X, Big, Small) elimina o elemento X da lista Big produzindo a lista Small. del(X, [X|L], L). del(X, [_|Big], Small) :- del(X, Big, Small). 5.2 Cut e fail Cut ´ o fato ! que sempre sucede. Em uma meta e ?- pA(X), pB(X), !, pC(X). o cut (!) impede que haja retrocesso de pC(X) para !. Considerando a base de dados pA(joao). pA(pedro). pB(pedro). pC(joao). a tentativa de satisfa¸˜o do goal acima resulta no seguinte: ´ encontrado matching para pA(X) com ca e X = joao. O goal pB(joao) falha e h´ retrocesso para pA(X). A busca por matching por pA(X) a continua, resultando em X = pedro. O goal pB(pedro) sucede, como tamb´m !. O goal pC(pedro) e falha e ´ tentado retrocesso para !, que ´ proibido, causando a falha de todo o goal. e e Se o cut estiver dentro de um predicado, como em pD(X) :- pA(X), !, pB(X). pD(X) :- pA(X), pC(X). a tentativa de retrocesso atrav´s do ! causar´ a falha de todo o predicado. Por exemplo, a meta e a ?- pD(joao). emparelhar´ com pD(X), resultando na meta a pA(joao), !, pB(joao) pA(joao) sucede e pB(joao) falha. A tentativa de retrocesso para ! causar´ a falha de todo o a predicado pD, isto ´, do goal pD(joao). Se apenas o subgoal pA(X) da primeira cl´usula do predicado e a pD falhasse, seria tentado a segunda, 87
  • 89. pD(X) :- pA(X), pC(X) que seria bem sucedida, j´ que pA(joao) e pC(joao) sucedem. a O cut ´ utilizado para eliminar algumas possibilidades da ´rvore de busca. Eliminar um retrocesso e a para um predicado pA significa que algumas possibilidades de pA n˜o foram utilizadas, poupando a tempo. O operador fail sempre falha e ´ utilizado para for¸ar o retrocesso para o goal anterior. Por e c exemplo, o goal ?- homem(X), write(X), write(’ ’), fail. jose joao pedro no for¸a o retrocesso por todas as cl´usulas que emparelham “homem(X)”. Este operador pode ser utilizado c a [19] para implementar um comando while em Prolog: while :- pertence( X, [1, 2, 3, 4, 5] ), body(X), fail. body(X) :- write(X), write(’ ’). ?- while. 1 2 3 4 5 no O operador fail com o cut pode ser utilizado para invalidar todo um predicado: fat(N, P) :- /* fatorial de N ´ P */ e N 0, !, fail. fat(0, 1). fat(N, P) :- N 0, N1 is N - 1, fat(N1, P1), P is N*P1. Assim, o goal ?- fat(-5, P). no falha logo na primeira cl´usula. Sem o cut/fail, todas as outras regras do predicado seriam testadas. a Com o cut podemos expressar o fato de que algumas regras de um predicado s˜o mutualmente a exclusivas. Isto ´, se uma sucede, obrigatoriamente as outras falham. Por exemplo, fat poderia ser e codificado como fat(0, 1) :- !. fat(N, P) :- /* fatorial de N ´ P */ e N 0, N1 is N - 1, 88
  • 90. fat(N1, P1), P is N*P1. Assim, em ?- fat(0, P). P = 1; no seria feito um emparelhamento apenas com a primeira cl´usula, “fat(0, 1)”. Sem o cut nesta a cl´usula, o “;” que se segue a “P = 1” causaria um novo emparelhamento como “fat(N, P)”, a a segunda cl´usula do predicado, que falharia. a Observe que o cut foi introduzido apenas por uma quest˜o de eficiˆncia. Ele n˜o altera em nada a e a o significado do predicado. Este tipo de cut ´ chamado de cut verde. e Um cut ´ vermelho quando a sua remo¸˜o altera o significado do predicado. Como exemplo temos e ca /* max(A, B, C) significa que C ´ o maximo entre A e B */ e max(X, Y, X) :- X = Y, !. max(X, Y, Y). pertence(X, [X|_]) :- !. pertence(X, [_|T]) :- pertence(X, T). ?- max(5, 2, M). M = 5; no ?- pertence(X, [1, 2, 3]). X = 1; no Retirando o cut dos predicados, ter´ ıamos /* max(A, B, C) significa que C ´ o maximo entre A e B */ e max(X, Y, X) :- X = Y. max(X, Y, Y). pertence(X, [X|_]). pertence(X, [_|T]) :- pertence(X, T). ?- max(5, 2, M). M = 5; 89
  • 91. M = 2; no ?- pertence(X, [1, 2, 3]). X = 1; X = 2; X = 3; no O cut pode tanto melhorar a eficiˆncia (verdes, vermelhos) e o poder expressivo da linguagem (verdes) e como tornar o c´digo dif´ de entender (vermelhos). Freq¨entemente o cut vermelho remove a bidi- o ıcil u recionalidade dos argumentos de um predicado, como no caso de pertence. Sem o cut, este predicado poderia tanto ser utilizado para recuperar os elementos da lista, um a um, como para testar se um elemento pertence ` lista. Com o cut, pertence permite recuperarmos apenas o primeiro elemento da a lista. 5.3 Erros em Prolog Prolog ´ uma linguagem dinamicamente tipada e, conseq¨entemente, podem ocorrer erros de tipo em e u tempo de execu¸˜o. Por exemplo, em ca add(X, Y) :- Y is X + 1. ?- add ( [a, b], Y). tenta-se somar 1 a uma lista. Contudo, a maior parte dos que seriam erros de tipo simplesmente fazem as opera¸˜es de empar- co elhamento falhar, sem causar erros em execu¸˜o. Exemplo: ca dias(jan, 31). ... dias(dez, 31). ?- dias(N, jan). no Os compiladores Prolog geralmente n˜o avisam se um predicado n˜o definido ´ utilizado: a a e while :- peretnce(X, [1, 2, 3]), write(X), write(’ ’), fail. ?- while. no Neste caso, pertence foi digitado incorretamente como peretnce que nunca suceder´. a 90
  • 92. 5.4 Reaproveitamento de C´digo o Prolog ´ dinamicamente tipada e portanto suporta o polimorfismo causado por esta caracter´ e ıstica. Os parametros reais passados a um predicado podem ser de qualquer tipo, o que torna todo predicado potencialmente polimorfico. Por exemplo, para o predicado length([], 0). length([_ | L], N) :- length(L, NL), N is NL + 1. podem ser passadas como parˆmetro listas de qualquer tipo, reaproveitamento o predicado: a ?- length([greem, red], NumCores). NumCores = 2 yes ?- lenght([1, -5, 12], NumElem). NumElem = 3 yes Em Prolog, n˜o h´ defini¸˜o de quais parˆmetros s˜o de entrada e quais s˜o de sa´ de um a a ca a a a ıda predicado. De fato, um parˆmetro pode ser de entrada em uma chamada e de sa´ em outra. a ıda Utilizando o predicado pertence(X, [X | _]). pertence(X, [_ | C]) :- pertence(X, C). podemos perguntar se um elemento pertence a uma lista: ?- pertence(a, [b, e, f, a, g]). e tamb´m que elementos pertencem ` lista: e a ?- pertence(E, [b, e, f, a, g]). E = b; E = e; E = f; E = a; E = g; no No primeiro caso, o parˆmetro formal X ´ de entrada (por valor — a) e no segundo (E), de sa´ a e ıda. A conseq¨ˆncia do rac´ ue ıocionio acima ´ que temos duas fun¸˜es diferentes utilizando um unico e co ´ predicado. Logo, existe reaproveitamento de c´digo por n˜o ser fixo o tipo de passagem de parˆmetros o a a em Prolog. Outras linguagens exigiriam a constru¸˜o de dois procedimentos, um para cada fun¸˜o de ca ca pertence. Podemos comparar a caracter´ ıstica acima de Prolog com l´gica : dada uma f´rmula do c´lculo o o a proposicional, como ((a∧b)∨c), e valores de algumas da vari´veis e/ou resultado da express˜o, pode- a a mos obter o valor das vari´veis restantes. Por exemplo a • se ((a∧b)∨c) = true e a = true, c = false, ent˜o b dever´ ser true. a a • se ((a∧b)∨c) = true e a = true, c = true, b poder´ ser true ou false. a 91
  • 93. No predicado pertence h´ uma constru¸˜o semelhante: a ca • se pertence(E, [b, e, f, a, g]) = true, ent˜o E = b ou E = e, ... ou E = g. a E pode assumir diversos valores para fazer pertence(E, [b, e, f, a, g]) true, da mesma forma que, na ultima f´rmula, b pode assumir dois valores (true ou false) para fazer a equa¸˜o verdadeira. ´ o ca Esta forma de reaproveitamento de c´digo ´ exclusiva das linguagens l´gicas e n˜o se relaciona a o e o a polimorfismo — s˜o conceitos diferentes. a 5.5 Manipula¸˜o da Base de Dados ca O predicado assert sempre sucede e introduz um novo fato ou regra na base de dados: ?- otimo( zagalo ). no ?- assert( otimo(zagalo) ). yes ?- otimo(zagalo). yes O predicado retract remove um fato ou regra da base de dados: ?- retract( otimo(zagalo) ). yes ?- otimo(zagalo). no ?- assert( otimo(luxemburgo) ). yes assert pode fazer um programa em Prolog “aprender” durante a sua execu¸˜o. O que ele ca aprende pode ser gravado em disco e recuperado posteriormente. Por exemplo, considere um predicado fatorial que calcula o fatorial de um n´mero e armazena os valores j´ calculados na base de dados. u a /* fat(N, P) significa que o fatorial de N ´ P */ e fat(0, 1). /* fatorial(N, P) significa que o fatorial de N ´ P */ e fatorial(N, P) :- fat(N, P). fatorial(N, P) :- N1 is N - 1, fatorial(N1, P1), P is P1*N, assert( fat(N, P) ). 92
  • 94. Inicialmente, h´ apenas um fato para o predicado fat. Quando fatorial for invocado, como em a ?- fatorial(3, P). P = 6 ser˜o introduzidos na base de dados fatos da forma fat(N, P). Neste caso, a base de dados conter´ a a os seguintes fatos de fat: fat(0, 1). fat(1, 1). fat(2, 2). fat(3, 6). Agora, quando o fatorial de 3 for novamente requisitado, ele ser´ tomado da base de dados, o que ´ a e muito mais r´pido do que calcul´-lo novamente por sucessivas multiplica¸˜es. a a co assert pode tamb´m incluir regras na base de dados: e ?- assert( (otimo(X) :- not (X = zagalo)) ). A regra deve vir dentro de parˆnteses. e Nos exemplos anteriores, admitimos que os fatos e regras introduzidos na base de dados por assert s˜o sempre colocados no fim da base. Se for necess´rio introduzi-los no in´ a a ıcio, podemos utilizar asserta. Se quisermos explicitar que os fatos ou regras s˜o introduzidos no final da base, a podemos utilizar assertz. 5.6 Aspectos N˜o L´gicos de Prolog a o Algumas constru¸˜es de Prolog e o pr´prio algoritmo de unifica¸˜o fazem com que esta linguagem n˜o co o ca a seja completamente l´gica. Em l´gica, uma express˜o “A and B” ´ idˆntica a “B and A” e “A or B” o o a e e ´ idˆntica a “B or A”. Em Prolog, isto n˜o ´ sempre verdadeiro. O “and” aparece em regras como e e a e R(X) :- A(X), B(X) em que R(X) ser´ verdadeiro se A(X) e B(X) forem verdadeiros. Em Prolog, a invers˜o de A e B na a a regra, resultando em R(X) :- B(X), A(X). pode produzir resultados diferentes da regra anterior, violando a l´gica. o O “or” aparece quando h´ mais de uma regra para um mesmo predicado ou quando usamos “;”: a R(X) :- A(X) ; B(X). S(X) :- A(X). S(X) :- B(X). R(X) (ou S(X)) ser´ verdadeiro se A(X) ou B(X) o forem. Novamente, os dois predicados acima podem a apresentar resultados diferentes se reescritos como R(X) :- B(X) ; A(X). S(X) :- B(X). S(X) :- A(X). Em l´gica matem´tica, dada uma express˜o qualquer como “A and B” e os valores das vari´veis, o a a a podemos calcular o resultado da express˜o. E dado o valor da express˜o e de todas as vari´veis, exceto a a a uma delas, podemos calcular o valor ou valores desta vari´vel. Assim, se “A and B” for falso e A for a verdadeiro, saberemos que B ´ falso. Em Prolog esta regra nem sempre ´ verdadeira. Quando n˜o for, e e a 93
  • 95. diremos que n˜o h´ bidirecionalidade entre os argumentos de entrada e sa´ a a ıda. Em uma linguagem l´gica pura, qualquer argumento pode ser de entrada ou de sa´ o ıda. Idealmente, um programador de Prolog deveria se preocupar apenas em especificar as rela¸˜es co l´gicas entre os parˆmetros de cada predicado. O programador n˜o deveria pensar em como o algoritmo o a a de unifica¸˜o trabalha para satisfazer as rela¸˜es l´gicas especificadas pelo programa. Desta forma, ca co o o programador estaria utilizando rela¸˜es l´gicas est´ticas, bastante abstratas, ao inv´s de pensar co o a e em rela¸˜es dinˆmicas que s˜o dif´ co a a ıceis de entender. Contudo, para tornar Prolog eficiente v´rias a constru¸˜es n˜o l´gicas, citadas a seguir, s˜o suportadas pela linguagem. co a o a • O is for¸a uma express˜o a ser avaliada quebrando a simetria exigida pela l´gica. Isto ´, um c a o e goal 6 is 2*X n˜o ser´ v´lido se X n˜o estiver instanciado. Por este motivo, a ordem dos goals no corpo de um a a a a predicado ´ importante. O predicado length (tamanho de uma lista) n˜o pode ser implementado e a como length([], 0). length([_|L], N) :- N is N1 + 1, length(L, N1). pois N n˜o estaria instanciado no goal “N1 is N + 1” em uma pergunta a ?- length([1, 2], X). • O cut tamb´m for¸a os goals a uma determinada ordem dentro de um predicado. Mudando-se a e c ordem, muda-se o significado do predicado. A ordem em que as cl´usulas s˜o colocadas podem a a se tornar importantes por causa do cut. Assim, o predicado max(X, Y, X) :- X = Y, !. max(X, Y, Y). n˜o poderia ser escrito como a max(X, Y, Y). max(X, Y, X) :- X = Y, !. O cut remove a bidirecionalidade da entrada e sa´ como no caso do predicado pertence. Se ıda este for definido como pertence(X, [X | _]) :- !. pertence(X, [_ | C]) :- pertence(X, C). 94
  • 96. o goal ?- pertence(X, [1, 2, 3]). n˜o serve para obter, por meio do X, todos os elementos da lista. Isto ´ o mesmo que dizer que, a e dado que A ´ verdadeiro e o resultado de A and B ´ falso, o sistema n˜o consegue dizer o valor e e a de B. • A ordem com que Prolog faz a unifica¸˜o altera o significado dos predicados. Por exemplo, ca suponha que o predicado ancestral fosse definido como ancestral(A, D) :- /* A ´ ancestral de D */ e ancestral(Y, D), paimaeDe(A, Y). ancestral(A, D) :- paimaeDe(A, D). Agora o goal ?- ancestral(jose, pedro). faz o sistema entrar em um la¸o infinito, apesar do goal ser verdadeiro. c • O not pode ser definido como not(P) :- P, !, fail. not(P). Para satisfazer um goal not(P), Prolog tenta provar que P ´ verdadeiro. Se for, not(P) falha. e Esta forma de avalia¸˜o pode fazer a ordem dos goals de um predicado importante. Um exemplo, ca tomado de [18], ´: e r(a). q(b). p(X) :- not r(X). ?- q(X), p(X). X = b Mas, invertendo o goal, ?- p(X), q(X). no o resultado ´ diferente. e • As rotinas assert e retract de manipula¸˜o da base de dados podem conduzir aos mesmos ca problemas que o cut e o not. Por exemplo, chuva :- assert(molhado). ?- molhado, chuva. no 95
  • 97. ?- chuva, molhado. yes ?- molhado, chuva. yes • rela¸˜es l´gicas n˜o podem representar entrada e sa´ de dados, que possuem problemas semel- co o a ıda hantes a assert e retract. Por exemplo, um predicado que lˆ um arquivo pode retornar em e um dos seus parˆmetros valores diferentes em diferentes chamadas. Ent˜o o conceito de estado, a a estranho ` l´gica, ´ introduzido na linguagem. a o e 5.7 Discuss˜o Sobre Prolog a Um programa em Prolog ´ formado pela base de dados (BD) (cl´usulas) e pelo algoritmo de unifica¸˜o e a ca (AU). A BD cont´m as rela¸˜es l´gicas entre objetos e o AU ´ o meio de descobrir se dado goal ´ e co o e e verdadeiro de acordo com a BD. Assim, programa = BD + AU O programa em Prolog possui instru¸˜es que est˜o impl´ co a ıcitas no algoritmo de unifica¸˜o e, portanto, ca n˜o precisam ser colocadas na base de dados. Por exemplo, usando o BD a dias(jan, 31). dias(fev, 28). ... dias(dez, 31). podemos saber o n´mero de dias do mˆs de Setembro sem precisar escrever nenhum algoritmo: u e ?- dias(set, N). a busca por N ´ feita pelo AU. No caso geral, parte das repeti¸˜es (la¸os, incluindo recurs˜o) e testes e co c a est˜o na BD e parte no AU. No caso acima, a BD n˜o contribui com nenhuma repeti¸˜o ou teste. a a ca Um outro exemplo ´ um predicado para verificar se elemento X pertence a uma lista: e membro(X, [X | L]). membro(X, [Y | L]) :- membro(X, L). A fun¸˜o equivalente utilizando a sintaxe de S ´ mostrada na Figura 5.1. ca e A fun¸˜o em S possui muito mais detalhes que a de Prolog. Contudo, o predicado em Prolog ca possui opera¸˜es equivalentes `quelas da fun¸˜o em S. Algumas destas opera¸˜es est˜o impl´ co a ca co a ıcitas no algoritmo de unifica¸˜o e outras expl´ ca ıcitas no programa. No predicado membro, as opera¸˜es co (p == nil) e (p.elem == x) est˜o impl´ a ıcitas no AU, pois estes testes s˜o feitos no emparelhamento a com as cl´usulas de membro. A opera¸˜o p == nil ´ equivalente a n˜o obter emparelhamento, j´ a ca e a a que a lista ´ vazia, e que resulta em falha do predicado. E p.elem == x ´ equivalente a obter e e emparelhamento com a primeira cl´usula, membro(X, [X|L]). a A obten¸˜o do elemento da frente da lista ´ feito com . em S e pela conven¸˜o de separar uma lista ca e ca em cabe¸a e cauda ([H | T]) em Prolog. No predicado membro, a unica opera¸˜o realmente expl´ c ´ ca ıcita ´ a recurs˜o na segunda cl´usula que aparece disfar¸ada de uma defini¸˜o de cl´usula. e a a c ca a A fun¸˜o e o predicado membro demonstram que nem todas as opera¸˜es precisam estar explicitadas ca co nas cl´usulas (BD), pois as opera¸˜es contidas no AU fazem parte do programa final. a co A linguagem Prolog ´ adequada justamente para aquele problemas cujas solu¸˜es algoritmicas e co possuem semelhan¸a com o algoritmo de unifica¸˜o. Sendo semelhantes, a maior parte da solu¸˜o c ca ca 96
  • 98. proc membro( p, x ) begin if p == nil then return false; else if p.elem == x then return true; else return membro(p.suc, x); endif endif end; Figura 5.1: Fun¸˜o para descobrir se x ´ membro da lista p ca e pode ser deixada para o AU, tornando a BD muito mais f´cil de se fazer (mais abstrata). A parte a n˜o semelhante ao AU deve ser codificada nas regras, como a chamada recursiva a membro na segunda a cl´usula do exemplo anterior. a De um modo geral, uma linguagem ´ boa para resolver determinados problemas se estes s˜o e a facilmente mapeados nela. Neste caso, a linguagem possui, implicitamente, os algoritmos e estruturas de dados mais comumente usados para resolver estes problemas. 97
  • 99. Cap´ ıtulo 6 Linguagens Baseadas em Fluxo de Dados Linguagens baseadas em fluxo de dados (data flow) obt´m concorrˆncia executando qualquer instru¸˜o e e ca t˜o logo os dados que ela utiliza estejam dispon´ a ıveis. Assim, uma chamada de fun¸˜o f(x) (ou uma ca atribui¸˜o “y = x”) ser´ executada t˜o logo a vari´vel x tenha recebido um valor em alguma outra ca a a a parte do programa — ent˜o vari´veis podem estar em um de dois estados: inicializadas ou n˜o. N˜o a a a a interessa onde f(x) (ou y = x) est´ no programa ou se as instru¸˜es que o precedem textualmente no a co c´digo fonte j´ tenham sido executadas: f(x) (ou y = x) ser´ executada t˜o logo a vari´vel x tenha o a a a a um valor. Ent˜o a execu¸˜o do programa obedece `s dependˆncias entre os dados e n˜o a ordem a ca a e a textual das instru¸˜es no c´digo fonte. co o A execu¸˜o de um programa em uma linguagem data flow utiliza um grafo de fluxo de dados (GFD) ca no qual os v´rtices representam instru¸˜es e as arestas as dependˆncias entre elas. Haver´ uma aresta e co e a (v, w) no grafo se w depender dos dados produzidos em v. Como exemplo, considere as atribui¸˜es co do procedimento proc m(b, d, e) { declara e ja inicializa as variaveis } var a = b + 1, { 1 } c = d/2 + e, { 2 } i = a + 1, { 3 } f = 2*i + c, { 4 } h = b + c, { 5 } k = f + c; { 6 } is return a + c + i + f + h + k; Ent˜o h´ uma aresta (representada por uma seta) de 1 para 3. O GFD das instru¸˜es acima est´ na a a co a Figura 6. As inicializa¸˜es do procedimento m podem ser executadas em v´rias ordens poss´ co a ıveis: 1 2 3 4 5 6 2 5 1 3 4 6 2 1 3 4 6 5 ... 98
  • 100. Figura 6.1: Um grafo do fluxo de dados Se n˜o h´ caminho ligando a instru¸˜o V a U, ent˜o estas instru¸˜es s˜o independentes entre si e a a ca a co a podem ser executadas concorrentemente. Por exemplo, podem ser executadas em paralelo as instru¸˜es co 1 e 2 3 e 5 4 e 5 5 e 6 ... Um programa em uma linguagem data flow (LDF) ´ transladado em um GDF que ´ executado e e por uma m´quina data flow.1 Esta m´quina tenta executar tantas intru¸˜es em paralelo quanto ´ a a co e poss´ ıvel, respeitando as dependˆncias entre elas. Conseq¨entemente, a ordem de execu¸˜o n˜o ´ e u ca a e necessariamente a ordem textual das instru¸˜es no programa. Por exemplo, a instru¸˜o 5 poderia ser co ca executada antes da 3 ou da 4. Podemos imaginar a execu¸˜o de um programa data flow como valores fluindo entre os n´s do ca o GDF. A instru¸˜o de um dado n´ poder´ ser executada se houver valores dispon´ ca o a ıveis nos n´s de que o ela depende. Um n´ representando uma vari´vel possui valor dispon´ o a ıvel ap´s ela ser usada do lado o esquerdo de uma atribui¸˜o: ca a = exp Por exemplo, a instru¸˜o 4 s´ poder´ ser executada se h´ valores em 2 e 3 (valores de c e i). ca o a a Uma conseq¨ˆncia das regras da dependˆncia ´ que cada vari´vel deve ser inicializada uma unica ue e e a ´ vez. Caso contr´rio, haveria n˜o determinismo nos programas. Por exemplo, em a a proc p() : integer var a = 1, { 1 } b = 2*a, { 2 } a = 5; { 3 } is a + b; 1 Obviamente, qualquer computador poderia executar este programa, mas sup˜e-se que um computador data flow o seria mais eficiente. 99
  • 101. Figura 6.2: Um grafo do fluxo de dados de um comando if Figura 6.3: Um grafo do fluxo de dados de um comando while o valor final de b poderia ser 2 ou 10, pois a seq¨ˆncia de execu¸˜o das atribui¸˜es poderia ser ue ca co 1 2 3 ou 3 2 1 entre outras. A exigˆncia de uma unica inicializa¸˜o ´ chamada de regra da atribui¸˜o unica. e ´ ca e ca ´ Um comando if em uma linguagem data flow t´ ıpica ´ da forma e if exp then exp1 else exp2 como em linguagens funcionais. A express˜o exp1 s´ ser´ avaliada se a express˜o exp do if for a o a a true. Como a ordem de execu¸˜o s´ depende da disponibilidade de dados, a express˜o exp1 ´ feita ca o a e dependente de exp da seguinte forma: se exp resultar em true, exp1 recebe um token que habilita a sua avalia¸˜o. Sem este token, exp1 n˜o ser´ avaliada mesmo que todos os outros valores de que ela ca a a depende estejam dispon´ ıveis. O grafo de fluxo de dados do if if a b then c + 1 else d - 1 est´ na Figura 6. a Comandos while funcionam de forma semelhante aos if’s. Os comandos do corpo do while s˜o a dependentes de um valor true resultante da avalia¸˜o da express˜o condicional. O GFD do while do ca a c´digo o i = 1; 100
  • 102. p = 1; while i = N do begin p = p*i; i = i + 1; end est´ na Figura 6. a Este c´digo permite a modifica¸˜o de vari´veis de controle dentro do la¸o, violando a regra de o ca a c atribui¸˜o unica. H´ duas atribui¸˜es para i e duas para p. Este problema ´ contornado permitindo ca ´ a co e a cria¸˜o de uma nova vari´vel i (e p) a cada itera¸˜o do la¸o. Assim, temos um stream de valores ca a ca c para i e outro para p. As m´quinas data flow associam tags aos valores de i e p de tal forma que os a valores de um passo do la¸o n˜o s˜o confundidos com valores de outros passos. c a a Considere que a multiplica¸˜o p*i seja muito mais lenta que a atribui¸˜o i = i + 1 e o teste ca ca i = N, de tal forma que o la¸o avan¸a rapidamente na instru¸˜o i = i + 1 e lentamente em p = p*i. c c ca Isto ´, poder´ e ıamos ter a situa¸˜o em que i = 12 (considerando N = 15) mas p ainda est´ sendo ca a multiplicado por i = 3. Haveria diversos valores de i esperando para serem multiplicados por p. Estes valores n˜o se confundem. O valor de p da k-´sima itera¸˜o ´ sempre multiplicado pelo valor de a e ca e i da k-´sima itera¸˜o para resultar no valor de p da (k + 1)-´sima itera¸˜o. e ca e ca Em uma chamada de fun¸˜o, alguns parˆmetros podem ser passados antes dos outros e podem ca a causar a execu¸˜o de instru¸˜es dentro da fun¸˜o. ca co ca Considere a fun¸˜o ca proc p(x, y : integer) : integer var z = x + 3, { 1 } t = x + 1; { 2 } is z + t + 2 * y; { 3 } e a chamada de fun¸˜o p ca proc q() var a = 1, b = f(2), k = p(a, b); is ... onde a j´ recebeu um valor, mas o valor de b ainda est´ sendo calculado. Admita que o c´lculo de a a a f(2) ´ demorado. O parˆmetro a ´ passado a p e causa a execu¸˜o das instru¸˜es 1 e 2. A instru¸˜o e a e ca co ca 3 ´ executada t˜o logo o valor de b esteja dispon´ e seja passado a p. e a ıvel Linguagens data flow utilizam granuralidade muito fina de paralelismo gerando uma quantidade enorme de tarefas2 executadas paralelamente. Os recursos necess´rios para gerenciar este paralelismo a s˜o gigantescos e exigem necessariamente uma m´quina data flow. Se o c´digo for compilado para uma a a o m´quina n˜o data flow, mesmo com v´rios processadores, haver´ uma enorme perda de desempenho. a a a a Uma linguagem contendo apenas os conceitos expostos neste cap´ ıtulo deixa de aproveitar as maiores 2 Tarefas referem-se a trechos de c´digo e n˜o a processos do Sistema Operacional. o a 101
  • 103. oportunidades de paralelismo encontradas em programas reais, que s˜o: a manipula¸˜o de vetores a ca inteiros de uma vez pela m´quina sobre os quais podem ser aplicadas as opera¸˜es aritm´ticas. Uma a co e granuralidade mais alta de paralelismo ´ tamb´m interessante pois a quantidade de comunica¸˜o entre e e ca os processos em paralelo ´ minimizada. Contudo a linguagem que definimos n˜o permite a defini¸˜o e a ca de processos com execu¸˜o interna seq¨encial mas executando em paralelo com outros processos. ca u 102
  • 104. Referˆncias Bibliogr´ficas e a [1] America, Pierre; Linden, Frank van der. A Parallel Object-Oriented Language with Inheritance and Subtyping, SIGPLAN Notices, Vol. 25, No. 10, October 1990. ECOOP/OOPSLA 90. [2] Lippman, Stanley B. C++ Primer. Addison-Wesley, 1991. [3] Deitel, H.M. e Deitel P.J. C++ How to Program. Prentice-Hall, 1994. [4] GC FAQ — draft. Available at http://www.centerline.com/people/chase/GC/GC-faq.html [5] Goldberg, Adele; Robson, David. Smalltalk-80: the Language and its Implementation. Addison- Wesley, 1983. [6] Hoare, Charles A. R. The Emperor’s Old Clothes. CACM, Vol. 24, No. 2, February 1981. [7] Guimar˜es, Jos´ de Oliveira. The Green Language home page. Available at a e http://www.dc.ufscar.br/~jose/green. [8] Guimar˜es, Jos´ de Oliveira. The Green Language. Computer Languages, Systems Structures, a e Vol. 32, Issue 4, pages 203-215, December 2006. [9] Guimar˜es, Jos´ de Oliveira. The Object Oriented Model and Its Advantages. OOPS Messenger, a e Vol. 6, No. 1, January 1995. [10] Kjell, Bradley. Introduction to Computer Science using Java. Available at http://chortle.ccsu.edu/CS151/cs151java.html. [11] Niemeyer, P. and Peck, J. (1997) Exploring Java. O’Reilly Associates, Sebastopol. [12] Rojas, Ra´l, et al. (2000). Plankalk¨l: The First High-Level Programming Language u u and its Implementation. Institut f¨r Informatik, Freie Universit¨t Berlin, Technical Report u a B-3/2000. Available at http://www.zib.de/zuse/Inhalt/Programme/Plankalkuel/Plankalkuel- Report/Plankalkuel-Report.htm. [13] Slater, Robert. Portraits in Silicon. MIT Press, 1989. [14] Stroustrup, Bjarne. The C++ Programming Language. Second Edition, Addison-Wesley, 1991. [15] Wegner, Peter. Research Directions in Object-Oriented Programming, chapter The Object- Oriented Classification Paradigm, pp. 479–559. MIT Press, 1987. [16] Weinberg, Gerald M. The Psychology of Computer Programming, Van Nostrand Reinhold, 1971. [17] Ben-Ari, M. Understanding Programming Languages. John Wiley Sons, 1996. 103
  • 105. [18] Bratko, Ivan. Prolog Programming for Artificial Intelligence. International Computer Science Series, 1986. [19] Finkel, Raphael. Advanced Programming Language Design. Addison-Wesley, 1996. 104