Block-Trick


Ich bin gerade auf Stack Overflow auf einen netten Trick gestoßen.

Die Frage war, warum so eine seltsame Syntax wie…

(1 to 3) foreach {  
  val line = Console.readLine  
  i: Int => println(line)  
}  

… erlaubt ist. Die Antwort ist einfach: foreach[A] erwartet als Argument eine Funktion A => Unit, und der Wert eines Blocks ist gleich seinem letzten Element, das hier auch eine passende Funktion liefert. Aus praktischer Sicht heißt das, dass in Methoden wie foreach, map u.s.w. vor der Funktionsdefinitions alles mögliche stehen kann. „Dem Faß die Krone“ aufgesetzt hat Dave Griffith in seiner Antwort mit folgendem Beispiel (ich habe es noch etwas vereinfacht):

val v = (1 to 6) map {
    var total = 1
    i:Int => {total *= i; total}
}
println(v)

Das Beispiel gibt die Fakultäten von 1 bis 6 zurück. Dabei „merkt“ sich die zurückgegebene Funktion die Variable total – genau diese „Einfangen“ der aktuellen Umgebung unterscheidet echte Closures von simplen Codeblöcken – und „recycled“ sie. Damit hat man ein interessantes Werkzeug, um die eigentlich voneinander unabhängigen Berechnungen in map oder foreach doch irgendwie miteinander zu verketten.

Und gerade das Beispiel mit der Fakultät scheint es in sich zu haben, denn wie soll man es sonst schreiben? Ich bin nur noch auf folgende Monströsität gekommen, die sicher keine Verbesserung ist:

val v = (1 to 6).foldLeft(List[Int]()){ (list, i) =>
    (list.headOption.getOrElse(1) * i) :: list }.reverse
println(v)

Wem fällt etwas besseres ein?

Nachtrag
Wenn wir schonmal beim Etwas-Besseres-Finden sind: Ich bin gerade über das Blog von James Carr gestolpert. Hier meine Lösung zu seiner Aufgabe, das Pascalsche Dreieck als Liste von Listen zu berechnen:

def pascal(n: Int) = {
  def loop(n:Int):List[List[Long]] = if (n == 1) List(List(1L)) else {
    val p = loop(n-1)
    val last = 0L :: p.head
    last.zip(last.reverse).map(t => t._1 + t._2) :: p
  }
  loop(n).reverse
}

Und wem fällt dazu etwas besseres ein?

Advertisements

4 Gedanken zu “Block-Trick

  1. Klar und deutlich ist ja meist besser:

    def fak (n: Int) : Int = {        
         if (n == 1) 1 else         
         n * fak (n-1) }
    
    val v2 = (1 to 6).map (fak)
    

    getreu dem Motto nicht zu clever zu sein.

  2. Nachtrag: Ein kurzer Hinweis zur Blogsyntax (wie formatiere ich Code [, Quotes, kursiv, …]?) wäre praktisch. Beim Raten liege ich oft daneben, und ich besuche zu viele Blogs, um mir all die Eigenheiten zu merken.

  3. Am besten sieht Code mit [sourcecode language=’scala‘] … [/sourcecode] aus.

    Bei der Frage ging es darum, Teilergebnisse vom letzten Schritt wiederverwenden zu können. Ich schätze schon bei einer Liste von 1000 Fakultäten (natürlich dann BigInt) wäre der Unterschied eklatant.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s