無限大な夢のあと

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

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

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料で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を本格的に勉強してみることにした。
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を本格的に勉強してみることにした。
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を本格的に勉強してみることにした。
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を本格的に勉強してみることにした。
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プログラミング入門

JJUG EclipseCollectionsで学ぶコード品質向上の勘所に参加してきた。 #ccc_gh2 #jjug_ccc

本日、参加したJJUGのセッションで、品質と言う面で色々考えさせられたセッション。

GSの伊藤さんが発表された資料はこちらになります。
http://www.goldmansachs.com/gs-collections/documents/2016-05-21_JJUG_CCC.pdf

今回は、品質向上という観点での話がメイン。
EclipseCollectionsの使い方ではない。

EclipseCollections
Eclipse Collections - Features you want with the collections you need. (日本語ページ)

特に刺さった部分を、走り書きですが、幾つかまとめます。

コードの品質とは?

ソフトウェアの品質とコードの品質はイコールではない。→ 実装時点で直接ソフトウェアの価値に影響を与えるコード品質
・インタフェースの品質(UI、API
・実装の要求適合性・バグの存在
・実装のパフォーマンス(メモリ使用量、実行速度)

実装時点では必ずしもソフトフェアの価値に影響を与えないが、時間軸に沿ったソフトウェアの価値の増加率(生産性)に影響を与えるコード品質

・保守性(可読性、一貫性、簡潔性、等々)
・拡張性、再利用性
・テストカバレッジ

技術的負債

・コードの品質と引き換えにソフトウェアをリリースするスピードを重視した実装
・時間を経るにつれ、利子を払うかのごとく開発生産性を圧迫する
・技術的負債は必ずしも悪いわけではない(会計のB/Sで例を示した)

コードの品質のバランス

・ソフトウェアの特性によってどこの品質に重きを置くかは変わってくる
・コードの品質を議論する際には文化的な側面も多く、開発チームで「良いコード」「悪いコード」の認識を共有することも非常に重要
  ・組織のエンジニアリング文化
  ・プロジェクトチームのエンジニアリング文化
  ・個人がエンジニアとして育ってきた環境)

技術的負債とのつきあい方

技術的負債を完全になくすのは、ある程度の規模のソフトウェア開発に
おいてはほぼ不可能といえる
良いソフトウェアは技術的負債を「コントロール」する
・費用対効果を見積もる
・優先順位をつけ、技術的負債を戦略的に採用する
・返済計画を立てる

• 例:UnifiedSetとUnifiedSetWithHashingStrategyの重複
– UnifiedSetWithHashingStrategyは初期実装時点でUnifiedSetとのコード重複
が多く存在
– 戦略的に反DRYを許容
・ そもそもの実装の複雑性から、重複の排除には時間がかかる
・ 重複は2クラスのみにしか存在せず、保守性への影響は限定的
・テストカバレッジを保障することで将来のリファクタリングは安全に実施できる
・すぐに重複を排除するよりも他の機能にリソースを投資したほうがライブラリにとっての価値が高い
– Issue Trackerに登録し、フォローアップ
– バージョン7.0のリリースでは重複を排除

文化の違いなどは、受け入れた上でコードレビューに臨むべきだということ。
実践編の技術的負債の付き合い方などの話は、今まで言語化できなかった部分が言語化されていて良い資料だと思いました。
すごく良いセッションだったので、是非資料を見てみてください。

スッキリわかるJava入門 第2版 (スッキリシリーズ)

スッキリわかるJava入門 第2版 (スッキリシリーズ)

[改訂新版]Javaポケットリファレンス

[改訂新版]Javaポケットリファレンス

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

改訂2版 パーフェクトJava

改訂2版 パーフェクトJava

The Go Programming Language 輪読会 #03 に参加してきました。

こちらのイベントに参加させて頂きました。
#03eure.connpass.com

輪読対象は下記の書籍です。

The Go Programming Language (Addison-Wesley Professional Computing Series)

The Go Programming Language (Addison-Wesley Professional Computing Series)


今回の輪読した章は「3. Basic Data Type(基本型)」

今回学べたことを走り書き。

整数型
・%演算子は左辺/右辺どちらとも整数でなければいけない
・-5/3 、-5/-3 の結果は、ともに-2である。(言語によっては動きが違うそう)
・^&演算子とは? →左辺かつ右辺の数を含まないものをあらわす。
・>> << はビットシフトだが、右は符号なしでなければいけない。
・符号なし整数で十分に見えるケースでも、符号付き整数を使うべき。

浮動小数点型
・Nanとの比較は常にfalseを返す。

複素数
・組み込み型で、複素数型が実装されているのはGolangPythonのみ。他の言語では標準ライブラリなどで使えるようになっているものはある。
→この実装の意図は、Cを意識しているのかな?という話になった。
複素数型 - Wikipedia


bool
・&&は、||より優先順いが高いので、以下のようなコードでは丸括弧は不要である。
 if 'a' <= c && c <= "z" || 'A' <= C && C <= 'Z'

その他
・go tool vet -- shadowは使える。
・以下のブログは勉強になる。
To Go Beartrap Lying in the Shadows - Qureet.com Qureet.com


すごくアットホームな勉強会だったので、今後も参加してきたいです。
ちなみに、私は5章 Functionの輪読担当となりました。
初めての洋書の翻訳頑張ってみよう。

Go言語によるWebアプリケーション開発

Go言語によるWebアプリケーション開発

スターティングGo言語

スターティングGo言語

改訂2版 基礎からわかる Go言語

改訂2版 基礎からわかる Go言語