関数型脳になろう!
どうも!




( ̄ω ̄*)ちゅーんでーす
自己紹介
   東京都在住の下っ端プログラマ
   7ヶ月ほど前にHaskellと運命の出会いを果たす
   誰かに語りたくてしょうがない所にたまたまこの
    会を知って何も考えずにノミネート
自己紹介
   東京都在住の下っ端プログラマ
   7ヶ月ほど前にHaskellと運命の出会いを果たす
   誰かに語りたくてしょうがない所にたまたまこの
    会を知って何も考えずにノミネート


            初心者です!お手柔らかに><
ちゅーんと関数型言語

    Haskell大好きです
   ちょっとだけLisp書きました

    Scalaもちょっとだけ書きました
   モナドって可愛いですね
   言ってるほどHaskell使えてません
   でもHaskell大好きです

    HaskellハァハァHaskellハァハァHaskellハァハァHaskellハァハァHaskellハァ
    ハァHaskellハァハァHaskellハァハァクンカクンカ(;´Д`)スーハースハー
と、いうわけで
Java
を書いてきました




        皆さんも好きですよね、Java
こんな感じに
こんな感じに
mainメソッドにこんなのを書くと・・・
こんなんが出てきます
ようは
    ループ構文禁止
    局所変数使用禁止
    フィールドは全てfinal
 
     if、switch分岐禁止


    三項演算子と再帰処
      理と、コンストラクタ
      のフィールドの初期
      化だけでBrainF*ckのイ
      ンタプリタ作った
BrainF*ck




かの有名な難解プログラミング言語

+-><[]., の8つの命令で構成され
シンプルながらも完全チューリング
ちなみに、参照透明とかなんとかの関係で
     入力はここに書きます
何が言いたいかというと
   ループなんか無くなって
   変数の再代入ができなくなって
   手続き的に逐次処理を書かなくたって

    if/switch文なんか使わなくたって


    BrainF*ckが実装できる=完全チューリング
           なのだ!(Java凄い!)
何が言いたいかというと
   ループなんか無くなって
   変数の再代入ができなくなって
   手続き的に逐次処理を書かなくたって

    if/switch文なんか使わなくたって
            ( ̄ω ̄*)え?スタックオーバーフロー?
             なんですかそれwww

    BrainF*ckが実装できる=完全チューリング
              なのだ!(Java凄い!)
それにしても・・・
とっても読みにくいです
ファイルもやたら多くなっちゃったし・・・




全体で446行あります(´;ω;`)ブワッ
            (普通に書けば100行以内)
やっぱり変数への再代入や
ループ構文は必要ですね!

   手続き型最高!!
じゃなくて・・・( ̄ω ̄;)
一生懸命書いたんです!
 評価点を探しましょう
例えばループ
      素直に書くとこんな風
        に冗長で何をやって
        いるか解らなくなり
        やすい。
      ネスト数を保持するた
        めの変数 l が邪魔
例えばループ
      ループの開始や終端
        を一文字づつ探して
        いるのでループ内の
        ステップ数が多くな
        ればなるほど高コス
        ト。
      予めプログラムを解
        析しておけば問題解
        決?
       →どうやって???
実は・・・
   最初は同じように1文字づつループの端を検索す
     る方法を考えてみた
   でも再帰でそれをやるとスタック領域の消費がハ
     ンパねー
   もうちょっと効率よく制御する方法は無いものか

    →BrainF*ckのコードを

    連結リストで表現してみたらどうだろう
連結リスト
    car
          cdr




1         2     3         ×


                    nil
BrainF*ck のコードを連結リストに

++[­­]

+        +       ×


             -


             -   ×
こうすれば(わりと)低コストで
   ループを制御できる
メモリの値が 0 ならこっち



                 ×


        -            0 以外ならこっちを実行して
                       戻ってきた返り値を元に
                         再び実行する

        -        ×
連結リストとS 式
連結リストはS式にして出力すれば
デバッグに便利!

                                   ++[>++<­]
                                        ↓
 ('+' . ('+' . (('>' . ('+' . ('+' . ('<' . ('­' . Nil))))) . ('+' . ('+' . Nil)))))


  S式は複雑なリストを再帰で簡単に出力できる
再帰と連結リスト
   このように、再帰処理と連結リストは相性が良い!
   というか、再帰的なデータ構造が再帰処理で操
    作しやすい。(木構造とか)




             という事がわかった(`・ω・´)
もう一つ問題が・・・

メモリとポインタはどうやって表現しよう?



       配列の値の書き換えは禁止してるけど
      ポインタの指し示す値を書きかえる度に
          配列を作り直すのはナンセンス
メモリも連結リストのペア!


 B   A        ×


C    D        ×

     ここが現在のポインタ
メモリも連結リストのペア!
ポインタを右に移動する場合


      C        B          A      ×


新しく作ってつなげる
               D          ×


C             副作用のある処理は禁止なので
             cdrを書き換えられないから捨てる
メモリも連結リストのペア!
ポインタを左に移動する場合(逆の事をする)

                   B
 A     ×
             捨てる



 B     C     D         ×
ここまでのまとめ
●   ループ/副作用なしでもチューリング完全
     (スタック上限を無視すればだけど・・・)
●   一見難しそうな部分も、連結リストを使って
      (思ったよりは)簡単に実装できる

でも、Javaでは二度と同じことやりたくない
●

   (´・ω・`)面倒くさいし読みにくいし
