Scalaプログラミング5 MapとStackを使ってみる2
初心者ぺちぱーがGitHubでScalaレッスンを始めたぞ。前回解いたハノイの塔を修正してtrait
を使ってみよう。
Lessen 5: Improve Tower of Hanoi
前回はこんなコードを書いた。
class State(val num: Int, val pegs: Map[String, mutable.Stack[Int]]) {
def this(num: Int) {
this(num, Map("A" -> mutable.Stack[Int](), "B" -> mutable.Stack[Int](), "C" -> mutable.Stack[Int]()))
clear(num)
}
def clear(num: Int) {
foreachPegName(pegs(_).clear())
// fill initial disks to peg "A"
(1 to num).reverse.foreach(pegs("A").push(_))
}
def move(a: String, b: String) = moveDisk(pegs(a), pegs(b))
private
def moveDisk(from: mutable.Stack[Int], to: mutable.Stack[Int]) =
if (to.isEmpty || from.head < to.head) {
to.push(from.pop()).head
}
else throw new IllegalArgumentException
def printResult(disk: Int, a: String, b: String) {
println("move disk %d from %s to %s".format(disk, a, b))
printState()
}
def printState() {
foreachPegName(name => println("%s: %s".format(name, pegs(name))))
println("")
}
private
def foreachPegName(func: String => Unit) { List("A", "B", "C").foreach(func) }
}
杭の状態をプリントする部分をState
クラスに持たせていたが、これを外部クラスに移動してみよう。
Trait
PHPでも5.4から導入された機能であるtrait
は、Scalaも持っている仕組みだ。foreachPegName()
はプリント部分にも必要で、pegs
の初期化でも必要となっている。しかし、プリンタークラスと、State
クラスには直接の継承関係は無さそうだ。そこで、このtrait
に持たせることで、共通の機能をまとめることにする。若干やっつけな気がするけど。
trait PegsContainer {
def foreachPegName(func: String => Unit) { List("A", "B", "C").foreach(func) }
}
State
クラスはextends
キーワードで、このPegsContainer
トレイトを利用する。
class State(val num: Int, val pegs: Map[String, mutable.Stack[Int]]) extends PegsContainer
プリンタークラスはSimplePrinter
クラスとしておこう。このクラスもextends PegsContainer
でトレイトを利用する。
class SimplePrinter(state: State) extends PegsContainer {
def printResult(disk: Int, a: String, b: String) {
println("move disk %d from %s to %s".format(disk, a, b))
printState()
}
def printState() {
foreachPegName(name => println("%s: %s".format(name, state.pegs(name))))
println("")
}
}
HanoiSolver
クラスはこのSimplePrinter
クラスを利用するように変更する。
class HanoiSolver {
def solve(n: Int) {
val state = new State(n)
val printer = new SimplePrinter(state)
printer.printState()
move(state, printer, n, "A", "B", "C")
}
private
def move(state: State, printer: SimplePrinter, n: Int, a: String, b: String, c: String) {
if (n > 0) {
move(state, printer, n-1, a, c, b)
val disk = state.move(a, c)
printer.printResult(disk, a, c)
move(state, printer, n-1, b, a, c)
}
}
}
Abstract class
HanoiSolver
クラスがSimplePrinter
クラス以外のプリンタークラスにも対応できるようにabstract class
を用意しておこう(trait
でもいいはずだけどなんとなく使ってみたかっただけ)。
abstract class StatePrinter {
def printResult(disk: Int, a: String, b: String)
def printState()
}
ここでは、SimplePrinter
の定義を抜き出しただけだ。中身はサブクラスで実装済み。
SimplePrinter
クラスでStatePrinter
を継承するように変更してみよう。
class SimplePrinter(state: State) extends StatePrinter with PegsContainer {
// ...
}
親クラスを継承する場合は、extends
には、継承しているクラス名を書く。利用しているtrait
を指定するには、with
キーワードを使う。HanoiSolver
クラスのmove()
メソッドの引数の型はStatePrinter
クラスに変更する。
class HanoiSolver {
// ...
private
def move(state: State, printer: StatePrinter, n: Int, a: String, b: String, c: String) {
// …
}
}
require precondition
前回の記事を書いた後、事前条件を定義するのにrequire
が使えるらしいというのを知ったので、ついでにこれも修正してみよう。コップ本のp.114に記述があった。
private
def moveDisk(from: mutable.Stack[Int], to: mutable.Stack[Int]) = {
require(to.isEmpty || from.head < to.head)
to.push(from.pop()).head
}
こうすると、コードの意味も、ただの分岐ではなくて、ハノイの塔のルールっぽく見えるようになったね。
assertions
Scalaには、require
に似たメソッドがいくつかあるようだ。Predef
に定義されているようなので、PredefのScala docを見てみよう。
assert
final def assert(assertion: Boolean, message: ⇒ Any): Unit
def assert(assertion: Boolean): Unit
Tests an expression, throwing an AssertionError if false. Calls to this method will not be generated if -Xelide-below is at least ASSERTION.
式をテストするのに使う。式がfalse
だとAssertionError
を投げる。-Xelide-below
がASSERTION
以上の場合は、実行されない。
assume
final def assume(assumption: Boolean, message: ⇒ Any): Unit
def assume(assumption: Boolean): Unit
Tests an expression, throwing an AssertionError if false. This method differs from assert only in the intent expressed: assert contains a predicate which needs to be proven, while assume contains an axiom for a static checker. Calls to this method will not be generated if -Xelide-below is at least ASSERTION.
式をテストするのに使う。式がfalse
だとAssertionError
を投げる。assert
とは、意図していることが違う。assert
は述語が正しい事を意味し、assume
は静的チェックの前提となる仮定を意味する。-Xelide-below
がASSERTION
以上の場合は、実行されない。
うーむ。分かったような分かんないような感じだな。。assert
は条件がtrueなのかどうかをチェックするだけで、assume
は条件がtrueじゃないとダメだってこと?いや、なんか違う気がする。。これ分からんわー。
require
final def require(requirement: Boolean, message: ⇒ Any): Unit
def require(requirement: Boolean): Unit
Tests an expression, throwing an IllegalArgumentException if false. This method is similar to assert, but blames the caller of the method for violating the condition.
式をテストするのに使う。式がfalse
だとIllegalArgumentException
を投げる。assert
と似ているが、条件を満たしていない場合、メソッド呼出側の責任とする。
ensuring
ensuring
だけはメソッドじゃなくて、クラスで定義されてた。これは戻り値のチェックに使うらしい。クラスの説明が書いてなかった。。
final class Ensuring[A] extends AnyVal
まとめると、こんな感じか。
assert
: 何らかの条件チェック。失敗するとAssertionError
を投げる。-Xelide-below
オプションで無視できる。assume
: 前提となる仮定のチェック。失敗するとAssertionError
を投げる。-Xelide-below
オプションで無視できる。require
: 事前条件のチェック(引数が満たすべき条件のチェック)。呼出側に責任がある事を明確にする。失敗するとIllegalArgumentException
を投げるensuring
: 事後条件のチェック(戻り値が満たすべき条件のチェック)。失敗するとAssertionError
を投げる。
Done!
さて、修正後のコード全体はこんな感じになった。
class HanoiSolver {
def solve(n: Int) {
val state = new State(n)
val printer = new SimplePrinter(state)
printer.printState()
move(state, printer, n, "A", "B", "C")
}
private
def move(state: State, printer: StatePrinter, n: Int, a: String, b: String, c: String) {
if (n > 0) {
move(state, printer, n-1, a, c, b)
val disk = state.move(a, c)
printer.printResult(disk, a, c)
move(state, printer, n-1, b, a, c)
}
}
}
class State(val num: Int, val pegs: Map[String, mutable.Stack[Int]]) extends PegsContainer {
def this(num: Int) {
this(num, Map("A" -> mutable.Stack[Int](), "B" -> mutable.Stack[Int](), "C" -> mutable.Stack[Int]()))
clear(num)
}
def clear(num: Int) {
foreachPegName(pegs(_).clear())
// fill initial disks to peg "A"
(1 to num).reverse.foreach(pegs("A").push(_))
}
def move(a: String, b: String) = moveDisk(pegs(a), pegs(b))
private
def moveDisk(from: mutable.Stack[Int], to: mutable.Stack[Int]) = {
require(to.isEmpty || from.head < to.head)
to.push(from.pop()).head
}
}
trait PegsContainer {
def foreachPegName(func: String => Unit) { List("A", "B", "C").foreach(func) }
}
abstract class StatePrinter {
def printResult(disk: Int, a: String, b: String)
def printState()
}
class SimplePrinter(state: State) extends StatePrinter with PegsContainer {
def printResult(disk: Int, a: String, b: String) {
println("move disk %d from %s to %s".format(disk, a, b))
printState()
}
def printState() {
foreachPegName(name => println("%s: %s".format(name, state.pegs(name))))
println("")
}
}
Conclustion
今回は、trait
を使ってみたり、abstract class
を使ってみたりした。まとめ!
trait
はextends
かwith
キーワードで利用する- 別のクラスを継承する場合は
extends
キーワードを使う - 継承+トレイトの場合は、
HogeClass extends SuperHogeClass with HogeTrait
になる - 事前条件は
require()
メソッドで表現できる - アサーションは他にも3つあるよ
Reference
- Scalaスケーラブルプログラミング第2版
- 作者
- Martin Odersky
- Lex Spoon
- Bill Venners
- 出版社
- インプレスジャパン
- 発売日
- 2011-09-27
- メディア
- 単行本(ソフトカバー)
- 価格
- ¥ 4,968