Scalaは(便利な)オブジェクト指向言語です

  • 53
    Like
  • 0
    Comment

プログラミング言語Scalaの特徴として、オブジェクト指向と関数型の融合とか、オブジェクト指向と関数型のハイブリッドといったことがよく言われます。これらの言説は間違っているわけではないものの、Scalaという言語の本質的な部分をよく表していないと私は思います。

また、関数型の要素を取り込んだ、という部分によって必要以上にScalaを恐れている人もいるように見受けられます。

そこで、「正しい」Scala言語の解説を簡単に書いてみることにしました。

クラスベースのオブジェクト指向言語である

object構文やその他一部を除いて、Scalaでは原則的にクラスからオブジェクトをnewしてオブジェクトを作成します。

class Point(val x: Int, val y: Int)
val p = new Point(1, 2)
println(p.x) // => 1
println(p.y) // => 2

全ての値はオブジェクトである(全ての型は間接的にAnyを継承している)

Java言語では、プリミティブ型として特別扱いされる値である、

  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • Boolean
  • Char

の値は全てオブジェクトです(Anyを継承したAnyValをさらに継承した型になっています)。もちろん、その他の値も全てオブジェクトなので、それぞれにメソッドを持っています。

(ほとんど)全ての操作はメソッドである

これは文字通りの意味で、

class Point(val x: Int, val y: Int)

で、xyはともにメソッドです。その証拠に、

val p = new Point(1, 2)
p.x _ // => () => Int
p.y _ // => () => Int

のように_をつけることでメソッドを関数オブジェクトに変換することができます。例外として、

def add(x: Int, y: Int): Int = {
  def a(x: Int, y: Int): Int = x + y
  a(x, y)
}

として定義された、メソッド内関数 a はメソッドではありません。

式を基本単位とする言語である

Scalaでは、

  • if式
  • while式
  • do-while式
  • for式
  • {} 式
  • メソッド呼び出し式
  • new式

など、ほとんど全ての制御構文が値を返します(Scala言語仕様に厳密に則った書き方ではないですが許してください)。Scalaにおけるメソッド定義の
構文は、

def f(arg1: Type1 ...): ReturnType = Expression

のようになっていて、

def f(x: Int, y: Int): Int = {
  x + y
}

のように書くことができるのは、単に{}式が値を返すからに過ぎません。この点についてよく誤解があるのですが、

def f(x: Int, y: Int): Int = x + y

という形式は、本体が一行の場合の特別な記法ではなく、単に{}式を使わないで書くとそのようになるというだけです。

100% interoperable with Java である

Kotlinの宣伝文句として、この「100% interoperable with Java」というのがありますが、この点についてはScalaもほぼ同様です(若干Kotlinの方が、JavaからKotlinを呼ぶのが楽ですが)

たとえば、

  • Javaのクラスを継承してScalaのクラスを作る
  • ジェネリックなJavaのクラスがScalaからもジェネリックに見える
  • ジェネリックなScalaのクラスがJavaからもジェネリックに見える
  • Javaのメソッドを呼び出す
  • Scalaのクラスを継承してJavaのクラスを作る
  • Javaのオブジェクトを生成する
  • Javaのstaticメソッドを呼び出す
  • Javaのstaticフィールドを参照する
  • SAM変換(from Scala 2.12)

などができます。JavaからScalaのオブジェクトを参照したり、メソッドを呼び出すときにはname manglingの規則を知っている必要があり、多少工夫が必要ですが、ルールさえわかっていればできる範囲でもあります。

また、一つのプロジェクトの中にJava/Scalaを混在させることもできます(Scalaの標準ビルドツールであるsbtはそのようなJava/Scala混在プロジェクトを問題なくコンパイルできます)。

豊富なコレクションライブラリがある

Scalaの標準ライブラリには多種多様なコレクションライブラリがあります。大きく分けて、

  • 不変コレクション(scala.collection.immutable._)
  • 可変コレクション(scala.collection.mutable._)

に分けられますが、どちらのコレクションの種類も充実しており、不変コレクションを使うことを強制させられることはありません(ただし、不変コレクションを使いこなせないと十分なメリットが受けられないことも確かです)

