A Pizza nyelv

Típusok, típuskonstrukciók

A Pizza lényeges újításként a Java típusrendszerét funkcionális típusrendszerrel egészítette ki.

E fejezet elolvasása előtt érdemes elolvasni a 'Sablon' fejezetet.

Konverzió primitív típus és dobozoló osztálya között

Pizza-ban lehetőség van castolni primitív típus és dobozoló osztálya között.

int basicX; Integer integerX; Object objectX; basicX = (int)integerX; basicX = (int)objectX; integerX = (Integer)basicX; objectX = (Object)basicX;

Idő közben a fenti példában lévő castolások nagy részét már a Java is támogatja, kivétel amikor egy Object típusú változót castolnánk primitív típusra. A Pizza a fenti kódot az alábbi Java kódra fordítja:

int basicX; Integer integerX; Object objectX; basicX = integerX.intValue(); basicX = ((Number)objectX).intValue(); integerX = new Integer(basicX); objectX = (Object)new Integer(basicX);

Mindazonáltal ezekkel óvatosan kell bánni, futás idejű hibákat eredményezhetnek.

Algebrai adattípus (algebraic datatype)

Algebrai adattípus olyan típus, ami azt határozza meg, hogy a típusba tartozó elemeket hogyan lehet létrehozni. Például:

import net.sf.pizzacompiler.util.Set; class Tree<A> extends Set<A> { case Empty; case Branch(A elem, Tree<A> left, Tree<A> right); }

A fenti példából egy olyan Tree (bináris fa) nevű Java osztály generálódik, melynek lesz egy statikus Tree típusú Empty (üres fa) nevű adattagja, valamint egy Tree -ből származtatott belső statikus osztálya Branch (csúcspont) néven, melynek lesz egy konstruktora a fent látható paraméterezéssel, és a konstruktor paraméterezésében felsorolt formális paraméterek mindegyikéhez készül egy-egy adattag is a Branch osztályba. Amennyiben csak egy case ág van az osztályban, akkor használhatjuk az adott esetre névnek az osztály nevét is, mivel ilyenkor nem generálódik külön belső osztály, hanem az aktuális osztály fog a case-ben megadott paraméterezéssel konstruktort és adattagokat kapni.

A Tree használatára példa:

import Tree.*; Tree<int> t = Branch( 2, Branch(1, Empty, Empty), Branch(3, Empty, Empty) );

Az import Tree.*;-re azért volt szükségünk, hogy a Tree.Branch és Tree.Empty helyett simán írhassunk Branch-et és Empty-t. Ezt algebrai típusok nélkül is meg lehetne csinálni. Készítenénk egy absztrakt bázisosztályt (Tree), és származtatnánk két másik osztályt, ami reprezentálja az algebrai típus eseteit (EmptyTree, Branch). A Tree típusú paramétert váró metódusokban pedig az instanceof operátort és sok-sok típuskényszerítést alkalmaznánk.

Speciális eset: felsorolási típus

A Pizza készítésekor a Java még nem rendelkezett felsorolási (enum) típussal. Felsorolási típus hiányában megoldható az adott probléma konstans egészek használatával is, de ennél jobb megoldást jelent a Pizza algebrai típusa. Olvasható kódot ad, valamint nagy előnye a konstans egészekkel szemben, hogy ekkor fordítási idejű típusellenőrzést kapunk a felsorolásban szereplő elemekre, így nem használhatunk definiálatlan értéket. Egy példa ennek használatára:

class Csapat { case Kispest; case Vasas; case MTK; case FTC; public String mezSzín() { switch( this ) { case Kispest: return "piros-fekete"; case Vasas: return "piros-kék"; case MTK: return "kék-fehér"; case FTC: return "zöld-fehér"; } } }

Figyeljük meg, hogy ebben az esetben a Java 5-ben bevezetett felsorolási típushoz hasonlóan elég egyszerűen készíthetnénk akár konstruktort és adattagokat a felsorolás elemei számára. A Pizza fordító ezt a kódot olyan Java kódra fordítja, ahol a felsorolás elemei a tartalmazó osztály típusával megegyező típusú adattagok lesznek.

Függvény típusú változó

Újdonság a Java-hoz képest, hogy megjelent a függvény típus, így alprogramokat tudunk tárolni objektumokban (olyan, mint C++-ban a függvényre mutató pointer). Természetesen a függvények paraméterei és visszatérési értéke lehetnek függvénytípusúak is. Az ilyen függvényeket hívjuk magasabbrendű függvényeknek. Azokat a függvényeket, amelyeknek nincs függvénytípusú paramétere vagy visszatérési érteke azokat elsőrendű függvénynek hívjuk. A szintaxis a következő:

(argtype1, ..., argtypen) -> resulttype

Ha a függvény kivételeket is dob:

(argtype1, ..., argtypen) throws exception1, ..., exceptionm -> resulttype

A Java szintaktikai tradícióját követve azt várhattuk volna, hogy a resulttype(argtype1, ..., argtypen) jelölést használják a függvénytípushoz. Szerencsére a Pizza tervezői más utat választottak. Két okból is:

Deklarálhatunk függvénytípusú változókat, pl:

(int, String) -> char CharAt;

Így kapunk egy (int, String) -> char típusú CharAt nevű változót. Ennek értékül adhatunk azonos szignatúrájú függvényeket. Itt a szignatúrába beletartozik a visszatérési érték típusa is. A függvénytípust például akkor tudjuk használni, amikor egy osztály nem implementált egy interfészt. Ahelyett, hogy egy wrappert írnánk (mint a későbbiekben látni fogjuk), a kért függvényt paraméterként adjuk át a metódusnak. Például nézzük a String Java osztály compareTo() metódusát. Ez lexikografikusan és kis-nagybetűt megkülönböztetve hasonlít össze két String-et. Tegyük fel, hogy String-eket akarunk rendezni. Ha meg akarjuk változtatni a rendezés viselkedését, akkor átadunk paraméterként egy függvényt, ami String-eket hasonlít össze:

class A { private int myCompare( String s1, String s2 ) { return s1.compareTo( s2 ); } public A() { B b = new B( myCompare ); } public static void main( String[] args ) { A a = new A(); } } class B { private (T, T) -> int cmp; public B( (T, T) -> int cmp ) { this.cmp = cmp; } public int compare( T s1, T s2 ) { return cmp( s1, s2 ); } }