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が評価される」っていうのはこういうことだったんですね。


結論:


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

0 件のコメント:

コメントを投稿