satococoa's blog

主にサーバーサイド、Web 系エンジニアのブログです。Go, Ruby, React, GCP, ...etc.

社内Scala勉強会始めました

Twitter社の公開したScala教育用のコンテンツである Scala School! を使って社内Scala勉強会を始めました。

第一回は先週の11/2で、Basicsのページを進めました。

形式はみんなで顔を合わせつつ、同時にSkypeもつないでハマったところを共有しながら各自進める、という形式でやってみました。残念ながら僕は序盤に打ち合わせが入ってしまい抜けたのですが、一応全員Basicsのページは一通り進めたようです。

というわけでちょっと間が空いてしまいましたが、第一回で学んだことを簡単にメモします。

valとvar

変数の宣言の仕方が2通りです。valにすると再代入ができません。

scala> val two = 1 + 1
two: Int = 2

scala> two = 3
<console>:8: error: reassignment to val
       two = 3
           ^

メソッド

色んな定義の仕方があってややこしい。

基本?

def addOne(m: Int): Int = m + 1
def メソッド名(仮引数: 仮引数の型): 返り値の型 = 処理内容

処理内容が複数の表現を含むときは、{}で囲むことができる

def timesTwo(i: Int): Int = {
  println("hello world")
  i * 2
}

最後に評価した値が返り値となるのはrubyっぽい。
追記:上記、誤解でした。 @bleisさんに『最後に評価した値が戻り値になるわけではないです。それだと型を付けることができないので』と教えていただきました。以下のように書くとエラーになりました。

scala> def two: Int = {
     | 2
     | "string"
     | }
<console>:9: error: type mismatch;
 found   : java.lang.String("string")
 required: Int
       "string"
       ^

ちなみにLLの感覚で文字列をシングルクォーテーションで囲むとエラー。Char型を表すのですね。

無名関数

scala> (x: Int) => x + 1

CoffeeScriptっぽい。もちろん変数に入れることも可能。

scala> val addOne = (x: Int) => x + 1

無名関数も{}で囲むことができる。

scala> { i: Int =>
  println("hello world")
  i * 2
}

引数を取らない場合はかっこを省略できる。

scala> def three() = 1 + 2
scala> three
res3: Int = 3

部分適用

メソッドの引数のうち、一部だけを与えて新しいメソッドを作ることができる。

scala> def adder(m: Int, n: Int) = m + n
scala> val add2 = adder(2, _:Int)
scala> add2(3)
res19: Int = 7

まだ使いどころがよくわからない。

カリー化

メソッドを返すメソッドを作ることができる?

scala> def multiply(m: Int)(n: Int): Int = m * n
scala> multiply(2)(3)
res0: Int = 6
scala> val timesTwo = multiply(2)(_)
scala> timesTwo(3)
res1: Int = 6

これも使いどころがよくわからない。
以下も同じような使い方ができると思うけど、使い分けは?

scala> def multiply(m:Int, n:Int) = m * n
multiply: (m: Int, n: Int)Int
scala> val timesThree = multiply(3, _:Int)
timesThree: Int => Int = <function1>
scala> timesThree(5)
res20: Int = 15

上記のメソッドは以下のようにしてカリー化できる。

scala> (multiply(_, _)).curried
res21: Int => Int => Int = <function1>

可変引数

def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}

次のrubyコードと動作は同じ。よく似てる。

def capitalizeAll(*args)
  args.map {|arg|
    arg.capitalize
  }
end

クラス

class Calculator {
  val brand: String = "HP"
  def add(m: Int, n: Int): Int = m + n
}

scala> val calc = new Calculator
scala> calc.brand
res23: String = HP
scala> calc.add(4, 3)
res24: Int = 7

プロパティとメソッドの定義。

コンストラクタは特定の名前のメソッドを定義するのではなく、クラス内のメソッド定義の外側のコードがコンストラクタ

class Calculator {
  /**
   * コンストラクタ
   */
  val color: String = if (brand == "TI") {
    "blue"
  } else if (brand == "HP") {
    "black"
  } else {
    "white"
  }

  // インスタンスメソッド
  def add(m: Int, n: Int): Int = m + n
}

継承

class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

オーバーロード

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int) = log(m, math.exp(1))
}

書いていないけど、オーバーライドのときはoverrideとつけないといけない。
いい例が思いつかなかったので、処理内容が同一です。

class MyScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  override def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

トレイト

PHPでも5.4から使えるあれですね。

trait Car {
  var brand: String
}

class BMW extends Car {
  var brand: "BMW"
}

インターフェイスとは違って抽象メソッドじゃなくても入れておけます。とか、複数のトレイトを使用する場合のこととか、そういうのはあとから出てくるのでしょうか。

型引数、とかジェネリクスとか言われると、Javaをやっていない身からするとちょっと厳しいです。
次のように、型引数を使用できます。

trait Cache[K, V] {
  def get(key: K): V
  def put(key: K, value: V)
  def delete(key: K)
}

メソッドも型引数をつけられるそう。

def remove[K](key: K)

まとめ

後半の内容は、正直使いどころとかが理解できていません。
頑張って引き続き学んでいこうと思います。

実は去年にコップ本は一通り読んだのですが、残念ながらほとんど覚えていませんでした・・・。
一通りこのScala Schoolが終わったら読み直さないとダメかな。

ちなみに僕が持っているのは第1版です。