ちょっとPartialFunctionについて考えてみた(・∀・)
その前に、scalaで、caseという予約語がありますが、これが結構厄介というか、初心者には一見わかりにくいとおもうんですが、役割としては(たぶん)4種類ありますよね?例外のcaseはたぶんべつだから5種類じゃね?
まず、そのcaseの使われ方に付いて簡単にまとめから・・・
match式でのcase
これはまあ大雑把にいってしまえば、Javaとかのswitchのcase節と同じですね。他にも機能色々あるけど
caseクラス
caseクラスを定義するときにつかう場合。これはこれで、かなり多機能でいろいろあるけど、今回は触れない
関数リテラルの引数のところでの、パターンマッチ*1
たとえば、
val list = List((3,"java"),(6,"scala"),(5,"ruby")) //(Int,String)のリスト list.map{ case ( n , str) => str * n ) } //型推論によって、自動的にnはInt,strはStringになる
ってなっているときのcaseですね。
慣れてくるとmapとかfilterとかの高階関数ではかなり使います。
PartialFunctionのシンタックスシュガー
で、今回の主題のPartialFunctionのシンタックスシュガーとしてのcase
そもそも、この使い方自体あまり知らなかった、ちゃんと理解できてない(今も?)ので調べてみたわけですが。
yuroyoroさんのエントリとかにシンタックスシュガーってかいてあって、どういうこと?と思ったので。
あとは、Listのcollectってメソッドがあって、最近使い方分かってきて、使ってるけど、よくよく考えると、PartialFunctionを引数にとるけど、PartialFunctionってなに?って考えると、全然理解できてないことに気づいたみたいな(´・ω・`)
まず、PartialFunctionのソース見ると、
trait PartialFunction[-A, +B] extends (A => B) {
ってなってて、(A => B) っていうの自体Function1[A,B] のシンタックスシュガーなんで、PartialFunctionはFunction1を継承しているtraitです。
で、Function1はapplyだけが抽象メソッド、またPartialFunctionはisDefinedAtってメソッドが抽象メソッドです。
ってことは、もし明示的に、
- PartialFunctionを継承したclassを作成→インスタンス化
- またはPartialFunctionを継承したobjectを定義
するには、isDefinedAtとapplyを定義しなければいけないことになります。
で、
val fooPf:PartialFunction[String,String] = { case "foo" => "bar" }
っていうのが、PartialFunctionのインスタンス作成のためのシンタックスシュガーってことは、何らかの形で、コンパイラが、isDefinedAtとapplyを自動で生成していることになるはず!(`・ω・´)
ということで、以下のようなソース
object hoge{ val fooPf:PartialFunction[String,String] = { case "foo" => "bar" } }
をscalacでコンパイル → jadで逆コンパイルしてみました。
するとなんとhoge$とhogeあわせて500行を越えるソースに(´;ω;`)
まぁそれはいいとして、肝心のisDefinedAtとapplyの部分は、javaに変換しきれてなくて、JVMの生の命令をそのまま訳したものが(´;ω;`)
public final String apply(String s) { String s1 = s; s1; String s2; s2 = "foo"; if(s1 != null) break MISSING_BLOCK_LABEL_19; JVM INSTR pop ; if(s2 != null) break MISSING_BLOCK_LABEL_30; break MISSING_BLOCK_LABEL_26; s2; equals(); JVM INSTR ifeq 30; goto _L1 _L2 _L1: break MISSING_BLOCK_LABEL_26; _L2: break MISSING_BLOCK_LABEL_30; return "bar"; throw new MatchError(s1); } public final boolean isDefinedAt(String s) { String s1 = s; s1; String s2 = "foo"; if(s1 != null) goto _L2; else goto _L1 _L1: JVM INSTR pop ; if(s2 == null) goto _L4; else goto _L3 _L2: s2; equals(); JVM INSTR ifeq 30; goto _L4 _L3 _L4: true; goto _L5 _L3: false; _L5: return; }
まぁでも大体意味はわかるんでいいんですが。
なんかmatch式のパターンマッチとか、逆コンパイルしてもjavaのソースには変換できないことがよくありますね。
その辺はできるだけJVMの命令直接使って、パフォーマンス引き出しているんですかね?(もしくはjadの力不足?)
なんか話し逸れたけど、まぁちょっとは前よりも仕組みはわかりました。コップ本にもPartialFunctionの記述はありますが(267ページあたり)
なんだかコップ本のはあまり詳しくないので、あれだけだと自分的にはあんまりしっくりこないです(´・ω・`)
っていうか、結局、PartialFunction自体は明示的に
new PartialFunction{ //自分で抽象メソッド実装
っていう使い方しないってことですね(たぶん)。PartialFunctionの場合、applyとisDefinedAtの定義って連動していなきゃおかしいですし。
たとえば、isDefinedAtでtrueが返ってくるのに、applyで呼んでみたらエラーが投げられるPartialFunctionのインスタンスがあったら、
はぁ?定義してあるっていったのになんでエラー投げるんだよ!(゚Д゚ )
って話になるので。
*1:これもPartialFunctionの場合あるから、正確な分類よくわからんけど・・・(´・ω・`)だれかもっとわかりやすく正確にまとめてくれ