Programmeringsglädje del 2: Scala

Programmeringsglädje del 2: Scala
juli 10, 2012 erik.lupander@squeed.com

För några dagar sedan bloggade jag om Programmeringsglädje. Idag var det dags att fortsätta min högst spontana programmeringsresa genom att skriva om patienslösarprogrammet i Scala.

Scala har de senaste åren blivit allt mer uppmärksammat även utanför entusiastkretsarna. Redan 2010 var IDG igång och propagerade för Scala som ett alternativ till Java. Själv har jag sneglat några gånger på språket – tilltalats av interoperabiliteten med Javas API:er, möjligheten att i viss mån blanda imperativ programmering med funktionell och inte minst möjligheten att på allvar lära mig ett funktionellt programmeringsspråk. Visst, jag testade på lite Haskell och Prolog på universitetet för tiotalet år sedan, men på en alldeles för ytlig nivå för att något skulle fastna.

Mitt problem med Scala har hittills varit relativt syntaxrelaterat. Till vardags programmerar jag vanlig gammal hederlig Java och relativt mycket Javascript. Jag har f.ö. ett något lätt perverst förhållande till Javascript. Jag känner en blandning av kärlek och lätt äckel när jag snickrar ihop webblösningar baserade på jQuery, DWR/CometD, XHTML, REST-tjänster, JSON etc. Underbar turn-around tid, funktionella inslag och framförallt en helt annan känsla av kontroll över applikationen jämfört med när jag använder GWT/JSF etcetera. Men också otaliga småbuggar pga. uselt IDE-stöd (har inte IntelliJ), genomkrångliga scope-konstruktioner och riskerna med dynamisk typning. Jag har också kodat en hel del C/C++, PHP, ASP och C# i mina dar. Och i stort sett ingenting ser likadant ut i Scala. Mycket möjligt att Ruby, Groovy, Python, Clojure etc. är mer likt Scala än de språk jag behärskar sedan tidigare.

Jag har tittat på Scalakod och ofta fastnat redan vid den första metoddeklarationen:

// SCALA
def myMethod(paramName1 : String, paramName2 : MyClass) : MyOtherClass = {
    // method body
}
// JAVA
MyOtherClass myMethod(String paramName1, MyClass paramName2) {
    // Method body
}

Nu, efter att jag tagit mig förbi den initiala inlärningströskeln tycker jag inte Scalas metoddeklaration ser särskilt konstig ut. Jag har börjat förstå skillnaden mellan scalas ”class” och ”object”, där class är vad man tror det är, medan object närmast är att betrakta som en singleton utan att behöva trixa till en GoF-singleton.

För att komma igång ordentligt laddade jag hem Scalatron och följde till punkt och pricka deras 10-stegs tutorial som på ett utmärkt vis introducerade språkets olika konstruktioner. Vissa saker är dock svåra att begripa enligt mitt tycke, men det ger sig ju mer man experimenterar och försöker. Nackdelen med Scalatron-tutorialen var kanske att man efteråt hade en robot som i stort sett inte kunde göra någonting. Och steget efter tutorialen var att kolla på deras referensimplementation av en robot vilken var långt mer komplex än det man gått genom fram tills dess. Tanken är förstås att man skall fortsätta själv efter tutorial, men jag kände att det var ett ganska stort steg att ta då man inte skriver någon kod själv under tutorialen.

Istället beslutade jag mig för att skriva om den tidigare nämnda patienslösaren i Scala. För det ändamålet installerade jag Scala IDE for Eclipse.

Problem nummer 1: Hur gör jag en klass med motsvarande main()-metod? Nätsökning på ”Scala Hello World” gav mest träffar som blandade in SBT, medan jag ville hålla mig till Eclipse. Lösningen var som tur var mycket enkel då det fanns en färdig template i Scala IDE.

object MainProgram extends App {
}

Här råkade jag omgående på en av Scalas ”egenheter”. Jag ville skriva mig en main()-metod, men det ville inte Scala IDE vara med om. Det visade sig att i ovanstående konstruktion, så skriver man sin ”main-metod” direkt i sitt objects body. Och att man skriver sina metoder direkt in i samma body:

