There is an English versionhereLet me stay here

Scalaの基礎まとめ

Scala発表 : 更新日 :

Functional Programming(関数型プログラミング、略してFP)を勉強しようと思って、scalaというFPとOOP(オブジェクト指向プログラミング)両方できる言語を選びました。この記事は自分のscala勉強メモをまとめて書いたものです。

変数

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

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の文字列オブジェクトはすごい使いやすい。その辺はスクリプト言語とかわらないぐらい便利:

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にもある:

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というメソードを使う:

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を使うと、$<変数名>で変数と文字列を混ぜて出力できる:

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

Function

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

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

無名関数:

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の無名関数はもっとシンプルな書き方ができます:

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

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

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

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は順番のイテレーションが得意だが、ランダムアクセスが苦手(特に最後の要素にアクセスには全ての要素を経由しないといけない)

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に要素を追加する

// 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によく使うメソード

// 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付きの配列です。

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を使ってください:

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を宣言して、結果を自身に再アサインするかのいずれかになります。

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はユニークな要素を格納するコンテナである。

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のほうが出番が多いと思います。

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という構造のコレクションです。

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

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

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がオブジェクトにできるのが少し新鮮で、結構便利ではないかと思いました。

クラス

ScalaはOOPによく使うclassというコンセプトもしっかりサポートしています。Scalaのクラスにはcase classclass二種類ある。case classは関数型プログラミングに特化したクラスであって、全てのメンバー変数がvalタイプになる。

class People(var name: String, val age: Int)
case class Animal(name: String, age: Int)

val p = new People("Hoge", 21)
p.name = "Mori"
println(p.name) // "Mori"

val a = new Animal("cat", 3)
a.name = "dog" // ERROR!

Class

Scalaのclassの定義は関数と少し似ています、しかし、引数がそのままclassのプロパティー(メンバー変数)になります。なお、scalaはプロパティーのタイプによって、自動的にgetterとsetterを作成します。

タイプ 権限
var GetterとSetter両方作成します
val Getterのみ作成します
private var GetterとSetter作成しません、class内アクセスのみ
private val GetterとSetter作成しません、class内アクセスのみ
<空> case classの場合はprivate valになる, classの場合はprivate varになる
class Player(var name: String, var score: Int = 0) {
  override def toString : String = s"$name : $score"
}
object NonePlayer extends Player(score = 0, name = "")
// objectはsingletonです、最初のアクセスのみで初期化します。

class Eva(
  val name: String,
  var power: Int = 0,
  private var _pilot: Player = NonePlayer)
// pilotのsetterとgetterをカスタマイズするので、privateにします
{
  // pilot getter
  def pilot: Player = _pilot

  // pilot setter
  def pilot_=(p: Player): Unit = {
    power = p.score
    _pilot = p
  }
  
  override def toString = _pilot match {
    case NonePlayer => s"[無人]$name(power: $power)"
    case _ => s"$name(power: $power, pilot: ${_pilot.name})"
  }
  // matchはScalaでよく使うメソードです、他の言語だとswitchのようなものだが、switchよりパワフルです。
}

val e = new Eva("初号機")
println(e) // [無人]初号機(power: 0)
e.pilot = new Player(name = "Shinji", score = 9000)
println(e) // 初号機(power: 9000, pilot: Shinji)

この例では、すべて戻り値のタイプを指定していますが、Scalaは十分スマートなので、戻り値のタイプは普通に書かなくても大丈夫です。もちろん、書いたほうがより良いプログラミング習慣を身につけるでしょう。

Object と trait

ScalaのObjactSingletonの作成、classと組んでCompinion Objectを作るとかに使います。traitはScalaでJavaのInterfaceのような役割をしています。JavaのInterfaceよりできること多いです。メソードを既存のclassにインジェクトしたりすることもできる。

// Compinion Object
// Evaクラスのstaticメソードなどは、object Evaの中で定義する
// (Scalaにはstaticというキーワードがないので、staticメソードはclassの中で直接定義できません。)
object Eva {
  var count = 0

  def status = s"Evaが${count}台作成されています"
}
trait EvaCounterTrait {
  Eva.count += 1
  // increase counter
}

既存のEvaクラスにEvaCounterTraitextendsします:

class Eva(
  val name: String,
  var power: Int = 0,
  private var _pilot: Player = NonePlayer
) extends EvaCounterTrait

すると、newで作成されたEvaの数がEva.statusで見れるようになります。

まとめ

Scalaを勉強して2週間ぐらい経って、文法に慣れつつあるものの、Funcation Programming(関数型プログラミング)の思考などに結構難航しています。FPをやると少し高度な数学知識(大学レベル)が必要だなと実感しています。でもScalaは少ないコードで安定したアプリを作れるというとこが魅力で、これからも長く付き合おうと思っています。

ちなみに、この記事は初心者向けて書いています。細かいところは省略しています。もし「Scalaが面白そう」と思ってもらえれば嬉しいです。

この記事をシェアする

このエントリーをはてなブックマークに追加