|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Arnaud learns Scala - part 1 |
| 4 | +--- |
| 5 | + |
| 6 | +After having been a C++ developer for years, I started coding in java. But that was 10 years ago |
| 7 | +and, although I am now a seasoned java developer, I feel kind of outdated when I see how functional |
| 8 | +programming became more than a fashion. Switching entirely to functional programming would be quite |
| 9 | +a big move but a language like scala claims to make object-oriented programming meet functional |
| 10 | +programming... So I decided to learn scala, I'm 10 years late but it's never too late! |
| 11 | + |
| 12 | +{: .arnodb-center-image } |
| 13 | + |
| 14 | +More than learning scala because everyone knows scala, this decision aims at getting familiar with |
| 15 | +real life functional programming. Morover a lot of scala developers claim it greatly improved the |
| 16 | +quality of their object-oriented code and I believe them. |
| 17 | + |
| 18 | +Entering the Scala world |
| 19 | +------------------------ |
| 20 | + |
| 21 | +Well, this is the simplest thing to so, scala has a website dedicated to itself: |
| 22 | +[The Scala Programming Language](http://www.scala-lang.org/) (http://www.scala-lang.org/). It |
| 23 | +provides a huge amount of links to various documentation. |
| 24 | + |
| 25 | +I started with the obvious [Getting |
| 26 | +Started](http://www.scala-lang.org/documentation/getting-started.html) document and made the *Hello |
| 27 | +World* program run properly. By the way, I am a linux user (this is not definitive ;-) ), more |
| 28 | +precisely I am a Debian user, so `apt-get install scala` was enough to start coding in scala. |
| 29 | + |
| 30 | +The computer I used is not very powerful, this is why I haven't installed any IDE yet. Fortunately |
| 31 | +scala can be scripted: |
| 32 | + |
| 33 | +{% highlight scala %} |
| 34 | +#!/bin/sh |
| 35 | +exec scala "$0" "$@" |
| 36 | +!# |
| 37 | +// Scala script |
| 38 | +... |
| 39 | +{% endhighlight %} |
| 40 | + |
| 41 | +Then I was unsure where to go next. I found [A Scala Tutorial for Java |
| 42 | +Programmers](http://docs.scala-lang.org/tutorials/scala-for-java-programmers.html). It is really |
| 43 | +worth a reading. |
| 44 | + |
| 45 | +Then I was eager to code a bit although I didn't know a lot. |
| 46 | + |
| 47 | +Tower of Hanoi |
| 48 | +-------------- |
| 49 | + |
| 50 | +This game is well known and I chose it to start my experiments (one would be surprised by the |
| 51 | +variety of tools allowing the implementation of Tower of Hanoi :-) ). It took me 2 days and a lot of |
| 52 | +stackoverflow pages) to write this small scala script: |
| 53 | +[hanoi-1.scala |
| 54 | +(GitHub)](https://github.com/arnodb/arnodb.learns.scala/blob/master/sandbox/hanoi-1.scala). A |
| 55 | +seasoned scala developer may find it extremely poor, not efficient etc. But I learned a lot with it. |
| 56 | + |
| 57 | +### Tower class ### |
| 58 | + |
| 59 | +{% highlight scala %} |
| 60 | +class Tower(val floors: ArrayStack[Int]) { |
| 61 | + |
| 62 | + def height = floors.size |
| 63 | + |
| 64 | + override def toString() = toStrings(24).mkString("\n") |
| 65 | + |
| 66 | + def floorSizeToString(s: Int, width: Int) = { |
| 67 | + val side = width / 2 - s |
| 68 | + (" " * side) + ("-" * (s * 2)) + (" " * side) |
| 69 | + } |
| 70 | + |
| 71 | + def floorToString(f: Int, width: Int) = { |
| 72 | + if(f < floors.size) floorSizeToString(floors(floors.size - f - 1), width) else (" " * width) |
| 73 | + } |
| 74 | + |
| 75 | + def toStrings(width: Int) = { |
| 76 | + floors.map(f => floorSizeToString(f, width)) |
| 77 | + } |
| 78 | + |
| 79 | +} |
| 80 | +{% endhighlight %} |
| 81 | + |
| 82 | +* the `val` keyword in front of `floors` automatically defines a getter, otherwise `myTower.floors` would not |
| 83 | +work |
| 84 | +* the `height` accessor is here to add a bit of semantic: the height of a tower is the number of |
| 85 | +floors |
| 86 | +* the rest is dedicated to generate a human readable representation of the tower, it helped me learn |
| 87 | + * how to join a list of strings (`mkString`) |
| 88 | + * how to repeat a string multiple times (`"..." * repeat `) |
| 89 | + * a simple map call, the first bit of functional programming (yeah!) |
| 90 | + |
| 91 | +### Tower companion ### |
| 92 | + |
| 93 | +{% highlight scala %} |
| 94 | +object Tower { |
| 95 | + |
| 96 | + def foundation() = new Tower(new ArrayStack) |
| 97 | + |
| 98 | + def complete(bottom: Int) = range(bottom, 1) |
| 99 | + |
| 100 | + private def range(bottom: Int, top: Int) = new Tower(ArrayStack.range(top, bottom + 1)) |
| 101 | + |
| 102 | +} |
| 103 | +{% endhighlight %} |
| 104 | + |
| 105 | +I was a bit lazy but apparently the *companion* object is the pattern to use in scala in order to |
| 106 | +create factory methods. My Tower companion allows to create a tower with no floor at all |
| 107 | +(`foundation`) or a tower fully built with the first floor specified as argument and the last one of |
| 108 | +size 1 (`complete`). |
| 109 | + |
| 110 | +For the java programmers: a companion is kind of a class with static methods. Actually it is a |
| 111 | +singleton but that makes no difference. |
| 112 | + |
| 113 | +Technically a companion has access to private members, that may be useful. Moreover factory methods |
| 114 | +in a companion do not require the `new` keyword whereas constructors do. Syntactic sugar... |
| 115 | + |
| 116 | +### City class and companion ### |
| 117 | + |
| 118 | +{% highlight scala %} |
| 119 | +class City(val towers: Array[Tower]) { |
| 120 | + |
| 121 | + def size = towers.size |
| 122 | + |
| 123 | + override def toString() = { |
| 124 | + val cityHeight = 12 |
| 125 | + // from top to bottom |
| 126 | + (for(level <- cityHeight - 1 to 0 by -1) yield { |
| 127 | + (for(tower <- towers) yield { |
| 128 | + tower.floorToString(level, 24) |
| 129 | + }).mkString(" ") |
| 130 | + }).mkString("\n") +"\n" + "^" * (24 * 3 + 2) |
| 131 | + } |
| 132 | + |
| 133 | +} |
| 134 | + |
| 135 | +object City { |
| 136 | + |
| 137 | + def apply(towers: Tower*) = new City(towers.toArray) |
| 138 | + |
| 139 | +} |
| 140 | +{% endhighlight %} |
| 141 | + |
| 142 | +Two points: |
| 143 | + |
| 144 | +* `yield` invokes the map function, very usefull! |
| 145 | +* `apply` allows the following syntax: `City(...)` instead of `City.factory(...)`, very useful! |
| 146 | + |
| 147 | +### Crane ### |
| 148 | + |
| 149 | +Then we need a crane to move one floor from one tower to another: |
| 150 | + |
| 151 | +{% highlight scala %} |
| 152 | +object Crane { |
| 153 | + |
| 154 | + def move(city: City, from: Int, to: Int) { |
| 155 | + val fromTower = city.towers(from) |
| 156 | + val toTower = city.towers(to) |
| 157 | + if(toTower accepts fromTower) { |
| 158 | + toTower.floors push fromTower.floors.pop |
| 159 | + } else { |
| 160 | + println("Beep! Tower " + (to + 1) + " cannot receive the top of tower " + (from + 1)) |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | +} |
| 165 | +{% endhighlight %} |
| 166 | + |
| 167 | +Its purpose is only to check that the destination tower accepts the top floor of the source tower. |
| 168 | +Note the infixed call to `Tower.accept` and `ArrayStack.push`. The `accepts` method is added to the |
| 169 | +`Tower` class: |
| 170 | + |
| 171 | +{% highlight scala %} |
| 172 | +class Tower { |
| 173 | + |
| 174 | + def accepts(from: Tower) = { |
| 175 | + from.floors.size > 0 && (floors.size == 0 || from.floors.top < floors.top) |
| 176 | + } |
| 177 | + |
| 178 | +} |
| 179 | +{% endhighlight %} |
| 180 | + |
| 181 | +### Movement algorithm, the functional way ### |
| 182 | + |
| 183 | +Then comes the algorithm to move the floors from one tower to another. A very silly one that |
| 184 | +recursively moves "all but the first floor" to the spare slot, moves "the first floor" to the |
| 185 | +destination, and repeat the first operation to rebuild the top of the tower. |
| 186 | + |
| 187 | + |
| 188 | +{% highlight scala %} |
| 189 | +object Move { |
| 190 | + |
| 191 | + def superMove(height: Int, from: Int, to: Int, move: (Int, Int) => Unit): () => Unit = { |
| 192 | + height match { |
| 193 | + case 0 => () => () |
| 194 | + case 1 => () => move(from, to) |
| 195 | + case _ => { |
| 196 | + val s = Set(0, 1, 2) |
| 197 | + s -= from |
| 198 | + s -= to |
| 199 | + () => { |
| 200 | + superMove(height - 1, from, s.head, move)() |
| 201 | + move(from, to) |
| 202 | + superMove(height - 1, s.head, to, move)() |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | +} |
| 209 | +{% endhighlight %} |
| 210 | + |
| 211 | +* `superMove` aims at moving a given amount of floors from one tower to another |
| 212 | +* it takes an elementary move callback whose prototype is defined by `(Int, Int) => Unit`, basically |
| 213 | +a function that takes two integers |
| 214 | +* it returns a function which we have to execute in order to perform the operation |
| 215 | + |
| 216 | +The algorithm is simple and uses pattern matching (another scala great feature!) on the height parameter: |
| 217 | + |
| 218 | +* moving 0 floors does nothing, a no-operation function with no parameter is returned: `() => ()` |
| 219 | +* moving one floor calls the elementary move callback, a function with no parameter and calling |
| 220 | +`move` is returned: `() => move(from, to)` |
| 221 | +* the general case consists in moving one less floor from the source to the spare, moving the |
| 222 | +underlying floor to the destination, and repeating the first operation to the destination |
| 223 | + |
| 224 | +### Execution ### |
| 225 | + |
| 226 | +{% highlight scala %} |
| 227 | +def play { |
| 228 | + |
| 229 | + println("Building initial city...") |
| 230 | + |
| 231 | + val tower1 = Tower.complete(9) |
| 232 | + val tower2 = Tower.foundation |
| 233 | + val tower3 = Tower.foundation |
| 234 | + |
| 235 | + val city = City(tower1, tower2, tower3) |
| 236 | + |
| 237 | + println(city) |
| 238 | + |
| 239 | + var success = false |
| 240 | + var moves = 0 |
| 241 | + println("Crane is on the move...") |
| 242 | + val go = Move.superMove(city.towers(0).height, 0, 2, |
| 243 | + (from: Int, to: Int) => { |
| 244 | + Crane move(city, from, to) |
| 245 | + println(city) |
| 246 | + moves += 1 |
| 247 | + if(city.towers(0).height == 0 && city.towers(1).height == 0) { |
| 248 | + success = true |
| 249 | + } |
| 250 | + }) |
| 251 | + |
| 252 | + go() |
| 253 | + |
| 254 | + println( |
| 255 | + if(success) |
| 256 | + "Clap clap clap! You are a certified Scala pig! :-) (" + moves + ")" |
| 257 | + else |
| 258 | + "Pffffff :-(( (" + moves + ")" |
| 259 | + ) |
| 260 | + |
| 261 | +} |
| 262 | + |
| 263 | +play |
| 264 | +{% endhighlight %} |
| 265 | + |
| 266 | +* instantiation via the companion doesn't require the `new` keyword (towers) |
| 267 | +* `apply` factory is implicit (city) |
| 268 | +* the implementation of the elementary move calls the `Crane` object, prints the new state, counts |
| 269 | +the moves and checks for success |
| 270 | + |
| 271 | +Conclusion |
| 272 | +---------- |
| 273 | + |
| 274 | +Phew, we made it! |
| 275 | + |
| 276 | +The functional part was not so easy to write for the compiler messages are not always obvious. |
| 277 | +Especially when the function return types are infered the error is often deported to the caller |
| 278 | +code. I guess I will get used to identify the real problems after a few weeks of training. |
| 279 | + |
| 280 | +When I started java I remember my first feeling: waouh, the compiler is so slow! That was biased by |
| 281 | +the time it takes to start a JVM. With scala I have the exact same impression. I don't know whether |
| 282 | +scala generates java byte code directly (I guess so) or asks the java compiler to compile some java |
| 283 | +source code but this phase is CPU consuming. Anyway this doesn't apply in a non scripted enviroment. |
| 284 | + |
| 285 | +The game also showed that I need to learn more about scala collections (mutable or immutable, etc.) |
| 286 | +because it is not as obvious as in java. So that is one of my next steps forward along with reading |
| 287 | +[Scala By Example](http://www.scala-lang.org/docu/files/ScalaByExample.pdf) |
| 288 | + |
| 289 | +Links |
| 290 | +----- |
| 291 | + |
| 292 | +* [The Scala Programming Language](http://www.scala-lang.org/) |
| 293 | +* [Getting Started](http://www.scala-lang.org/documentation/getting-started.html) |
| 294 | +* [A Scala Tutorial for Java |
| 295 | +Programmers](http://docs.scala-lang.org/tutorials/scala-for-java-programmers.html) |
| 296 | +* [hanoi-1.scala on GitHub](https://github.com/arnodb/arnodb.learns.scala/blob/master/sandbox/hanoi-1.scala) |
| 297 | +* [Scala By Example](http://www.scala-lang.org/docu/files/ScalaByExample.pdf) |
| 298 | + |
| 299 | + |
0 commit comments