k47.cz
mastodon twitter RSS
bandcamp explorer

Scala - kratší implicitní konverze

25. 1. 2011 (aktualizováno 9. 7. 2020) — k47 (CC by-sa)

Když ve Scale implicitními konverzemi přidáváme nové metody k již existujícím typům (stejnou věc jako extension methods v C#), typicky definujeme wrapper W, třídu jejíž konstruktor přijímá jeden parametr typu T, a pak implicitní funkci, která se postará o konverzi z T na W.

Například:

class StringWrapper(s: String) {
  def uc = s.toUpperCase
  def lc = s.toLowerCase
  def ucFirst = if (s.isEmpty) s else s.head.toUpper + s.tail
  def lcFirst = if (s.isEmpty) s else s.head.toLower + s.tail
}

implicit def wrapString(s: String) = new StringWrapper(s)

Definovat wrapper není nezbytně nutné, existuje o něco málo kratší zápis. Implicitní funkce může jednoduše vrátit anonymní objekt, který má všechny požadované metody:

implicit def wrapString(s: String) = new {
  def uc = s.toUpperCase
  def lc = s.toLowerCase
  def ucFirst = if (s.isEmpty) s else s.head.toUpper + s.tail
  def lcFirst = if (s.isEmpty) s else s.head.toLower + s.tail
}

Když jsem tento koncept prozkoumával dál, na stránkách Scaly jsem narazil na maličkou ukázku rozšiřování zabudovaných typů implicitní konverzí. Integeru se přidala metoda ! rekurzivně počítající faktoriál. Typická implementace: rekurzivní funkce, wrapper a implicitní konverze.

def fact(n: Int): Int = if (n == 0) 1 else fact(n-1) * n
class Factorizer(n: Int) {
  def ! = fact(n)
}
implicit def int2fact(n: Int) = new Factorizer(n)

Napadlo mě, že by se celý tenhle kód dal zredukovat do jediné metody:

implicit def int2fact(n: Int) = new {
  def ! : Int = if (n == 0) 1 else ((n-1)!) * n
}

Bohužel, takhle jednoduše to nefungovalo.

error: value ! is not a member of Int

Note: implicit method int2fact is not applicable here because it comes after the application point and it lacks an explicit result type

Kompilátor si stěžoval, že Int nemá metodu ! s poznámkou, že nemůže použít implicitní konverzi int2fact a vyžaduje návratový typ rekurzivní funkce int2fact. Ta je rekurzivní zvláštním způsobem, ne klasicky, že volá sama sebe, ale nepřímo: vytváří objekt v němž se sama volá.

Explicitní zavolání konverze vyřeší jenom polovinu problému. Pořád je tu neznámý návratový typ. Ale jaký je typ anonymní třídy? V Javě nebo C# neznámý. Ve Scale je to strukturální typ. V tomhle případě { def ! : Int}, tedy libovolný objekt, který má metodu !.

Finální podoba kódu se může zdát lekce kryptická, ale funguje.

implicit def int2fact(n: Int): { def ! : Int} = new {
  def ! : Int = if (n == 0) 1 else ((n-1)!) * n
}

Od verze Scaly 2.11 tuto roli lépe zastane implicitní třída a nehrozí, že by se strukturálně deklarované metody volaly prostřednictvím reflexe.

implicit class Int2fact(n: Int) {
  def ! : Int = if (n == 0) 1 else ((n-1)!) * n
}

Krása, co?

píše k47, ascii@k47.cz