| author | Christian Urban <urbanc@in.tum.de> | 
| Tue, 16 Jan 2018 10:47:29 +0000 | |
| changeset 168 | bb69fdebf05a | 
| parent 158 | f60e0908f80b | 
| child 170 | 3d760b06befa | 
| permissions | -rw-r--r-- | 
| 67 | 1 | // Scala Lecture 3 | 
| 2 | //================= | |
| 3 | ||
| 155 | 4 | // Pattern Matching | 
| 5 | //================== | |
| 6 | ||
| 7 | // A powerful tool which is supposed to come to Java in a few years | |
| 8 | // time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already | |
| 158 | 9 | // has it for many years. Other functional languages have it already for | 
| 10 | // decades. I think I would be really upset if a programming language | |
| 11 | // I have to use does not have pattern matching....its is just so | |
| 12 | // useful. ;o) | |
| 155 | 13 | |
| 14 | // The general schema: | |
| 15 | // | |
| 16 | //    expression match {
 | |
| 17 | // case pattern1 => expression1 | |
| 18 | // case pattern2 => expression2 | |
| 19 | // ... | |
| 20 | // case patternN => expressionN | |
| 21 | // } | |
| 22 | ||
| 23 | ||
| 24 | // remember | |
| 25 | val lst = List(None, Some(1), Some(2), None, Some(3)).flatten | |
| 26 | ||
| 27 | ||
| 28 | def my_flatten(xs: List[Option[Int]]): List[Int] = {
 | |
| 158 | 29 | if (xs == Nil) Nil | 
| 30 | else if (xs.head == None) my_flatten(xs.tail) | |
| 31 | else xs.head.get :: my_flatten(xs.tail) | |
| 155 | 32 | } | 
| 33 | ||
| 34 | ||
| 35 | ||
| 158 | 36 | val lst = List(None, Some(1), Some(2), None, Some(3)) | 
| 155 | 37 | |
| 38 | def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
 | |
| 39 | case Nil => Nil | |
| 40 | case None::xs => my_flatten(xs) | |
| 41 | case Some(n)::xs => n::my_flatten(xs) | |
| 42 | } | |
| 43 | ||
| 158 | 44 | my_flatten(lst) | 
| 45 | ||
| 46 | Nil == List() | |
| 47 | ||
| 155 | 48 | |
| 49 | // another example including a catch-all pattern | |
| 50 | def get_me_a_string(n: Int): String = n match {
 | |
| 51 | case 0 => "zero" | |
| 52 | case 1 => "one" | |
| 53 | case 2 => "two" | |
| 54 | case _ => "many" | |
| 55 | } | |
| 56 | ||
| 158 | 57 | get_me_a_string(10) | 
| 155 | 58 | |
| 59 | // you can also have cases combined | |
| 60 | def season(month: String) = month match {
 | |
| 61 | case "March" | "April" | "May" => "It's spring" | |
| 62 | case "June" | "July" | "August" => "It's summer" | |
| 63 | case "September" | "October" | "November" => "It's autumn" | |
| 64 | case "December" | "January" | "February" => "It's winter" | |
| 65 | } | |
| 66 | ||
| 67 | println(season("November"))
 | |
| 68 | ||
| 69 | // What happens if no case matches? | |
| 70 | ||
| 71 | println(season("foobar"))
 | |
