LL脳がscalaの勉強を始めたよ その28


Scalaコップ本の9章の続きをやりますよー、今回は新しい制御構造の作成やらですよー

新しい制御構造を作る

引数として関数をとるメソッドを書く事で新しい制御構造を作る事ができるらしいです。ちなみに制御構造はifとかswitchとかループとかのアレコレですねー

とりあえずサンプルを書いてみますよー、サンプルは同じ操作を2回繰り返すという制御構造です

// 引数は実際に処理する関数と、演算の基になる数字ですね
scala> def twice(op:Double => Double, x:Double) = op(op(x))
twice: ((Double) => Double,Double)Double

// 実行してみますよー
// 1を加える処理を2回行ないますよー
scala> twice(x => x + 1, 5)                                
res3: Double = 7.0
// プレースホルダーを使って若干簡潔にー
scala> twice(_ + 1, 5)                                     
res4: Double = 7.0

// 違う関数も引数として渡してみますかねー
// こちらは2倍する処理を2回行ないますよ
scala> twice(_ * 2, 5)                                     
res5: Double = 20.0

うん、書いてみると結構シンプルですねー

実用的っぽい制御構造を作ってみますよ

もう少し実用的なサンプルとして、コップ本的オススメとして挙げられているファイル操作用の制御構造を試してみますかねー

サンプルとなるファイル(リソース)操作制御構造では、ファイルオープン・ファイル書き込み・ファイルクローズの一連の手続をまとめてしまいますよー

// 必要なjavaパッケージを読み込みますよー
import java.io.{File, PrintWriter}

// 引数としてファイルとファイルに対する操作をとる制御構造を定義します
def withPrintWriter(file:File, op:PrintWriter => Unit){
  // ファイル書き込みのインスタンスを取得
  val writer = new PrintWriter(file)
  try{
    // ファイル操作をしますよー
    op(writer)
  // 成功失敗にかかわらずファイルクローズしますね
  } finally {
    writer.close()
  }
}

ちなみにPrintWriterは"こんな感じのパッケージですねメモ"

さて実行コードは次のようになりますねー

withPrintWriter(
  // ファイルオブジェクトを取得して渡しますねー
  new File("date.txt"),
  // ファイル書き込みでは現在時刻を一行書き込みますよー
  writer => writer.println(new java.util.Date)
)

うん、問題なく書き込めましたよー

// date.txt
Mon Jul 12 11:29:40 JST 2010
ローンパターン

上のサンプルではファイル操作終了後のファイルクローズ処理をユーザから隔離することが出来ているので、ファイルクローズ忘れを防止することができるみたいですねー

ちなみに上のサンプルのような構造だと、制御構造から引数として渡された関数に対してリソース(上の例だとファイル)を貸し出す構造なので”ローンパターン”と呼ぶみたいですな。

なお、関数処理が終了→貸出終了になるので帰ってきたリソースは制御構造側でゴニョゴニョ後処理をすることができるわけデス。ちなみに上の例では後処理としてのfinallyブロック内ファイルクローズが定義されております。

制御構造風味の見た目に…引数リストを中カッコで

引数1個だけを渡すメソッド呼び出しの場合は引数を囲む括弧を中カッコにしても良いそうですのでやってみますです。

とりあえず簡単なサンプルを

// ごく普通のprintlnを
scala> println("Hello Scala!")
Hello Scala!
// 中カッコにしてみますよ
scala> println{"Hello Scala!"}
Hello Scala!

// 引数2つのときに中カッコを使うと怒られますねー
scala> println{"Hello", "Scala!"}
<console>:1: error: ';' expected but ',' found.
       println{"Hello", "Scala!"}
                      ^
// 括弧ならいくら引数があっても安心
// でもprintlnに2つ引数をわたすとどうなんのかは不明(´・ω・`)
scala> println("Hello", "Scala!")
(Hello,Scala!)
カリー化使って中カッコ

さて、前の方で定義したwithPrintWriterを中カッコを使って制御構文風に定義しますかねー。でも残念ながらwithPrintWiterの引数は2つでした。でも大丈夫、そんなこそカリー化です(`・ω・´)

そんなわけでwithPrintWiterカリー化しますよー

// カリー化して引数の呼び出し方を変えますよー
def withPrintWriter(file:File)(op:PrintWriter => Unit){
  val writer = new PrintWriter(file)
  try{
    op(writer)
  } finally {
    writer.close()
  }
}

カリー化すること自体は引数部分を書き換えればよいのであっさりですねー

んじゃ、カリー化部分適用で中カッコでの制御構造風呼出しますですよ

// 関数実行部分を中カッコでくくりましたよー
withPrintWriter(new File("date2.txt")){
  writer => writer.println(new java.util.Date)
}

うん、断然制御構造っぽくなりましたねー

特定のプログラムの中で繰り返し使う共通処理はこんな感じで制御構造風にまとめてしまえば、末端作業員へのツール提供的な開発ワークフローが整理できそうな感じかも(´・ω・`)

名前渡しパラメーター

上で書いた制御構造風味のコードではコップ本的にひとつ不満点があるそうです