object MainProgram extends App {
    println("Starting the main() method")
    myMethod("Erik")

    def myMethod(name : String) = {
        println(name)
    }
}

Något förvirrande för en Javautvecklare. Jag bestämde mig för att snickra ihop samma Card och Stack-klasser som jag använde i Javaversionen av min patienslösare. I Scala kan man skriva klasserna i vilken .scala fil som helst, men av gammal vana skapade jag ändå filerna Card.scala och Stack.scala.

Låt oss ta en titt på Stack.scala, klassen som representerar ett kort eller (n) kort som ligger staplade på varandra.

package com.squeed.scalapatiens
import scala.collection.mutable.ArrayBuffer

case class Stack(cards: ArrayBuffer[Card]) {
  def getTopCard(): Card = {
    this.cards.apply(0)
  }

  def clear() = {
    this.cards.clear();
  }

  def addCard(card: Card) = {
    this.cards.insert(0, card)
  }

  def addStack(stack: Stack) = {
    this.cards.insertAll(0, stack.cards)
  }

}

case class? Nyckelordet ”case” ger oss bl.a. autogenererade equals och toString metoder samt någon form av ”getter” för ”cards”. Tar man bort ”case” ger ovanstående kod kompileringsfel på

this.cards.insertAll(0, stack.cards)

då stack.cards inte verkar vara synlig när den refereras utanför this-kontexten. Med följande ändringar fungerar det:

class Stack(cards: ArrayBuffer[Card]) {
  def getTopCard(): Card = {
    this.cards.apply(0)
  }

  def clear() = {
    this.cards.clear();
  }

  def addCard(card: Card) = {
    this.cards.insert(0, card)
  }

  def addStack(stack: Stack) = {
    this.cards.insertAll(0, stack.getCards)
  }
  
  def getCards() : ArrayBuffer[Card] = {
    cards
  }
}

Dvs. jag har skrivit en enkel getCards()-metod. Notera Scalas något luriga syntax för att returnera ett värde. Scala är väldigt bra på att skala bort onödigt syntaktiskt socker. getCards-metoden kan faktiskt skrivas så här:

def getCards = cards

Nu ska vi se: Först så kan vi ta bort () efter metodnamnet. Då vår metod inte tar något argument så tvingar inte Scala oss heller att skriva ut parenteserna. Och genom ”type inference” (vet ej hur man översätter det till svenska) kan Scalas kompilator titta på vad ”cards” är för typ och då behöver man inte explicit skriva ut returtypen. Och slutligen kan vi också skippa {} då en ensam kodrad i metoden inte kräver {} kring sig.

Själv är jag något kluven. Å ena sidan är def getCards = cards oerhört lättläst. Men å andra sidan är jag van som javaprogrammerare att alltid ha samma syntax för alla metoder. I Scalas fall är det långt ifrån alltid man slipper undan typdeklarationer och jag tycker på ett sätt att det är enkelt och tydligt när typerna skrivs ut och när man alltid har {} kring sin metodkropp. Kanske en smaksak?

Om vi återgår till huvudprogrammet, kreativt nog kallat MainProgram.scala, så finns det ett par andra egenheter att lägga märke till.

#1: Rekursion
Då jag översatte javaprogrammet ganska rakt av så blev det förstås en del rekursion även här. Men Scala tillåter inte metoder utan returtyp att kallas rekursivt! Se här för en mer detaljerad förklaring. Men kort och gott så är lösningen mycket enkel då Scala har ett ”specialobjekt” som heter Unit som helt enkelt är en typrepresentation av void. Så här blir en sådan metod deklarerad:

def tryToStack(stacks: ArrayBuffer[Stack]): Unit = {
    // Method body
    if(someCondition)
        tryToStack(stacks)
}