| 72 | ||
| 73 | ||
| 158 | 74 | // we can also match more complicated pattern | 
| 75 | // | |
| 76 | // let's look at the Collatz function on binary strings | |
| 155 | 77 | |
| 78 | // adding two binary strings in a very, very lazy manner | |
| 152 | 79 | |
| 80 | def badd(s1: String, s2: String) : String = | |
| 81 | (BigInt(s1, 2) + BigInt(s2, 2)).toString(2) | |
| 82 | ||
| 83 | ||
| 158 | 84 | "111".dropRight(1) | 
| 85 | "111".last | |
| 152 | 86 | |
| 87 | def bcollatz(s: String) : Long = (s.dropRight(1), s.last) match {
 | |
| 158 | 88 |   case ("", '1') => 1                               // we reached 1
 | 
| 89 | case (rest, '0') => 1 + bcollatz(rest) | |
| 90 | // even number => divide by two | |
| 91 | case (rest, '1') => 1 + bcollatz(badd(s + '1', s)) | |
| 92 | // odd number => s + '1' is 2 * s + 1 | |
| 93 | // add another s gives 3 * s + 1 | |
| 152 | 94 | } | 
| 95 | ||
| 158 | 96 | bcollatz(6.toBinaryString) | 
| 152 | 97 | bcollatz(837799.toBinaryString) | 
| 98 | bcollatz(100000000000000000L.toBinaryString) | |
| 99 | bcollatz(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))
 | |
| 100 | ||
| 155 | 101 | |
| 102 | ||
| 103 | ||
| 104 | // User-defined Datatypes | |
| 105 | //======================== | |
| 106 | ||
| 107 | abstract class Colour | |
| 158 | 108 | case object Red extends Colour | 
| 109 | case object Green extends Colour | |
| 110 | case object Blue extends Colour | |
| 155 | 111 | |
| 112 | def fav_colour(c: Colour) : Boolean = c match {
 | |
| 158 | 113 | case Red => false | 
| 114 | case Green => true | |
| 115 | case Blue => false | |
| 152 | 116 | } | 
| 117 | ||
| 158 | 118 | fav_colour(Green) | 
| 119 | ||
| 152 | 120 | |
| 155 | 121 | // actually colors can be written with "object", | 
| 122 | // because they do not take any arguments | |
| 152 | 123 | |
| 124 | ||
| 158 | 125 | // ... a bit more useful: Roman Numerals | 
| 67 | 126 | |
| 153 | 127 | abstract class RomanDigit | 
| 128 | case object I extends RomanDigit | |
| 129 | case object V extends RomanDigit | |
| 130 | case object X extends RomanDigit | |
| 131 | case object L extends RomanDigit | |
| 132 | case object C extends RomanDigit | |
| 133 | case object D extends RomanDigit | |
| 134 | case object M extends RomanDigit | |
| 135 | ||
| 136 | type RomanNumeral = List[RomanDigit] | |
| 67 | 137 | |
| 153 | 138 | def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { 
 | 
| 139 | case Nil => 0 | |
| 140 | case M::r => 1000 + RomanNumeral2Int(r) | |
| 141 | case C::M::r => 900 + RomanNumeral2Int(r) | |
| 142 | case D::r => 500 + RomanNumeral2Int(r) | |
| 143 | case C::D::r => 400 + RomanNumeral2Int(r) | |
| 144 | case C::r => 100 + RomanNumeral2Int(r) | |
| 145 | case X::C::r => 90 + RomanNumeral2Int(r) | |
| 146 | case L::r => 50 + RomanNumeral2Int(r) | |
| 147 | case X::L::r => 40 + RomanNumeral2Int(r) | |
| 148 | case X::r => 10 + RomanNumeral2Int(r) | |
| 149 | case I::X::r => 9 + RomanNumeral2Int(r) | |
| 150 | case V::r => 5 + RomanNumeral2Int(r) | |
| 151 | case I::V::r => 4 + RomanNumeral2Int(r) | |
| 152 | case I::r => 1 + RomanNumeral2Int(r) | |
| 67 | 153 | } | 
| 154 | ||
| 153 | 155 | RomanNumeral2Int(List(I,V)) // 4 | 
| 158 | 156 | RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number) | 
| 153 | 157 | RomanNumeral2Int(List(V,I)) // 6 | 
| 158 | RomanNumeral2Int(List(I,X)) // 9 | |
| 159 | RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 | |
| 160 | RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 | |
| 67 | 161 | |
| 162 | ||
| 155 | 163 | |
| 164 | // another example | |
| 165 | //================= | |
| 166 | ||
| 158 | 167 | // Once upon a time, in a complete fictional country there were Persons... | 
| 67 | 168 | |
| 155 | 169 | abstract class Person | 
| 158 | 170 | case object King extends Person | 
| 155 | 171 | case class Peer(deg: String, terr: String, succ: Int) extends Person | 
| 172 | case class Knight(name: String) extends Person | |
| 173 | case class Peasant(name: String) extends Person | |
| 158 | 174 | case object Clown extends Person | 
| 155 | 175 | |
| 176 | def title(p: Person): String = p match {
 | |
| 158 | 177 | case King => "His Majesty the King" | 
| 155 | 178 |   case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
 | 
| 179 |   case Knight(name) => s"Sir ${name}"
 | |
| 180 | case Peasant(name) => name | |
| 158 | 181 | case Clown => "My name is Boris Johnson" | 
| 182 | ||
| 67 | 183 | } | 
| 184 | ||
| 158 | 185 | title(Clown) | 
| 186 | ||
| 187 | ||
| 67 | 188 | |
| 155 | 189 | def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
 | 