可変コレクションはたとえば次のようにして使うことができます:

scala> import scala.collection.mutable.Buffer
import scala.collection.mutable.Buffer

scala> val buffer = Buffer[Int]()
buffer: scala.collection.mutable.Buffer[Int] = ArrayBuffer()

scala> buffer += 1
res1: buffer.type = ArrayBuffer(1)

scala> buffer += 2
res2: buffer.type = ArrayBuffer(1, 2)

scala> buffer += 4
res3: buffer.type = ArrayBuffer(1, 2, 4)

Bufferjava.util.List と思えば、ほとんど同じように使えるのがわかると思います。また、可変コレクションを不変コレクションに変換したい、という要求にも簡単に答えられます。たとえば、 toList メソッドを呼び出すことで、 Buffer を 不変 List に変換することができます。

scala> buffer.toList
res4: List[Int] = List(1, 2, 4)

このようなことができるため、可変コレクションを内部で使って、外側のインタフェースは不変コレクションにそろえるといったことが簡単にできます。

case classによってボイラープレートの削減が可能である

Javaで

class Person {
  private final String name;
  private final int age;
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }
  public int hashCode() { .... }
  public boolean equals(Object other) { ... }
}

と書かなければいけないところを、Scalaでは単に

case class Person(name: String, age: Int)

と書くことができます。このとき、 nameage もメソッド扱いであるため、後から内部表現を変更してもクライアントのコードが壊れることはありません。また、case classのメンバーから、適切な hashCodeequals の定義も自動生成されます。

関数の型は単なるFunctionNの構文糖である

Scalaでは、関数を表す型があります。、たとえば、 Int を受け取って Int を返す関数の型は Int => Int と表記できますが、これは単に scala.Function1[Int, Int] と書いているのとほとんど変わりがありません。

関数オブジェクトの生成は単なる無名クラスのオブジェクトの生成である

また、

val x: Int => Int = {x => x}

と書いているのは、単に

val x: Function1[Int, Int] = new Function1[Int, Int] {
  def apply(x: Int): Int = x
}

と書いているのと変わりありません。厳密には細かい違い(たとえば関数オブジェクトの中で return したときの挙動)はありますし、Scala 2.12からはSAM変換も入ったのでその点は違いますが、そうとらえておおむね問題ありません。

その他の点

  • 代数的データ型: sealedによる継承の制限とクラスのグループ化による
  • パターンマッチング: unapply メソッドを持ったオブジェクトの呼び出し
  • 型クラス: implicit parameter
  • object 構文: シングルトン

- 複数行文字列:

"""
Hoge
Foo
Bar
"""
  • 文字列補間: s"1 + 2 = ${1 + 2}" // => "1 + 2 = 3"
  • enrich my library: いわゆる拡張メソッド
implicit class RichString(self: String) {
  def show(): Unit = println(self)
}
"Hello, World!".show() // => "Hello, World!"
  • ...

まとめ

このように、Scalaの多くの構文は、昔ながらのオブジェクト指向言語をより使いやすくしたものとして見ることができます。また、関数型プログラミングを行うための機能も多くは、オブジェクト指向の機能を使ってエンコードすることで実現されています。

このようにして見ていくと、通常のオブジェクト指向プログラミングをScalaで行うことは必ずしも悪いことではないと私は思います。

もちろん、標準のコレクションライブラリの不変コレクションは使いこなせないと困るとか、色々ありますが、言語仕様としては、「便利なオブジェクト指向言語」として使うことを十分許容していると言えます。

というわけで、Scalaを怖がらずに是非チャレンジしてみて欲しいと思うのでした。もちろん、プロダクション環境でScalaを使う上では、それぞれのチームの規約に則ってコードを書く必要があるのはいうまでもありませんが、とりあえず試す上では自由に書いても構わないと私は思います。

なお、型システムの面で優れている点(高階多相、抽象型、パス依存型等)についての説明はあえてほとんどはしょりましたが、その辺のツッコミは勘弁していただけると助かります。

Scalaは楽して色々なことを書けて便利なので、皆さん、もっとScalaを気軽に始めてみましょう。