Liberty Eiffel
The GNU Eiffel compiler

Paolo Redaelli - Co-maintainer

Eiffel - il linguaggio

  • 1985 - prof. Bertrand Meyer
  • Ad oggetti "puro" anzi, purissimo. Tutto è un oggetto, sì, anche gli interi
  • Sintassi tipo Pascal
  • Design by contract nativo
  • Ereditarietà multipla.
    class DOTTORANDO inherit STUDENTE DOCENTE … 
  • Semantica per valore (tipi espansi) o per riferimento
  • Classi generiche, cioè parametrizzate da altre classi e parametrizzazione vincolata
                class PERSONS inherit DICTIONARY[NAME,PERSON->COMPARABLE]…
                  
  • Overloading degli operatori (ma non dei metodi)

Liberty Eiffel

  • 1994: Origini accademiche SmallEiffel (accademico, "con radici in Smalltalk"): 1994, 1997 part of GNU
  • SmartEiffel:
    2002 (1.0): 8, 16, 64-bit integers,
    2004 (2.0): non-conforming inheritance, not implementing ECMA TC39-TG4,
    2005 (2.1): safe downcasting ?:= ::= , 32-64-128-bits floats
    2006 (2.2): plugin for interfacing, language made case-sensitive,
    2007 (2.3): eiffeltest, eiffeldoc NONE {},
  • 2013: Liberty Eiffel "free as in freedom", liberated from "academic constraints" (and aiming to "reconcile the scism").
    • 2013: Adler: inlined dynamic dispatch, NATURAL, FIXED_STRING
    • 2016: Bell: closures, wrappers-generator
    • … Curtiss: *BSD support, convert

Separation of concerns

  • Modularity
  • Encapsulation
  • Information hiding

Esempio di codice

class EULERO -- Project Euler Question 1
create {ANY} make
feature {ANY}
   low: INTEGER 1; high: INTEGER 999
   make do
     io.put_string("Sum of all naturals divisible by 3 or 5 lower than 1000 is: ")
     (low |..| high).aggregate(
        agent (progressive, i: INTEGER): INTEGER
           do
              if i.divisible(3) or i.divisible(5) then Result := progressive + i
              else Result := progressive
            end
         end (?, ?), 0).print_on(std_output)
    end
  end -- class EULERO
          

Eiffel - le peculiarità

Design by Contract

Ovvero della formalizzazione dei contratti.

  • Precondizioni
  • Postcondizioni
  • Invarianti (di classe e di ciclo)… e varianti

Precondizioni e postcondizioni

             deferred class INDEXABLE [E_]
…
  item (i: INTEGER): E_
    -- Item at the corresponding index `i'.
  require valid_index(i)
  deferred
  end
            
          deferred class COLLECTION [E_]
inherit STORABLE… TRAVERSABLE[E_]…  SEARCHABLE[E_]…
frozen infix "@" (i: INTEGER): E_
    -- The infix notation, just a synonym for `item'.
  require valid_index(i)
  do Result := item(i)
  ensure definition: Result = item(i)
  end

feature add_last (element: E_) -- Add a new item at the end.
  deferred
  ensure
    last = element
    count = 1 + old count
    lower = old lower
    upper = 1 + old upper         
          

Precondizioni, postcondizioni ed ereditarietà

Le precondizioni possono solo essere allentate, meno obblighi per il chiamante.

Le postcondizioni posono solo essere rinforzate, più garanzie per il cliente.