| 158 | 190 | case (King, _) => true | 
| 155 | 191 | case (Peer(_,_,_), Knight(_)) => true | 
| 192 | case (Peer(_,_,_), Peasant(_)) => true | |
| 158 | 193 | case (Peer(_,_,_), Clown) => true | 
| 155 | 194 | case (Knight(_), Peasant(_)) => true | 
| 158 | 195 | case (Knight(_), Clown) => true | 
| 196 | case (Clown, Peasant(_)) => true | |
| 155 | 197 | case _ => false | 
| 198 | } | |
| 199 | ||
| 200 | val people = List(Knight("David"), 
 | |
| 201 |                   Peer("Duke", "Norfolk", 84), 
 | |
| 202 |                   Peasant("Christian"), 
 | |
| 158 | 203 | King, | 
| 204 | Clown) | |
| 155 | 205 | |
| 206 | println(people.sortWith(superior(_, _)).mkString(", "))
 | |
| 67 | 207 | |
| 208 | ||
| 155 | 209 | |
| 210 | ||
| 211 | // Tail recursion | |
| 212 | //================ | |
| 72 | 213 | |
| 67 | 214 | |
| 215 | def fact(n: Long): Long = | |
| 216 | if (n == 0) 1 else n * fact(n - 1) | |
| 217 | ||
| 155 | 218 | fact(10) //ok | 
| 219 | fact(10000) // produces a stackoverflow | |
| 220 | ||
| 221 | def factT(n: BigInt, acc: BigInt): BigInt = | |
| 222 | if (n == 0) acc else factT(n - 1, n * acc) | |
| 223 | ||
| 158 | 224 | factT(10, 1) | 
| 155 | 225 | factT(100000, 1) | 
| 226 | ||
| 227 | // there is a flag for ensuring a function is tail recursive | |
| 228 | import scala.annotation.tailrec | |
| 67 | 229 | |
| 72 | 230 | @tailrec | 
| 67 | 231 | def factT(n: BigInt, acc: BigInt): BigInt = | 
| 232 | if (n == 0) acc else factT(n - 1, n * acc) | |
| 233 | ||
| 234 | ||
| 235 | ||
| 155 | 236 | // for tail-recursive functions the Scala compiler | 
| 71 | 237 | // generates loop-like code, which does not need | 
| 67 | 238 | // to allocate stack-space in each recursive | 
| 155 | 239 | // call; Scala can do this only for tail-recursive | 
| 67 | 240 | // functions | 
| 241 | ||
| 155 | 242 | |
| 243 | ||
| 244 | // sudoku again | |
| 245 | ||
| 246 | val game0 = """.14.6.3.. | |
| 247 | |62...4..9 | |
| 248 | |.8..5.6.. | |
| 249 | |.6.2....3 | |
| 250 | |.7..1..5. | |
| 251 | |5....9.6. | |
| 252 | |..6.2..3. | |
| 253 | |1..5...92 | |
| 254 |               |..7.9.41.""".stripMargin.replaceAll("\\n", "")
 | |
