Il linguaggio Fantom

Le classi - basi              

Come ogni linguaggio a oggetti anche Fantom ha un pieno supporto per queste entità che da anni, diciamo dalla seconda metà degli anni 80, cosituiscono lo strumento principale delle architetture di programmazione, almeno in termini di diffusione.
Come è noto in chi lavora in questo ambito, le classi sono sostanzialmente dei tipi. Possono essere predefiniti dal linguaggio e dalle sue librerie oppure possono essere creati dall'utente. Gli oggetti sono istanze di una classe, ovvero sia sono entità create a partire dalle classi e che sono effettivamente manipolabili. Il type system di Fantom prevede una classe root che si chiama Obj che, come tale, è l'unica a non avere una superclasse, ovvero una classe di livello superiore. Questo detto molto alla veloce. Ogni variabile ha un tipo associato che può essere recuperato tramite l'istruzione Type.of o object.typeof. Type.of vale per tutte le variabili e per tutti i tipi, qui di seguito vediamo un veloce esempio:

  Esempio 3.1
1
2
3
4
5
6
7
8
class Test
{
  static Void main()
  {
    Int x := 3
    echo(Type.of(x))
  }
}

In Fantom le classi si dichiarano tramite la keyword class (sorpresi eh?). Ogni classe appartiene ad un pod e sintatticamente la sua identificazione avviene come:

pods::classe

la documentazione ufficiale (che, come detto nell'introduzione è praticamente l'unica realmente utile e disponibile) ci insegna che:
  • le classi possono contenere zero o più slots
  • le classi ereditano da non più di una classe (quindi in Fantom non abbiamo il supporto per l'ereditarietà multipla)
  • le classi possono ereditare da uno o più mixin (il che, come vedremo, è cosa buona)

abbiamo poi i consueti modificatori di classe:

  • public - permette un uso completamente aperto della classe
  • internal - la classe è visibile all'interno del pod che la contiene
  • abstract - la classe non è istanziabile
  • final - la classe non può avere sottoclassi che la estendono
  • const - la classe è immutabile, ovvero non può avere cambiamenti di stato.

Approfondiremo queste cose piano piano. Interessante notare che molti classi interne alla libreria di Fantom sono const. E possibile utilizzare più di un modificatore ad esempio:

internal abstract classe c1()

Ecco ora un semplice esempio

  Esempio 3.2
1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo
{
  Int x := 0
}

class Test
{
  static Void main()
  {
    f := Foo()
    echo(f.x)
  }
}

la riga 10 presenta una istanziazione. La riga 11 invece ci mostra come accedere ad un campo interno alla classe, ovvero grazie all'operatore . (punto).

L'esempio seguente invece schematizza il processo di ereditarietà. Creeremo un'altra classe che discenderà da Foo, presente nell'esempio 3.2, condividendone lo slot presente (la variabile x).

  Esempio 3.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Foo
{
  internal Int x := 0
}

class Foo2 : Foo
{
}

class Test
{
  static Void main()
  {
    f := Foo()
    echo(f.x)
    f.x = 9
    echo(f.x)
    f2 := Foo2()
    echo(f2.x)
  }
}

Quindi alla riga 6 viene definita la classe Foo2 che eredita da Foo tramite l'operatore : (due punti). La variabile interna x è disponibile anche per le variabili create a partire da Foo2 (precisamente ne creiamo una alla riga 18). Questo è il significato della ereditarietà. cioè il poter usare di quanto la classe padre, la superclasse, mette a disposizione.
La classe Foo2 non contiene altro che quanto viene ereditato da Foo. Tuttavia nulla impedisce che abbia degli slots propri, ad esempio potete modificarla come segue:

class Foo2 : Foo
{
Int y := 7
}

e y sarà quindi disponibile anche per f2. Ovviamente y non è disponibile per la classe Foo e i suoi discendenti diretti. Ma è disponibile per eventuali discendenti di Foo2. Tuttavia non è possibile, in questa forma modificare Foo2 in questo modo:

class Foo2 : Foo
{
Int x := 7
}

in quanto il compilatore si arrabbierebbe:

Cannot override non-virtual slot

lo slot x è già presente nella superclasse di Foo2, ovvero Foo, e quindi ci sarebbe un conflitto.

Prima di concludere questo breve capitolo introduttivo con un esempio che mostra che all'interno di una classe abbiamo anche dei metodi, ovvero, se preferite, delle funzioni che compiono della azioni vere e proprie, quindi non sono solo dei valori. L'esempio che segue estende un qeusto senso il 3.2

  Esempio 3.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
23
24
25
26
27
28
29
class Foo
{
  internal Int x := 0
}

class Foo2 : Foo
{
  public Void saluta()
  {
    echo("io sono la classe Foo2")
  }
  Int y := 7
}

class Test
{
  static Void main()
  {
    f := Foo()
    echo(f.x)
    f.x = 9
    echo(f.x)
    f2 := Foo2()
    echo(f2.x)
    echo(f2.y)
    f2.saluta()
  }
}

Le righe il rosso mettono in pratica il concetto espresso. Dalla riga 8 alla 11 abbiamo la definizione di una funzione interna alla classe Foo2 e tale funzione viene utilizzata tramite l'istanza di f2 definita alla 24.
Tutto quanto visto è molto basilare e, immagino, tedioso per chi già conosce la programmazione a oggetti. Tuttavia quanto esposto dovrebbe essere sufficiente per fare qualche prova anche per chi è agli inizi.