読者です 読者をやめる 読者になる 読者になる

無限大な夢のあと

テニスとアニメが大好きな厨二病なブログ

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その7(関数)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【関数】
Scalaの関数

Scalaの関数は、他の言語の関数と扱いが異なります。Scalaの関数は単に Function0 〜 Function22 までのトレイトの無名サブクラスのインスタンスなのです。

たとえば、2つの整数を取って加算した値を返すadd関数は次のようにして定義することができます

scala> val add = new Function2[Int, Int, Int]{
     |   def apply(x: Int, y: Int): Int = x + y
     | }
add: (Int, Int) => Int = <function2>

scala> add.apply(100, 200)
res0: Int = 300

scala> add(100, 200)
res1: Int = 300

Function0からFunction22までの全ての関数は引数の数に応じたapplyメソッドを定義する必要があります。 applyメソッドはScalaコンパイラから特別扱いされ、x.apply(y)は常にx(y)のように書くことができます。後者の方が関数の呼び方としては自然ですね。

また、関数を定義するといっても、単にFunction0からFunction22までのトレイトの無名サブクラスのインスタンスを作っているだけです。

関数の作り方は理解。
確かもっと簡単に作れていた印象。
省略しないで書くと、このような処理をしているのか。
トレイトの無名サブクラスのインスタンス
無名サブクラスと言われてもピンとこない。。

・無名関数

前項でScalaで関数を定義しましたが、これを使ってプログラミングをするとコードが冗長になり過ぎます。そのため、 ScalaではFunction0〜Function22までのトレイトのインスタンスを生成するためのシンタックスシュガーが用意されています。たとえば、先ほどのadd関数は

scala> val add = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = <function2>

と書くことができます。ここで、addには単に関数オブジェクトが入っているだけであって、関数本体には何の名前も付いていないことに注意してください。この、addの右辺のような定義をScalaでは無名関数と呼びます。無名関数は単なるFunctionNオブジェクトですから、自由に変数や引数に代入したり返り値として返すことができます。このような、関数を自由に変数や引数に代入したり返り値として返すことができる性質を指して、Scalaでは関数が第一級の値(First Class Object)であるといいます。

無名関数の一般的な構文は次のようになります。

(n1: N1, n2: N2, n3: N3, ...nn: NN) => B

n1からnnまでが仮引数の定義でN1からNNまでが仮引数の型です。Bは無名関数の本体です。無名関数の返り値の型は通常は Bの型から推論されます。先ほど述べたように、Scalaの関数はFunction0〜Function22までのトレイトの無名サブクラスのインスタンスですから、引数の最大個数は22個になります。

JavaScriptの無名関数と同じイメージ。
関数が「First Class Object」であるというのはこういうことか。
ちなみに、Golangも関数は「First Class Object」です。
また、Golangジェネリクスがないので、リフレクションで頑張らないと関数型の汎用的なmap関数やreduce関数のようなことはできないのです。

・関数の型

このようにして定義した関数の型は、本来はFunctionN[...]のようにして記述しなければいけませんが、関数の型については特別にシンタックスシュガーが設けられています。一般に、

(n1: N1, n2: N2, n3: N3, ...nn: NN) => B

となるような関数の型はFunctionN[N1, N2, N3, ...NN, Bの型]と書く代わりに

(N1, N2, N3, ...NN) => Bの型

として記述することができます。直接FunctionNを型として使うことは稀なので、こちらのシンタックスシュガーを覚えておくと良いでしょう。

具体例のサンプルが欲しかった。
シンタックスシュガーの記述に関しては後ほど、コップ本で調べる。

・関数のカリー化

関数型言語ではカリー化というテクニックがよく使われます。カリー化とは、たとえば (Int, Int) => Int 型の関数のように複数の引数を取る関数があったとき、これを Int => Int => Int 型の関数のように、1つの引数を取り、残りの引数を取る関数を返す関数のチェインで表現するというものです。試しに上記のaddをカリー化してみましょう。

scala> val add = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = <function2>

scala> val addCurried = (x: Int) => ((y: Int) => x + y)
addCurried: Int => (Int => Int) = <function1>

scala> add(100, 200)
res2: Int = 300

scala> addCurried(100)(200)
res3: Int = 300

無名関数を定義する構文をネストさせて使っているだけで、何も特別なことはしていないことがわかります。

また、Scalaではメソッドの引数リストを複数に分けることで簡単にカリー化された関数を得ることができます。このことをREPLを用いて確認してみましょう。

scala> def add(x: Int, y: Int): Int = x + y
add: (x: Int, y: Int)Int

scala> add _
res0: (Int, Int) => Int = <function2>

scala> def addCurried(x: Int)(y: Int): Int = x + y
addCurried: (x: Int)(y: Int)Int

scala> addCurried _
res1: Int => (Int => Int) = <function1>

引数リストを複数に分けたaddCurriedから得られた関数は1引数関数のチェインになっていて、確かにカリー化されています。

Scalaのライブラリの中にはカリー化された形式の関数を要求するものがあったりするので、とりあえず技法として覚えておくのが良いでしょう。

メソッドの引数リストを複数に分けることで簡単にカリー化された関数を得ることができるのか。
Underscore.jsではそこまで便利ではなかったかなー
以下の書籍で少しだけ関数型の勉強はしたことがあったが、途中で挫折して積読になってしまっていた。。

JavaScriptで学ぶ関数型プログラミング

JavaScriptで学ぶ関数型プログラミング

また、Golangではシンタックスシュガーは用意されていなかった。
以下の記事でカリー化のサンプルがあった。
Go言語で関数のカリー化(currying)入門 - Qiita

・メソッドと関数の違い

メソッドについては既に説明しましたが、メソッドと関数の違いについてはScalaを勉強する際に注意する必要があります。本来はdefで始まる構文で定義されたものだけがメソッドなのですが、説明の便宜上、所属するオブジェクトの無いメソッド(今回は説明していません)やREPLで定義したメソッドを関数と呼んだりすることがあります。書籍やWebでもこの2つを意図的に、あるいは無意識に混同している例が多々あるので(Scalaバイブル『Scalaスケーラブルプログラミング』でも意図的なメソッドと関数の混同の例がいくつかあります)注意してください。

再度強調すると、メソッドはdefで始まる構文で定義されたものであり、それを関数と呼ぶのはあくまで説明の便宜上であるということです。ここまでメソッドと関数の違いについて強調してきましたが、それは、メソッドは第一級の値ではないのに対して関数は第一級の値であるという大きな違いがあるからです。メソッドを取る引数やメソッドを返す関数、メソッドが入った変数といったものはScalaには存在しません。

ここはしっかりと押さえたいところ。

高階関数

関数を引数に取ったり関数を返すメソッドや関数のことを高階関数と呼びます。先ほどメソッドと関数の違いについて説明したばかりなのに、メソッドのことも関数というのはいささか奇妙ですが、慣習的にそう呼ぶものだと思ってください。

早速高階関数の例についてみてみましょう。

scala> def double(n: Int, f: Int => Int): Int = {
     |   f(f(n))
     | }
double: (n: Int, f: Int => Int)Int

これは与えられた関数fを2回nに適用する関数doubleです。ちなみに、高階関数に渡される関数は適切な名前が付けられないことも多く、その場合はfやgなどの1文字の名前をよく使います。他の関数型プログラミング言語でも同様の慣習があります。呼び出しは次のようになります。

scala> double(1, m => m * 2)
res4: Int = 4

scala> double(2, m => m * 3)
res5: Int = 18

scala> double(3, m => m * 4)
res6: Int = 48

最初の呼び出しは1に対して、与えられた引数を2倍する関数を渡していますから、1 * 2 * 2 = 4になります。2番めの呼び出しは2に対して、与えられた引数を3倍する関数を渡していますから、2 * 3 * 3 = 18になります。最後の呼び出しは、3に対して与えられた引数を4倍する関数を渡していますから、3 * 4 * 4 = 48になります。

上記のようなサンプルだと、累乗計算を始め計算系の処理が楽にできそうなイメージ。
高階関数に渡される関数の名前が適切ではない1文字の名前が渡されるのは覚えておこう。

もう少し意味のある例を出してみましょう。プログラムを書くとき、

初期化
何らかの処理
後始末処理
というパターンは頻出します。これをメソッドにした高階関数aroundを定義します。

