Scala
コップ本

「Scalaスケーラブルプログラミング」まとめ ~ 2章&3章 ~

この記事は?

Scalaスケーラブルプログラミング第3版を読んで、個人的に気になった箇所や勉強になった箇所のまとめです。
認識誤りなどがあればご指摘いただけると幸いです。

1章のまとめはこちら

第2章 Scalaプログラミングの第一歩

変数は2種類ある

var は何度でも代入可能な変数。
val は初期化後別の値を代入できない変数。

関数定義の仕方

基本的には以下のように定義するが、省略可能なものもある。
関数が1文のみの場合中カッコは省略できる。

Javaではメソッドが返す値の型を「戻り値(return type)」と言うが、Scalaでは「結果型(result type)」と呼ぶ。

def  max(x: Int, y: Int): Int = {
  if (x > y)
     x
  else
     y
}

関数が意味のある値を返さない場合Unit型が返る

Unit型はJavaのvoidに似ており、Javaのvoidを返すメソッドはUnitを返すメソッドにマッピングされる。

scala> def greet() = println("Hello, World!")
greet: ()Unit

配列の要素にアクセスするときは()を使う

Javaでは配列の要素にアクセスする場合は[]を使用するがScalaでは()を使用する

// これはJava
String hoge = args[0]

// これはScala
val hoge = args(0)

whileifは基本的にJavaと同じ

以下のようにScalaのwhileifは基本的にJavaと同じ

var i = 0
while (i < args.length) {
    if (i != 0)  // Java同様に中カッコを省略することもできる
        print(" ")
    print(args(i))
    i += 1
}

foreachは関数リテラル、右矢印、関数本体を使う

関数リテラルの構文は以下のように関数パラメータ、右矢印、関数本体の3つからなる

(x: Int, y: Int) => x + y

下の例ではargが関数リテラルでprintln(arg)が関数本体である。

// 例
args.foreach(arg => println(arg))

また関数リテラルが1個の引数をとる1文から成る場合は引数を省略できる

args.foreach(println)

for式では<-を使う

// 例
for (arg <- args)
   println(arg)

上の例のargは必ずvalになる。

<- は「in」と読むと理解しやすいかもしれない。

for (arg in args)

第3章 Scalaプログラミングの次の一歩

Scalaではnewを使ってインスタンスを生成できる

インスタンスを生成する際に型を指定することもできる

val hoge = new Array[String](3)

Scalaではすべての演算がメソッド呼び出し

1 + 2(1).+(2)と同じで1+メソッドを呼び出し引数に2を渡している

配列の要素アクセスや初期化も同様にメソッドを呼び出している

// 要素アクセス
hoge(0)  hoge.apply(0) は同じ
hoge(0) = "piyo"  hoge.update(0, "piyo") は同じ

// 初期化
val numNames = Array("zero", "one", "two") 
val numNames = Array.apply("zero", "one", "two") と同じ

ScalaのListはイミュータブルである

リストの内容を変更するようなメソッドを呼ぶと、新しい値を持つ新しいリストが作成され、返される。

例えば::はリストの先頭に新しい要素を追加して得られるリストを返す。

scala> val oneTwoThree = 1 :: 2 :: 3 :: Nil
oneTwoThree: List[Int] = List(1, 2, 3)

ちなみにListを初期化する場合は最後にNilをつける必要がある。

scala> val oneTwoThreeFour = 1 :: 2 :: 3 :: 4
<console>:7: error: value :: is not a member of Int
       val oneTwoThreeFour = 1 :: 2 :: 3 :: 4
                                         ^

scala> val oneTwoThreeFour = 1 :: 2 :: 3 :: 4 :: Nil
oneTwoThreeFour: List[Int] = List(1, 2, 3, 4)

タプル(tuple)はリスト同様イミュータブルだが、異なる方の要素を持つことができる

タプルの個々の要素にアクセスするには._と1から始まる要素の番号を指定する

scala> val messi = ("Barcelona", 10)
messi: (String, Int) = (Barcelona,10)

scala> messi._1
res14: String = Barcelona

scala> messi._2
res15: Int = 10

集合(Set)とマップ(map)にはミュータブルなものとイミュータブルなものがある

Setはデフォルトだとイミュータブルになる

//デフォルトだとイミュータブルなSetインスタンスが返る
scala> var realMadrid = Set("Ronald", "Ramos", "Isco", "Modric")
realMadrid: scala.collection.immutable.Set[String] = Set(Ronald, Ramos, Isco, Modric) 

// 新しい要素を追加するとイミュータブルなSetの場合、新しいインスタンスを返す
scala> realMadrid + "Kroos"
res16: scala.collection.immutable.Set[String] = Set(Ronald, Kroos, Ramos, Modric, Isco)

// 元のインスタンスの要素は変わっていない
scala> realMadrid
res17: scala.collection.immutable.Set[String] = Set(Ronald, Ramos, Isco, Modric)

ミュータブルなSetを使うにはimportが必要

scala> import scala.collection.mutable
import scala.collection.mutable

scala> val bigFour = mutable.Set("Federer", "Nadal", "Djokovic")
bigFour: scala.collection.mutable.Set[String] = Set(Djokovic, Nadal, Federer)

scala> bigFour += "Murray"
res18: bigFour.type = Set(Djokovic, Nadal, Murray, Federer)

scala> bigFour
res19: scala.collection.mutable.Set[String] = Set(Djokovic, Nadal, Murray, Federer)

Mapもデフォルトはイミュータブルで、ミュータブルなMapを作るにはimportする必要がある。

scala> val artists = Map(1 -> "Suchmos", 2 -> "BUMP OF CHICKEN", 3 -> "Utada Hikaru") 
artists: scala.collection.immutable.Map[Int,String] = Map(1 -> Suchmos, 2 -> BUMP OF CHICKEN, 3 -> Utada Hikaru)
scala> import scala.collection.mutable
import scala.collection.mutable

scala> val rappers = mutable.Map(1 -> "Jay-Z", 2 -> "Nas")
rappers: scala.collection.mutable.Map[Int,String] = Map(2 -> Nas, 1 -> Jay-Z)

scala> rappers += (3 -> "Q-Tip")
res20: rappers.type = Map(2 -> Nas, 1 -> Jay-Z, 3 -> Q-Tip)

scala> rappers
res21: scala.collection.mutable.Map[Int,String] = Map(2 -> Nas, 1 -> Jay-Z, 3 -> Q-Tip)

関数型のスタイルに近づくにはvarを使わないことが大事

// これは命令型スタイル
def printArgs(args : Array[String]): Unit = {
  var i = 0
  while(i < args.length) {
    println(args.(i))
    i += 1
  }
}

// これは関数型スタイル(純粋な関数型ではない!)
def printArgs(args : Array[String]): Unit = {
  args.foreach(println)
}

純粋な関数型は副作用を持たない。結果型がUnitになっていると副作用があることを意味している。