ところで
ループ/副作用が無いって
 どういう事なんだろう



改めてコードを見なおしてみよう(`・ω・´)
このへんとか
このへんに注目
あとコレは最初に決めたルールだけど
      とっても大事
全体の特徴

     基本的にJavaなので
全体を通してはオブジェクト指向してるけど

      全てのメソッドが
   必ず何か値を返す必要があり

(あるインスタンスの)メソッドが返却する値は
      引数によって決定する
言葉遊びは好きですか( ̄ω ̄)?
言い換えてみよう



メソッドが返却する値は
引数によって決定する
引数→入力 x



   変数 x があり
メソッドが返却する値は
入力 x によって決定する
返却する値→出力 y



二つの変数 x と y があり
 メソッドの出力 y は
入力 x によって決定する
X   と y の語順を入れ替える



    二つの変数 x と y があり
    メソッドは、入力 x に対して
     出力 y の値を決定する
ちょっと補完



      二つの変数 x と y があり
      メソッドは、入力 x に対して
出力 y の値を決定する規則が与えられている
倒置法


      二つの変数 x と y があり、
入力 x に対して、出力 y の値を決定する規則
      が与えられているメソッド
Wikipedia   「関数(数学)」より

      二つの変数 x と y があり、
入力 x に対して、出力 y の値を決定する規則
       が与えられているとき、

 変数 y を 「xを独立変数とすると関数」
  或いは簡単に「xの    関数」という。
まぁ、あの・・・


     半分こじつけなので、
細かい定義についてツッコミ入れられると
   泣いちゃうしか無いんですが。

 詳しいことはWikipedia見てください
とにかく



 引数によって決まった値を返す
   メソッド(の返却値)は
数学的な意味での関数と表現できる
つまり


       今回、Javaを使って
「関数」の組み合わせによってプログラミングをした

      とゆー事です(`・ω・´)
関数と言えばラムダ計算




λ ← こんなん出てきました
Wikipedia 先生再登場




    ラムダ計算(lambda calculus)は、
  理論計算機科学や数理論理学における、
関数の定義と実行を抽象化した計算体型である。

        ラムダ算法とも言う。
