Summa sidvisningar

tisdag 22 november 2011

Funktioner

I detta inlägg tänkte jag börja titta lite på hur funktioner kan användas i Scala. En trevlig sak med Scala är att det både är objektorienterat och funktionellt. I Scala och andra funktionella språk kan man skicka med en funktion som argument till en metod. Funktionen kan antingen definieras och instansieras direkt vid själva anropet (benämns function value i boken) eller först tilldelas en variabel (function literal). Låt oss testa detta, koden finns även här:

Exempel 5 - function literals

package nu.tengstrand.scalalab.programminginscala

/**
 * Example of function literals.
 * Page 148 in the book "Programming in Scala, 2nd edition".
 */
object Example005FunctionLiterals {

  def main(args: Array[String]) {
    val integers = List(1,2,3,4,5)
    val odds = integers.filter(_ % 2 == 1)
    val evenFilter = (_:Int) % 2 == 0
    println(odds) // output: List(1, 3, 5)
    println(integers.filter(evenFilter))  // output: List(2, 4)
  }
}

På rad 11 skickar vi in en funktion som argument till metoden filter som returnerar en ny lista innehållandes de ojäma värdena. På rad 12 väljer vi att först tilldela funktionen till en variabel som sedan används på rad 14 för att filtrera listan. I första fallet förstår kompilatorn att typen är Int då listan består av Int:ar medan vi i andra fallet explicit måste ange att typen är Int.

Om vi kompilerar koden ser vi att den producerar fyra klasser:
  1. Example005FunctionLiterals.class
  2. Example005FunctionLiterals$.class
  3. Example005FunctionLiterals$$anonfun$1.class
  4. Example005FunctionLiterals$$anonfun$2.class
I exemplet har vi bara ett objekt (nyckelordet object) med namnet Example005FunctionLiterals, dock ingen klass (nyckelordet class). Konventionen i Scala är att klassfilen får samma namn som klassen medan objektfilen får klassnamnet plus ett avslutande dollartecken, i det här fallet Example005FunctionLiterals$.class.

När jag tittar på källkoden kan jag konstatera att all nödvändig kod hamnat i objektfilen. Koden i klassfilen verkar inte innehålla något vettigt! Koden i de två anonyma funktionerna anonfun$1 och anonfun$2 finns även representerad i objektklassen och verkar även de vara överflödiga. De skapas möjligen för att kunna accessas utifrån. Jag blev lite konfunderad av detta beteende och skrev därför ihop ett liknande program i Java och kunde då konstatera att Java-kompilatorn uppförde sig på samma sätt.

Låt oss decompilera objektklassen till Java:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Example005FunctionLiterals.scala

package nu.tengstrand.scalalab.programminginscala;

import scala.*;
import scala.collection.TraversableLike;
import scala.collection.immutable.List;
import scala.collection.immutable.List$;
import scala.runtime.BoxesRunTime;

public final class Example005FunctionLiterals$ implements ScalaObject {

    public void main(String args[]) {
        List integers = List$.MODULE$.apply(Predef$.MODULE$.wrapIntArray(new int[] {
            1, 2, 3, 4, 5
        }));
        List odds = (List)integers.filter(new Serializable() {

            public final boolean apply(int i) {
                return apply$mcZI$sp(i);
            }

            public boolean apply$mcZI$sp(int v1) {
                return v1 % 2 == 1;
            }

            public final volatile Object apply(Object v1) {
                return BoxesRunTime.boxToBoolean(apply(BoxesRunTime.unboxToInt(v1)));
            }

            public static final long serialVersionUID;

            static {
                0L;
                serialVersionUID = 0L;
            }
        }
    );

    scala.Function1 evenFilter = new Serializable() {
            public final boolean apply(int i) {
                return apply$mcZI$sp(i);
            }

            public boolean apply$mcZI$sp(int v1) {
                return v1 % 2 == 0;
            }

            public final volatile Object apply(Object v1) {
                return BoxesRunTime.boxToBoolean(apply(BoxesRunTime.unboxToInt(v1)));
            }

            public static final long serialVersionUID;

            static {
                0L;
                serialVersionUID = 0L;
            }
        };
        Predef$.MODULE$.println(odds);
        Predef$.MODULE$.println(integers.filter(evenFilter));
    }

    private Example005FunctionLiterals$() {
    }

    public static final Example005FunctionLiterals$ MODULE$ = this;

    static {
        new Example005FunctionLiterals$();
    }
}
Här kan vi se att varje rad i Scala-koden har en motsvarande rad i Java-koden vilket gör den lätt att följa. På rad 20 skickas en anonym klass in som argument till metoden filter och som implementerar metoden apply(int) på rad 22, vilket är allt som behövs för att filtrera listan. På rad 43 kan vi se att våra filter hanteras av trait:et scala.Function1.

Sammantaget är inte funktioner så komplicerat i Scala!

Inga kommentarer:

Skicka en kommentar