Scala(プログラミング言語):Dev Basics/Keyword
ScalaはJava仮想マシン上で動作する「スケーラブル」で、オブジェクト指向言語と関数型言語の双方の性質を持った言語だ。
ScalaはJava仮想マシン上で動作する「スケーラブルなプログラミング言語」だ(「Scala」という名前は「Scalabel Language」に由来する)。オブジェクト指向言語と関数型言語の双方の性質を持ち、ワンライナーのような簡単なプログラムから大規模プログラムまでを対象領域とする。
Scalaの特徴
Scalaの特徴を以下に挙げる。
- オブジェクト指向言語と関数型言語の特徴を持つ
- 全てがオブジェクト
- Javaとのシームレスな相互運用性
- 簡潔な記述
- 静的な型付けと型推測
- traitを使用した振る舞いのミックスイン
静的な型付けのプログラミング言語でありながら、型推測機構により、簡潔な記述とコンパイル時の型チェックが両立していることや、オブジェクト指向言語と関数型言語の両者の特徴を持つことなどは最近のプログラミング言語によく見られる特徴だ。
また、ScalaはJava仮想マシン上に構築されており、Javaとのシームレスな相互運用性が実現されている。つまり、Java用に作成された数多くのフレームワークやライブラリを、Scalaプログラムから利用できる(その逆も可)。
以下では幾つかの特徴を実際のコードで見てみよう(個別の構文要素などについての詳しい説明は割愛する)。なお、以下ではScalaのREPL環境を利用している。
簡潔な記述
クラス定義の例を以下に示す。
class Person(var first:String, var last:String)
ほんの1行でfirstとlastという2つのメンバを持つクラスPersonを定義できている。「first」と「last」の前にある「var」はこれらが変更可能なメンバ(フィールド)であることを表している。以下にREPL環境での実行結果を示す。
> scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) Client VM, Java 1.7.0_55).
Type in expressions for evaluation. Or try :help.
scala> class Person(var first:String, var last:String)
defined class Person
scala> var p = new Person("shinji", "kawasaki")
p: Person = Person@1678cc3
scala> p.last
res0: String = kawasaki
コンストラクターの記述まで行った例を次に示す。
class Person(val first:String, val last:String) {
private val full = first + " " + last
def printFullName() = println(full)
}
ここでは、first/lastという2つのメンバの前に先ほどの「var」とは異なり「val」と書かれている(強調表示部分)。これは2つのメンバが変更不可能(定数)であることを意味している。同時に、フルネームを保持するfullフィールドとフルネームをコンソールに表示するためのメソッドを追加した。fullフィールドは「private」なメンバだ。一方のprintFullNameメソッドの定義にはアクセス修飾子が付加されていないので、これはパブリックなメンバになる(メソッド定義も簡潔に行えていることに注目)。
実行結果を以下に示す。
scala> class Person(val first:String, val last:String) {
| private val full = first + " " + last
| def printFullName() = println(full)
| }
defined class Person
scala> var p = new Person("shinji", "kawasaki")
p: Person = Person@6a7392
scala> p.printFullName()
shinji kawasaki
scala> p.full
<console>:14: error: value full in class Person cannot be accessed in Person
p.full
^
全てがオブジェクト
Scalaでは関数を含む全てがオブジェクトである。例えば、整数値もオブジェクトだ。そのため次のような記述ができる(REPL環境での実行例)。
scala> 10.toString()
res4: String = 10
「10」はオブジェクトであり、そのオブジェクトに対してtoStringメソッドを呼び出している。
Scalaでは関数もオブジェクトだ。つまり、関数を他の関数に引数として渡し、ある関数が戻り値として関数を返すといったことが可能だ。以下に簡単な例を示す。
def getSum(array: Array[Int]):Int = {
array.sum
}
def getAvg(array: Array[Int]):Double = {
array.sum / array.length
}
def doCalc(func: Array[Int]=>Any, array: Array[Int]):Any = {
func(array)
}
var array = Array(1,3,5,7,9)
println(doCalc(getSum, array))
println(doCalc(getAvg, array))
ここで定義しているgetSumとgetAvgの2つの関数は見ての通り、配列を受け取ってその和あるいは平均値を返す。doCalc関数の第1パラメーターの型は「Array[Int]=>Any」となっている。これは「整数配列を取り、何らかの型の値を返す関数」であることを意味している。そして、doCalc関数にgetSum関数あるいはgetAvg関数と配列を渡して、計算をさせている。
オブジェクト指向/関数型
Scalaはオブジェクト指向言語であり、関数型言語でもある。そのため、ある処理をそれぞれの流儀で記述できる。例としてScalaの公式サイトで配布されている「Scala By Example」で紹介されているクイックソート関数を以下に示す。まずはオブジェクト指向的(というか、命令型言語的)な書き方をしたものだ。
def sort(xs: Array[Int]) {
def swap(i: Int, j: Int) {
val t = xs(i); xs(i) = xs(j); xs(j) = t
}
def sort1(l: Int, r: Int) {
val pivot = xs((l + r) / 2)
var i = l; var j = r
while (i <= j) {
while (xs(i) < pivot) i += 1
while (xs(j) > pivot) j -= 1
if (i <= j) {
swap(i, j)
i += 1
j -= 1
}
}
if (l < j) sort1(l, j)
if (j < r) sort1(i, r)
}
sort1(0, xs.length - 1)
}
Scala公式サイトで配布されている「Scala By Example」より。
このsort関数は「配列の中央要素の値(pivot)を得て、その値よりも小さい値/大きい値を配列に前半部/後半部に移動させて、その後配列を分割し、同様な操作を行っていくことで、配列の要素を並べ替える」という手順を記している(命令型言語的な記述)。並べ替えが終了すると、sort関数に渡した配列にはその結果が保持される(この関数は副作用を持つ)。
同様な処理を関数型言語的に記述したものが以下になる(先ほどと同じく「Scala By Example」より)。
def sort(xs: Array[Int]): Array[Int] = {
if (xs.length <= 1) xs
else {
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
}
Scala公式サイトで配布されている「Scala By Example」より。なお「import scala.language.postfixOps」を実行しないと警告が表示されるので注意しよう。
こちらのsort関数も同様な計算をするが、pivotの値を使って配列をフィルタリング/分割し、できた部分配列に対して再帰的にソート処理を呼び出すようになっている。
両者は同様な処理を行うが大きな違いがある。前者は副作用を持ち、後者は副作用を持たない。つまり、前者ではsort関数に渡した配列が書き換えられるが、後者では並べ替えられた配列が返される。これに関係して、最初のsort関数の定義では特殊な関数定義方法が使われている。ここまできちんと説明していないが、通常、Scalaの関数は大体次のような形式で定義する。
def 関数名(パラメーターリスト): 戻り値の型 = {
…… 関数定義の本体 ……
}
強調表示した部分に注目してほしい。これに対して、副作用を持つ関数(戻り値を持たず、内部で何らかの状態変化を起こす関数)は次のように戻り値の型と「=」を省略して記述できる。
def 関数名(パラメーターリスト) {
…… 関数定義の本体 ……
}
戻り値型と「=」がないことに注目。
オブジェクト指向的な記述と関数型言語的な記述のどちらが好みかはさておき、Scalaではどちらの両者の記述がサポートされている。
trait
最後にtraitについて簡単に見ておこう。これは、Javaにおけるインタフェースあるいは抽象クラスに相当するものだ。Java 8ではインタフェースでデフォルトメソッドを宣言できるようになったが、同様なことがScalaのtraitでも可能だ。
trait Human {
def sleep() = println("zzz")
}
trait Developer {
def coding() = println("I'm coding, now")
}
class Editor extends Human with Developer{
override def coding() = println("oops!")
}
class Programmer extends Human with Developer {
override def sleep() = println("I don't sleep!")
}
ここではHumanとDeveloperの2つのtraitを定義している。それぞれがメソッドを1つ持ち、そのデフォルトの振る舞いも定義されている。EditorとProgrammerの2つのクラスは2つのtraitを実装している(複数のtraitを実装する場合、最初のtraitについては「extends」キーワードを、それ以降のtraitについては「with」キーワードを使って指定する)。
ScalaはJava仮想マシン上で動作するオブジェクト指向言語と関数型言語の両者の側面を持ったプログラミング言語だ。簡単なプログラムから大規模プログラムまで、その適用範囲は幅広い。本稿で紹介して特徴以外にも、パターンマッチによる強力な分岐、並列処理など、Scalaに独特な機能が数多く提供されている。
Copyright© Digital Advantage Corp. All Rights Reserved.