| 53 | 255 | |
| 155 | 256 | type Pos = (Int, Int) | 
| 257 | val EmptyValue = '.' | |
| 258 | val MaxValue = 9 | |
| 259 | ||
| 260 | val allValues = "123456789".toList | |
| 261 | val indexes = (0 to 8).toList | |
| 262 | ||
| 263 | ||
| 264 | def empty(game: String) = game.indexOf(EmptyValue) | |
| 265 | def isDone(game: String) = empty(game) == -1 | |
| 266 | def emptyPosition(game: String) = | |
| 267 | (empty(game) % MaxValue, empty(game) / MaxValue) | |
| 268 | ||
| 67 | 269 | |
| 155 | 270 | def get_row(game: String, y: Int) = | 
| 271 | indexes.map(col => game(y * MaxValue + col)) | |
| 272 | def get_col(game: String, x: Int) = | |
| 273 | indexes.map(row => game(x + row * MaxValue)) | |
| 274 | ||
| 275 | def get_box(game: String, pos: Pos): List[Char] = {
 | |
| 276 | def base(p: Int): Int = (p / 3) * 3 | |
| 277 | val x0 = base(pos._1) | |
| 278 | val y0 = base(pos._2) | |
| 279 | val ys = (y0 until y0 + 3).toList | |
| 280 | (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue))) | |
| 281 | } | |
| 282 | ||
| 283 | // this is not mutable!! | |
| 284 | def update(game: String, pos: Int, value: Char): String = | |
| 285 | game.updated(pos, value) | |
| 286 | ||
| 287 | def toAvoid(game: String, pos: Pos): List[Char] = | |
| 288 | (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos)) | |
| 289 | ||
| 290 | def candidates(game: String, pos: Pos): List[Char] = | |
| 291 | allValues.diff(toAvoid(game,pos)) | |
| 292 | ||
| 293 | //candidates(game0, (0,0)) | |
| 294 | ||
| 295 | def pretty(game: String): String = | |
| 296 | "\n" + (game sliding (MaxValue, MaxValue) mkString "\n") | |
| 297 | ||
| 158 | 298 | ///////////////////// | 
| 155 | 299 | // not tail recursive | 
| 300 | def search(game: String): List[String] = {
 | |
| 301 | if (isDone(game)) List(game) | |
| 302 |   else {
 | |
| 303 | val cs = candidates(game, emptyPosition(game)) | |
| 304 | cs.map(c => search(update(game, empty(game), c))).toList.flatten | |
| 67 | 305 | } | 
| 306 | } | |
| 307 | ||
| 155 | 308 | // tail recursive version that searches | 
| 158 | 309 | // for all solutions | 
| 310 | ||
| 155 | 311 | def searchT(games: List[String], sols: List[String]): List[String] = games match {
 | 
| 312 | case Nil => sols | |
| 313 |   case game::rest => {
 | |
| 314 | if (isDone(game)) searchT(rest, game::sols) | |
| 315 |     else {
 | |
| 316 | val cs = candidates(game, emptyPosition(game)) | |
| 317 | searchT(cs.map(c => update(game, empty(game), c)) ::: rest, sols) | |
| 318 | } | |
| 319 | } | |
| 67 | 320 | } | 
| 321 | ||
| 158 | 322 | searchT(List(game3), List()).map(pretty) | 
| 323 | ||
| 324 | ||
| 155 | 325 | // tail recursive version that searches | 
| 326 | // for a single solution | |
| 158 | 327 | |
| 155 | 328 | def search1T(games: List[String]): Option[String] = games match {
 | 
| 67 | 329 | case Nil => None | 
| 155 | 330 |   case game::rest => {
 | 
| 331 | if (isDone(game)) Some(game) | |
| 332 |     else {
 | |
| 333 | val cs = candidates(game, emptyPosition(game)) | |
| 334 | search1T(cs.map(c => update(game, empty(game), c)) ::: rest) | |
| 335 | } | |
| 336 | } | |
| 67 | 337 | } | 
| 338 | ||
| 158 | 339 | search1T(List(game3)).map(pretty) | 
| 340 | ||
| 155 | 341 | // game with multiple solutions | 
| 342 | val game3 = """.8...9743 | |
| 343 | |.5...8.1. | |
| 344 | |.1....... | |
| 345 | |8....5... | |
| 346 | |...8.4... | |
| 347 | |...3....6 | |
| 348 | |.......7. | |
| 349 | |.3.5...8. | |
| 350 |               |9724...5.""".stripMargin.replaceAll("\\n", "")
 | |
