このページの記事一覧

2011年4月20日水曜日

名前渡しと高階関数について

ふと疑問に思ったポスト

#Scala の名前渡しと高階関数の違いってなんだろう?結局名前渡しって言うのはその場で関数オブジェクトを作って渡すだけの高階関数で、呼び出す側にそれと意識させない仕組みって事なんだろうか?less than a minute ago via web Favorite Retweet Reply

まぁ、なんともしょうもないことですな!
でもご丁寧にkmizuさんがReplyくれまして、

@aoi0308 呼び出され側での扱いも異なるので(明示的な呼び出しなしでもthunkが評価される)、やはり明確に異なりますが、内部実装としては大して変わらないはずです。ですので、実装側からの理解としてはそんな感じで問題無いと思います。 #scalaless than a minute ago via web Favorite Retweet Reply

とのこと。

正直、thunk?何それおいしいの?って感じだったんですがとりあえず名前渡しと高階関数ははっきりと、呼び出し側からも呼び出される側からも別物らしい。

さて、詳細を調べる前にkmizuさんが言っていたthunkを調べました。
色々探していてYoshifumi YAMAGUCHIさんのbitbucketにちょろっと載ってました。
10行目ですが、「未だ実行されていない計算」の事らしいです。

まぁそもそも何で最初のポストがあったかっていうと、名前渡しも高階関数も結局は遅延評価(呼び出し先で評価されるって意味)で、イマイチ違いがピンとこなかったためでした。

で、それぞれを比較するために書いてみたコード

   1: object Main {
   2:  
   3:   def main(args: Array[String]) {
   4:     byname(println("main1"))
   5:     println("---")
   6:     byvalue(println("main2"))
   7:     println("---")
   8:     higher(() => println("main3"))
   9:     println("---")
  10:     higher2(() => println("main4"))
  11:     println("---")
  12:   }
  13:  
  14:   def byname(f: => Unit) = { println("byname"); f }
  15: //  def byname2(f: => Unit) = { println("byname2"); f() }
  16:   def byvalue(f: Unit) = { println("byvalue"); f }
  17: //  def byvalue2(f: Unit) = { println("byvalue2"); f() }
  18:   def higher(f: () => Unit) = { println("higher"); f }
  19:   def higher2(f: () => Unit) = { println("higher2"); f() }
  20:  
  21: }

↓結果↓


byname
main1
---
main2
byvalue
---
higher
---
higher2
main4
---


ふむふむ。
まず、15行目と17行目はコメントになっていますが、コンパイルが通りません。
名前渡しされるものは関数ではなくUnit型の値なので、引数つけて呼び出すことはできないわけですね。
17行目のfは値渡しされたUnit型の値なので、同じく呼び出すなんてことはできない、と。


結果を見てみると、bynameとhigher2は遅延評価されるので、渡したprintlnは後から実行されてます。
一方でbyvalueはmainメソッド内でprintlnが実行されてからUnitが渡されるのでbyvalueが後に表示されてます。


higherだけが渡したprintlnが実行されていないことが興味深いです。
というのも、実はhigherメソッドは「引数なし・戻り値Unitの関数を受け取り、引数なし・戻り値Unitの関数を返す」というメソッドに仕上がってます。
つまりhigherメソッドの最後に f と書いてありますが、これは評価されずにそのまま戻り値になってしまっています。
higher2は明示的に f() と関数呼び出しを行っているため、higher2メソッドの戻り値は f の実行結果であるUnitが戻り値として処理されます。


kmizuさんが言っていた「明示的な呼び出しなしでもthunkが評価される」っていうのはこういうことだったんですね。


結論:


名前渡しの場合、メソッド(関数)に渡されるのは未評価の計算(値)である。
高階関数の場合、メソッド(関数)に渡されるのは関数オブジェクトである。
よって名前渡しの場合は値として(呼び出しなしで)使用するときに評価されるのに対して、高階関数の場合は明示的に関数呼び出しを行わないと評価がされない。
(何かこう書いてみると至極当たり前のことしか言ってないなぁ)

2011年4月19日火曜日

C#のusing構文をScalaで

や、もちろんVB.NETのUsing構文でもいいんだけどね。

やっぱりJava触った後にC#とか触るとusing構文素敵だよね。
Scalaにはないのかにゃー?というとそういう構文はありません。
だがしかし!それっぽい構文を作ることはできるぞー、ということで作ってみよう。

まずはusing関数の定義

   1: def using[A <: {def close()}, B](r: A)(f: A => B) = {
   2:   try {
   3:      f(r)
   4:   } finally {
   5:      r.close
   6:   }
   7: }

ジェネリクス、カリー化、Structual type、高階関数辺り。
using関数はclose()メソッドを持つ何らかのオブジェクトと、A型(つまりcloseメソッドを持つ何らかの型)を受け取ってB型の値を返す関数、を受け取ってB型の結果を返す関数になります。
処理はいたって普通で、受け取った関数fを呼び出して、finallyでcloseするだけ。


んで、これを使うコード



   1: import java.sql.{Array => _, _}
   2:  
   3: object Main {
   4:  
   5:   def main(args: Array[String]) {
   6:     Class.forName("com.mysql.jdbc.Driver")
   7:  
   8:     val list =
   9:     using(DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "")){ conn =>
  10:       using(conn.prepareStatement("SELECT * FROM test")){ stmt =>
  11:         val rs = stmt.executeQuery
  12:         val list = scala.collection.mutable.ListBuffer[String]()
  13:  
  14:         while (rs.next) {
  15:           list += rs.getString("name")
  16:         }
  17:  
  18:         list.toList
  19:       }
  20:     }
  21:  
  22:     list.foreach(println)
  23:   }
  24: }

最初のimportはjava.sql.Arrayは除外してインポートする。
そうしないと普通のArrayとバッティングしてエラーになる。


あとは普通にusingを使ってやる、と。上のコードはMySQLに接続するコードですけど、深い意味はありません。
C#と違って構文ではなくて関数として作ったので、using全体の戻り値を変数に代入することもできます。


あれ?そういえばScalaって代入って言葉使っていいんだっけ?
変数をusing関数の戻り値に束縛することができる、って言ったほうが正確?

2011年4月8日金曜日

nullや""をfalseに

http://d.hatena.ne.jp/xuwei/20110227/1298779615 のブログを見て、ふむふむと。
まだまだ勉強中なので、色々と参考になります。
んで、コメント欄に
>Groovyだと実行時にnullも""もfalseに変換してくれるのでif文の条件に直接文字列を渡せます
あーなるほど。たしかに、それはjavaにもscalaにないというか、scalaでも直接は表現できない様な便利な機能かもしれませんね。ありがとうございます (原文ママ)
ってあったので、あれ?と思ってしまった。
まぁ「scalaでも直接は表現できない様な
」っていう意味がちょっと分からなかったんですが、Scalaでもそういう動きをさせることはできますよね。
scala> implicit def stringToBoolean(s: String) = s != null && s != ""
stringToBoolean: (s: String)Boolean
 
scala> var str = "Hoge"
str: java.lang.String = Hoge
 
scala> println(if (str) "Hello!" else "Empty")
Hello!
 
scala> str = null
str: java.lang.String = null
 
scala> println(if (str) "Hello!" else "Empty")
Empty
 
scala> str = ""
str: java.lang.String =
 
scala> println(if (str) "Hello!" else "Empty")
Empty

こんな感じで。