それは中カッコの中で処理に値を渡しているこの部分

  writer => writer.println(new java.util.Date)

だってifとかwhileとかだと値渡ししないじゃん(´;ω;`)ヤダー(超意訳)、って言われたので名前私パラメーターについてやっていきますよー

名前渡しパラメーターを使ってみる

サンプルとしてアサーションを行う関数myAssertを書いてみますよー。myAssertはパラメーターとして関数をとって、外部のフラグをみてアサーションを行うかどうかを判定しますデス。

ちなみに”アサーション”とは特定の条件をチェックして、条件が成立していないときにAssertionErrorを発生させることでコードの検証を行ったりする構文らしいです。Scalaは組み込みでassertメソッドをもっているので、今回の独自に作成するサンプルではmyAssertという名前になっておりますよー

まずは名前渡しをしないサンプルですー

// アサーション用の条件(フラグ)ですよー
var assertionsEnabled = true
def myAssert(predicate: () => Boolean) = {
  // アサーションの条件と引数の条件から判定しますよー
  if(assertionsEnabled && !predicate()) throw new AssertionError
}

とりあえず実行してみますよ

// パラメータ関数の結果がtrueならば何もおこりませんな
scala> myAssert(() => 5 < 7) 

// falseでAssertionErrorを発生させます
scala> myAssert(() => 5 < 3)
java.lang.AssertionError
	at .myAssert(<console>:7)
	at .<init>(<console>:7)
	at .<clinit>(<console>)
	at RequestResult$.<init>(<console>:3)
	at RequestResult$.<clinit>(<console>)
	at RequestResult$result(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.Delegatin...


さて、こいつの使い方が下のようになっているのがちょっといや(´・ω・`)

 myAssert(() => 5 > 3) 

できればこういうふうにしてみたいところ

myAssert(5 < 3)

// でも関数リテラル渡せよーってエラー
scala> myAssert(5 < 3)
<console>:7: error: type mismatch;
 found   : Boolean(false)
 required: () => Boolean
       myAssert(5 < 3)
                  ^

ここで名前渡しパラメーターでなんとかしましょう

名前渡しパラメーターでで解決します

名前渡しパラメーターでmyAssertを書き換えますよー

// パラメーター関数の引数"()"を省略しましたよ
def myAssert(predicate:  => Boolean) = {
  // predicateの呼び出し時の"()"を省略しましたよ
  if(assertionsEnabled && !predicate) throw new AssertionError
}

// 実行します
scala> myAssert(5 < 3)
// AssertionErrorが正常に発生されましたー
java.lang.AssertionError
	at .myAssert(<console>:7)
	at .<init>(<console>:7)
	at .<clinit>(<console>)
	at RequestResult$.<init>(<console>:3)
	at RequestResult$.<clinit>(<console>)
	at RequestResult$result(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.Delegatin...

やってることは空の引数を省略しただけなんですが、制御構造っぽく使えるようになりましたー
。なお、()省略での名前私ができるのは引数部分だけみたいデス(´・ω・`)

引数型をBooleanにすれば良いじゃない(`・ω・´)

引数やらの値渡しをしたくないならそもそも関数で渡さなきゃいいんじゃない?って反論は、渡された引数処理の実行タイミングが変わっちゃうのでダメよ!ってことらしいです。

たとえばこんな感じのアサーション処理の引数predicateに結果型がBooleanの評価式を渡した場合、評価式の実行タイミングはmyBoolAssert実行前になります。

def myBoolAssert(predicate:Boolean){ // 評価式はこの行で実行されます
  if(assertionsEnabled && !predicate) throw new AssertionError
}

それに対して名前渡しパラメーターを使っているmyAssertでは、処理内の関数呼び出し部分で実行され積みたいです。(実際は式を評価するapplyメソッドを持っている関数値が自動的に生成されて実行されるみたいですケドも)

def myAssert(predicate:  => Boolean) = {
  if(assertionsEnabled && !predicate) throw new AssertionError // 評価式はこの行(!predicate部)で実行されます
}
実際の実行タイミングの違いを試してみますかねー

アサーションを無効(フラグをFalse)にして、10 / 0 == 0 という(絶賛エラーの)ダメ評価式を試してみますよー

// アサーションフラグをFalseに設定しますよー
var assertionsEnabled = false

// まずはtype Booleanから
// 処理前に評価されるのでエラー出まくりです
scala> myBoolAssert(10 / 0 == 0)
java.lang.ArithmeticException: / by zero
	at .<init>(<console>:7)
	at .<clinit>(<console>)
	at RequestResult$.<init>(<console>:3)
	at RequestResult$.<clinit>(<console>)
	at RequestResult$result(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAcce...

// 次にtype名前渡しパラメーターです
// エラーの箇所がassertionsEnabled = false条件で回避されるのでエラー出ません
scala> myAssert(10 / 0 == 0)  

こんな感じで実行タイミングを実際に試してみましたよー

いじょー

ようやく9章終わりですよー、次は10章ですねー、結構量があるのでがんばります(`・ω・´)