scala> def around(init: () => Unit, body: () => Any, fin: () => Unit): Any = {
     |   init()
     |   try {
     |     body()
     |   } finally {
     |     fin()
     |   }
     | }
around: (init: () => Unit, body: () => Any, fin: () => Unit)Any

try-finally 構文は、後の例外処理の節でも出てきますが、大体Javaのそれと同じだと思ってください。このaround関数は次のようにして使うことができます。

scala> around(
     |   () => println("ファイルを開く"),
     |   () => println("ファイルに対する処理"),
     |   () => println("ファイルを閉じる")
     | )
ファイルを開くファイルに対する処理ファイルを閉じる
res7: Any = ()

aroundに渡した関数が順番に呼ばれていることがわかります。ここで、bodyの部分で例外を発生させてみます。throwはJavaのそれと同じで例外を投げるための構文です。

scala> around(
     |   () => println("ファイルを開く"),
     |   () => throw new Exception("例外発生!"),
     |   () => println("ファイルを閉じる")
     | )
ファイルを開くファイルを閉じる
java.lang.Exception: 例外発生!
  at $anonfun$3.apply(<console>:16)
  at $anonfun$3.apply(<console>:16)
  at .around(<console>:15)
  ... 906 elided

のそれぞれを部品化して、「何らかの処理」の部分で異常が発生しても必ず後始末処理を実行できています。このaroundメソッドは1〜3の手順を踏む様々な処理に流用することができます。一方、1〜3のそれぞれは呼び出し側で自由に与えることができます。このように処理を値として部品化することは高階関数を定義する大きなメリットの1つです。

ちなみに、Java 7では後始末処理を自動化するtry-with-resources文が言語として取り入れられましたが、高階関数のある言語では、言語に頼らず自分でそのような働きをするメソッドを定義することができます。

後のコレクションの節を読むことで、高階関数のメリットをより具体的に理解できるようになるでしょう。

高階関数の使いどころをよくわかっていなかったけど、このような形で使うこともできるんだ。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その6(型パラメータと変位指定)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【型パラメータ】
気になった部分を以下に記載

クラスの節では触れませんでしたが、クラスは0個以上の型をパラメータとして取ることができます。これは、クラスを作る時点では何の型か特定できない場合(たとえば、コレクションクラスの要素の型)を表したい時に役に立ちます。型パラメータを入れたクラス定義の文法は次のようになります

class クラス名[型パラメータ1, 型パラメータ2, ..., 型パラメータN](コンストラクタ引数1 :コンストラクタ引数1の型, コンストラクタ引数2 :コンストラクタ引数2の型, ...)
{
  0個以上のフィールドの定義またはメソッド定義
}

型パラメータ1から型パラメータNまでは好きな名前を付け、クラス定義の中で使うことができます。とりあえず、簡単な例として、1個の要素を保持して、要素を入れる(putする)か取りだす(getする)操作ができるクラスCellを定義してみます。Cellの定義は次のようになります。

class Cell[T](var value: T) {
  def put(newValue: T): Unit = {
    value = newValue
  }

  def get(): T = value
}

これをREPLで使ってみましょう。

scala> class Cell[T](var value: T) {
     |   def put(newValue: T): Unit = {
     |     value = newValue
     |   }
     |   
     |   def get(): T = value
     | }
defined class Cell

scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@192aaffb

scala> cell.put(2)

scala> cell.get()
res1: Int = 2

scala> cell.put("something")
<console>:10: error: type mismatch;
 found   : String("something")
 required: Int
              cell.put("something")
                       ^
scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@6a01a75b

で、型パラメータとしてInt型を与えて、その初期値として1を与えています。型パラメータにIntを与えてCellをインスタンス化したため、REPLではStringをputしようとして、コンパイラにエラーとしてはじかれています。Cellは様々な型を与えてインスタンス化したいクラスであるため、クラス定義時には特定の型を与えることができません。そういった場合に、型パラメータは役に立ちます。

うん、ここまではJavaの型クラスと変わりはないかな?というイメージ。

次に、もう少し実用的な例をみてみましょう。メソッドから複数の値を返したい、という要求はプログラミングを行う上でよく発生します。そのような場合、型パラメータが無い言語では、

・片方を返り値として、もう片方を引数を経由して返す
・複数の返り値専用のクラスを必要になる度に作る
という選択肢しかありませんでした。しかし、前者は引数を返り値に使うという点で邪道ですし、後者の方法は多数の引数を返したい、あるいは解く問題上で意味のある名前の付けられるクラスであれば良いですが、ただ2つの値を返したいといった場合には小回りが効かず不便です。こういう場合、型パラメータを2つ取るPairクラスを作ってしまいます。Pairクラスの定義は次のようになります。toStringメソッドの定義は後で表示のために使うだけなので気にしないでください。

class Pair[T1, T2](val t1: T1, val t2: T2) {
  override def toString(): String = "(" + t1 + "," + t2 + ")"
}

このクラスPairの利用法としては、たとえば割り算の商と余りの両方を返すメソッドdivideが挙げられます。divideの定義は次のようになります。

def divide(m: Int, n: Int): Pair[Int, Int] = new Pair[Int, Int](m / n, m % n)

これらをREPLにまとめて流し込むと次のようになります。

scala> class Pair[T1, T2](val t1: T1, val t2: T2) {
     |   override def toString(): String = "(" + t1 + "," + t2 + ")"
     | }
defined class Pair

scala> def divide(m: Int, n: Int): Pair[Int, Int] = new Pair[Int, Int](m / n, m % n)
divide: (m: Int, n: Int)Pair[Int,Int]

scala> divide(7, 3)
res0: Pair[Int,Int] = (2,1)

7割る3の商と余りがres0に入っていることがわかります。なお、ここではnew Pair[Int, Int](m / n, m % n)としましたが、引数の型から型パラメータの型を推測できる場合、省略できます。この場合、Pairのコンストラクタに与える引数はIntとIntなので、new Pair(m / n, m % n)としても同じ意味になります。このPairは2つの異なる型(同じ型でも良い)を返り値として返したい全ての場合に使うことができます。このように、どの型でも同じ処理を行う場合を抽象化できるのが型パラメータの利点です。

ちなみに、このPairのようなクラスはScalaではよく使われるため、Tuple1からTuple22(Tupleの後の数字は要素数)があらかじめ用意されています。また、インスタンス化する際も、

scala> val m = 7
m: Int = 7

scala> val n = 3
n: Int = 3

scala> new Tuple2(m / n, m % n)
res1: (Int, Int) = (2,1)

などとしなくても、

scala> val m = 7
m: Int = 7

scala> val n = 3
n: Int = 3

scala> (m / n, m % n)
res2: (Int, Int) = (2,1)

とすれば良いようになっています。

うん、タプルでこのようなことができるのは把握した。
標準クラスで準備されているのは嬉しい。
Golangでは2値を返すメソッドが作れたりする。
成功時の結果と失敗時のエラーを常に返すメソッドを作るイメージ。


【変位指定(variance)】
・共変(covariant)

Scalaでは、何も指定しなかった型パラメータは通常は非変(invariant)になります。非変というのは、型パラメータを持ったクラスG、型パラメータT1とT2があったとき、T1 = T2のときにのみ

val : G[T1] = G[T2]

というような代入が許されるという性質を表します。これは、違う型パラメータを与えたクラスは違う型になることを考えれば自然な性質です。ここであえて非変について言及したのは、Javaの組み込み配列クラスは標準で非変ではなく共変であるという設計ミスを犯しているからです。

ここでまだ共変について言及していなかったので、簡単に定義を示しましょう。共変というのは、型パラメータを持ったクラスG、型パラメータT1とT2があったとき、T1 が T2 を継承しているときにのみ、

val : G[T2] = G[T1]
class G[+T]

のように型パラメータの前に+を付けるとその型パラメータは(あるいはそのクラスは)共変になります。

このままだと定義が抽象的でわかりづらいかもしれないので、具体的な例として配列型を挙げて説明します。配列型はJavaでは共変なのに対してScalaでは非変であるという点において、面白い例です。まずはJavaの例です。G = 配列、 T1 = String, T2 = Objectとして読んでください。

Object[] objects = new String[1];
objects[0] = 100;