Vediamo un esempio "al negativo"

               
              class PARENT
              feature negate(x: INTEGER): INTEGER
                require positive: x>0
              
            
               
              class HEIR 
              inherit PARENT
                redefine negate
              feature negate (x: INTEGER): INTEGER
              require big: x>100
              
            

            class MY_PROGRAM
            …
            feature p: PARENT; h: HEIR
           
          
            @startuml
            abstract class ABSTRACT_HASHED_DICTIONARY
            abstract class ABSTRACT_STRING {
              infix "+"
              infix "|"
              infix "&"
              infix "#"
              infix "^"
            }
            abstract class ARRAYED_COLLECTION
            abstract class COLLECTION {
              infix "@"
              put
              has
            }
            abstract class COMPARABLE {
              infix "<"
              infix ">"
              compare
              in_range 

            }
            abstract class HASHABLE { 
              hash_code 
              }
            abstract class HOARD {
              count
              is_empty
            }
            abstract class INDEXABLE { 
              item 
              lower
              upper 
              }
            abstract class ITERABLE {
              new_iterator
              for_each
              for_all
              exists
              aggregate
            }
            abstract class MAP {
              has
              at
            }
            abstract class NATIVELY_STORED_STRING
            abstract class PARTIALLY_FILLED_STRING
            abstract class SEARCHABLE {
              has
              index_of
            }
            abstract class TRAVERSABLE {
              enumerate
            }
            abstract class ABSTRACT_AVL_DICTIONARY
            class AVL_DICTIONARY 
            abstract class ABSTRACT_PYTHON_DICTIONARY
            abstract class AVL_TREE
            class ARRAY
            abstract class ARRAYED_DICTIONARY
            abstract class DICTIONARY {
              at
              put
              remove
            }
            class FAST_ARRAY
            class FIXED_STRING
            class HASHED_DICTIONARY
            class LAZY_STRING
            abstract class LINKED_COLLECTION
            class LINKED_LIST
            class PYTHON_DICTIONARY
            class QUEUE
            class RING_ARRAY
            class ROPE
            abstract class SIMPLE_DICTIONARY
            abstract class STORABLE
            class STRING {
              put
            }
            class TWO_WAY_LINKED_LIST

            ABSTRACT_AVL_DICTIONARY --|> SIMPLE_DICTIONARY
            ABSTRACT_AVL_DICTIONARY ..|> AVL_TREE
            ABSTRACT_HASHED_DICTIONARY --|> SIMPLE_DICTIONARY
            ABSTRACT_PYTHON_DICTIONARY --|> SIMPLE_DICTIONARY
            ABSTRACT_STRING --|> COMPARABLE
            ABSTRACT_STRING --|> HASHABLE
            ABSTRACT_STRING --|> SEARCHABLE
            ABSTRACT_STRING --|> STORABLE
            ABSTRACT_STRING --|> TRAVERSABLE
            AVL_DICTIONARY --|> ABSTRACT_AVL_DICTIONARY

            ARRAY --|> COLLECTION
            ARRAY --|> ARRAYED_COLLECTION
            FAST_ARRAY --|> COLLECTION
            FAST_ARRAY --|> ARRAYED_COLLECTION
            ARRAYED_COLLECTION --|> COLLECTION
            ARRAYED_DICTIONARY --|> DICTIONARY
            COLLECTION --|> SEARCHABLE
            COLLECTION --|> STORABLE
            COLLECTION --|> TRAVERSABLE
            DICTIONARY --|> MAP
            FIXED_STRING --|> NATIVELY_STORED_STRING
            HASHED_DICTIONARY --|> ABSTRACT_HASHED_DICTIONARY
            INDEXABLE --|> HOARD
            ITERABLE --|> HOARD
            LAZY_STRING --|> ABSTRACT_STRING
            LINKED_COLLECTION --|> COLLECTION
            LINKED_LIST --|> COLLECTION
            LINKED_LIST ..|> LINKED_COLLECTION
            MAP --|> TRAVERSABLE
            NATIVELY_STORED_STRING --|> ABSTRACT_STRING
            PARTIALLY_FILLED_STRING --|> ABSTRACT_STRING
            PYTHON_DICTIONARY --|> ABSTRACT_PYTHON_DICTIONARY
            QUEUE ..|> RING_ARRAY
            RING_ARRAY --|> COLLECTION
            RING_ARRAY ..|> ARRAYED_COLLECTION 
            ROPE --|> ABSTRACT_STRING
            SEARCHABLE --|> INDEXABLE
            SIMPLE_DICTIONARY --|> DICTIONARY
            STRING --|> NATIVELY_STORED_STRING
            TRAVERSABLE --|> INDEXABLE
            TRAVERSABLE --|> ITERABLE
            TWO_WAY_LINKED_LIST  --|> COLLECTION
            TWO_WAY_LINKED_LIST ..|> LINKED_COLLECTION
            @enduml
          
        
