Il linguaggio Fantom

Mixin              

Ecco ora a parlare di un potente meccanismo presente ma non in questa forma anche in altri linguaggi. In C# o Java abbiamo le ben note interfacce attraverso le quali è anche possibile utilizzare l'ereditarietà multipla, ovvero un oggetto può ereditare da più interfacce. Nei due linguaggi citati, e anche in altri, una interfaccia non prevede alcuna implementazione interna dei propri metodi. Fantom prevede i mixin i quali invece permettono ciò (un meccanismo simile mi pare sia implementato in Ruby). Sinteticamente, come riportato sul sito ufficiale si può così schematizzare il comportamento dei mixin:
  • I mixins sono definiti all'interno di uno slot e sono identificabili  con la seguente sintassi: podName::MixinName
  • I mixins possono contenere zero o più slots univocamente definiti
  • I mixins sono implicitamente astratti
  • I mixins possono ereditare da zero a più mixins
  • I mixins non possono definire costruttori
  • I mixins possono dichiarare metodi astratti
  • I mixins possono dichiarare metodi statici
  • I mixins possono dichiarare campi astratti
  • I mixins possono dichiarare campi statici costanti
  • I mixins possono dichiarare costruttori statici come segue: static {}
Questo, come detto, in forma riassunta.
La keyword che introduce i mixins è semplicemente mixin, usata come segue:

mixin MioMixin
{
}

Un mixin di per se stesso, è evidente data la loro natura stratta, non serve a nulla se non è poi ereditato da una classe. Vediamo un semplice esempio

  Esempio 4.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mixin Foo
{
  abstract Int x
  abstract Int y
  Void scrivi()
  {
    echo("ciao")
  }
}

class FromFoo : Foo
{
  override Int x := 8
  override Int y := 7
}

class Test
{
  static Void main()
  {
    f := FromFoo()
    echo(f.x)
    echo(f.y)
    f.scrivi
  }
}

i campi, nell'esempio x e y, devono essere dichiarati astratti e successivamente devono essere inizializzati. Un mixin può definire dei setter e dei getter ma non memorizzano valori. La classe che eredita dal mixin deve implementare tutti i campi astratti. Come è evidente, riga 11, l'ereditarietà si esplica attraverso i due punti (:). Alla riga 5 abbiamo la definizione esplicita di un metodo che poi può essere usato pari pari dalla classe che eredita dal mixin. Anche i metodi comunque possono essere soggetti ad overriding; per permette ciò si deve usare la keyword virtual:

  Esempio 4.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mixin Foo
{
  abstract Int x
  abstract Int y
  virtual Void scrivi()
  {
    echo("ciao")
  }
}

class FromFoo : Foo
{
  override Int y := 7
  override Int x := 9
  override Void scrivi()
  {
    echo("hello")
  }
}

class Test
{
  static Void main()
  {
    f := FromFoo()
    echo(f.x)
    echo(f.y)
    f.scrivi
  }
}

la riga 28 espone a video la scritta "hello" (senza apici) mentre se non vi fossero le istruzioni dalla riga 15 alla 18 l'output sarebbe "ciao".
Una classe, come accennato, può ereditare da più mixin. Per indicare questo si usa come separatore per il nome dei vari mixins, la virgola. Vediamo l'esempio completo:

  Esempio 4.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
mixin Foo1
{
  abstract Int x
  abstract Int y
  virtual Void scrivi()
  {
    echo("ciao")
  }
}

mixin Foo2
{
  abstract Int z
}

class FromFoo : Foo1, Foo2
{
  override Int y := 7
  override Int x := 9
  override Int z := 3
  override Void scrivi()
  {
    echo("hello")
  }
}

class Test
{
  static Void main()
  {
    f := FromFoo()
    echo(f.x)
    echo(f.y)
    echo(f.z)
    f.scrivi
  }
}