このコード断片はJavaのコードとしてはコンパイルを通ります。ぱっと見でも、Objectの配列を表す変数にStringの配列を渡すことができるのは理にかなっているように思えます。しかし、このコードを実行すると例外 java.lang.ArrayStoreException が発生します。これは、objectsに入っているのが実際にはStringの配列(Stringのみを要素として持つ)なのに、2行目でint型(ボクシング変換されてInteger型)の値である100を渡そうとしていることによります。

共変や非変という概念を初めて聞いたので、ここは勉強になりました。
コンパイルではなく、実行時例外で発生するのは辛い。。
最近見た例だと、JDK のCollections.unmodifiableList()の実行時例外とかか。
以下の記事の中で言及されています。
qiita.com

一方、Scalaでは同様のコードの一行目に相当するコードをコンパイルしようとした時点で、次のようなコンパイルエラーが出ます(Anyは全ての型のスーパークラスで、AnyRefに加え、AnyVal(値型)の値も格納できます)。

scala> val arr: Array[Any] = new Array[String](1)
<console>:7: error: type mismatch;
 found   : Array[String]
 required: Array[Any]

このような結果になるのは、Scalaでは配列は非変だからです。静的型付き言語の型安全性とは、コンパイル時により多くのプログラミングエラーを捕捉するものであるとするなら、配列の設計はScalaの方がJavaより型安全であると言えます。

さて、Scalaでは型パラメータを共変にした時点で、安全ではない操作はコンパイラがエラーを出してくれるので安心ですが、共変をどのような場合に使えるかを知っておくのは意味があります。たとえば、先ほど作成したクラスPair[T1, T2]について考えてみましょう。Pair[T1, T2]は一度インスタンス化したら、変更する操作ができませんから、ArrayStoreExceptionのような例外は起こり得ません。実際、Pair[T1, T2]は安全に共変にできるクラスで、class Pair[+T1, +T2]のようにしても問題が起きません。

scala> class Pair[+T1, +T2](val t1: T1, val t2: T2) {
     |   override def toString(): String = "(" + t1 + "," + t2 + ")"
     | }
defined class Pair

scala> val pair: Pair[AnyRef, AnyRef] = new Pair[String, String]("foo", "bar")
pair: Pair[AnyRef,AnyRef] = (foo,bar)

ここで、Pairは作成時に値を与えたら後は変更できず、したがってArrayStoreExceptionのような例外が発生する余地がないことがわかります。一般的には、一度作成したら変更できない(immutable)などの型パラメータは共変にしても多くの場合問題がありません。

immutableだったら、共変にしても問題は起こらないということか。
利用シーンがまだ思い浮かばない。

演習問題

次のimmutableなStack型の定義(途中)があります。???の箇所を埋めて、Stackの定義を完成させなさい。なお、E >: Tは、EはTの継承元である、という制約を表しています。また、Nothingは全ての型のサブクラスであるような型を表現します。Stack[T]は共変なので、Stack[Nothing]はどんな型のStack変数にでも格納することができます。

trait Stack[+T] {
  def pop: (T, Stack[T])
  def push[E >: T](e: E): Stack[E]
  def isEmpty: Boolean
}

class NonEmptyStack[+T](private val top: T, private val rest: Stack[T]) extends Stack[T] {
  def push[E >: T](e: E): Stack[E] = ???
  def pop: (T, Stack[T]) = ???
  def isEmpty: Boolean = ???
}

case object EmptyStack extends Stack[Nothing] {
  def pop: Nothing = throw new IllegalArgumentException("empty stack")
  def push[E >: Nothing](e: E): Stack[E] = new NonEmptyStack[E](e, this)
  def isEmpty: Boolean = true
}

object Stack {
  def apply(): Stack[Nothing] = EmptyStack
}

解答を見て納得した感じ。
自力では厳しかったっす。。
型を見てなんとなくは解答できても、よくわかっていないと実感。

・反変(contravariant)

次は共変とちょうど対になる性質である反変です。簡単に定義を示しましょう。反変というのは、型パラメータを持ったクラスG、型パラメータT1とT2があったとき、T1 が T2 を継承しているときにのみ、

val : G[T1] = G[T2]

というような代入が許される性質を表します。Scalaでは、クラス定義時に

class G[-T]

のように型パラメータの前に-を付けるとその型パラメータは(あるいはそのクラスは)反変になります。

反変の例として最もわかりやすいものの1つが関数の型です。たとえば、型T1とT2があったとき、

val x1: T1 => AnyRef = T2 => AnyRef型の値
x1(T1型の値)

というプログラムの断片が成功するためには、T1がT2を継承する必要があります。その逆では駄目です。仮に、T1 = String, T2 = AnyRef として考えてみましょう。

val x1: String => AnyRef = AnyRef => AnyRef型の値
x1(String型の値)

ここでx1に実際に入っているのはAnyRef => AnyRef型の値であるため、引数としてString型の値を与えても、AnyRef型の引数にString型の値を与えるのと同様であり、問題なく成功します。T1とT2が逆で、T1 = AnyRef, T2 = Stringの場合、String型の引数にAnyRef型の値を与えるのと同様になってしまうので、これはx1へ値を代入する時点でコンパイルエラーになるべきであり、実際にコンパイルエラーになります。

実際にREPLで試してみましょう。

scala> val x1: AnyRef => AnyRef = (x: String) => (x:AnyRef)
<console>:7: error: type mismatch;
 found   : String => AnyRef
 required: AnyRef => AnyRef
       val x1: AnyRef => AnyRef = (x: String) => (x:AnyRef)
                                              ^

scala> val x1: String => AnyRef = (x: AnyRef) => x
x1: String => AnyRef = <function1>

このように、先ほど述べたような結果になっています。

反変という概念は初めて聞いたけど、こっちは制限をかけるというイメージで使えるので、使いどころはいつかはあるかもという印象。

【型パラメータの境界(bounds)】

型パラメータTに対して何も指定しない場合、その型パラメータTは、どんな型でも入り得ることしかわかりません。そのため、何も指定しない型パラメータTに対して呼び出せるメソッドはAnyに対するもののみになります。しかし、たとえば、順序がある要素からなるリストをソートしたい場合など、Tに対して制約を書けると便利な場合があります。そのような場合に使えるのが、型パラメータの境界(bounds)です。型パラメータの境界には2種類あります。

・上限境界(upper bounds)

1つ目は、型パラメータがどのような型を継承しているかを指定する上限境界(upper bounds)です。上限境界では、型パラメータの後に、

abstract class Show {
  def show: String
}
class ShowablePair[T1 <: Show, T2 <: Show](val t1: T1, val t2: T2) extends Show {
  override def show: String = "(" + t1.show + "," + t2.show + ")"
}

ここで、型パラメータT1、T2ともに上限境界としてShowが指定されているため、t1とt2に対してshowを呼び出すことができます。なお、上限境界を明示的に指定しなかった場合、Anyが指定されたものとみなされます。

うん、ここは記法が違うだけで、Javaと同じイメージ。
Javaはextendsキーワードを使う。

・下限境界(lower bounds)

2つ目は、型パラメータがどのような型のスーパータイプであるかを指定する下限境界(lower bounds)です。下限境界は、共変パラメータと共に用いることが多い機能です。実際に例を見ます。

まず、共変の練習問題であったような、イミュータブルなStackクラスを定義します。このStackは共変にしたいとします。

abstract class Stack[+E]{
  def push(element: E): Stack[E]
  def top: E
  def pop: Stack[E]
  def isEmpty: Boolean
}

しかし、この定義は、以下のようなコンパイルエラーになります。

error: covariant type E occurs in contravariant position in type E of value element
         def push(element: E): Stack[E]
                           ^

このコンパイルエラーは、共変な型パラメータEが反変な位置(反変な型パラメータが出現できる箇所)に出現したということを言っています。一般に、引数の位置に共変型パラメータEの値が来た場合、型安全性が壊れる可能性があるため、このようなエラーが出ます。しかし、このStackは配列と違ってイミュータブルであるため、本来ならば型安全性上の問題は起きません。この問題に対処するために型パラメータの下限境界を使うことができます。型パラメータFをpushに追加し、その下限境界として、Stack の型パラメータEを指定します。

abstract class Stack[+E]{
  def push[F >: E](element: F): Stack[F]
  def top: E
  def pop: Stack[E]
  def isEmpty: Boolean
}

