Scalaの基礎まとめ その1

ChainZ(クリエイター)
いろいろやってます。

変数

Scalaの変数にはValue(val)とVaraible(var)二種類あります。valは一度定義したら、値を変えることができないので、関数型プログラミングによく使われます。

1
2
3
4
val a = 1
a = 2 // ERROR
var b = 1
b = 2 // 2

val or var? これ結構自分の中でも質問になっていました。Function Programming(FP)は不変ということを重視していて、つまりサイドエフェクトを避けるため、変数の値を変えるのが良くないと考えられます。なので、FPではvalを使うのが普通です。「じゃあ、varいらなくない?」という疑問があります。Scalaは純粋のFP言語ではなく、OOPもサポートしているので、varが存在するのはもう一つの選択肢を提供することで、使うかどうかは人によります。なお、varをキャッシュなどに利用することで、FPプログラムのパフォーマンス向上に役立ちます。

文字列(String)

Scalaの文字列オブジェクトはすごい使いやすい。その辺はスクリプト言語とかわらないぐらい便利:

1
2
3
4
val hello = "Hello World"
hello.drop(6).take(2).toUpperCase // WO
hello.endsWith("World") // true
hello.drop(2).take(2).equals("ll") // true

Scalaの文字列は配列型なので、JavaScriptのsplitのようなメソードはScalaにもある:

1
2
val animal = "dog, cat, pig, cow, duck"
animal.split(',').map(_.trim) // Array(dog, cat, pig, cow, duck)

mapは配列の要素を指定したメソードに渡すメーソドです。_.trimはscalaの無名関数で、_は渡された引数を格納されている。つまり、map(_.trim)は配列にあるすべての要素の前後のスペースを消すという挙動になる。(詳しくは後ほど)

ScalaのStringはCharの配列として扱われる。例えば、”Hello”(1) //e、文字列の直後にindexを書けば、そのindexにある文字が取得できる。

指定したフォーマットで文字列をプリントするには、Scalaのfというメソードを使う:

1
2
3
val money = "1000"
println(f"You got $money%.2f yen!") // You got 1000.00 yen!
println(f"You got $money%.0f yen!") // You got 1000 yen!

sを使うと、$<変数名>で変数と文字列を混ぜて出力できる:

1
2
3
val name = "Asuka"
println(s"My name is $name")
// My name is Asuka

Function

Functionの定義はdefを使います:

1
2
3
def incrOne(a: Int): Int = a + 1
val a = 1
val b = incrOne(1) // 2

無名関数:

1
2
3
4
5
6
7
val list = List(1, 2, 3, 4, 5)
list.foreach( (a: Int) => println(a * 5))
// 5
// 10
// 15
// 20
// 25

ListはscalaのCollectionタイプで、foreachmapなど便利なイテレーションメソードが使えます。

上記の例では(a: Int) => println(a * 5)が無名関数です。=>の左側が入力で、右側が出力となります。この例でa: Intというタイプを指定していますが、Scalaは自動的にタイプを認識してくれるので、a => println(a * 5)と書いても大丈夫です。

Scalaの無名関数はもっとシンプルな書き方ができます:

1
2
3
4
5
6
7
val list = List(1, 2, 3, 4, 5)
list.map(_ * 5).foreach(println)
// 5
// 10
// 15
// 20
// 25

この例では_ * 5が無名関数です。_は入力される変数になります。これを穴埋め問題と考えるとわかりやすいかもしれません。list中の全ての要素をこの穴に埋めて、結果を返すって感じです。

初めてこの文法を見てわけわからなかったが、慣れたらすごいわかりやすい記述だなと思いました。これはオブジェクトのリストにも活用できます:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Player(var name: String, var score: Int = 0) {
  override def toString = s"$name : $score"
}
// これがscalaのクラスです、詳しくは後で説明します。
// パッと見ると関数の定義と似ていますが、nameとscoreはPlayerクラスのメンバー変数になります。
// toStringをoverrideで定義すると、インスタンスをprintlnに渡すと、このメソードをコールされます。

val players = List[Player](
  new Player("Asuka", 9000),
  new Player("Rei", 999999),
  new Player("Shinji", 200)
)
val ranking = players.sortWith(_.score > _.score)
ranking.foreach(println)
// Rei : 999999
// Asuka : 9000
// Shinji : 200

_.score > _.scoreはソート用の無名関数で、最初の_は関数の第一引数で、次の_が第二引数になります。この関数をちゃんと書くと(a: Int, b: Int) => a.score > b.scoreになります。Scalaは無名関数の引数の数を見て引数をスマートに_に入れてくるので、簡単な処理はこの短いフォーマットがおすすめです。

List, Array, Set, Map

scalaのコレクションタイプの中では、 よく使うのはList, Array, SetMapです。

List

Listは1種類のデータを順番に繋がるデータ構造である。なので、Listは順番のイテレーションが得意だが、ランダムアクセスが苦手(特に最後の要素にアクセスには全ての要素を経由しないといけない)

