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!

torsdag 17 november 2011

Programming in Scala - fördjupning

Nu har jag programmerar Scala på ledig tid i ganska exakt ett år. Det har varit roligt och lärorikt och något jag kan rekommendera insnöade Java-programmerare och andra som vill testa ett elegant språk med stöd för funktionell programmering!

Jag har hittills bara läst 200-300 sidor ut boken Programming in Scala second edition men planen är nu att läsa hela boken och att samtidigt förstå språket lite mer på djupet. Sättet jag tänkt göra det på är i huvudsak genom kodexempel. Jag kommer att göra nedslag på sådant jag tycker förtjänar extra uppmärksamhet eller som är svårsmält för en icke insatt, dit jag än så länge räknar mig själv! Jag kommer att läsa boken från pärm till pärm och rapportera om mina "upptäckter". Projektet Tetris Analyzer återkommer jag till vid ett senare tillfälle.

Exempel 1 - Referera arrayer och listor


I Scala kan man referera arrayer och listor med syntaxen (t ex) minLista(0) när man vill hämta ut ett element ur listan. Kompilatorn kommer då att göra om anropet till minLista.apply(0). Detta var jag förstås tvungen att testa (all kod finns på GitHub):
package nu.tengstrand.scalalab.programminginscala

/**
 * How to build your own List/Array classes.
 * Page 39 in the book "Programming in Scala, 2nd edition".
 *
 * Author: Joakim Tengstrand
 * Repository: git@github.com:tengstrand/Scala-Lab.git
 */
object Example001ListSyntax {
  def main(args: Array[String]) {
    val myArray = new MyArray()
    println(myArray(0)) // output: 1
    println(myArray.apply(1)) // output: 2
  }
}

class MyArray {
  val array = Array(1,2,3)

  def apply(i: Int) = array(i)
}

Allt som behövs är att implementera metoden apply(Int) på rad 21 varefter vi på rad 13 hämtar första elementet i listan. Metoden apply är en helt vanlig metod, vilket demonstreras på rad 14. Scala löser denna och andra liknande uppgifter genom konventioner. Konventionen i detta fall är att kompilatorn förväntar sig finna metoden apply när den stöter på en variabel följt av parenteser, vilket i mitt tycke är ett elegant sätt att lösa problemet.

Exempel 2 - Symboler


Nästa konvention är att om ett '-tecken "enkelfnutt" föregår en variabel så kommer kompilatorn uppfatta det som en Symbol vars attribut name är själva värdet på variabeln, detta beskrivs förvisso bra i boken men jag kände mig ändå sugen på att testa:
package nu.tengstrand.scalalab.programminginscala

/**
 * Example how to use symbols.
 * Page 80 in the book "Programming in Scala, 2nd edition".
 */
object Example002Symbol {
  def main(args: Array[String]) {
    new Example().print('KalleKanin) // output: Value: KalleKanin
  }
}
g
class Example {
  def print(value: Symbol) {
    println("Value: " + value.name)
  }
}
Verkar ju fungera! KalleKanin skickas in från rad 9 och skrivs ut på rad 15.

Exempel 3 - Teckensättning


I nästa exempel tänkte jag testa hur man gör sin egen klass som hanterar teckensättning:
package nu.tengstrand.scalalab.programminginscala

/**
 * Example how to use unary operators on own classes
 * Page 83 in the book "Programming in Scala, 2nd edition".
 */
object Example003UnaryOperatorSyntax {
  def main(args: Array[String]) {
    val value = new MyType(123)
    println(-value) // output: -123
    println(+value) // output: 123
    println(value.unary_-) // output: -123
  }
}

class MyType(value: Int) {
  def unary_- = new MyType(-value)
  def unary_+ = this
  override def toString = value.toString
}

Metoden unary_- implementeras på rad 17 och anropas från rad 10, metoden unary_+ implementeras på rad 18 och anropas från rad 11. Rad 12 visar att det är en helt vanlig metod som kan anropas som vilken annan metod som helst.

Exempel 4 - Lokala funktioner


I Scala kan man definiera funktioner i andra funktioner och metoder, så kallade lokala funktioner. Detta kan vara användbart när man vill öka läsbarheten och minska kodduplicering i koden:
package nu.tengstrand.scalalab.programminginscala

/**
 * Shows how to use local functions to simplify the code.
 * Page 144 in the book "Programming in Scala, 2nd edition".
 */
object Example004LocalFunctions {

  def main(args: Array[String]) {
    println(format(1,2,3,4, 10)) // output: (13:23)(33:43)
  }

  /**
   * The outer variable 'factor' is accessible from the local method multiply.
   * This technique can be used to simplify a function/method.
   */
  def format(a1: Int, a2: Int, b1: Int, b2: Int, factor: Int) = {
    val x = 3

    def multiply(value: Int) = value * factor + x
    def concat(value1: Int, value2: Int) = "(" + multiply(value1) + ":" + multiply(value2) + ")"

    concat(a1,a2) + concat(b1,b2)
  }
}

Det fina med lokala funktioner är att de har åtkomst till variabler och argument definierade i den omslutande funktionen vilket gör att dessa inte behöver läggas till argumentlistan i den lokala funktionen. I det här exemplet har metoden multiply tillgång till variabeln x och argumentet factor.

Det var allt för denna gång!