このようにすることによって、コンパイラは、StackにはEの任意のスーパータイプの値が入れられる可能性があることがわかるようになります。そして、型パラメータFは共変ではないため、どこに出現しても構いません。このようにして、下限境界を利用して、型安全な Stackと共変性を両立することができます。

>:で下限境界は設定できるのか。
うん、ここは記法が違うだけで、Javaと同じイメージ。
Javaはsuperキーワードを使う。

Scalaではなぜワイルドカード指定がないのだろうと思ったけど、それ相当のことが今まで紹介したものでできるのかな?
以下の記事の中で解説あり
itpro.nikkeibp.co.jp

ワイルドカードは,型を使う側に付加するものであり,共変/反変な型の変数を宣言する必要があるたびに記述しなければならないため,煩雑であるという欠点がありますが,一方で,型の定義時点では共変/反変でないものを共変/反変であるものとして扱うことができるという利点もあります。

Scalaでも,Existential Typeという機能を使うことで,ワイルドカードと同じことを実現することができます。例えば,上記のワイルドカードを使ったJavaのコードと同じことは,次のようなScalaのコードによって実現できます。

//java.util.List[_ <: Any]と略記することもできる
var s1: java.util.List[String]  = new java.util.ArrayList
var s2: java.util.List[T] forSome { type T <: Any } = s1 
//java.util.List[_ >: String]と略記することもできる
var s3: java.util.List[Any] = new java.util.ArrayList
var s4: java.util.List[T] forSome { type T >: String} = s3

型システム、奥が深いでござる。

他の参考記事
Scalaのジェネリックスを学ぶ - じゅんいち☆かとうの技術日誌



ちなみに、Golangはクラスではなく、すべてInterfaceや構造体で継承のようなことをやるので、今回は全体的に記述なし。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その5(トレイト)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【トレイト】
・トレイトの基本
気になった部分を以下に記載

Scalaのトレイトはクラスに比べて以下のような特徴があります。

・複数のトレイトを1つのクラスやトレイトにミックスインできる
・直接インスタンス化できない
・クラスパラメータ(コンストラクタの引数)を取ることができない
以下、それぞれの特徴の紹介をしていきます。

トレイトの必要性については、後述してあるかもしれないが、以下の書籍ではこう記載してある。

クラスには 2 つの相反する役割があります。
1 つ目は「インスタンスを作るためのもの」という役割で、このためには「完結した、必要なものを全部持った、大きなクラス」である必要があります。
2 つ目は「再利用の単位」という役割で、このためには「機能ごとの、余計な物を持っていない、小さな クラス」である必要があります。
クラスが「インスタンスを作るためのもの」として使われているときには、 再利用の単位としては大きすぎるのです。
それならば、再利用の単位とい う役割に特化した、もっと小さい構造(トレイト=メソッドの束)を作ればよいのではないか?──これがトレイトの考え方です。
再利用の単位をクラスと別に作るという点では、Ruby のモジュールに似ていますね。

Rubyのモジュールとの違いは、Ruby1.9.3では、後からincludeしたモジュールはで既存のメソッドが上書きされる中で、トレイトは順番を変えても挙動が変わらないうえに、名前衝突が起こった際には明示的にエラーが発生します。

・複数のトレイトを1つのクラスやトレイトにミックスインできる

Scalaのトレイトはクラスとは違い、複数のトレイトを1つのクラスやトレイトにミックスインすることができます。

trait TraitA

trait TraitB

class ClassA

class ClassB

// コンパイルできる
class ClassC extends ClassA with TraitA with TraitB

scala> // コンパイルエラー!
     | class ClassD extends ClassA with ClassB
<console>:15: error: class ClassB needs to be a trait to be mixed in
       class ClassD extends ClassA with ClassB

上記の例ではClassAとTraitAとTraitBを継承したClassCを作ることはできますがClassAとClassBを継承したClassDは作ることができません。「class ClassB needs to be a trait to be mixed in」というエラーメッセージが出ますが、これは「ClassBをミックスインさせるためにはトレイトにする必要がある」という意味です。複数のクラスを継承させたい場合はクラスをトレイトにしましょう。

うん、ここは問題なし。
クラスを多重継承しようとした時に、コンパイルエラーが出るということで。

・直接インスタンス化できない
Scalaのトレイトはクラスと違い、直接インスタンス化できません。

scala> trait TraitA
defined trait TraitA
scala> object ObjectA {
     |   // コンパイルエラー!
     |   val a = new TraitA
     | }
<console>:15: error: trait TraitA is abstract; cannot be instantiated
         val a = new TraitA

この制限は回避する方法がいくつかあります。1つはインスタンス化できるようにトレイトを継承したクラスを作ることです。もう1つはトレイトに実装を与えてインスタンス化する方法です。

trait TraitA

class ClassA extends TraitA

object ObjectA {
  // クラスにすればインスタンス化できる
  val a = new ClassA

  // 実装を与えてもインスタンス化できる
  val a2 = new TraitA {}
}

このように実際使う上では、あまり問題にならない制限でしょう。

うん、ここは、実装を与えてもインスタンス化できるというところが、まだピンとこない。
このコード上では、実装を与えているということになるのか??
{}で実装を与えているという解釈で良いのかな。
空メソッド?

・クラスパラメータ(コンストラクタの引数)を取ることができない
Scalaのトレイトはクラスと違いパラメータ(コンストラクタの引数)を取ることができないという制限があります1。

// 正しいプログラム
class ClassA(name: String) {
  def printName() = println(name)
}
scala> // コンパイルエラー!
     | trait TraitA(name: String)
<console>:3: error: traits or objects may not have parameters
trait TraitA(name: String)

これもあまり問題になることはありません。トレイトに抽象メンバーを持たせることで値を渡すことができます。インスタンス化できない問題のときと同じようにクラスに継承させたり、インスタンス化のときに抽象メンバーを実装をすることでトレイトに値を渡すことができます。

trait TraitA {
  val name: String
  def printName(): Unit = println(name)
}

// クラスにして name を上書きする
class ClassA(val name: String) extends TraitA

object ObjectA {
  val a = new ClassA("dwango")

  // name を上書きするような実装を与えてもよい
  val a2 = new TraitA { val name = "kadokawa" }
}

以上のようにトレイトの制限は実用上ほとんど問題にならないようなものであり、その他の点ではクラスと同じように使うことができます。つまり実質的に多重継承と同じようなことができるわけです。そしてトレイトのミックスインはモジュラリティに大きな恩恵をもたらします。是非使いこなせるようになりましょう。

大まかにトレイトの基本的な使い方はこれで大丈夫そう。

・菱形継承問題
Scalaではoverride指定なしの場合メソッド定義の衝突はエラーになるということだけ理解できればOK。
ただ、Trait同士でメソッド定義がぶつかった時に、overrideとトレイト名の指定で呼び出せることだけは覚えておく。
上記のように、複数継承したものを全てを明示的に呼ぶ方法として、線形化(linearization)」という機能があるそう。

・線形化(linearization)

Scalaのトレイトの線形化機能とは、トレイトがミックスインされた順番をトレイトの継承順番と見做すことです。

次に以下の例を考えてみます。先程の例との違いはTraitBとTraitCのgreetメソッド定義にoverride修飾子が付いていることです。

trait TraitA {
  def greet(): Unit
}

trait TraitB extends TraitA {
  override def greet(): Unit = println("Good morning!")
}

trait TraitC extends TraitA {
  override def greet(): Unit = println("Good evening!")
}

class ClassA extends TraitB with TraitC

この場合はコンパイルエラーにはなりません。ではClassAのgreetメソッドを呼び出した場合、いったい何が表示されるのでしょうか?実際に実行してみましょう。

scala> (new ClassA).greet()
Good evening!

ClassAのgreetメソッドの呼び出しで、TraitCのgreetメソッドが実行されました。これはトレイトの継承順番が線形化されて、後からミックスインしたTraitCが優先されているためです。つまりトレイトのミックスインの順番を逆にするとTraitBが優先されるようになります。以下のようにミックスインの順番を変えてみます

class ClassB extends TraitC with TraitB

するとClassBのgreetメソッドの呼び出して、今度はTraitBのgreetメソッドが実行されます。

scala> (new ClassB).greet()
Good morning!

superを使うことで線形化された親トレイトを使うこともできます

trait TraitA {
  def greet(): Unit = println("Hello!")
}

trait TraitB extends TraitA {
  override def greet(): Unit = {
    super.greet()
    println("My name is Terebi-chan.")
  }
}