qui per ingrandire

Invarianti

Per garantire proprietà riflessive, i.e. "io sono il coniuge del mio coniuge":
            
            class PERSON
            feature spouse: PERSON
            invariant
            (spouse /= Void) implies (spouse.spouse = Current) 
            
            

Vale anche per "germano" che significa "fratello o sorella".

Il classico Fibonacci, ma con un ciclo

 fibonacci_loop (n: INTEGER): like n
    require n>=0
    local i,p,q: like n
    do
      inspect n
      when 0 then Result := 0
      when 1 then Result := 1
      else
        from i:=2; p:=0; q:=1; Result:=1
        invariant Result > 0
        variant n-i -- shall decrease
        until i>=n
        loop 
          p := q; q := Result
          Result := p + q; i := i+1
        end
      end 
    end
         

Command-query separation

  • Command: feature che cambia lo stato dell'oggetto. Non ha valori di ritorno
  • Query: feature ("funzione") che ha un risultato. Non deve dovrebbe avere effetti collaterali.
    ensure no_side_effects: Current.is_equal(old Current) 

Uniform-access principle

Attributo o funzione? È una scelta dell'implementazione!

Il perimetro dell'ellisse (vedi ELLIPSE negli esempi


class ELLIPSE
inherit ANY 
insert MATH_CONSTANTS export {} all end
create{ANY}
  with_axes, 
  with_foci_semidistance_and_eccentricity
feature {ANY}
  perimeter: REAL 
    -- Implemented using a Ramanujan's approximation https://en.wikipedia.org/wiki/Perimeter_of_an_ellipse
  do
    Result := Pi * (3 * (a+b) - ((3*a+b)*(a+3*b)).sqrt)
  end

  a: REAL -- major semiaxis
  b: REAL -- minor semiaxis

feature {ANY}
  set_a (a_value: REAL) assign a do a:=a_value end 
  set_b (a_value: REAL) assign b do b:=a_value end 
feature {ANY}
  with_axes (major,minor: like a) 
  require major>minor 
  do
    a := major/2
    b := minor/2
  end
  with_foci_semidistance_and_eccentricity (c, e: REAL)
    require 
      meaningful_foci_semidistnace: c>0
      meaningful_eccentricity: e>0
    do
      a := c/e
      b := (a^2-c^2).sqrt
    end
end 

          
 
             class ELLIPSE ... 
feature {ANY}
  perimeter: REAL do
    Result := Pi * (3 * (a+b) - ((3*a+b)*(a+3*b)).sqrt)
  end
  ...
end 
              
 
             class ELLIPSE2
inherit ELLIPSE redefine with_axes, perimeter
create{ANY} with_axes 
feature {ANY} 
  perimeter: REAL attribute
  with_axes (major,minor: like a) 
  do
    Precursor(major, minor)
    perimeter := 2 * Pi * ((a^2+b^2)/2).sqrt
  end
end 
              
 
             class UNIFORM_ACCESS_PRINCIPLE create {} compute_perimeter
