k47.cz
mastodon twitter RSS
bandcamp explorer

Scala - klasický for cyklus

(aktualizováno ) — k47 (CC by)

Scala, na rozdíl od jazyků z rodiny C/C++, nenabízí klasický for cyklus. Podle Martina Oderskyho je příliš imperativní a Scala jako taková tíhne k funkcionálnímu stylu.

Nicméně, to nám nebrání si tuto jazykovou konstrukci doplnit. Tady jsou moje tři pokusné implementace.

// negenerická verze
def for0(init: Int, cond: Int => Boolean, incr: Int => Int)(op: Int => Unit) {
  var i = init; while (cond(i)) {
    op(i)
    i = incr(i)
  }
}

for0(0, _ < 10, _ + 1) { i => println(i) }
// generická verze 1
def for1[@specialized T](init: T, cond: T => Boolean, incr: T => T)(op: T => Unit) {
  var i = init; while (cond(i)) {
    op(i)
    i = incr(i)
  }
}

// typová anotace [Int] je nutná, bez ní kompilátor neodvodí typ fukncí cond a incr
for1[Int](0, _ < 10, _ + 1) { i => println(i) }
// generická verze 2
def for2[@specialized T](init: T)(cond: T => Boolean, incr: T => T)(op: T => Unit) {
  var i = init; while (cond(i)) {
    op(i)
    i = incr(i)
  }
}

// žádná typová anotace netřeba za cenu jiného zápisu
for2(0)(_ < 10, _ + 1) { i => println(i) }

Výsledek bohužel není tak flexibilní jako for cyklus v jazycích, které ho mají v sobě přímo vestavěný.


Scala 3 do hry vnáší klíčové slovo inline a to zcela mění situaci. Kód vypadá, chová se a používá se stejně, jen se liší pod kapotou.

inline def for3[T](inline init: T)(inline cond: T => Boolean, inline incr: T => T)(inline op: T => Unit) = {
  var i = init
  while (cond(i)) {
    op(i)
    i = incr(i)
  }
}

Klíčové slovo inline znamená, že každé použití funkce for3 bude plně inlinováno scala kompilátorem. Když pak napíšu následující funkci sčítající prvky pole, bytekód vypadá identicky, jako kdybych while smyčku napsal ručně. Není v něm žádná abstrakce, žádní indirekce, žádné volání anonymních funkcí, žádná alokace proměnné s, ke níž se přistupuje z closure, na heapu.

def sum(xs: Array[Double]) = {
  var s = 0.0
  for3(0)(_ < xs.length, _ + 1){ i => s += xs(i) }
  s
}

JVM JIT by udělal podobné optimalizace, inlinoval by vše, co se dá a eliminovat abstrakce, ale nemusí se mu to vždy povést. Někdy heuristiky selžou, třeba při příliš hlubokém zanoření. S inline máme garanci, že se abstrakce zbavíme a kód ke prostá jednoduchá a snadno optimalizovatelná smyčka.

píše k47, ascii@k47.cz