(´ ・ω ・`) ?




よーわからん
あの、あれ、



 α変換とか、β簡約とか
めんどくせー話は置いといて
簡単な例




(λx . 5 + x) 3
こいつが関数




(λx . 5 + x) 3
これが引数




(λx . 5 + x) 3
引数 x に 3 を束縛




  (λx . 5 + x) 3
結果




5+3=8
この作業を簡約といいます
2つの引数を取る関数


  (λx . (λy . x + y))
     は、略記で

    (λxy . x + y)
    とか書けます
高階関数




(λfx . f (x * 2)) (λn . n + 2) 5
高階関数




(λfx . f (x * 2)) (λn . n + 2) 5
高階関数



(λn . n + 2) (5 * 2)

  = 10 + 2 = 12
部分適用


(λf . f 2)((λxy . x * y) 5)

 = (λf . f 2)(λy . 5 * y)

 = (λy . 5 * y) 2 = 10
部分適用


(λf . f 2)((λxy . x * y) 5)

 = (λf . f 2)(λy . 5 * y)
                              ここに注目!

 = (λy . 5 * y) 2 = 10
注:カリー化≠部分適用
閑話休題
先ほどのJavaプログラム
関数の組み合わせでプログラミングしてるなら
 ラムダ式で表現できるんじゃなかろーか
private Container parseProgram(int idx,char odr){
     return
          odr == ';' || odr == ']' ? new Container(idx, new Nil()) :
          odr == '[' ? packLoop(parseProgram(idx+1, program.charAt(idx+1))) :
               packOrder(odr, parseProgram(idx+1, program.charAt(idx+1)));
}

private Container packLoop(Container sorce){
     return
          _packLoop(sorce, parseProgram(sorce.getIdx()+1, program.charAt(sorce.getIdx()+1)));
}

private Container _packLoop(Container loopin, Container loopout){
     return new Container(loopout.getIdx(),
          new ProgramList(loopin.getState(),loopout.getState()));
}




                                     BrainF*ckのプログラムを
                                  線形リストに変換する部分のコード片
private Container parseProgram(int idx,char odr){
         odr == ';' || odr == ']' ? new Container(idx, new Nil()) :
         odr == '[' ? packLoop(parseProgram(idx+1, program.charAt(idx+1))) :
              packOrder(odr, parseProgram(idx+1, program.charAt(idx+1)))
}

private Container packLoop(Container sorce){
         _packLoop(sorce, parseProgram(sorce.getIdx()+1, program.charAt(sorce.getIdx()+1)))
}

private Container _packLoop(Container loopin, Container loopout){
     new Container(loopout.getIdx(),
         new ProgramList(loopin.getState(),loopout.getState()))
}



                                           Retun文は
                                       かならず最初に来るので省略

                            どうせ1ステップなのでセミコロンいらないです
private Container parseProgram(int idx,char odr){
     odr == ';' || odr == ']' ? Container(idx, Nil) :
     odr == '[' ? packLoop(parseProgram(idx+1, program.charAt(idx+1))) :
          packOrder(odr, parseProgram(idx+1, program.charAt(idx+1)))
}

private Container packLoop(Container sorce){
     _packLoop(sorce , parseProgram(sorce.idx+1, program.charAt(sorce.idx+1)))
}

private Container _packLoop(Container loopin, Container loopout){
     Container(loopout.idx, ProgramList(loopin.state, loopout.state))
}


                                アクセッサはgetterだけなので
                             getHoge()はhogeだけで良いですね

                           コンストラクタも
                 初期化したインスタンスを取得する関数と見なせるので
                       newは省略してしまいましょう
PARSEPROGRAM := λ idx odr .
    odr == ';' || odr == ']' ? Container idx Nil :
    odr == '[' ? PACKLOOP (PARSEPROGRAM (idx+1) (program.charAt (idx+1)) :
         packOrder odr PARSEPROGRAM (idx+1) (program.charAt (idx+1))

PACKLOOP := λ sorce .
   _PACKLOOP sorce (PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1) )

_PACKLOOP := λloopin loopout .
    Container loopout.idx (ProgramList loopin.state loopout.state)




                                             ラムダ式っぽく体裁

                              詳しい説明はしませんが推論が可能なので
                                    型も省略します
PARSEPROGRAM := λ idx odr .
    odr == ';' || odr == ']' ? Container idx Nil :
    odr == '[' ? PACKLOOP (PARSEPROGRAM (idx+1) (program.charAt (idx+1)) :
         packOrder odr (PARSEPROGRAM (idx+1) (program.charAt (idx+1)))

PACKLOOP := λ sorce .
   _PACKLOOP sorce (PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1) )

_PACKLOOP := λloopin loopout .
    Container loopout.idx (ProgramList loopin.state loopout.state)




                                     副作用が無いので
                                これらのラムダ式は展開しても等価です
できたはできたけど
                                    これは酷い
                                             どこか間違えてそうな気もする




PARSEPROGRAM := λ idx odr . odr == ';' || odr == ']' ? Container
idx Nil : odr == '[' ? (λ sorce . (λloopin loopout . Container
loopout.idx (ProgramList loopin.state loopout.state)) sorce
(PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1)))
(PARSEPROGRAM (idx+1) (program.charAt (idx+1)) : packOrder
odr (PARSEPROGRAM (idx+1) (program.charAt (idx+1)))
ちなみに、自分自身をこうやって再帰呼び出しするのは
                           正式なラムダ計算としてはルール違反
                                 です(´・ω・`)
                         誰かがYコンビネータの話をしてくれるに違いない
                         その他色々と説明のために無視してる事あるです




PARSEPROGRAM := λ idx odr . odr == ';' || odr == ']' ? Container
idx Nil : odr == '[' ? (λ sorce . (λloopin loopout . Container
loopout.idx (ProgramList loopin.state loopout.state)) sorce
(PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1)))
(PARSEPROGRAM (idx+1) (program.charAt (idx+1)) : packOrder
odr (PARSEPROGRAM (idx+1) (program.charAt (idx+1)))
と・・・とにかく!


 関数的に書いたプログラムは
ラムダ式で表現できる
というイメージが伝わればOKです

   (´・ω・`)わざわざ宣言的に書かれているものを
    一行にまとめれば読めなくなるのは当たり前
結局何が言いたかったかと言うと
    関数型プログラミングの基本的なテクニック
   高階閑数
   カリー化
   部分適用
    これらは全て「ラムダ計算」の上に成り立ってい
     るという事。
     (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを
     勉強しようという事だったので、Javaを採用してみたらひど
     い目にあいました
結局何が言いたかったかと言うと
    関数型プログラミングの基本的なテクニック
   高階閑数
                       【注】
   カリー化        発表後読み返してみたんですが
              カリー化はちょっと違うかもです(´・ω・`)
   部分適用
    これらは全て「ラムダ計算」の上に成り立ってい
     るという事。
     (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを
     勉強しようという事だったので、Javaを採用してみたらひど
     い目にあいました
でも実際には
副作用が無いと辛い事も多いですよね




         計算途中の状態を
        引数として引き回してる
なので・・・

    ScalaやLispは、副作用を認めています

    Haskellにはモナドがあります

    Haskellにはモナドがあります

    Haskellにはモナドがあります

    Haskellにはm(ry
でもやっぱり
   プログラムに副作用が無く、式として表現できる
     事によって得られるものは大きいです

    (`・ω・´)関数型言語を学ぶ事によって「純粋なプ
     ログラム」を心がけるようになれば、スパゲッティ
     なプログラムから解放される

                       ・・・はず!!
   何より、関数型言語は面白い!!
なんか、終止Javaの話ばっかりしてた気もしますが
      多分それは気のせいです

       さぁみなさん、一緒に
 関数型脳になりましょう(`・ω・´)

                      おしまい

Material