feature {ANY} compute_perimeter do
    create ellipse.with_axes(5.0, 3.0)
    ("Perimeter of ellipse with semi-axes #(1) and #(2) equals #(3)%N" 
    # ellipse.a.out # ellipse.b.out
    # ellipse.perimeter.out).print_on(std_output)
    ellipse.b := 2.0 
    create {ELLIPSE2} another.with_axes(5.0, 3.0) 
    ("Crudely precomputed perimeter of another ellipse with same semi-axes is #(1)%N" 
    # another.perimeter.out).print_on(std_output)
  end
  ellipse: ELLIPSE; another: ELLIPSE
end
              

Eiffel vs C#

 
public class Foo {
    private string _name;

    public int Size { // Property
        get;    // Getter
        set;    // Setter
    }

    public string Name { // Property
        get { return _name; }     // Getter
        set { _name = value; }    // Setter
    }
}
```
               
 
class FOO
feature {ANY}
  name: STRING
  size: INTEGER

 feature {PIPPO}
  set_name (a_value: STRING) assign name 
    do name:=a_value end 
feature {BAR}
  set_size (a_value: like size) assign size 
    require a_value > 12
    do size:=a_value end
end
               

Single-choice principle

Or "Don't repeat yourself" (DRY) Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list.

Open-closed principle

Mi apro alla chiusura Mi apro alla chiusura
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be extended without modifying its source code.
Square symmetries from https://en.wikipedia.org/wiki/Rectangle

option–operand separation

Gli argomenti, solo quelli necessari … E le opzioni? Sono comandi separati! Ovvero non

 
            operazione (arg1, arg2, option="->", another_option=23)
         

Bensì:

        set_option("->"); set_another_option(23)
            operazione (arg1,arg2)  

O meglio ancora (usando assign)

         option:="->"; another_option:=23; be_verbose; operazione(arg1, arg2) 
         

Perché la option-operand separation?

  • Facilità di apprendimento: richieste (queries/funzioni) e comandi (commands) sono più semplici
  • Non perdi flessibilità
  • Facilità di evoluzione: le opzioni cambiano spesso
  • Minore complessità: default, argomenti posizionali e/o obbligatori…
  • Argomenti opzionali ed overloadig complicano molto il linguaggio

Ereditarietà multipla

Sì, è possibile TODO: finire

 
              class DOTTORANDO
              DOTTORANDO --|> DOCENTE
              DOTTORANDO --|> STUDENTE
              DOCENTE --|> PERSON
              STUDENTE --|> PERSON
               

          class STUDENTE inherit PERSON … end
          class DOCENTE inherit PERSON … end
          class DOTTORANDO 
            inherit 
              STUDENTE 
                rename
                export
                undefine
                redefine
              DOCENTE … 
          

Visibilità ed accessibilità

public, private, friend non bastano


class EFFECTIVE_ARG_LIST_0 -- An empty effective argument list (for a command, aka routine call).
…
feature {ANY}
…
end_position: POSITION
…
feature {EIFFEL_PARSER, EFFECTIVE_ARG_LIST}
set_end_position (p: POSITION) assign end_position
do
end_position := p
end


          

And, or, not… and then, or else, implies

and then, or else, implies: operatori logici semistrict, evaluation stops when the result is known.

implies

has_value (v: V_): BOOLEAN
  -- Is there a value `v'?
  require v /= Void
  deferred
  ensure 
    Result implies has(key_at(v))
    Result = (occurrences(v) = 1)
  end

          

Da BIJECTIVE_DICTIONARY

aba implies b

Old

 feature
  apply_interests
  deferred
  ensure interest>0 implies capital > old capital
 
feature
             deferred class DICTIONARY [A_VALUE, A_KEY]
            ....
            add (a_value: A_VALUE; a_key: A_KEY)          
            -- Adds 'a_value' with 'a_key' to Current DICTIONARY.
            require not has(a_key)
            deferred
            ensure count = 1 + old count; v = at(a_key)  
            end

          

Agents, aka chiusure/ closure

"True Eiffel" resembles "pseudo code in English": inline agents are closures

repeat (i: INTEGER; n: STRING): like n
  require i>0; n/=Void
  local repeated: like n
  do
    repeated := ""
    i.times(agent do repeated.append(n) end)
    Result := repeated
  end

           

Infix, prefix, alias


deferred class INTEGER_GENERAL
  feature {ANY}
  infix "+" (other: like Current): like Current
    external "built_in"
  end

  infix "-" (other: like Current): like Current
    external "built_in"
  end

  multiply alias "*"  (other: like Current): like Current
    external "built_in"
  end

          

alias "()" e alias "[]"

"zucchero sintattico"

class FUNZIONE -- A function-like object 
create default_create
feature
  compute alias "()" (i,j: INTEGER): INTEGER do
    Result := i * j
    std_output.put_line("i:=#(1), j:=#(2) → result #(3)" # &i # &j # &Result)
  end
end
 
          
 foo (f: FUNZIONE) do 
      print (f(12,33).out)
    end
 
        
 class MATRIX 
  -- A simplicistic matrix with C-like setters and getters
inherit FAST_ARRAY2[REAL]
create make
feature
  item_at alias "[]" (i,j: INTEGER): like item do
    Result := item(i,j)
  end

  assigner (x: REAL; i,j: INTEGER) assign item_at do
    put(x,i,j)
  end
end
 class PARENTESES 
      -- An example on how to use "[]" and "()" operators
create make
feature
  m: MATRIX; f: FUNZIONE
  make do
    create m.make(6,6)
    create f
    m[2,2] := 12.3
    print("m[2,2] = " + (m[2,2].out)+"%N")
    print("f is an object but f(3,7) = "+f(3,7).out+"%N")
  end
end -- class PARENTESES

Operatori liberi

class INTEGER_32
  … 
  infix "|..|" (other: INTEGER): INTEGER_RANGE[INTEGER]
  require Current <= other
  do
    create Result.make(Current, other, integer_range_noop, integer_range_noop)
  end
        

Inizia e finisce con uno dei +-*/\=<>@#|&~

Eiffel design goals

  • 👍 declarative statements, 👎❌ procedural code
  • 👎❌ bookkeeping instructions (GC).
  • 👎❌ coding tricks or techniques intended as program optimization hints to a compiler.
  • 👍 readable code

The best optimization is a good design

Usare il DbC

Solo un metodo di documentazione? O piuttosto quasi un linguaggio di specifica? Di certo guida l'implementazione

C'era una volta un re!una STRINGa!

SmallEiffel "+"… e finisci con un O(n²)!

 
              class STRING
              abstract class HASHABLE
              abstract class COMPARABLE
              STRING --|> HASHABLE
              STRING --|> COMPARABLE
             

SmartEiffel 2.3

 
              class STRING
              abstract class HASHABLE
              abstract class COMPARABLE
              abstract class STORABLE
              abstract class TRAVERSABLE
              STRING --|> HASHABLE
              STRING --|> COMPARABLE
              STRING --|> STORABLE
              STRING --|> TRAVERSABLE
             

LibertyEiffel le precondizioni e le postcondizioni hanno guidato l'implementazione di ROPE e delle altre

 
            abstract class ABSTRACT_STRING {
              infix "+"
              infix "|"
              infix "&"
              infix "#"
              infix "^"
            }
            abstract class COMPARABLE {
              infix "<"
                     infix ">"
              compare
              in_range 

            }
            abstract class HASHABLE { 
              hash_code 
              }
            abstract class HOARD {
              count
              is_empty
            }
            abstract class INDEXABLE { 
              item 
              lower
              upper 
              }
            abstract class ITERABLE {
              new_iterator
              for_each
              for_all
              exists
              aggregate
            }
            abstract class NATIVELY_STORED_STRING
            abstract class PARTIALLY_FILLED_STRING
            abstract class TRAVERSABLE {
              enumerate
            }
            ABSTRACT_STRING --|> COMPARABLE
            ABSTRACT_STRING --|> HASHABLE
            ABSTRACT_STRING --|> SEARCHABLE
            ABSTRACT_STRING --|> STORABLE
            ABSTRACT_STRING --|> TRAVERSABLE
            FIXED_STRING --|> NATIVELY_STORED_STRING 
            INDEXABLE --|> HOARD
            ITERABLE --|> HOARD
            LAZY_STRING --|> ABSTRACT_STRING
            NATIVELY_STORED_STRING --|> ABSTRACT_STRING
            PARTIALLY_FILLED_STRING --|> ABSTRACT_STRING
            ROPE --|> ABSTRACT_STRING
            SEARCHABLE --|> INDEXABLE
            STRING --|> NATIVELY_STORED_STRING
            TRAVERSABLE --|> INDEXABLE
            TRAVERSABLE --|> ITERABLE
               
          
            @startuml
            abstract class ABSTRACT_STRING {
              infix "+"
              infix "|"
              infix "&"
              infix "#"
              infix "^"
            }
            abstract class COMPARABLE {
              infix "<"
              infix ">"
              compare
              in_range 

            }
            abstract class HASHABLE { 
              hash_code 
              }
            abstract class HOARD {
              count
              is_empty
            }
            abstract class INDEXABLE { 
              item 
              lower
              upper 
              }
            abstract class ITERABLE {
              new_iterator
              for_each
              for_all
              exists
              aggregate
            }
            abstract class NATIVELY_STORED_STRING {
              storage,
              count 

            }
            abstract class PARTIALLY_FILLED_STRING
            abstract class SEARCHABLE {
              has
              index_of
            }
            abstract class TRAVERSABLE {
              enumerate
            }
            class FIXED_STRING {
              substring,
            }
            class LAZY_STRING
            class ROPE
            abstract class STORABLE
            class STRING {
              resize,
              append,
              prepend,
              insert_string,
              replace_substring,
              put, ...
            }
            ABSTRACT_STRING --|> COMPARABLE
            ABSTRACT_STRING --|> HASHABLE
            ABSTRACT_STRING --|> SEARCHABLE
            ABSTRACT_STRING --|> STORABLE
            ABSTRACT_STRING --|> TRAVERSABLE

            FIXED_STRING --|> NATIVELY_STORED_STRING
            INDEXABLE --|> HOARD
            ITERABLE --|> HOARD
            LAZY_STRING --|> ABSTRACT_STRING
            NATIVELY_STORED_STRING --|> ABSTRACT_STRING
            PARTIALLY_FILLED_STRING --|> ABSTRACT_STRING
            ROPE --|> ABSTRACT_STRING
            SEARCHABLE --|> INDEXABLE
            STRING --|> NATIVELY_STORED_STRING
            TRAVERSABLE --|> INDEXABLE
            TRAVERSABLE --|> ITERABLE
            @enduml
          
        

LibertyEiffel ≠ EiffelStudio?

mio caro FrodoMio caro Frodo. … perché per quanto io ti abbia raccontato la verità, magari non era tutta tutta Liberty implementa Eiffel, magari non lo implementa tutto tutto

Null References: The Billion Dollar Mistake Tony Hoare

Fiumi di require foo/=Void


  compute (a_foo: FOO) do
    if a_foo attached as f then
      -- f is garanteed to be non-Void
    else
      compute_without_foo
    end

  compute (a_foo: attached FOO) 

            

∄ conversioni automatiche… Convert


class INTEGER_32
convert to_natural
feature
  to_natural: NATURAL_32 -- aka unsigned int!

 
          

Multiprocessing

che è più di multithreading o di fork

Operatori liberi Unicode?

E perché non permettere cose come ≔ ≅ ⊂ ⊃ a² a³

Ma è prolisso!

Non è il COBOL. Anche l'estrema concisione è pericolosa

 
$ node
Welcome to Node.js v20.19.0.
Type ".help" for more information.
> (() => {})();
undefined
> ((() => {}))();
undefined
> (() => ({}))();
{}
         

  (agent do end).call -- "empty command"
  (agent do end).call -- "empty command in a parentesis"
  (agent: ANY do end).item() -- "a Void reference to ANY object, returned by a closure"


        

Ma il codice va impaginato con un carattere monospaziato!

Lo pseudo-codice lo impaginate col Times New Roman!

La sintassi di Eiffel è pensata esplicitamente per sembrare pseudo-codice...

Quindi largo al Serif!

Se Eiffel è così buono perché non si è ovunque?

  • Prima implementazione proprietaria (e costosa!)
  • C++, Objective C ed Eiffel quasi contemporanei e solo uno senza "pesi massimi" a spingerlo
  • Eiffel è più complesso da "implementare": perlomeno all'inizio
  • Sintassi non C-like
  • - Not invented here syndrome?

The Secret of a Successful Programming Language? A Really Great Beard … Bertrand Meyer non porta la barba! C Dennis Ritiche

Bibliografia

Libri liberamente accessibili di Bertrand Meyer

Contatti e domande

paolo@monodes.com
paolo.redaelli@gmail.com
paolo.redaelli@polimi.it