Scala言語の概要
Table of Contents
1 概要
Scala言語の基本について学ぶ.
以下は,作成中である.
1.1 注意
本Webページの作成には Emacs org-mode を用い, 数式等の表示は MathJax を用いています. IEでは正しく表示されないことがあるため, Firefox, Safari等のWebブラウザでJavaScriptを有効にしてお使いください. また org-info.js を利用しており, 「m」キーをタイプするとinfoモードでの表示になります. 利用できるショートカットは「?」で表示されます.
2 字句要素
2.1 識別子
識別子 (identifiers)は,変数名,関数名,クラス名などに利用される. 他のプログラミング言語と同様に,通常は英文字から始まる英数字の列を用いるが, それ以外にも様々な文字列が利用できる.
- 英文字から始まる英数字の列 (例:
abc
,x_1
) - Unicode文字の列 (例:
あいう
,αβγ
) - 英数字の列の後に,下線と記号の列を付けた文字列 (例:
empty_?
) - 記号の列 (例:
+
,+=
,+:
)
記号の列の識別子は,演算子を実現するために関数名に用いられることがある.
記号が連続している場合,最長の文字列が識別子として取り出される. したがって,必要に応じて空白を挿入しておく必要がある.
scala> 1+-2 <console>:1: error: value +- is not a member of Int 1+-2 ^ scala> 1 + -2 res: Int = -1
- 参考リンク: Scala spec: Identifiers
2.2 整数リテラル
整数リテラル (integer literals)は Int
や Long
型の定数を表す.
L
の修飾子を付けない限りは Int
と解釈される.
Int
リテラル (例:123456789
,0x75BCD15
)Long
リテラル (例:12345678901L
,0x2DFDC1C35L
)
scala> 123456789 res: Int = 123456789 scala> 0x75BCD15 res: Int = 123456789 scala> 12345678901L res: Long = 12345678901 scala> 0x2DFDC1C35L res: Long = 12345678901
- 参考リンク: Scala spec: Integer Literals
- 参考リンク: Scala book: Literals
2.3 浮動小数点数リテラル
浮動小数点数リテラル (floating point literals)は Float
や Double
型の定数を表す.
f
の修飾子を付けない限りは Double
と解釈される.
scala> 3.141592f res: Float = 3.141592 scala> 3.141592653589793d res: Double = 3.141592653589793 scala> 3.141592653589793 res: Double = 3.141592653589793
2.4 ブール・リテラル
ブール・リテラル (Boolean literals)は Boolean
型の定数を表す.
scala> true res: Boolean = true scala> false res: Boolean = false
- 参考リンク: Scala spec: Boolean Literals
2.5 文字リテラル
文字リテラル (character literals)は Char
型の定数を表す.
シングル・クォートでくくって表す.
scala> 'a' res: Char = a scala> 'あ' res: Char = あ scala> '\'' res: Char = '
以下のエスケープ文字 (Scala spec: Escape Sequences)が利用できる.
エスケープ文字 | Unicode | 名前 |
---|---|---|
\b | \u0008 | backspace |
\t | \u0009 | horizontal tab |
\n | \u000a | linefeed |
\f | \u000c | form feed |
\r | \u000d | carriage return |
\" | \u0022 | double quote |
\' | \u0027 | single quote |
\\ | \u005c | backslash |
\uXXXX | \uXXXX | unicode character |
2.6 文字列リテラル
文字列リテラル (string literals)は String
型の定数を表す.
ダブル・クォートでくくって表す.
また,文字リテラルと同様にエスケープ文字 (Scala spec: Escape Sequences)が利用できる.
scala> "Hello,\n神戸!\n" res: String = "Hello, 神戸! " scala> "\\d+" res: String = \d+
また,三重のダブル・クォート ("""
)でくくると,エスケープ文字が無効になり,
改行も含めることができる.
scala> """Hello, 神戸! """ res: String = "Hello, 神戸! " scala> """\d+""" res: String = \d+
文字列の前に s
を付けることで,文字列補間 (String Interpolation)を利用できる.
文字列中に $変数名
あるいは ${式}
と記述することで,
値を toString
で文字列に変換した結果を挿入できる.
scala> val x = 123 x: Int = 123 scala> s"Answer is $x." res: String = Answer is 123. scala> s"Answer is ${2*x}." res: String = Answer is 246.
- 参考リンク: Scala spec: String Literals
2.7 コメント
コメント (comments)は,Javaと同様に
//
から行末までと, /*
から */
までの二種類が利用できる.
なお, /*
から */
までのコメントは,入れ子にすることができる.
scala> 123 // コメント res: Int = 123 scala> 123 /* コメント /* 注釈 */ コメント */ + 456 res: Int = 579
2.8 改行
ScalaではJavaと同様の文末にセミコロン (;
)を付けるが,
行末の場合に省略することができる.
scala> 123; res: Int = 123 scala> 123 res: Int = 123
3 式
3.1 メソッド呼び出し
Javaと同様に x.m(args)
で,オブジェクト x
に定義されているメソッド m
を呼び出すことができる.
scala> "Kobe".length res: Int = 4 scala> 123.toBinaryString res: String = 1111011 scala> 123.toBinaryString.length res: Int = 7 scala> "Kobe".charAt(1) res: Char = o scala> 123.+(1) res: Int = 124 scala> 1.to(5) res: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5) scala> 1.to(9).take(5) res: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)
Scalaでは 123
は Int
型のオブジェクトであり,加算 (+
), toBinaryString
, to
などのメソッドが定義されている.
3.2 演算子と優先度
Scalaではメソッド呼び出しに前置記法,挿入記法,後置記法を利用でき, 柔軟な構文が実現できている.
まず x.m(y)
は,ピリオドやカッコを省略して x m y
と挿入記法で記述できる.
scala> 123 + 1 res: Int = 124 scala> 1 to 5 res: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5) scala> 1 to 9 take 5 res: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5)
1 to 9 take 5
のように,複数の挿入記法がある場合,基本的には左結合的に (1 to 9) take 5
として解釈される.
ただし,以下のように演算子名 (メソッド名)の 最初の文字 によって優先度が定義されており,下のものほど強い結合になる.
(all letters) |
| |
^ |
& |
= ! |
< > |
: |
+ - |
* / % |
(all other special characters) |
たとえば 1 + 2 * 3
は, *
のほうが優先度が高いから 1.+(2.*(3))
と解釈される.
ただし,上記の規則には以下の例外がある.
=
で終わる演算子の一部 (+=
,-=
など)は,代入演算子と呼ばれ=
と同じ優先度と解釈される. なお=
で終わる演算子であっても,以下は代入演算子とは見なされない.- さらに
=
で始まっている (==
など). >=
,<=
,!=
のいずれか.
- さらに
:
で終わる演算子 (::
,+:
など)は右結合的に解釈される. さらにx m y
はy.m(x)
と解釈される.
特に,2番目の例外がリストの表記で有効に利用されている.
たとえば 1 :: 2 :: Nil
は,右結合的に解釈されて 1 :: (2 :: Nil)
と同様であり,
さらにこれは (Nil.::(2)).::(1)
と同一である.
scala> 1 :: 2 :: Nil res: List[Int] = List(1, 2) scala> (Nil.::(2)).::(1) res: List[Int] = List(1, 2)
つまり x.::(y)
は,リスト x
の先頭に要素 y
を付け加えるメソッドであり,
これにより 1 :: 2 :: Nil
で List(1, 2)
が生成されている.
演算子 +
, -
, !
, ~
は,前置演算子として利用できる.
これらを前置演算子として利用した場合,以下の対応でメソッドが呼び出される.
前置記法 | メソッド呼び出し |
---|---|
+ x | x.unary_+ |
- x | x.unary_- |
! x | x.unary_! |
~ x | x.unary_~ |
3.3 ブロック式
Scalaではブロック (blocks)も式として値を持つ.
{ 文1; 文2; ...; 式 }
というブロックの場合,最後の式が値となる.
また { 文1; 文2; ...; 式 }
は,以下のように記述できる.
{ 文1 文2 ... 式 }
scala> { val x = 123; x + 456 } res: Int = 579 scala> { val x = 123 x + 456 } res: Int = 579 scala> val y = { val x = 123; x + 456 } y: Int = 579 scala> val y = { val x = 123 x + 456 } y: Int = 579
- 参考リンク: Scala spec: Blocks
3.4 条件式
Scalaで条件式 (conditional expressions)は以下のように記述する.
if (条件式) 式1 else 式2
「条件式」が成り立てば「式1」,成り立たなければ「式2」が値となる.
scala> if (true) "OK" else "NG" res: String = OK scala> val score = 60 score: Int = 60 scala> val mark = if (score >= 90) "S" else if (score >= 80) "A" else if (score >= 70) "B" else if (score >= 60) "C" else "D" mark: String = C
3.5 for内包表記
for内包表記 (for comprehensions)は,リストなどを生成するために利用できる.
scala> for (i <- 1 to 5) yield i + 1 res: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4, 5, 6)
1から5の各 i
に対して i + 1
が計算され,それらがリスト (Vector
)の要素としてまとめられている.
複数の変数でループすることもできる.
scala> for (i <- 0 to 2; j <- 0 to 2) yield 3*i+j res: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 2, 3, 4, 5, 6, 7, 8)
ループ定義部分に if
で条件を記述することもできる.
scala> for (i <- 0 to 2; j <- 0 to 2; if i != j) yield 3*i+j res: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 5, 6, 7)
ループ定義部分が複雑な場合, {}
でくくって複数行で記述する.
scala> for { i <- 0 to 2 j <- 0 to 2 if i != j } yield 3*i+j res: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 5, 6, 7)
また,変数を定義し,その値を利用することもできる.
scala> for { i <- 0 to 2 j <- 0 to 2 k = 3*i + j if k % 2 == 0 } yield k res: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4, 6, 8)
当然,yieldの式にブロックを用いることもできる.
scala> for (i <- 0 to 2; j <- 0 to 2) yield { val k = 3*i + j if (k % 2 == 0) k else -k } res: scala.collection.immutable.IndexedSeq[Int] = Vector(0, -1, 2, -3, 4, -5, 6, -7, 8)
4 パターンマッチ
Scalaのmatch式を用いると,Javaのswitch文と同様の処理を行える.
式 match { case パターン1 => 式1 case パターン2 => 式2 ... }
ただし,match式は式の一種であり値を持つ. 「式」の値が「パターンi」にマッチする場合,「式i」の値が返される. 複数のパターンにマッチする場合は,一番上のパターンが選ばれる.
以下は,変数 c
に代入された値の種類によって異なる文字列を返すmatch式の例である.
scala> val c = 4 c: Int = 4 scala> c match { case 0 => "なし" case 1 => "ひとつ" case 2 => "ふたつ" case 3 => "みっつ" case k if k > 3 => "たくさん" case _ => "わかりません" } res: String = たくさん
Javaのswitch文とは異なりcaseの後ろに "if
" で条件を記述できる.
また,すべてにマッチするパターンとして "_
" を利用できる.
さらに List
や Seq
などのデータ構造にマッチするパターンも記述できる
(素晴らしい!).
以下は,変数 xs
に代入された列の長さによって異なる文字列を返すmatch式の例である.
scala> val xs = Seq(1) xs: Seq[Int] = List(1) scala> xs match { case Seq() => "no elements" case Seq(x) => s"single element $x" case _ => "two or more elements" } res: String = single element 1
5 変数の宣言と定義
変数の宣言には var
(variable)または val
(value)を用いる.
var
で宣言した変数は,Javaの通常の変数と同様に何度でも値を代入できる.
val
で宣言した変数は,Javaのfinal変数と同様で,初期値を一度だけ代入でき,その後は値を変更できない.
var 変数名: データ型 = 初期値 val 変数名: データ型 = 初期値
データ型を指定する部分は,Scalaの型推論により省略できる場合がある.
6 関数の宣言と定義
6.1 defによる関数定義
Scalaでは以下のように関数を定義する.
def 関数名(引数1: データ型1, ..., 引数n: データ型n): データ型 = 関数本体
例えば,以下は整数の引数 x
に1を加えた値を返す関数 inc
の定義である.
return文は必要ない.
def inc(x: Int): Int = x + 1
この場合,Scalaの型推論が結果のデータ型を推論できるから,その部分は省略できる.
def inc(x: Int) = x + 1
返り値を持たない関数は以下のように定義する.
def 関数名(引数1: データ型1, ..., 引数n: データ型n) { 関数本体 }
例えば,以下は整数の引数 x
の値を表示する関数の定義である.
def p(x: Int) { println(x) }
Scalaでリスト処理 で学ぶように,Scalaには関数を引数とする関数やメソッドが用意されている. これらは高階関数と呼ばれる.
例えば,リスト (List
)に対して用意されている map(f)
メソッドは,
リストの各要素に対して関数 f
を適用した新しいリストを返す.
scala> val list = List(3, 1, 4, 2) list: List[Int] = List(3, 1, 4, 2) scala> list.map(inc) res: List[Int] = List(4, 2, 5, 3)
この例では,リスト List(3, 1, 4, 2)
の各要素に関数 inc
が適用され,
その結果として新しいリスト List(4, 2, 5, 3)
が求められている.
6.2 匿名関数
匿名関数 (anonymous function)を用いると, def
で関数を定義せずに,
高階関数に関数を渡すことができる.
匿名関数は以下のように記述する.
(引数名1: データ型1, ..., 引数名n: データ型n) => 関数本体
特に引数が1つの場合は,カッコを省略して良い.
引数名: データ型 => 関数本体
また,型推論できる場合はデータ型の指定は省略できる.
以下は list.map(inc)
と同様の処理を行う例である.
scala> list.map(x => x + 1) res: List[Int] = List(4, 2, 5, 3)
また,引数名を省略して "_
" で表すことも可能である.
scala> list.map(_ + 1) res: List[Int] = List(4, 2, 5, 3)
すなわち _ + 1
は x => x+1
と同一である.
6.3 caseを用いた匿名関数
Scalaでは,match式で用いられているcaseの部分を記述することでも匿名関数を実現できる.
{ case パターン1 => 式1 ... case パターンn => 式n }
以下は list.map(x => x+1)
と同様の処理を行う例である.
scala> list.map({ case x => x + 1 }) res: List[Int] = List(4, 2, 5, 3) scala> list.map { case x => x + 1 } res: List[Int] = List(4, 2, 5, 3)
以下は,別の例である.
scala> list.map { case 0 => "なし" case 1 => "ひとつ" case 2 => "ふたつ" case 3 => "みっつ" case k if k > 3 => "たくさん" case _ => "わかりません" } res: Seq[String] = List(みっつ, ひとつ, たくさん, ふたつ)
7 クラス定義
Scalaのクラス,オブジェクト,トレイトについて説明する.
オブジェクト指向プログラミングの考え方については, Javaを用いて説明した以下のWebページを参照のこと.
- 参考リンク: Javaプログラミング入門
7.1 class
クラスの定義はJavaと同様である.
以下の例は STACK
クラスを定義している
class STACK { var elems: List[Int] = List() def push(x: Int) { elems = x :: elems } def pop: Int = elems match { case Nil => 0 case x :: xs => { elems = xs; x } } }
elems
がインスタンス変数, push
と pop
がメソッドである.
利用例は以下の通り.
scala> val s = new STACK s: STACK = STACK@10993713 scala> s.push(123) scala> s.elems res: List[Int] = List(123) scala> s.pop res: Int = 123 scala> s.elems res: List[Int] = List()
以下のように記述すれば,コンストラクタに引数を指定できる.
なお List()
は,引数が指定されなかった場合のデフォールト値である.
class STACK(var elems: List[Int] = List()) { def push(x: Int) { elems = x :: elems } def pop: Int = elems match { case Nil => 0 case x :: xs => { elems = xs; x } } }
利用例は以下の通り.
scala> val s = new STACK(123 :: 456 :: Nil) s: STACK = STACK@45592af7 scala> s.elems res: List[Int] = List(123, 456) scala> s.pop res: Int = 123
7.2 object
object
により,単一のオブジェクトを生成し名前を与えることができる.
また,その中で変数やメソッドの定義ができるから,
Javaでのstatic変数やstaticメソッドと同様の事を行える.
特に, main
メソッドを定義すれば,コマンドラインから実行できる.
class STACK(var elems: List[Int] = List()) { def push(x: Int) { elems = x :: elems } def pop: Int = elems match { case Nil => 0 case x :: xs => { elems = xs; x } } } object Test { def main(args: Array[String]) { val elems = args.map(_.toInt).toList val s = new STACK(elems) println(s.elems) } }
以下は,上の内容のファイル Class.scala をコンパイルし,コマンドラインから実行する例である.
$ scalac Class.scala $ scala Test 12 34 56 List(12, 34, 56)
また,クラスと同じ名前のオブジェクトを定義し,そこにファクトリーメソッドなどを定義することがある. このようなオブジェクトはコンパニオンオブジェクト (companion object)と呼ばれる.
7.3 case classとcase object
Scalaには case class
と case object
と呼ばれる
特別なクラス定義およびオブジェクト定義方法が用意されている.
通常のクラス定義およびオブジェクト定義とは異なり,以下の特徴を持つ.
- インスタンス生成に
new
が必要ない. toString
メソッドが自動的に定義される.hashCode
,equals
メソッドが自動的に定義され, オブジェクトの構造同値性 (structural equivalence)を実現できる.apply
,unapply
メソッドが自動的に定義され, match式のパターンマッチに利用できる.
以下は,命題論理式の構造を定義したプログラム例である (CaseClass.scala).
abstract class Formula case object False extends Formula case object True extends Formula case class Var(name: String) extends Formula case class Not(x1: Formula) extends Formula case class And(x1: Formula, x2: Formula) extends Formula case class Or(x1: Formula, x2: Formula) extends Formula case class Imp(x1: Formula, x2: Formula) extends Formula
以下は,利用例である.
scala> Imp(And(Var("x"), Not(Var("x"))), False) res: Imp = Imp(And(Var(x),Not(Var(x))),False)
さらに進んだ利用については Scalaで複素数計算 および Scalaと命題論理 を参照のこと.
7.4 TODO trait
trait
はJavaの interface
と同様であり,クラス階層による継承とは異なり,
クラスに機能を追加するために用いられる.
=trait=には以下の特徴がある.
- インスタンスを作成することはできないし,インスンタンス生成時のパラメータも存在しない.
with
キーワードを用いて,1つのクラスに複数のtrait
の機能を追加 (ミックスイン)できる.
8 Javaとの連携
8.1 ScalaからJavaを呼び出す
ScalaからJavaのプログラムを利用するのは簡単だ. 例えば,REPLで以下を実行すると100x100のサイズのフレームが画面に表示される.
scala> val frame = new java.awt.Frame scala> frame.setSize(400, 200) // フレームのサイズを指定 scala> frame.add(new java.awt.Button("Hello Kobe!")) // フレームにボタンを追加 scala> frame.setVisible(true) // フレームを表示 scala> frame.dispose // フレームを消す
次に,自分で作成したJavaプログラムを呼ぶ出す方法を説明する.
以下のようなJavaプログラムがあり,それをコンパイルして作成された
classファイルが XXX
ディレクトリに保存されているとする.
public class A { public void a(int x) { System.out.println("Method a: " + x); } }
この場合,以下のようにclassファイルの置かれているディレクトリ名を
classpathオプションに指定してscalaを起動する.
ただし XXX
がカレント・ディレクトリの場合は, -classpath XXX
の部分は省略できる.
$ scala -classpath XXX
この後,REPLから以下のようにJavaのクラスAのメソッドaを呼び出せる.
scala> val z = new A z: A = A@29a33620 scala> z.a(10) Method a: 10
8.2 JavaからScalaを呼び出す
Java側のプログラムを B.java
とする.
public class B { public static void main(String[] args) { System.out.println(C.c(10)); } }
Scala側のプログラムは C.scala
とする.
object C { def c(x: Int) = s"Method c: $x" }
Javaのプログラムをコンパイルし実行するには,scalaのインストール時に作成された
ディレクトリ scala-2.12.5/lib
の場所の指定が必要になる.
以下では,そのディレクトリを LIB
として説明する.
- Scalaプログラム
C.scala
をコンパイルする.scalac C.scala
- Javaプログラム
B.java
をコンパイルする.- Windowsの場合
javac -classpath .;LIB\* B.java
- Mac, Linuxの場合
javac -classpath .:LIB/"*" B.java
- Windowsの場合
- Javaプログラムを実行する.
- Windowsの場合
java -classpath .;LIB\* B
- Mac, Linuxの場合
java -classpath .:LIB/"*" B
- Windowsの場合