1
2
3
4
5
6
7
8
9
10
11
12
13
val list1 = List(1, 2, 3, 4, 5, 6)
// List(1, 2, 3, 4, 5, 6)

println(list1.head)
// List(1)
println(list1.tail)
// List(2, 3, 4, 5, 6)

val list2 = List.range(1, 10, 2)
// List(1, 3, 5, 7, 9)

val list3 = 2 :: 4 :: 6 :: 8 :: 10 :: Nil
// List(2, 4, 6, 8, 10)

Listに要素を追加する

1
2
3
4
5
6
7
8
9
10
11
// Listの頭に要素を追加
val list4 = list2 :: 100 :: 200
// List(100, 200, 1, 3, 5, 7, 9)
val list5 = Seq(100, 200) ++ list2
// List(1, 3, 5, 7, 9, 100, 200)

// Listの尻に要素を追加
val list6 = list2 :+ 100 :+ 200
// List(1, 3, 5, 7, 9, 100, 200)
val list7 = list1 ++ Seq(7, 8, 9, 10);
// List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

SeqList, Setの親クラスである

Listによく使うメソード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// map
val list8 = list1.map(_ * 5)
// List(5, 10, 15, 20, 25, 30)

// foreach
list1.foreach(a => println(a*a))
// 1
// 4
// 9
// 16
// 25
// 36

val sorted = list1.sortWith(_ > _)
// List(6, 5, 4, 3, 2, 1)

Array

Arrayは簡単に言えばindex付きの配列です。

1
2
3
4
5
6
7
8
9
val array = Array("Asuka", "Rei", "Shinji")

array(2) = "Misato"
// array自身はイミュータブルなんですが(サイズは変えられない)、中の要素はミュータブルです。

array.foreach(i => println(s"Index of $i is " + array.indexOf(i)))
// Index of Asuka is 0
// Index of Rei is 1
// Index of Misato is 2

もしサイズ可変の配列を使いたければ、Scala.collection.ArrayBufferを使ってください:

1
2
3
4
5
6
7
8
9
10
11
val arraybuf = scala.collection.mutable.ArrayBuffer("Asuka", "Rei")
    arraybuf += "Shinji"
    arraybuf += "Misato"
    arraybuf ++= Seq("Illustrious", "Ayanamirei")
    arraybuf.foreach(i => println(s"Index of $i is " + arraybuf.indexOf(i)))
// Index of Asuka is 0
// Index of Rei is 1
// Index of Shinji is 2
// Index of Misato is 3
// Index of Illustrious is 4
// Index of Ayanamirei is 5

Arrayはイミュータブルので、要素の削除するには、要素を削除した結果を他のvalに保存するか、最初にvarを宣言して、結果を自身に再アサインするかのいずれかになります。

1
2
3
4
5
6
7
8
9
10
val array1 = Array("Asuka", "Shinji")
array1(1) = null // remove Shinji
val array2 = array1.filter(_ != null)
array2.foreach(println)
// Asuka

var array3 = Array("Rei", "Shinji")
array3 = array3.take(1)
array3.foreach(println)
// Rei

Set

Setはユニークな要素を格納するコンテナである。

1
2
val set1 = Set(1, 2, 2, 3, 4, 4, 5, 6, 3, 6, 7, 8)
// Set(5, 1, 6, 2, 7, 3, 8, 4)

イミュータブルバージョンのSetよりも個人的には可変のscala.collection.mutable.Setのほうが出番が多いと思います。

1
2
3
4
5
6
7
8
9
10
11
var set2 = scala.collection.mutable.Set("Rei", "Asuka")
set2 += "Shinji"
set2 += "Asuka"
set2 += "Misato"
set2.add("Rei")
set2 ++= Seq("Shinji", "Misato")
set2.foreach(println)
// Rei
// Asuka
// Misato
// Shinji

ちなみに、Setに重複してる要素を追加しても、Exceptionが投げられません。その特徴を利用して、ユニークな要素を確保したい場合はscala.collection.mutable.Set を使えば良いと思います。

Map

ScalaのMapkey -> valueという構造のコレクションです。

1
2
val map = Map( "name" -> "Asuka", "power" => 9999 )
println("The power of" + map("name") + " is " + map("power"))

もちろん、可変バージョンのscala.collection.mutable.Mapもあります:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Player(var score: Int = 0, val name : String)
class Eva(val name: String, var power: Int = 0)

val evas = scala.collection.mutable.Map(
  new Player(name = "Rei", score = 100) -> new Eva("零号機", 9000),
  new Player(name = "Asuka", score = 200) -> new Eva("弐号機", 9999)
)
evas(new Player(name = "Shinji", score = 900)) = new Eva("初号機", 7000)
evas.foreach(x => {
  println(s"${x._1.name} is using ${x._2.name}")
})
// Asuka is using 弐号機
// Rei is using 零号機
// Shinji is using 初号機

Script言語を使ってきた私は、keyがオブジェクトにできるのが少し新鮮で、結構便利ではないかと思いました。

引き続きはhttp://befool.co.jp/blog/chainzhang/basic-of-scala-2/