trait TraitC extends TraitA {
  override def greet(): Unit = {
    super.greet()
    println("I like niconico.")
  }
}

class ClassA extends TraitB with TraitC
class ClassB extends TraitC with TraitB

このgreetメソッドの結果もまた継承された順番で変わります。

scala> (new ClassA).greet()
Hello!
My name is Terebi-chan.
I like niconico.

scala> (new ClassB).greet()
Hello!
I like niconico.
My name is Terebi-chan.

線形化の機能によりミックスインされたすべてのトレイトの処理を簡単に呼び出せるようになりました。このような線形化によるトレイトの積み重ねの処理をScalaの用語では積み重ね可能なトレイト(Stackable Trait)と呼ぶことがあります。

この線形化がScalaの菱形継承問題に対する対処法になるわけです。

overrideしたメソッドの中でsuperを使用して、新たに定義したい内容を記述する。
このような設計にならないようにはするが、やり方は覚えておこう。

・abstract override

通常のメソッドのオーバーライドでsuperを使ってスーパークラスのメソッドを呼びだす場合、当然のことながら継承元のスーパークラスにそのメソッドの実装がなければならないわけですが、 Scalaには継承元のスーパークラスにそのメソッドの実装がない場合でもメソッドのオーバーライドが可能なabstract overrideという機能があります。

abstract overrideではないoverrideとabstract overrideを比較してみましょう。

trait TraitA {
  def greet(): Unit
}
scala> // コンパイルエラー!
     | trait TraitB extends TraitA {
     |   override def greet(): Unit = {
     |     super.greet()
     |     println("Good morning!")
     |   }
     | }
<console>:16: error: method greet in trait TraitA is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override'
           super.greet()
// コンパイルできる
trait TraitC extends TraitA {
  abstract override def greet(): Unit = {
    super.greet()
    println("Good evening!")
  }
}

abstract修飾子を付けていないTraitBはコンパイルエラーになってしまいました。エラーメッセージの意味は、TraitAのgreetメソッドには実装がないのでabstract overrideを付けない場合オーバーライドが許されないということです。

オーバーライドをabstract overrideにすることでスーパークラスのメソッドの実装がない場合でもオーバーライドすることができます。この特性は抽象クラスに対しても積み重ねの処理が書けるということを意味します。

しかしabstract overrideでも1つ制約があり、ミックスインされてクラスが作られるときにはスーパークラスのメソッドが実装されてなければなりません。

trait TraitA {
  def greet(): Unit
}

trait TraitB extends TraitA {
  def greet(): Unit =
    println("Hello!")
}

trait TraitC extends TraitA {
  abstract override def greet(): Unit = {
    super.greet()
    println("I like niconico.")
  }
}
scala> // コンパイルエラー!
     | class ClassA extends TraitC
<console>:15: error: class ClassA needs to be a mixin, since method greet in trait TraitC of type ()Unit is marked `abstract' and `override', but no concrete implementation could be found in a base class
       class ClassA extends TraitC
// コンパイルできる
class ClassB extends TraitB with TraitC

abstract overrideの制約のミックスインされてクラスが作られるときにはスーパークラスのメソッドが実装されてならないということだけ、覚えておこう。(当たり前といえば、当たり前だが)

・自分型

Scalaにはクラスやトレイトの中で自分自身の型にアノテーションを記述することができる機能があります。これを自分型アノテーション(self type annotations)や単に自分型(self types)などと呼びます。

trait Greeter {
  def greet(): Unit
}

trait Robot {
  self: Greeter =>

  def start(): Unit = greet()
  override final def toString = "Robot"
}

このロボット(Robot)は起動(start)するときに挨拶(greet)するようです。 Robotは直接Greeterを継承していないのにもかかわらずgreetメソッドを使えていることに注意してください。

このロボットのオブジェクトを実際に作るためにはgreetメソッドを実装したトレイトが必要になります。 REPLを使って動作を確認してみましょう。

scala> trait HelloGreeter extends Greeter {
     |   def greet(): Unit = println("Hello!")
     | }
defined trait HelloGreeter

scala> val r = new Robot with HelloGreeter
r: Robot with HelloGreeter = Robot

scala> r.start()
Hello!

自分型を使う場合は、抽象トレイトを指定し、後から実装を追加するという形になります。このように後から(もしくは外から)利用するモジュールの実装を与えることを依存性の注入(Dependency Injection)と呼ぶことがあります。自分型を使われている場合、この依存性の注入のパターンが使われていると考えてよいでしょう。

ではこの自分型によるトレイトの指定は以下のように直接継承する場合と比べてどのような違いがあるのでしょうか。

trait Greeter {
  def greet(): Unit
}

trait Robot2 extends Greeter {
  def start(): Unit = greet()
  override final def toString = "Robot2"
}

オブジェクトを生成するという点では変わりません。 Robot2も先程と同じように作成することができます。ただし、このトレイトを利用する側や、継承したトレイトやクラスにはGreeterトレイトの見え方に違いができます。

scala> val r: Robot = new Robot with HelloGreeter
r: Robot = Robot
scala> r.greet()
<console>:17: error: value greet is not a member of Robot
       r.greet()
         ^
scala> val r: Robot2 = new Robot2 with HelloGreeter
r: Robot2 = Robot2

scala> r.greet()
Hello!

継承で作られたRobot2オブジェクトではGreeterトレイトのgreetメソッドを呼び出せてしまいますが、自分型で作られたRobotオブジェクトではgreetメソッドを呼びだすことができません。

Robotが利用を宣言するためにあるGreeterのメソッドが外から呼び出せてしまうことはあまり良いことではありません。この点で自分型を使うメリットがあると言えるでしょう。逆に単に依存性を注入できればよいという場合には、この動作は煩わしく感じられるかもしれません。

テスタビリティ的には、可能な限りDIした方が良いと思うので、自分型は積極的に使っていった方が良いと思う。

もう1つ自分型の特徴としては型の循環参照を許す点です。

自分型を使う場合は以下のようなトレイトの相互参照を許しますが、

trait Robot {
  self: Greeter =>

  def name: String

  def start(): Unit = greet()
}

// コンパイルできる
trait Greeter {
  self: Robot =>

  def greet(): Unit = println(s"My name is $name")
}

これを先ほどのように継承に置き換えることではできません。

scala> trait Robot extends Greeter {
     |   def name: String
     | 
     |   def start(): Unit = greet()
     | }
