k47.cz  — každý den dokud se vám to nezačne líbit
foto Praha výběr povídky kultura
twitter FB RSS

Scala type bounds vs. C# constraints on type parameters

18. 2. 2011 — k47 (CC by-nc-sa) (♪)

C# má tak­zvané type con­stra­int, kte­rými můžeme blíže spe­ci­fi­ko­vat typový pa­ra­metr ge­ne­rické třídy:

// C#
class X<T> where T: ISomething

Java nic po­dob­ného nemá, což je škoda, pro­tože ge­ne­rické třídy jsou velice obecné a ne­mů­žou si nijak vy­nu­tit, aby měl typový pa­ra­metr nějaké kon­krétní vlast­nosti.

Scala je na tom lépe než Java a type con­stra­ints ob­sa­huje v podobě upper bound ope­rá­toru <:.

Aniž bych si to uvě­do­mil, dlou­hou dobu jsem to po­u­ží­val a ne­při­šlo mi, že jde o ně­ja­kou extra vy­chy­távku. Jde zkrátka o ne­díl­nou sou­část ty­po­vého sys­tému, která se po­u­žívá na mnoha mís­tech ve stan­dardní knihovně. Kon­cept ty­po­vých ome­zení je ve Scale o něco obec­nější a vy­u­žívá kom­pakt­nější a o něco jas­nější syn­taxi (tedy aspoň podle mě).

// Scala
class X[T <: ISomething]

C# má do­hro­mady 5 typů con­stra­ints, které mohou být apli­ko­vány na třídu, in­ter­face, metodu nebo kon­struk­tor; zkrátka na všechny de­kla­race, kde se můžou vy­sky­to­vat špi­čaté zá­vorky. Stejně tak to platí i pro Scalu a hra­naté zá­vorky.


// C#
class X<T> where T: struct

T musí být hod­no­tový typ, což v C# od­po­vídá pri­mi­tiv­ním da­to­vým typům nebo struct.

// Scala
class X[T <: AnyVal]

Scala dokáže to samé celkem po­ho­dlně, ale je tu ně­ko­lik pro­blémů: zaprvé JVM nemá obdobu struk­tur a za­druhé se v tomto pří­padě ne­po­u­žijí pri­mi­tivní datové typy, ale jejich bo­xo­vané verze (pokud ano­tace @spe­ci­a­li­zed ne­vy­nutí opak). Takže body dolů a smut­níky pro Scalu.


// C#
class X<T> where T: class

T musí být re­fe­renční typ (v C# od­po­vídá třídě, in­ter­face, de­le­gátu nebo poli).

// Scala
class X[T <: AnyRef]

Ve Scale to samé: T musí být třída nebo trait.


// C#
class X<T> where T: new()

T musí mít ve­řejný bez­pa­ra­me­t­rický kon­struk­tor.

Tohle se ve Scale nedá na­po­do­bit ani pomocí struk­tu­rál­ních typů, ale ani to nedává smysl. JVM na rozdíl od .NETího vir­tu­ál­ního stroje po­u­žívá type era­sure, takže potom, co kom­pi­lá­tor ověří správ­nost pro­gramu, se všechny in­for­mace o ty­po­vých pa­ra­me­t­rech zahodí a v by­te­kódu po nich není ne­zů­stane vůbec nic. Z toho plyne, že v ge­ne­rické třídě s ty­po­vým pa­ra­me­t­rem T ne­mů­žeme během run-time vy­tvo­řit novou in­stanci třídy T, pro­tože nevíme, jakou hod­notu T má.

I když někdy může být type era­sure ome­zu­jící, je to de­fakto stan­dardní za­chá­zení s ge­ne­ric­kými typy.


// C#
class X<T> where T: BaseClass

T musí dědit z BaseC­lass.

// C#
class X<T> where T: ISomething1, ISomething2

T musí být nebo im­ple­met­no­vat roz­hraní ISo­me­thing1 a ISo­me­thing2. Takhle může být uve­deno víc roz­hraní a do­vo­leny jsou i ge­ne­rické.

Ve Scale se žádné pře­kva­pení nekoná:

// Scala
class X[T <: BaseClass]
class X[T <: Trait1 with Trait2]

// C#
class X<T, U> where T: U

Tzv. „naked type con­stra­int“: T musí být po­tom­kem ty­po­vého pa­ra­me­tru U.

Ve Scale ne­pře­kva­pivě:

// Scala
class X[T <: U, U]

Scala jde ještě o krok dál a prin­cip ty­po­vých pod­mí­nek zo­bec­ňuje. Kromě upper bound nabízí ještě lower boundview bound.

Lower bound je přesný opak upper bound, takže na­pří­klad [T >: U] – zna­mená, že třída T musí být U nebo předek U. Pomocí lower bound je na­pří­klad de­fi­no­vána metoda cons třídy List[+A]: def ::[B >: A] (x: B): List[B].

View bound [T <% U] zna­mená, že typ T se dá im­pli­citně zkon­ver­to­vat na typ U. Takže T v [T <% Int] vy­ho­vuje Byte, Char, Short a Int. Atd…

píše k47 & hosté, ascii@k47.cz