scalaのPartialFunction

ちょっと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の場合あるから、正確な分類よくわからんけど・・・(´・ω・`)だれかもっとわかりやすく正確にまとめてくれ