Sunday, February 16, 2020

07 - trait, összeg típus

Mondjuk, hogy van egy geometriai formákkal dolgozó appunk. Van benne pont, háromszög (ami három pontból tevődik össze), kör (ami egy középpontból és egy sugárból), pl. így:

case class Pont(x: Int, y: Int)

case class Haromszog(p: Pont, q: Pont, r: Pont)
case class Kor(p: Pont, r: Double)
és szeretnénk valami olyan típust (pl. mert valami kollekcióba, listába, whatever, akarunk gyűjteni geometriai formákat (egyszerűség kedvéért most a pontot ne, csak köröket meg háromszögeket akarunk uniform módon kezelni) és ne kelljen ehhez két különböző típusú (mondjuk) tömböt deklaráljunk.
Vagyis: van egy $T_1$ típusunk, aminek az értéktartománya a $D_1$ halmaz, van egy $T_2$ típusunk, aminek az értéktartománya pedig a $D_2$ halmaz és ezekből szeretnénk készíteni egy olyan $T$ típust, aminek az értéktartománya a $D_1\uplus D_2$ halmaz, a két értéktartomány diszjunkt uniója. Ez azt jelenti, hogy még ha van is átfedés a két halmaz közt, akkor is minden elem "eredeti típus infóval" kerüljön bele, azaz ha van a két halmaznak közös eleme, akkor kétszer, egyszer $T_1$ típusúként, egyszer pedig $T_2$ típusúként. Pl. ha $T_1=\mathrm{Int}$ és $T_2=\mathrm{Double}$, akkor a $T$ típus értéktartományában szerepeljen a double $0$ és az int $0$ is külön-külön. Ha egy ilyen $T$ típust hozunk létre ezzel az értéktartománnyal, akkor azt úgy mondjuk, hogy a $T$ a $T_1$ és $T_2$ típusok összege, jelben $T=T_1+T_2$.

Most pont ezt szeretnénk: van egy $\mathrm{Haromszog}$ és egy $\mathrm{Kor}$ típusunk, és valami olyan típust szeretnénk készíteni, amibe bármelyiket rakhatjuk.

Ennek C-ben egy implementációja például így festhet:

typedef struct { int x; int y; } Pont;
typedef struct { Pont p; Pont q; Pont r; } Haromszog;
typedef struct { Pont p; double r; } Kor;

typedef struct {
  int type; // 0 -- haromszog 1 -- kor
  union{
    Haromszog haromszog;
    Kor kor;
  };
} Shape;

vagyis: egy olyan structot készítünk, aminek a $\mathrm{type}$ mezője tárolja, hogy most épp kör vagy háromszög amire tulajdonképp mutatunk, és ennek függvényében van vagy egy $\mathrm{Kor}$, vagy egy $\mathrm{Haromszog}$ adatmezőnk, de mivel egyszerre csak az egyik lehet valid, ezért őket unionba tesszük. Egy példa használata ennek az új típusnak:

Pont p; p.x = 1; p.y = 2;
Haromszog h; h.p = p; h.q = p; h.r = p;
Kor k; k.p = p; k.r = 1.0;
Shape shape;
shape.type = 0;
shape.haromszog = h;
  
printf( "Shape is: %s\n", shape.type ? "circle" : "triangle" );
switch( shape.type )
{
    case 0:
        printf( "Nodes: (%d,%d), (%d,%d), (%d,%d)\n",
            shape.haromszog.p.x,
            shape.haromszog.p.y,
            shape.haromszog.q.x,
            shape.haromszog.q.y,
            shape.haromszog.r.x,
            shape.haromszog.r.y
        ); break;
    case 1:
        printf( "Center: (%d,%d), radius: %lf\n",
            shape.kor.p.x,
            shape.kor.p.y,
            shape.kor.r
        );break;
    }

Világos, hogy mi történik: amikor értéket adunk egy $\mathrm{Shape}$ változónak, akkor be kell állítsuk a típusát is és a union megfelelő mezőjét állítjuk az értékre; amikor meg dolgozunk egy $\mathrm{Shape}$ változóval, akkor legelőször is le kell switchelnünk, hogy épp mi is ez, és ezután típusfüggően dolgozhatunk vele. És létrehozhatunk egy $\mathrm{Shape}$ tömböt, amibe aztán pakolhatunk háromszögeket vagy köröket vegyesen ízlés szerint, ezzel megvalósítjuk az absztrakt $\mathrm{Shape}=\mathrm{Kor}+\mathrm{Haromszog}$ adattípust.

Scalában ugyenezt elérhetjük pl. így:

case class Pont(x: Int, y: Int)

trait Shape
case class Haromszog(p: Pont, q: Pont, r: Pont) extends Shape
case class Kor(p: Pont, r: Double) extends Shape

val p = Pont(1,2)
val h = Haromszog(p,p,p)
val k = Kor(p,1.0)

val shape: Shape = h

shape match {
  case Haromszog(p,q,r) => println("Shape is: triangle\nNodes: " + p + "," + q + "," + r)
  case Kor(p,r) => println("Shape is: circle\nCenter: " + p + ", radius: " + r)
}

Ebből a $\mathrm{match}$ kifejezés egy külön poszt lesz, most a $\mathrm{trait}$re fókuszálunk a típusdeklarációban. A $\mathrm{Pont}$, $\mathrm{Haromszog}$, $\mathrm{Kor}$ típusok definíciója ugyanaz, mint az előbb, azzal, hogy utóbbi kettő kapott egy $\mathrm{extends~Shape}$-et a végére és a definíciókat megelőzi egy $\mathrm{trait~Shape}$ sor. Ez utóbbira egyelőre tekinthetünk úgy, mint egy típusdefinícióra: ha egy típust a $\mathrm{trait}$ kulcsszóval vezetünk be, akkor az még csak a nevét vezeti be a típusnak, egyelőre üres értéktartománnyal. Minden további típus, ami $\mathrm{extend}$eli ezt a traitet, pedig hozzácsapja a saját értéktartományát a traitéhez, diszjunkt unió formában - mivel most két típusunk volt, aki azt mondta, hogy ő $\mathrm{extends~Shape}$, ezért most a $\mathrm{Shape}$ típus a $\mathrm{Haromszog}$ és a $\mathrm{Kor}$ típusok összege lesz. Láthatunk példát értékadásra is, a $\mathrm{val~shape:Shape~=~h}$ értékdeklarálásnál azért írtam ki explicit a típust, mert különben a Scala fordító (mivel a $h$ az egy háromszög) háromszögnek következteti ki a $\mathrm{shape}$ érték típusát.

A kód végén pedig azt láthatjuk, hogy ha van egy, a diszjunkt összegbe tartozó értékünk, akkor hogyan tudjuk meg róla azt, hogy éppen melyik típusnak az adattartományába valósi; a match kifejezésekről egy későbbi posztban többet fogunk tudni.

A trait egy ennél sokkal sokoldalúbb konstrukció lesz - most egyelőre csak az a lényeges tulajdonsága, hogy ezzel a kulcsszóval tudunk összeg típust létrehozni. Az összeg és szorzat típusok kombinációjával pedig tudunk építeni...
folytköv

1 comment:

  1. Harrah's Resort And Casino - Mapyro
    Welcome to Harrah's Resort 안동 출장샵 And 전라남도 출장샵 Casino. 777 광주광역 출장안마 Harrahs Blvd, Henderson, NV 89101, US. Directions. (775) 622-5000. Directions · (775) 강원도 출장샵 622-5000. Call Now · More Info. Hours, Accepts Credit Cards, Attire, Wi-Fi  Rating: 4.1 · ‎8,951 votes 포천 출장마사지

    ReplyDelete