#2: For-loopen
Syntaxen för for-loopen är också lite annorlunda än vad jag är van vid. Och då jag vill avbryta och börja om försöken att flytta korthögar varje gång en hög flyttats vill jag hemskt gärna kunna använda break för att någorlunda elegant ta mig ur min for-loop. Det visar sig att Scala inte riktigt gillar break och continue. Det finns några workarounds såsom att explicit köra return inne i loopen (return anses tydligen fult i scala-sammanhang). Jag hittade en konstruktion där man får deklarera for loopen inuti ett breakable {} block:

breakable {
      for (i <- (stacks.size - 1) to 1 by -1) {

        val card1 = stacks.apply(i).getTopCard()
        if (i - 1 >= 0) {
          val adjacentCard = stacks.apply(i - 1).getTopCard()
          if (card1.canStackCard(adjacentCard)) {
            performStacking(i, i - 1, stacks)
            tryToStack(stacks)
            break
          }
        }
        if (i - 3 >= 0) {
          val threeSlotsAwayCard = stacks.apply(i - 3).getTopCard()
          if (card1.canStackCard(threeSlotsAwayCard)) {
            performStacking(i, i - 3, stacks)
            tryToStack(stacks)
            break
          }
        }
      }
    }

Om ni tittade på Javakoden i förra blogginlägget ser koden helt klart lik ut. Det är ungefär samma if-satser, samma typ av sjunkande for-loop, rekursivt anrop när man utfört en ”stackning” osv. For-loopen är som sagt lite annorlunda:

for (i <- (stacks.size - 1) to 1 by -1)

Ingångsvärdet i sätts till stacks.size-1. Värdet skall ändras till det blir 1. Och ändringen skall vara -1 för varje iteration. Ganska klart, även om jag inte riktigt läst in mig på varför man skriver "i <- initalValue". Avslutningsvis kan vi kika lite på koden som gör iordning kortleken och blandar den:

def setupDeck(deck: ArrayBuffer[Card]): Array[Card] = {
    for (i <- 0 until 13) {
      deck + (new Card(i, "SPADES"))
      deck + (new Card(i, "HEARTS"))
      deck + (new Card(i, "CLUBS"))
      deck + (new Card(i, "DIAMONDS"))
    }

    return Shuffler.shuffle(deck.toList.toArray).toArray
  }

object Shuffler {
  def shuffle[T](array: Array[T]): Array[T] = {
    val rnd = new java.util.Random
    for (n <- Iterator.range(array.length - 1, 0, -1)) {
      val k = rnd.nextInt(n + 1)
      val t = array(k); array(k) = array(n); array(n) = t
    }
    return array
  }
}

Kortleken representeras alltså som en ArrayBuffer[Card], vilket på ett ungefär verkar motsvara en ArrayList i Java. En enkel for-sats skapar de 52 korten och sedan blandar jag den mha en singleton "object" jag kallar Shuffler. Först försökte jag använda Collections.shuffle(..) men misslyckades. Själva shuffle-metoden plockade jag från StackOverflow, men den borde vara ganska lättläst. Det intressanta är dels hur enkelt det är att använda en klass (java.util.Random) från Javas API:er, dels hur anropet till shuffle blir. Det blev flera konverteringssteg (toList.toArray) för att få datatyperna att samarbeta.

Prestanda
Jag var förstås tvungen att jämföra prestandan på Scalaimplementationen och Javaimplementationen. Och det var rejäl skillnad. För att få patiensen att gå ut krävs fortfarande runt 1350 försök när man kör tills patiensen gått ut 1000 gånger.

Java: ca 35 sekunder
Scala: ca 85 sekunder

Som sagt, en mycket stor differens till Scalas nackdel. Detta var som sagt mitt första Scalaprogram någonsin och jag har knappast lyckats särskilt bra med det hela heller. Finns nog mängder med förbättringar för att göra det hela effektivare, mer funktionellt och definitivt mer lättläst. Källkoden finns på min publika GitHub-sida: https://github.com/eriklupander/ScalaPatiens/

Hoppas någon orkat läsa ända hit och att jag fått fram lite om mina första erfarenheter av detta spännande programmeringsspråk!

0 Kommentarer

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *

*

Denna webbplats använder Akismet för att minska skräppost. Lär dig hur din kommentardata bearbetas.