はじめに
みなさんこんにちは、プロダクト開発本部の亀梨です。 普段はXmediaOneというメディアプランニング・広告運用管理・トラッキング・マーケティング分析を行う 統合プラットフォームの開発を担当しています。
さて、今回はScala入門第二弾として、Scalaの書き方を紹介する基礎編をお送りします。 Scala初心者のわたくしが、言語習得のために得た知識をここにまとめていきたいと思います。 主に参考にした記事及び書籍は以下の通りでっす。
また適宜コードの実行例を記していきますので、実際に動かしてみたい方は 事前にScalaおよびsbtをインストールしておいていただければと思います。 詳しい方法は前回の記事「Scala入門 準備編」をご覧ください。
基礎
変数
変数は「val」と「var」の二種類。valは後から変更できない変数で、副作用が起きないため使用を推奨されています。
- val = イミュータブル(変更不可)
- var = ミュータブル(変更可)
// valはイミュータブル scala> val msg = "Hello, world!" msg: String = Hello, world! scala> msg = "Goodbye cruel world!" <console>:8: error: reassignment to val msg = "Goodbye cruel world!" // varはミュータブル scala> var greeting = "Hello, world! greeting: String = Hello, world! scala> greeting = "Leave me alone, world!" greeting: String = Hello, world!
演算子
Scalaは基本型を操作する演算子を豊富に提供しています。
算術演算子
scala> 1.2 + 2.3 res: Double = 3.5 scala> 3 - 1 res: Int = 2 scala> 'b' - 'a' res: Int = 1 scala> 2L * 3L res: Long = 6 scala> 11 / 4 res: Int = 2 scala> 11 % 4 res: Int = 3 scala> 11.0f / 4.0f res: Float = 2.75 scala> 11.0 % 4.0 res: Double = 3.0
関係演算子と論理演算子
scala> 1 > 2 res: Boolean = false scala> 1 < 2 res: Boolean = true scala> 1.0 <= 1.0 res: Boolean = true scala> 3.5f >= 3.6f res: Boolean = false scala> 'a' >= 'A' res20: Boolean = true scala> val untrue = !true untrue: Boolean false scala> val toBe = true toBe: Boolean = true scala> val question = toBe || !toBe question: Boolean = true scala> val paradox = toBe && !toBe paradox: Boolean = false
演算子の優先順位
- (他のすべての特殊文字)
- * / %
- + -
- :
- = !
- < >
- &
- ^
- \|
- (すべての英字)
- (すべての代入演算子)
関数
関数の主な定義は以下の通りです。
- 関数定義はdefで始まる
- 関数名は慣例的にキャメルケースを使用(Javaと同じ)
- 引数の定義は[変数名]: [データ型]の形式(複数可)
- 引数のデータ型は省略不可
- 返り値のデータ型は引数の後に: [データ型]の形式で行う
- 返り値のデータ型は省略可能
- 関数の定義と関数本体のブロックを=で繋ぐ必要がある
- 関数本体が1行のみの場合は本体ブロックの中括弧{}は省略可
- 本体ブロックで最後に実行された式が返り値となるのでreturnは不要
// 基本形 def max(x: Int, y: Int): Int = { if (x > y) x else y } // 省略した書き方(関数が一文のみから構成される場合) def max(x: Int, y: Int) = if (x > y) x else y
引数の無い関数では、括弧を省略することができます
scala> def three() = 1 + 2 three: ()Int scala> three() res2: Int = 3 scala> three res3: Int = 3
匿名(無名)関数
他の言語同様、Scalaでも匿名(無名)関数をつくることができます
scala> (x: Int) => x + 1 res2: (Int) => Int = <function1>
この関数はxという名前のIntに1を加えます
scala> res2(1) res3: Int = 2
匿名関数を渡したり、valに保存することができます
scala> val addOne = (x: Int) => x + 1 addOne: (Int) => Int = scala> addOne(1) res4: Int = 2
関数が複数の式で構成されている場合は、{}を使用します
def timesTwo(i: Int): Int = { println("hello world") i * 2 }
可変長引数も使えます
def capitalizeAll(args: String*) = { args.map { arg => arg.capitalize } } scala> capitalizeAll("rarity", "applejack") res2: Seq[String] = ArrayBuffer(Rarity, Applejack)
型推論
Scalaでは型を明示的に指定しなくても、セットされた値から自動的に型を推測、判断し設定しています。 ただ、可読性や保守性を考えると基本的に型は明示的に設定すべきです。
scala> val msg = "Hello, world!" msg: String = Hello, world! ^^^^^^ 型推論によって自動的に型が割当てられている
制御構文
他の言語と同様、while、foreach、forがあります。
whileによるループ
var i = 0 while (i < args.length) { if (i != 0) print(" ") print(args(i)) i += 1 } println()
foreachとforによる反復実行
// foreach args.foreach(arg => println(arg)) // 型を明示したパターン args.foreach((arg: String) => println(arg)) // 引数を明示せず省略して書くパターン args.foreach(println)
// for for (arg <- args) println(arg) ※ <-記号を「in」と読めば、「for arg in args」ということになる // 初めと終わりを指定するパターン for (i <- 0 to 2) print(greetStrings(i))
基本(データ)型
整数リテラル
Byte, Short, Int, Long, Charをまとめて整数値型と呼びます。 そしてFloat, Doubleを合わせて数値型と呼びます。
基本型 | 範囲 |
---|---|
Byte | 2の補数表現の8ビット符号付き整数(-2の7乗以上、2の7乗-1以下) |
Short | 2の補数表現の16ビット符号付き整数(-2の15乗以上、2の15乗-1以下) |
Int | 2の補数表現の32ビット符号付き整数(-2の31乗以上、2の31乗-1以下) |
Long | 2の補数表現の64ビット符号付き整数(-2の63乗以上、2の63乗-1以下) |
Char | 16ビット符号なしUnicode文字(0以上、2の16乗-1以下) |
String | Charのシーケンス |
Float | 32ビットのIEEE 754単精度浮動小数点数 |
Double | 64ビットのIEEE 754単精度浮動小数点数 |
Boolean | trueまたはfalse |
Int
scala> val dec = 31 dec: Int = 31
Long
整数リテラルの末尾にLまたはlが付きます
scala> val tower = 35L tower: Long = 35
Short、Byte
Short及びByte型は、Intリテラルを代入した場合、値がそれらの型の範囲内に収まっていれば、リテラルの型はShortやByteとして扱われます
scala> val littie: Short = 367 little: Short = 367 scala> val littler: Byte = 38 littler: Byte = 38
浮動小数点数リテラル
Float, Double = 小数点が含まれている、末尾にオプションのEまたはeが付いている Float = 末尾にFまたはfが付いています Double = Floatのオプションが付いていない、または末尾にDが付いている
scala> val big = 1.2345 big: Double = 1.2345 scala> val little = 1.2345F little: Float = 1.2345 scala> val anotherDouble = 3e5 // or 3e5D anotherDouble: Double = 300000.0
文字リテラル
シングルクォートで一文字を囲んだもの
scala> val a = 'A' a: Char = A
文字列リテラル
ダブルクォートで文字を囲んだもの
scala> val hello = "hello" hello: String = hello
複数行文字列リテラル
"""で囲むことで複数上の文字を表現できます
scala> var strings = """aaa | bbb | ccc""" strings: String = aaa bbb ccc scala> println(strings) aaa bbb ccc
シンボルリテラル
Scalaのシンボルは、シングルクォーテーション「'」に続く英文字で構成された認別子を使って表します。
scala> val s = 'asymbol s: Symbol = 'aSymbol Scala> val nm = s.name nm: String = aSymbol
Booleanリテラル
trueとfalseの2個のリテラル
scala> val bool = true bool: Boolean = true scala> val fool = false fool: Boolean = false
文字列補間
Scalaは柔軟な文字列補間の仕組みを備えており、それによって文字列リテラルを加工して式に含めることができます。
S補間子
文字列の中に変数の値を呼び出すことができます
val name = "reader" println(s"Hello, $name!") // 出力:Hello, reader! scala> s"The answer is ${6 * 7}." res(): String = The answer is 42
raw補間子
S補間子と違い、文字列リテラルのエスケープシーケンスを認識しません
println(raw"No¥¥¥¥escape!") // 出力:NO¥¥¥¥escape!
f補間子
書式を指定して出力します
scala> f"${math.Pi}%.5f}" res1: String = 3.14159
UNIT
関数が意味する値を返さない場合の結果型(returnしていない)
scala> def greet() = println("Hello, world!) greet: ()Unit
配列(Array)
配列は順序を保持し、重複を含むことができ、変更可能、異なる型の値を保持できる
scala> val numbers = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) numbers: Array[Int] = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) scala> numbers(3) = 10
リスト(List)
リストは順序を保持し、重複を含むことができ、不変、異なる型の値を保持できる
scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) scala> numbers(3) = 10 :9: error: value update is not a member of List[Int] numbers(3) = 10
重要なListメソッドとその使い方
コード | 意味 |
---|---|
List() または Nil | 空リスト |
List("Cool", "tools", "rule") | "Cool", "tools", "rule" の3個の値を持つ新しいList[String]を作る |
val thrill = "Will" :: "fill" :: "until" :: Nil | "Will", "fill", "until" の3個の値を持つ新しいList[String]を作る |
List("a", "b") ::: List("c", "d") | 2個のリストを連結する("a", "b", "c", "d"の4個の値を持つ新しいList[String]を返す) |
thrill(2) | thrillリストの添字2(先頭は0)の要素を返す("until"が返される) |
thrill.count(s => s.length == 4) | thrillの中で長さが4の要素の数を返す(2が返される) |
thrill.drop(2) | 先頭の2要素を取り除いたthrillリストを返す(List("until")が返される |
thrill.dropRight(2) | 末尾の2要素を取り除いたThrillリストを返す(List("Will")が返される) |
thrill.exists(s => S == "until") | thrillの中に値が"until"の要素があるかどうかを返す(trueを返す) |
thrill.filter(s => s.length == 4) | thrillに含まれる長さが4のすべての要素を順に並べたリストを返す(List("Will", "fill")が返される) |
thrill.forall(s => s.endsWith("l") | thrillリストのすべての要素について、末尾の文字が"l"になっているかどうかを返す(trueを返す) |
thrill.foreach(s => print(s)) | thrillリストに含まれる1つ1つの文字列を対象としてprint文を実行する("Willfilluntil"と表示される) |
thrill.foreach(print) | 上の省略版 |
thrill.head | thrillリストの戦闘要素を返す("Will"が返される) |
thrill.init | thrillリストから末尾の要素を除いた残りのリストを返す(List("Will", "fill")が返される) |
thrill.isEmpty | thrillリストが空かどうかを返す(falseが返される) |
thrill.last | thrillリストの最後の要素を返す("until"が返される) |
thrill.map(s => s + "y") | thrillリストの各要素に"y"を追加したものから構成されるリストを返す(List("Willy", "filly", "untily")が返される |
thrill.mkString(", ") | 引数に渡した区切り文字でリストの要素を結合した文字列を作る("Will, fill, until"が返される) |
thrill.filterNot(s => s.length == 4) | thrillリストに含まれる長さが4以外のすべての要素を順に並べたリストを返す(List("until")が返される) |
thrill.reverse | thrillリストのすべての要素を逆に並べたリストを返す(List("until", "fill", "Will")が返される) |
thrill.sortWith((s, t) => s.charAt(0).toLower < t.charAt(0).toLower) | thrillリストのすべての要素から、先頭文字を小文字にしてアルファベット順を決めて、その順序に従ったリストを作って返す(List("fill", "until", "Will")が返される) |
thrill.tail | thrillリストから戦闘要素を取り除いたものを返す(List("fill", "until")が返される) |
Set(集合)
Setは順序を保持せず、重複を持たない
scala> val numbers = Set(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) numbers: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4) // イミュータブルな集合の作成・初期化・操作 var jetSet = Set("Boeing", "Airbus") jetSet += "Lear" // jetSet = jetSet + "Lear" の略記法 println(jetSet.contains("Cessna")) // false // ミュータブルな集合の作成・初期化・操作 import scala.collection.mutable val movieSet = mutable.Set("Hitch", "Poltergeist") movieSet += "Shrek" println(movieSet)
Tuple(タプル)
リストと同様にイミュータブルだが、リストとは異なり、違う型の要素を持つことができる
// タプルを作って使う val pair = (99, "Luftballons") println(pair._1) println(pair._1) ※上記の場合の型は「pair2[Int, String]」となる。
ケースクラスとは異なり、名前付きアクセサーはない。 代わりに、位置に基づいて名前が付けられ、0ベースではなく1ベースのアクセサーがある。
scala> hostPort._1 res0: String = localhost scala> hostPort._2 res1: Int = 80
タプルはパターンマッチングにうまく収まります。
hostPort match { case ("localhost", port) => ... case (host, port) => ... }
Map(マップ)
// イミュータブルマップの作成・初期化・操作 val romanNumeral = Map( 1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V" ) println(romanNumeral(4)) // ミュータブルマップの作成・初期化・操作 import scala.collection.mutable val treasureMap = mutable.Map[Int, String]() treasureMap += (1 -> "Go to island.") treasureMap += (2 -> "Find big X on ground") treasureMap += (3 -> "Dig.") println(treasureMap(2))
クラスとオブジェクト
概要をまとめると以下の通りです。
-
- クラス定義の中には、フィールド(field)とメソッド(method)を配置する
- フィールドはオブジェクトを参照する変数で、valまたはvarで定義する
<liメソッドは実行可能なコードを格納するもので、defを使って定義する。
アクセス修飾子
Scalaでは明示的にアクセス修飾子を指定しなければ、メンバーは公開(public)となる。つまりデフォルトが公開なので明示的に書かなくて良いです。 また、フィールドの前に「private」というアクセス修飾子を挿入すると非公開となります。 privateを宣言した非公開フィールドにアクセスできるのは、同じクラス内で定義されたコードだけになります。
class ChecksumAccumulator { private var sum = 0 } // 以下はsumが非公開なのでコンパイラーを通らない val acc = new ChecksumAccumulator acc.sum = 5
メソッド
メソッドに渡されるパラメーターはvarではなくvalなので、再代入ができません。
def add(b: Byte): Unit = { b = 1 // bはvalなので、コンパイラーを通らない }
コーディングスタイル
基本形
- 文末のセミコロンはオプションのため記述不要
class ChecksumAccumulator { private var sum = 0 def add(b: Byte): Unit = { sum += b } def checksum(): Int ={ return ~(sum & 0xFF) + 1 } }
省略形
- returnを明示しない
- 中括弧を省略
- 結果型を省略
class ChecksumAccumulator { private var sum = 0 def add(b: Byte) = sum += b def checksum() = ~(sum & 0xFF) + 1
推奨形
- returnを明示しない
- 型は省略せず(型推論を使わず)書く
class ChecksumAccumulator { private var sum = 0 def add(b: Byte): Unit = { sum += b } def checksum(): Int = ~(sum & 0xFF) + 1
コンストラクタ
コンストラクタはクラス内のメンバーフィールドを初期化するのに使います
class Calculator(brand: String) { /** * A constructor. */ val color: String = if (brand == "TI") { "blue" } else if (brand == "HP") { "black" } else { "white" } // An instance method. def add(m: Int, n: Int): Int = m + n }
シングルトンオブジェクト
Scalaはクラスが静的メンバー(static member)を持てない代わりに、シングルトンオブジェクト(singleton objects)を持ちます。 特徴は以下の通りです。
- シングルトンオブジェクトの定義は、classキーワードの代わりにobjectキーワードを使う
- シングルトンオブジェクトがクラスと同じ名前を持つとき、そのクラスの「コンパニオンオブジェクト」と呼ばれる
- クラスとコンパニオンオブジェクトは互いの非公開メンバーにアクセスできる
オブジェクト
オブジェクトは、クラスの単一のインスタンスを保持するために使用されます。
object Timer { var count = 0 def currentCount(): Long = { count += 1 count } } // 使い方 scala> Timer.currentCount() res0: Long = 1
継承
他の言語と同様、継承が使えます
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): Double = log(m, math.exp(1))
}
抽象クラス
抽象クラスは、いくつかのメソッドを定義して実装していないクラスを定義することができます。 代わりに、抽象クラスを継承するサブクラスがこれらのメソッドを定義します。 抽象クラスのインスタンスを作成することはできません。
scala> abstract class Shape {
| def getArea():Int // subclass should define this
| }
defined class Shape
scala> class Circle(r: Int) extends Shape {
| def getArea():Int = { r * r * 3 }
| }
defined class Circle
scala> val s = new Shape
:8: error: class Shape is abstract; cannot be instantiated
val s = new Shape
^
scala> val c = new Circle(2)
c: Circle = Circle@65c0035b
トレイト
Scalaのトレイトはクラスに比べて以下のような特徴があります。
- 1つのクラスでは、withキーワードを使用して複数のトレイトを継承することができます
- 直接インスタンスすることができない
- コンストラクタの引数を取ることができない
trait Car { val brand: String } trait Shiny { val shineRefraction: Int }
class BMW extends Car { val brand = "BMW" }
class BMW extends Car with Shiny { val brand = "BMW" val shineRefraction = 12 }
パッケージ
パッケージ内でコードを整理することができます。 ファイルの先頭に、そのパッケージ内にあるすべてのファイルが宣言されます。
値と関数は、クラスやオブジェクトの外に置くことはできません。 オブジェクトは、静的関数を編成するのに便利なツールです。
package com.twitter.example object colorHolder { val BLUE = "Blue" val RED = "Red" }
パッケージ内のメンバーに直接アクセスできます。
println("the color is: " + com.twitter.example.colorHolder.BLUE)
パターンマッチング
Scalaでは便利なパーツに match があります
val times = 1 // valueにマッチ times match { case 1 => "one" case 2 => "two" case _ => "some other number" } // if文での判定もできる times match { case i if i == 1 => "one" case i if i == 2 => "two" case _ => "some other number" }
様々なタイプのマッチング
さまざまな型の値を別々に扱うためにmatchを使うことができます。
def bigger(o: Any): Any = { o match { case i: Int if i i - 1 case i: Int => i + 1 case d: Double if d d - 0.1 case d: Double => d + 0.1 case text: String => text + "s" } }
以上
というわけで、ズラッとScalaの書き方についてまとめてみました。 ただのまとめっぽくなってしまいましたが、Scalaの雰囲気を感じ取ってもらえればと思います。
次回は、実際にScalaで何か作っていく内容にして、より実践的にしていこうかと思います。 それではまた次回!