| 351 | ||
| 158 | 352 | searchT(List(game3), Nil).map(pretty) | 
| 155 | 353 | search1T(List(game3)).map(pretty) | 
| 67 | 354 | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 355 | // Moral: Whenever a recursive function is resource-critical | 
| 158 | 356 | // (i.e. works with large recursion depth), then you need to | 
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 357 | // write it in tail-recursive fashion. | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 358 | // | 
| 155 | 359 | // Unfortuantely, Scala because of current limitations in | 
| 360 | // the JVM is not as clever as other functional languages. It can | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 361 | // only optimise "self-tail calls". This excludes the cases of | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 362 | // multiple functions making tail calls to each other. Well, | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 363 | // nothing is perfect. | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 364 | |
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 365 | |
| 67 | 366 | |
| 367 | ||
| 71 | 368 | // Polymorphic Types | 
| 369 | //=================== | |
| 370 | ||
| 72 | 371 | // You do not want to write functions like contains, first | 
| 71 | 372 | // and so on for every type of lists. | 
| 373 | ||
| 67 | 374 | |
| 72 | 375 | def length_string_list(lst: List[String]): Int = lst match {
 | 
| 67 | 376 | case Nil => 0 | 
| 72 | 377 | case x::xs => 1 + length_string_list(xs) | 
| 67 | 378 | } | 
| 379 | ||
| 158 | 380 | def length_int_list(lst: List[Int]): Int = lst match {
 | 
| 381 | case Nil => 0 | |
| 382 | case x::xs => 1 + length_int_list(xs) | |
| 383 | } | |
| 67 | 384 | |
| 158 | 385 | length_string_list(List("1", "2", "3", "4"))
 | 
| 386 | length_int_list(List(1, 2, 3, 4)) | |
| 67 | 387 | |
| 158 | 388 | //----- | 
| 67 | 389 | def length[A](lst: List[A]): Int = lst match {
 | 
| 390 | case Nil => 0 | |
| 391 | case x::xs => 1 + length(xs) | |
| 392 | } | |
| 158 | 393 | length(List("1", "2", "3", "4"))
 | 
| 394 | length(List(King, Knight("foo"), Clown))
 | |
| 395 | length(List(1, 2, 3, 4)) | |
| 53 | 396 | |
| 158 | 397 | def map[A, B](lst: List[A], f: A => B): List[B] = lst match {
 | 
| 67 | 398 | case Nil => Nil | 
| 399 | case x::xs => f(x)::map_int_list(xs, f) | |
| 400 | } | |
| 401 | ||
| 402 | map_int_list(List(1, 2, 3, 4), square) | |
| 403 | ||
| 404 | ||
| 405 | // Remember? | |
| 406 | def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ... | |
| 407 | ||
| 408 | ||
| 409 | ||
| 158 | 410 | |
| 411 | ||
| 155 | 412 | // Cool Stuff | 
| 413 | //============ | |
| 72 | 414 | |
| 155 | 415 | |
| 416 | // Implicits | |
| 417 | //=========== | |
| 418 | // | |
| 419 | // For example adding your own methods to Strings: | |
| 420 | // Imagine you want to increment strings, like | |
| 421 | // | |
| 422 | // "HAL".increment | |
| 423 | // | |
| 424 | // you can avoid ugly fudges, like a MyString, by | |
| 425 | // using implicit conversions. | |
| 67 | 426 | |
| 427 | ||
| 155 | 428 | implicit class MyString(s: String) {
 | 
| 429 | def increment = for (c <- s) yield (c + 1).toChar | |
| 67 | 430 | } | 
| 431 | ||
| 155 | 432 | "HAL".increment | 
| 67 | 433 | |
| 53 | 434 | |
| 435 | ||
| 436 | ||
| 71 | 437 | // Regular expressions - the power of DSLs in Scala | 
| 438 | //================================================== | |
| 67 | 439 | |
| 440 | abstract class Rexp | |
| 155 | 441 | case object ZERO extends Rexp // nothing | 
| 442 | case object ONE extends Rexp // the empty string | |
| 443 | case class CHAR(c: Char) extends Rexp // a character c | |
| 71 | 444 | case class ALT(r1: Rexp, r2: Rexp) extends Rexp // alternative r1 + r2 | 
| 155 | 445 | case class SEQ(r1: Rexp, r2: Rexp) extends Rexp // sequence r1 o r2 | 
| 71 | 446 | case class STAR(r: Rexp) extends Rexp // star r* | 
| 67 | 447 | |
| 448 | ||
| 158 | 449 | |
| 67 | 450 | // (ab)* | 
| 72 | 451 | val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))
 | 
