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タイプで、foreach
、map
など便利なイテレーションメソードが使えます。
上記の例では(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
, Set
とMap
です。
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)
Seq
はList
,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のMap
はkey -> 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 class
とclass
二種類ある。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のObjact
はSingleton
の作成、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
クラスにEvaCounterTrait
をextends
します:
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が面白そう」と思ってもらえれば嬉しいです。