<console>:16: error: illegal inheritance;
 self-type Robot does not conform to Greeter's selftype Greeter with Robot
       trait Robot extends Greeter {
                           ^

scala> // コンパイルエラー
     | trait Greeter extends Robot {
     |   def greet(): Unit = println(s"My name is $name")
     | }
<console>:15: error: illegal inheritance;
 self-type Greeter does not conform to Robot's selftype Robot with Greeter
       trait Greeter extends Robot {

しかし、このように循環するような型構成を有効に使うのは難しいかもしれません。

依存性の注入を使う場合、継承を使うか、自分型を使うかというのは若干悩ましい問題かもしれません。機能的には継承があればよいと言えますが、上記のような可視性の問題がありますし、自分型を使うことで依存性の注入を利用しているとわかりやすくなる効果もあります。利用する場合はチームで相談するとよいかもしれません。

型の循環参照はやめておいたほうが良いという印象。
ここはこういうことができるよという紹介かな。

・落とし穴:トレイトの初期化順序

Scalaのトレイトのvalの初期化順序はトレイトを使う上で大きな落とし穴になります。以下のような例を考えてみましょう。トレイトAで変数fooを宣言し、トレイトBがfooを使って変数barを作成し、クラスCでfooに値を代入してからbarを使っています。

trait A {
  val foo: String
}

trait B extends A {
  val bar = foo + "World"
}

class C extends B {
  val foo = "Hello"

  def printBar(): Unit = println(bar)
}

REPLでクラスCのprintBarメソッドを呼び出してみましょう。

scala> (new C).printBar()
nullWorld

nullWorldと表示されてしまいました。クラスCでfooに代入した値が反映されていないようです。どうしてこのようなことが起きるかというと、Scalaのクラスおよびトレイトはスーパークラスから順番に初期化されるからです。この例で言えば、クラスCはトレイトBを継承し、トレイトBはトレイトAを継承しています。つまり初期化はトレイトAが一番先におこなわれ、変数fooが宣言され、中身は何も代入されていないので、nullになります。次にトレイトBで変数barが宣言されnullであるfooと"World"という文字列から"nullWorld"という文字列が作られ、変数barに代入されます。先ほど表示された文字列はこれになります。

このような簡単な例なら気づきやすいのですが、似たような形の大規模な例もあります。先ほど自分型で紹介した「依存性の注入」は、上位のトレイトで宣言したものを、中間のトレイトで使い、最終的にインスタンス化するときにミックスインするという手法です。ここでもうっかりすると同じような罠を踏んでしまいます。 Scala上級者でもやってしまうのがvalの初期化順の罠なのです。

これは絶対どこかでやってしまいそう。
Scalaスーパークラスから順番に初期化されることは覚えておこう。

・トレイトのvalの初期化順序の回避方法

では、この罠はどうやれば回避できるのでしょうか。上記の例で言えば、使う前にちゃんとfooが初期化されるように、barの初期化を遅延させることです。処理を遅延させるにはlazy valかdefを使います。

具体的なコードを見てみましょう。

trait A {
  val foo: String
}

trait B extends A {
  lazy val bar = foo + "World" // もしくは def bar でもよい
}

class C extends B {
  val foo = "Hello"

  def printBar(): Unit = println(bar)
}

先ほどのnullWorldが表示されてしまった例と違い、barの初期化にlazy valが使われるようになりました。これによりbarの初期化が実際に使われるまで遅延されることになります。その間にクラスCでfooが初期化されることにより、初期化前のfooが使われることがなくなるわけです。

今度はクラスCのprintBarメソッドを呼び出してもちゃんとHelloWorldと表示されます。

scala> (new C).printBar()
HelloWorld

lazy valはvalに比べて若干処理が重く、複雑な呼び出しでデッドロックが発生する場合があります。 valのかわりにdefを使うと毎回値を計算してしまうという問題があります。しかし、両方とも大きな問題にならない場合が多いので、特にvalの値を使ってvalの値を作り出すような場合はlazy valかdefを使うことを検討しましょう。

トレイトのvalの初期化順序を回避するもう1つの方法としては事前定義(Early Definitions)を使う方法もあります。事前定義というのはフィールドの初期化をスーパークラスより先におこなう方法です。

trait A {
  val foo: String
}

trait B extends A {
  val bar = foo + "World" // valのままでよい
}

class C extends {
  val foo = "Hello" // スーパークラスの初期化の前に呼び出される
} with B {
  def printBar(): Unit = println(bar)
}

上記のCのprintBarを呼び出しても正しくHelloWorldと表示されます。

この事前定義は利用側からの回避方法ですが、この例の場合はトレイトBのほうに問題がある(普通に使うと初期化の問題が発生してしまう)ので、トレイトBのほうを修正したほうがいいかもしれません。

トレイトの初期化問題は継承されるトレイト側で解決したほうが良いことが多いので、この事前定義の機能は実際のコードではあまり見ることはないかもしれません。

トレイトの初期化問題、lazy valあるいはdefで解決するとのこと。
事前定義などと裏技もあるのか。
デッドロック問題も頭に留めておこう。


ちなみに、Golangはクラスではなく、すべてInterfaceや構造体で継承のようなことをやるので、今回は全体的に記述なし。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その4(オブジェクト)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【オブジェクト】
・オブジェクトに関して

Scalaでは、全ての値がオブジェクトです。また、全てのメソッドは何らかのオブジェクトに所属しています。そのため、Javaのようにクラスに属するstaticフィールドやstaticメソッドといったものを作成することができません。その代わりというと若干語弊があるのですが、objectキーワードによって、同じ名前のシングルトンオブジェクトをグローバルな名前空間に1つ定義することができます。objectキーワードによって定義したオブジェクトもオブジェクトであるため、メソッドやフィールドを定義することができます。

全ての値がオブジェクトであるのは、Rubyなどと一緒なので、よしとするが、staticフィールドやstaticメソッドが作成できないのか。
staticの代わり?にobjectキーワードで作成されたシングルトンオブジェクト内に定義するイメージか。
・object構文の主な用途

object構文の主な用途としては、

・ユーティリティメソッドやグローバルな状態の置き場所(Javaで言うstaticメソッドやフィールド)
・オブジェクトのファクトリメソッド
・Singletonパターン
3つが挙げられます。とはいえ、Singletonパターンを実現するために使われることはほとんどなく、もっぱら最初の 2つの用途で使われます。

objectの基本構文はクラスとほとんど同じで、

object オブジェクト名 extends クラス名 with トレイト名1 with トレイト名2 ... {
  本体
}

のようになります。Scalaでは標準でPredefというobjectが定義・インポートされており、これは最初の使い方に当てはまります。たとえば、println()などとなにげなく使っていたメソッドも実はPredef objectのメソッドなのです。

Object構文の主な用途が3つというのはなんとなくイメージできた。
Predefオブジェクトの話は、コップ本に書いていたから理解済み。

一方、2番めの使い方について考えてみます。点を表すPointクラスのファクトリを objectで作ろうとすると、次のようになります。applyという名前のメソッドはScala処理系によって特別に扱われ、Point(x)のような記述があった場合で、Point objectにapplyという名前のメソッドが定義されていた場合、Point.apply(x)と解釈されます。これを利用してPoint objectの applyメソッドでオブジェクトを生成するようにすることで、Point(3, 5)のような記述でオブジェクトを生成できるようになります。

scala> class Point(val x:Int, val y:Int)
defined class Point

scala> object Point {
     |   def apply(x: Int, y: Int): Point = new Point(x, y)
     | }
defined object Point
warning: previously defined class Point is not a companion to object Point.
Companions must be defined together; you may wish to use :paste mode for this.

これは、new Point()で直接Pointオブジェクトを生成するのに比べて、
・クラス(Point)の実装詳細を内部に隠しておける(インタフェースのみを外部に公開する)
・Pointではなく、そのサブクラスのインスタンスを返すことができる
といったメリットがあります。

サブクラスのインスタンスを返すことができるというのが、イマイチまだピンと来ていないが、他は大まかに理解。

なお、上記の記述はケースクラスを用いてもっと簡単に

scala> case class Point(x: Int, y: Int)
defined class Point

と書けます。ケースクラスは後述するパターンマッチのところでも出てきますが、ここではその使い方については触れません。簡単に言うとケースクラスは、それをつけたクラスのプライマリコンストラクタ全てのフィールドを公開し、equals()・hashCode()・toString()などのオブジェクトの基本的なメソッドを持ったクラスを生成し、また、そのクラスのインスタンスを生成するためのファクトリメソッドを生成するものです。たとえば、 case class Point(x: Int, y: Int)で定義した Point クラスは equals() メソッドを明示的に定義してはいませんが、

Point(1, 2).equals(Point(1, 2))

を評価した値はtrueになります。

ケースクラスにした場合は、Javaでいう自分でクラスを作成した時に作るequals、hashCode、toStringを作ってくれるのか。
これは便利。
フィールドが公開となっているのは、case classでmatch文を使う時にgetter/setterを書いていると冗長になるからかな。

・コンパニオンオブジェクトに関して

クラス名と同じ名前のシングルトンオブジェクトはコンパニオンオブジェクトと呼ばれます。コンパニオンオブジェクトは対応するクラスに対して特権的なアクセス権を持っています。たとえば、 weightをprivateにした場合、

class Person(name: String, age: Int, private val weight: Int)

object Hoge {
  val taro = new Person("Taro", 20, 70)
  println(taro.weight)
}

はNGですが、

class Person(name: String, age: Int, private val weight: Int)

object Person {
  val taro = new Person("Taro", 20, 70)
  println(taro.weight)
}

はOKです。なお、コンパニオンオブジェクトでも、private[this](そのオブジェクト内からのみアクセス可能)なクラスのメンバーに対してはアクセスできません。単にprivateとした場合、コンパニオンオブジェクトからアクセスできるようになります。

class Point(val x: Int, val y: Int) {
  def +(p: Point): Point = {
    new Point(x + p.x, y + p.y)
  }
  override def toString(): String = "(" + x + ", " + y + ")"
}

上記のような感じでメソッド定義の中から直接コンストラクタ引数を参照できるそうです。

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その3(クラス)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【クラス】
・クラス定義
気になった部分を以下に記載

まず、最初の点ですが、Scalaでは1クラスに付き、基本的には1つのコンストラクタしか使いません。文法上は複数のコンストラクタを定義できるようになっていますが、実際に使うことはまずないので覚える必要はないでしょう。一応、Scalaでは複数のコンストラクタが定義できるので、この最初の1つのコンストラクタをプライマリコンストラクタとして特別に扱っています。

確かにコップ本では複数のコンストラクタを定義する例は書いてたけど、実際には使うことはまずないのか。
なぜかは置いておき、一旦そういうものだということにしておこう。

プライマリコンストラクタの引数にval/varをつけるとそのフィールドは公開され、外部からアクセスできるようになります。なお、コンストラクタ引数のスコープはクラス定義全体におよびます。

class Point(val x: Int, val y: Int) {
  def +(p: Point): Point = {
    new Point(x + p.x, y + p.y)
  }
  override def toString(): String = "(" + x + ", " + y + ")"
}

上記のような感じでメソッド定義の中から直接コンストラクタ引数を参照できるそうです。

・メソッド定義

先ほど既にメソッド定義の例として+メソッドの定義が出てきましたが、一般的には、

(private[this]/protected[package名]) def メソッド名(引数名1: 引数1の型, 引数名2: 引数2の型, ...): 返り値の型 = {
  本体のコード
}

という形をとります。ちなみに、メソッド本体が1つの式だけからなる場合は、

(private[this]/protected[package名]) def メソッド名(引数名1: 引数1の型, 引数名2: 引数2の型, ...): 返り値の型 = 本体のコード

と書けます(実際には、こちらの方が基本形で、= {}を使ったスタイルの方が{}内に複数の式を並べて書けることを利用した派生形になりますが、前者のパターンを使うことが多いでしょう)。
返り値の型は省略しても特別な場合以外型推論してくれますが、読みやすさのために、返り値の型は明記する習慣を付けるようにしましょう。
また、privateを付けるとそのクラス内だけから、protectedを付けるとそのクラスの派生クラスからのみアクセスできるフィールドになります。さらに、 private[this] をつけると、同じオブジェクトからのみ、 protected[パッケージ名] をつけると、追加で同じパッケージに所属しているもの全てからアクセスできるようになります。privateもprotectedも付けない場合、そのフィールドはpublicとみなされます。

private[this]という定義方法があるのか。
返り値の明示的に書くということは、いくら型推論してくれるからとはいえ、確かに心がけたいところ。

複数の引数リストを持つメソッドには、Scalaの糖衣構文と組み合わせて流暢なAPIを作ったり、後述するimplicit parameterのために必要になったり、型推論を補助するために使われたりといった用途があります

implicit parameterは名前だけ聞いたことあるが、まだわからん。
デフォルト引数のようなものかな。

何はともあれ、複数の引数リストを持つ加算メソッドを定義してみましょう。

scala> class Adder {
     |   def add(x: Int)(y: Int): Int = x + y
     | }
defined class Adder

scala> val adder = new Adder()
adder: Adder = Adder@7263d8eb

scala> adder.add(2)(3)
res1: Int = 5

scala> adder.add(2) _
res2: Int => Int = <function1>

複数の引数リストを持つメソッドはobj.m(x, y)の形式でなくobj.m(x)(y)の形式で呼びだすことになります。また、一番下の例のように最初の引数だけを適用して新しい関数を作る(部分適用)こともできます。

obj.m(x, y)の形式でなくobj.m(x)(y)の形式で呼びだすのかー。
JavaScriptで学ぶ関数型言語の時に、undersocre.jsでそのような指定の仕方があったような気がする、ちょい違和感ある。
引数を一つだけ渡して、部分適用するのは自分の頭の中では違和感なし。

・フィールド定義
特に言及するポイントなし。

・継承

クラスのもう1つの機能は、継承です。継承には2つの目的があります。 1つは継承によりスーパークラスの実装をサブクラスでも使うことで実装を再利用することです。もう1つは複数のサブクラスが共通のスーパークラスのインタフェースを継承することで処理を共通化することです1。

実装の継承には複数の継承によりメソッドやフィールドの名前が衝突する場合の振舞いなどに問題があることが知られており、Javaでは実装継承が1つだけに限定されています。 Java 8ではインタフェースにデフォルトの実装を持たせられるようになりましたが、変数は持たせられないなどの制約があります。 Scalaではトレイトという仕組みで複数の実装の継承を実現していますが、トレイトについては別の節で説明します。

1. このように継承などにより型に親子関係を作り、複数の型に共通のインタフェースを持たせることをサブタイピング・ポリモーフィズムと呼びます。Scalaでは他にも構造的部分型というサブタイピング・ポリモーフィズムの機能がありますが、実際に使われることが少ないため、このテキストでは説明を省略しています

多重継承問題を取り上げていて、良い資料だなと感じました。

基本的に、継承のはたらきはJavaのクラスと同じですが、既存のメソッドをオーバーライドするときはoverrideキーワードを使わなければならない点が異なります。たとえば、

scala> class APrinter() {
     |   def print(): Unit = {
     |     println("A")
     |   }
     | }
defined class APrinter

scala> class BPrinter() extends APrinter {
     |   override def print(): Unit = {
     |     println("B")
     |   }
     | }
defined class BPrinter

scala> new APrinter().print
A

scala> new BPrinter().print
B

のようにすることができます。ここでoverrideキーワードをはずすと、

scala> class BPrinter() extends APrinter {
     |   def print(): Unit = {
     |     println("B")
     |   }
     | }
<console>:14: error: overriding method print in class APrinter of type ()Unit;
 method print needs `override' modifier
         def print(): Unit = {
             ^

のようにメッセージを出力して、コンパイルエラーになります。Javaではしばしば、気付かずに既存のメソッドをオーバーライドするつもりで新しいメソッドを定義してしまうというミスがありましたが、Scalaではoverrideキーワードを使って言語レベルでこの問題に対処しているのです。

継承時にメソッドをoverrideする時はJavaでやるようなoverrideアノテーションではなく、言語機能としてoverrideキーワードを使用するそうです。
overrideキーワードを使わないとコンパイルエラーになるなんて、良い仕様だな。

ちなみに、Goはクラスではなく、すべてInterfaceや構造体で継承のようなことをやるので、今回は全体的に記述なし。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その2(制御構文)

Scala Golang

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。

構文(Syntax)
→そのプログラミング言語内でプログラムが構造を持つためのルールです。

「式(Expression)」
→プログラムを構成する部分のうち、評価することで値になるものです。
たとえば1や1 + 2、"hoge"などです。これらは評価することにより、数値や文字列の値になります。

「文(Statement)」
→式とは対照的にプログラムを構成する部分のうち、評価しても値にならないものです。たとえば変数の定義であるval i = 1は評価しても変数iが定義され、iの値が1になりますが、この定義全体としては値を持ちません。よって、これは文です。


・{}式
一般形

{ exp1; exp2; ... expN; }

{}式はexp1からexpNを順番に評価し、expNを評価した値

・制御構造
if式はJavaとは違い、値を返す。

if式は3項演算子のような形で使えるイメージ

object Expression {
  def main(args: Array[String]): Unit = {
    var age: Int = 5
    var isSchoolStandard: Boolean = false
    println(if (isSchoolStandard && 1 <= age && age < 6) "幼児ではありません" else "幼児です")
  }
}

while式
Unit型(Javaでいうvoid相当を返す)ので、Javaのwhile文とあまり変わらないイメージ。

for式
本当の力を理解するには、flatMap, map, withFilter, foreachといったメソッドについて知る必要があるとのこと。
1 to 10は1から10まで(10を含む)の範囲で、1 until 10は1から10まで(10を含まない)の範囲でループを回せる。
また、今自分がわかっているのは、コップ本に書かれていたyieldと組み合わせるとmap処理と同様の処理を実行できること。
多重ループみたいなことをしたい時にはmapメソッドを使うより便利とのこと(まだ聞いただけで書いてない)
→特にyieldキーワードを使ったfor式を特別に for-comprehensionと呼ぶことがあるそうです。

以下、サンプル。

scala> for(e <- List("A", "B", "C", "D", "E")) yield {
     | "Pre" + e
     | }
res4: List[String] = List(PreA, PreB, PreC, PreD, PreE)

練習問題解答

scala> for (x <- 1 to 1000; y <- 1 to 1000; z <- 1 to 1000; if ((x * x) == (y * y + z * z))) println(x, y, z)
(出力結果は省略)

match式
Javaのswitch文のようなイメージ。
JavaJavaScriptのswitch文にあるようなフォールスルーはない。

scala> "abc" match {
     |   case "abc" => println("first")   // ここで処理が終了
     |   case "def" => println("second") // こっちは表示されない
     | }
first

ちなみに、Golangもフォールスルーはありません。
Golangでフォールスルー動作を使用したい場合は、fallthroughステートメントを指定すると使用できる

k := 6
switch k {
case 4: fmt.Println("was <= 4"); fallthrough;
case 5: fmt.Println("was <= 5"); fallthrough;
case 6: fmt.Println("was <= 6"); fallthrough;
case 7: fmt.Println("was <= 7"); fallthrough;
case 8: fmt.Println("was <= 8"); fallthrough;
default: fmt.Println("default case") 
}

Javaでのswitch-case文は、int、enum、String(Java7から)にマッチすることができます。
Scalaのmatch式では、JavaのSwitchより強力で、コレクションの要素の一部にマッチさせる使い方があります。

scala> val lst = List("A", "B", "C", "D", "E")
lst: List[String] = List(A, B, C, D, E)

scala> lst match {
     |   case List("A", b, c, d, e) =>
     |     println("b = " + b)
     |     println("c = " + c)
     |     println("d = " + d)
     |     println("e = " + e)
     |   case _ =>
     |     println("nothing")
     | }
b = B
c = C
d = D
e = E

また、Javaのswitch-case文は、定数式である必要がありますが、Scalaのmatch式もScalaの場合はそうでなくても処理できるようです。

scala>   def count(n: Int, arg: Int) = n match {
     |     case 1 => "one"
     |     case 2 => "two"
     |     case arg => "m"
     |   }
count: (n: Int, arg: Int)String

scala> count(3,3)
res8: String = m

ちなみに、GolangのSwitch文では文字列、数値やインタフェース変数の動的な型まで対応している。
また、Scalaと同様に、定数や整数である必要はありません。
実践Go言語(part5) - golang.jp

パターンマッチではガード式を用いて、パターンにマッチして、かつ、ガード式(Boolean型でなければならない)にもマッチしなければ右辺の式が評価されないような使い方もできます。

scala> val lst = List("A", "B", "C", "D", "E")
lst: List[String] = List(A, B, C, D, E)

scala> lst match {
     |   case List("A", b, c, d, e) if b != "B" =>
     |     println("b = " + b)
     |     println("c = " + c)
     |     println("d = " + d)
     |     println("e = " + e)
     |   case _ =>
     |     println("nothing")
     | }
nothing

また、パターンマッチのパターンはネストが可能です。先ほどのプログラムを少し改変して、先頭がList("A")であるようなListにマッチさせてみましょう。

scala> val lst = List(List("A"), List("B", "C", "D", "E"))
lst: List[List[String]] = List(List(A), List(B, C, D, E))

scala> lst match {
     |   case List(a@List("A"), x) =>
     |   println(a)
     |   println(x)
     |   case _ => println("nothing")
     | }
List(A)
List(B, C, D, E)

パターンの前に@がついているのはasパターンと呼ばれるもので、@の後に続くパターンにマッチする式を@の前の変数(ここではa)に束縛します。asパターンはパターンが複雑なときにパターンの一部だけを切り取りたい時に便利です。
→asパターン覚えよう。

型によるパターンマッチもあり、かなり強力な模様。

scala> import java.util.Locale
import java.util.Locale

scala> val obj: AnyRef = "String Literal"
obj: AnyRef = String Literal

scala> obj match {
     |   case v:java.lang.Integer =>
     |     println("Integer!")
     |   case v:String =>
     |     println(v.toUpperCase(Locale.ENGLISH))
     | }
STRING LITERAL

練習問題2つ目はこんな感じ。

for(i <- 1 to 1000) {
  val s = new scala.util.Random(new java.security.SecureRandom()).alphanumeric.take(5).toList match {
    case List(a,b,c,d,_) => List(a,b,c,d,a).mkString
  }
  println(s)
}

Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その1(〜Scalaの基本)

Scala

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのでは、多少趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
・0xffはInt型→0xは16進数表記。16進数でffは255。 Int型は符号あり 32-ビット 整数

scala> 0xff
res1: Int = 255
[http://www16.plala.or.jp/fm677/16hex.htm:title]

・1e308はDouble型→64bit浮動小数点数

scala> 1e308
res2: Double = 1.0E308

・9223372036854775807LはLong型→特に言及することはなし

scala> 9223372036854775807L
res3: Long = 9223372036854775807

・9223372036854775808L→Longの最大値を超える模様。

scala> 9223372036854775808L
<console>:1: error: integer number too large
       9223372036854775808L

・"\u3042"はString型→Unicodeのコードポイントを与えてあげると、自動的に文字列変換される模様。Scalaでも、コードポイントが 16ビットに収まらない文字は以下みたいな感じでサロゲートペア で指定する必要がある。

scala>  "\uD840\uDC0B"
res7: String =𠀋はてなブログでは表示されない文字

・%は符号がある際に、言語によって実装が違う。Scalaでの実装は以下のような感じ。

scala> 4 % -3
res11: Int = 1

scala> 4 % 3
res12: Int = 1

scala> -4 % 3
res13: Int = -1

scala> -4 % -3
res14: Int = -1

4 % -3が違和感あるが、Golangでも同じ動き。
The Go Playground

Double型からInt型へのキャスト

scala> 1.1 + 2.2
res15: Double = 3.3000000000000003(誤差)

scala> res15.asInstanceOf[Int]
res16: Int = 3

scala> res15.toInt
res17: Int = 3

Intの範囲超えの場合、オーバーフローしたのに例外が発生しないだと。。

scala> 2147483647 + 1
res18: Int = -2147483648

ちなみにGolangでは、オーバフローした場合は例外を吐きます。

prog.go:8: constant 2147483648 overflows int

The Go Playground

Longも同様の模様。言語実装の意図としては、どういう意図があるのだろうか。

scala> 9223372036854775807L + 1
res20: Long = -9223372036854775808

もちろん、Golangは例外発生。

prog.go:8: constant 9223372036854775808 overflows int64

The Go Playground

1e308 + 1はあまりに小さい値は無視される模様。

scala> 1e308 + 1
res21: Double = 1.0E308
scala> 1 + 0.0000000000000000001
res22: Double = 1.0

引き算系は直感と変わらず。

scala> 1 - 1
res23: Int = 0

scala> 1 - 0.1
res24: Double = 0.9

scala> 0.1 - 1
res25: Double = -0.9

scala> 0.1 - 0.1
res26: Double = 0.0

scala> 0.0000000000000000001 - 1
res27: Double = -1.0

Golangでも0.1-0.1は0として、表示はされているが、float64型なので、64-ビット浮動小数値でScalaのDouble型と一緒だということがわかる。
The Go Playground


掛け算、割り算系は特に違和感なし。

scala> 0.1 * 0.1
res28: Double = 0.010000000000000002

scala> 20 * 0.1
res29: Double = 2.0

scala> 1 / 3
res30: Int = 0

scala> 1.0 / 3
res31: Double = 0.3333333333333333

scala> 1 / 3.0
res32: Double = 0.3333333333333333

scala> 3.0 / 3.0
res33: Double = 1.0

scala> 1.0 / 10 * 1 / 10
res34: Double = 0.01

scala> 1 / 10 * 1 / 10.0
res35: Double = 0.0

The Go Playground

練習問題
Q. ¥3,950,000を年利率2.3%の単利で8か月間借り入れた場合の利息はいくらか(円未満切り捨て)

scala> 3950000
res36: Int = 3950000

scala> 2.3
res37: Double = 2.3

scala> res36 * res37 * 8 /100/12
res38: Double = 60566.666666666664

scala> res38.toInt
res39: Int = 60566

Q. 定価¥1,980,000の商品を値引きして販売したところ、原価1.6%にあたる¥26,400の損失となった。割引額は定価の何パーセントであったか

scala> 1980000.0
res40: Double = 1980000.0

scala> 26400.0 * 1000 /16
res41: Double = 1650000.0
//割引額を求める式 定価-原価+損失額

scala> (res40-res41+26400)/res40
res42: Double = 0.18


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門