| 67 | 452 | |
| 453 | ||
| 454 | // some convenience for typing in regular expressions | |
| 455 | import scala.language.implicitConversions | |
| 456 | import scala.language.reflectiveCalls | |
| 457 | ||
| 458 | def charlist2rexp(s: List[Char]): Rexp = s match {
 | |
| 459 | case Nil => ONE | |
| 460 | case c::Nil => CHAR(c) | |
| 461 | case c::s => SEQ(CHAR(c), charlist2rexp(s)) | |
| 462 | } | |
| 463 | implicit def string2rexp(s: String): Rexp = charlist2rexp(s.toList) | |
| 464 | ||
| 465 | ||
| 466 | val r1 = STAR("ab")
 | |
| 158 | 467 | val r2 = STAR(ALT("ab"))
 | 
| 72 | 468 | val r3 = STAR(ALT("ab", "baa baa black sheep"))
 | 
| 67 | 469 | |
| 470 | implicit def RexpOps (r: Rexp) = new {
 | |
| 471 | def | (s: Rexp) = ALT(r, s) | |
| 472 | def % = STAR(r) | |
| 473 | def ~ (s: Rexp) = SEQ(r, s) | |
| 474 | } | |
| 475 | ||
| 476 | implicit def stringOps (s: String) = new {
 | |
| 477 | def | (r: Rexp) = ALT(s, r) | |
| 478 | def | (r: String) = ALT(s, r) | |
| 479 | def % = STAR(s) | |
| 480 | def ~ (r: Rexp) = SEQ(s, r) | |
| 481 | def ~ (r: String) = SEQ(s, r) | |
| 482 | } | |
| 483 | ||
| 153 | 484 | //example regular expressions | 
| 67 | 485 | val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | 
| 486 | val sign = "+" | "-" | "" | |
| 487 | val number = sign ~ digit ~ digit.% | |
| 488 | ||
| 489 | ||
| 490 | ||
| 491 | ||
| 492 | ||
| 493 | // The End | |
| 494 | //========= | |
| 495 | ||
| 496 | // A function should do one thing, and only one thing. | |
| 497 | ||
| 498 | // Make your variables immutable, unless there's a good | |
| 499 | // reason not to. | |
| 500 | ||
| 501 | // You can be productive on Day 1, but the language is deep. | |
| 158 | 502 | // | 
| 503 | // http://scalapuzzlers.com | |
| 504 | // | |
| 505 | // http://www.latkin.org/blog/2017/05/02/when-the-scala-compiler-doesnt-help/ | |
| 67 | 506 | |
| 158 | 507 | List(1, 2, 3) contains "your mom" | 
| 508 | ||
| 509 | // I like best about Scala that it lets me often write | |
| 155 | 510 | // concise, readable code. | 
| 68 | 511 |