Type class pattern ve Scale

Jedním z příkladů na květnovém Scala Coding Dojo byla demonstrace type class patternu. Jedná se o užitečnou techniku, pomocí které lze implementovat velmi flexibilní přizpůsobení (již existujících) typů pro jejich použití v nově vytvářených operacích/algoritmech.

Algoritmy pracují s obecnou abstrakcí typu, na kterou se konkrétní typy implicitně adaptují, aniž by přímo musely implementovat nějaké rozhraní. Jedinou podmínkou je, aby adaptované typy nabízely pro implementovaný algoritmus dostatečná data nebo chování. Algoritmus nemusí znát různé konkrétní typy, se kterými dokáže fungovat, stačí mu znát abstrakci, se kterou pracuje, typicky definovanou pomocí traitu, který je algoritmu přímo šitý na míru.

Type classes se poprvé objevily v Haskellu jako konstrukce typového systému, která podporuje ad-hoc polymorfismus. Ve Scale, která staví na malém množství silných obecných jazykových konstrukcí, nejsou type classes přímo zakódované v typovém systému, ale stejnou techniku lze použít díky dostupným implicitním parametrům a jejich lookupu.

Implementace type class patternu

Pattern se skládá z následujících částí:

  1. Generický trait (tzv. type class trait) s jedním typovým parametrem A, který reprezentuje typ používaný obecnou operací/algoritmem. Trait vystupuje jako helper/utilita s operacemi pro typ A. Operace jsou deklarovány "jako statické" - instance typu A je přijímána vždy jako jeden z parametrů operací. Tento trait je abstrakcí, na kterou lze adaptovat jednotlivé typy používané algoritmem.
  2. Implicitně vyhledatelné implementace type class traitu, typicky companion object pro type class trait (se stejným názvem jako trait), který jako své členy (definované jako implicitní) obsahuje výchozí implementace type class traitu pro různé typy, se kterými chceme jednotným způsobem manipulovat v algoritmu.
  3. Operace/algoritmus v podobě nějaké funkce, která přijímá jako implicitní parametr výše zmíněný trait.

Příklad implementace

Type class pattern si demonstrujeme na příkladu operace, která obecně dokáže pracovat s různými zdroji, které mohou tvořit hierarchii (mohou mít děti). Konkrétně budeme pracovat se soubory a URL adresami, které budou adaptovány na rozhraní obecného zdroje. Výsledkem bude výpis všech dětí zdroje: U souborů, které jsou adresářem, to budou soubory uložené v adresáři, u URL adres to budou odkazy na další stránky, které se objevují v markupu stránky na dané adrese.

Jak je patrné z příkladu použití, podle typu vstupního parametru se automaticky (lookupem v implicitním systému) musí najít implementace Resource[URL], resp. Resource[File]. Implicitní systém má několik pravidel s různými prioritami, podle kterých implicitní implementaci hledá. Jedním z posledních míst, kam se "dívá", jsou členy companion objectu pro Resource (pokud vůbec companion object existuje). V našem případě se právě tady najdou výchozí implementace.

Není problém podle potřeby "předhodit" implicitnímu systému pozměněnou implementaci pro URL adresy. Například lokálně před použitím metody ResourceOperations.listChildren můžeme do scope importovat jinou implementaci, která bude mít v mechanismu implicitního lookupu přednost před výchozí implementací v companion objectu:

Výhody type class patternu

Type classes jsou mocným nástrojem pro flexibilní návrh knihoven. Mohou významně navýšit znovupoužitelnost kódu. Abstrakce typů (type classes) mohou být případně dále kombinovány, poskládány z dílčích abstrakcí (tzv. higher-level type classes).

Separování abstrakcí

Abstrakce (algoritmy) jsou patternem separovány od konkrétních typů, se kterými mohou pracovat. Chování může být takto odděleno od konkrétní hierarchie tříd. Konkrétní typy mohou být na abstrakci adaptovány. Logika pracující s mnoha různými typy může být implementována na jediném místě v duchu DRY principu. Poté již stačí jen "dodat" implementace pro konkrétní typy, které chceme používat.

Flexibilita

Je vyšší než při dědičnosti (z hlediska možností umísťování implementace algoritmů a dalšího rozšiřování typů; je možné vytvářet volné vazby mezi částmi systému). Adaptování konkrétních typů na abstrakci je díky implicitnímu lookupu snadné, prakticky transparentní pro uživatele abstrakce. Flexibilní je rovněž použití konkrétní implementace. Díky implicitnímu systému ve Scale mohou být výchozí implementace chování pro vybrané typy snadno přepsány jinými implementacemi. Stačí v místě, kde potřebujeme vlastní implementaci, naimportovat svou vlastní implementaci, která bude mít vyšší prioritu než výchozí implementace v companion objectu, a implicitně nebo explicitně ji předat algoritmu.

Typová bezpečnost

Kompilátor kód nezkompiluje, pokud nebude pro konkrétní typ vstupního parametru algoritmu nalezena implicitním lookupem implementace type class traitu. Výběr konkrétní implementace je převeden na problém implicitního systému.

Type classes v knihovnách Scaly

Type class pattern umožňuje rozšiřovat existující knihovny o další chování, které lze přizpůsobovat namíru jednotlivým již existujícím typům. Například pro knihovnu kolekcí ve Scale mohou být vyvíjeny další algoritmy, které mohou být díky type classes přizpůsobovány jednotlivým typům kolekcí tak, aby byly efektivní (pro odlišné kategorie kolekcí budou existovat jiné implementace dostupné přes implicitní systém).

Například Numeric[T] umožňuje na kolekcích s prvky typu T, který má implementován Numeric[T], zavolat metody sum[T] nebo product[T]. Seq(1, 2, 6, 3).sum lze díky tomu zkompilovat, kdežto Seq("a", "b", "c", "d").sum nikoliv (pro řetězec neexistuje implicitně dostupná implementace Numeric[String], ale můžeme si takovou sami naimplementovat).

Metody sortBy[T] a sorted[T] na Scala kolekcích přijímají implicitní parametr Ordering[T], který je implementován pro základní typy dostupné ve Scale a umožňuje implementovat řazení pro další uživatelsky definované typy. Např. po vytvoření implementace Ordering[Product] pro naši třídu Product bude možné řadit kolekci typu Seq[Product] a další kolekce reprezentující sekvenci.

Další zdroje

  1. Suereth, Joshua D. Scala in Depth. Manning Publications Co., 2012. Web: http://www.manning.com/suereth/.
  2. Algorithmically challenged: Implicit tricks -- the Type Class pattern. Web: http://dcsobral.blogspot.cz/2010/06/implicit-tricks-type-class-pattern.html.
  3. Wikipedia: Type class. Web: http://en.wikipedia.org/wiki/Type_class.
  4. Statically Typed: Gang of Four Patterns With Type-Classes and Implicits in Scala. Web: http://staticallytyped.wordpress.com/2013/03/09/gang-of-four-patterns-with-type-classes-and-implicits-in-scala/.
  5. Westheide, D. The Neophyte's Guide to Scala Part 12: Type Classes. Web: http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html.

Článek obsahuje 0 komentářů