初心者ぺちぱーがGitHubでScalaレッスンを始めたぞ。 前回解いたハノイの塔を修正してtrait
を使ってみよう。
Lessen 5: Improve Tower of Hanoi
前回はこんなコードを書いた。
State.scala class State ( val num : Int , val pegs : Map [ String , mutable.Stack [ Int ]]) {
this ( num , Map ( "A" -> mutable . Stack [ Int ](), "B" -> mutable . Stack [ Int ](), "C" -> mutable . Stack [ 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 ))
def moveDisk ( from : mutable.Stack [ Int ], to : mutable.Stack [ Int ]) =
if ( to . isEmpty || from . head < to . 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 ))
foreachPegName ( name => println ( "%s: %s" . format ( name , pegs ( name ))))
def foreachPegName ( func : String => Unit ) { List ( "A" , "B" , "C" ). foreach ( func ) }
杭の状態をプリントする部分をState
クラスに持たせていたが、これを外部クラスに移動してみよう。
Trait
PHPでも5.4から導入された機能であるtrait
は、Scalaも持っている仕組みだ。foreachPegName()
はプリント部分にも必要で、pegs
の初期化でも必要となっている。しかし、プリンタークラスと、State
クラスには直接の継承関係は無さそうだ。そこで、このtrait
に持たせることで、共通の機能をまとめることにする。若干やっつけな気がするけど。
PegsContainer.scala 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
でトレイトを利用する。
SimplePrinter.scala 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 ))
foreachPegName ( name => println ( "%s: %s" . format ( name , state . pegs ( name ))))
HanoiSolver
クラスはこのSimplePrinter
クラスを利用するように変更する。
val printer = new SimplePrinter ( state )
move ( state , printer , n , "A" , "B" , "C" )
def move ( state : State , printer : SimplePrinter , n : Int , a : String , b : String , c : String ) {
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
でもいいはずだけどなんとなく使ってみたかっただけ)。
StatePrinter.scala abstract class StatePrinter {
def printResult ( disk : Int , a : String , b : String )
ここでは、SimplePrinter
の定義を抜き出しただけだ。中身はサブクラスで実装済み。
SimplePrinter
クラスでStatePrinter
を継承するように変更してみよう。
SimplePrinter.scala class SimplePrinter ( state : State ) extends StatePrinter with PegsContainer {
親クラスを継承する場合は、extends
には、継承しているクラス名を書く。利用しているtrait
を指定するには、with
キーワードを使う。HanoiSolver
クラスのmove()
メソッドの引数の型はStatePrinter
クラスに変更する。
HanoiSolver.scala def move ( state : State , printer : StatePrinter , n : Int , a : String , b : String , c : String ) {
require precondition
前回の記事を書いた後、事前条件を定義するのにrequire
が使えるらしいというのを知ったので、ついでにこれも修正してみよう。コップ本のp.114に記述があった。
State.scala def moveDisk ( from : mutable.Stack [ Int ], to : mutable.Stack [ Int ]) = {
require ( to . isEmpty || from . head < to . 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!
さて、修正後のコード全体はこんな感じになった。
val printer = new SimplePrinter ( state )
move ( state , printer , n , "A" , "B" , "C" )
def move ( state : State , printer : StatePrinter , n : Int , a : String , b : String , c : String ) {
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 {
this ( num , Map ( "A" -> mutable . Stack [ Int ](), "B" -> mutable . Stack [ Int ](), "C" -> mutable . Stack [ 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 ))
def moveDisk ( from : mutable.Stack [ Int ], to : mutable.Stack [ Int ]) = {
require ( to . isEmpty || from . head < to . head )
def foreachPegName ( func : String => Unit ) { List ( "A" , "B" , "C" ). foreach ( func ) }
abstract class StatePrinter {
def printResult ( disk : Int , a : String , b : String )
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 ))
foreachPegName ( name => println ( "%s: %s" . format ( name , state . pegs ( name ))))
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