material

86
関数型脳になろう! 関数型脳になろう!

Upload: tune

Post on 02-Jul-2015

2.804 views

Category:

Documents


0 download

DESCRIPTION

2012/5/19 関数型言語勉強会発表資料( ̄ω ̄*) 最後のページ、「終止」ってなってるのは「終始」の間違いですね、ハイ

TRANSCRIPT

Page 1: Material

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

Page 2: Material

どうも!

( ̄ω ̄*)ちゅーんでーす

Page 3: Material

自己紹介

東京都在住の下っ端プログラマ 7ヶ月ほど前にHaskellと運命の出会いを果たす 誰かに語りたくてしょうがない所にたまたまこの

会を知って何も考えずにノミネート

Page 4: Material

自己紹介

東京都在住の下っ端プログラマ 7ヶ月ほど前にHaskellと運命の出会いを果たす 誰かに語りたくてしょうがない所にたまたまこの

会を知って何も考えずにノミネート

初心者です!お手柔らかに><

Page 5: Material

ちゅーんと関数型言語

Haskell大好きです ちょっとだけLisp書きました Scalaもちょっとだけ書きました モナドって可愛いですね 言ってるほどHaskell使えてません でもHaskell大好きです HaskellハァハァHaskellハァハァHaskellハァハァHaskellハァハァHaskellハァ

ハァHaskellハァハァHaskellハァハァクンカクンカ(;´Д`)スーハースハー

Page 6: Material

と、いうわけで

Page 7: Material

Java

を書いてきました

皆さんも好きですよね、Java

Page 8: Material

こんな感じに

Page 9: Material

こんな感じに

Page 10: Material

mainメソッドにこんなのを書くと・・・

Page 11: Material

こんなんが出てきます

Page 12: Material

ようは ループ構文禁止 局所変数使用禁止 フィールドは全てfinal

if、switch分岐禁止

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

Page 13: Material

BrainF*ck

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

+-><[]., の8つの命令で構成されシンプルながらも完全チューリング

Page 14: Material

ちなみに、参照透明とかなんとかの関係で入力はここに書きます

Page 15: Material

何が言いたいかというと

ループなんか無くなって 変数の再代入ができなくなって 手続き的に逐次処理を書かなくたって if/switch文なんか使わなくたって

BrainF*ckBrainF*ckが実装できる=完全チューリングが実装できる=完全チューリング

なのだ!(Java凄い!)

Page 16: Material

何が言いたいかというと

ループなんか無くなって 変数の再代入ができなくなって 手続き的に逐次処理を書かなくたって if/switch文なんか使わなくたって

BrainF*ckBrainF*ckが実装できる=完全チューリングが実装できる=完全チューリング

なのだ!(Java凄い!)

( ̄ω ̄*)え?スタックオーバーフロー?なんですかそれwww

Page 17: Material

それにしても・・・

Page 18: Material

とっても読みにくいです

Page 19: Material

ファイルもやたら多くなっちゃったし・・・

全体で446行あります(´;ω;`)ブワッ(普通に書けば100行以内)

Page 20: Material

やっぱり変数への再代入やループ構文は必要ですね!

手続き型最高!!

Page 21: Material

じゃなくて・・・( ̄ω ̄;)

Page 22: Material

一生懸命書いたんです!評価点を探しましょう

Page 23: Material

例えばループ

素直に書くとこんな風に冗長で何をやっているか解らなくなりやすい。

ネスト数を保持するための変数 l が邪魔

Page 24: Material

例えばループ

ループの開始や終端を一文字づつ探しているのでループ内のステップ数が多くなればなるほど高コスト。

予めプログラムを解析しておけば問題解決?

→どうやって???

Page 25: Material

実は・・・

最初は同じように1文字づつループの端を検索する方法を考えてみた

でも再帰でそれをやるとスタック領域の消費がハンパねー

もうちょっと効率よく制御する方法は無いものか →BrainF*ckのコードを連結リストで表現してみたらどうだろう

Page 26: Material

連結リスト

1 2 ×3

car

cdr

nil

Page 27: Material

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

++[­­]

+ + ×

-

- ×

Page 28: Material

こうすれば(わりと)低コストでループを制御できる

×

-

- ×

メモリの値が 0 ならこっち

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

再び実行する

Page 29: Material

連結リストとS式

連結リストはS式にして出力すれば

デバッグに便利!

++[>++<­]

('+' . ('+' . (('>' . ('+' . ('+' . ('<' . ('­' . Nil))))) . ('+' . ('+' . Nil)))))

S式は複雑なリストを再帰で簡単に出力できる

Page 30: Material

再帰と連結リスト

このように、再帰処理と連結リストは相性が良い! というか、再帰的なデータ構造が再帰処理で操

作しやすい。(木構造とか)

という事がわかった(`・ω・´)

Page 31: Material

もう一つ問題が・・・

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

配列の値の書き換えは禁止してるけどポインタの指し示す値を書きかえる度に

配列を作り直すのはナンセンス

Page 32: Material

メモリも連結リストのペア!

B A ×

C D ×

ここが現在のポインタ

Page 33: Material

メモリも連結リストのペア!

ポインタを右に移動する場合

B A ×C

D ×

副作用のある処理は禁止なのでcdrを書き換えられないから捨てる

C

新しく作ってつなげる

Page 34: Material

メモリも連結リストのペア!

ポインタを左に移動する場合(逆の事をする)

C D ×B

A ×捨てる

B

Page 35: Material

ここまでのまとめ

Page 36: Material

●ループ/副作用なしでもチューリング完全(スタック上限を無視すればだけど・・・)

●一見難しそうな部分も、連結リストを使って(思ったよりは)簡単に実装できる

●でも、Javaでは二度と同じことやりたくない(´・ω・`)面倒くさいし読みにくいし

Page 37: Material

ところで

Page 38: Material

ループ/副作用が無いってどういう事なんだろう

改めてコードを見なおしてみよう(`・ω・´)

Page 39: Material

このへんとか

Page 40: Material

このへんに注目

Page 41: Material

あとコレは最初に決めたルールだけどとっても大事

Page 42: Material

全体の特徴

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

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

(あるインスタンスの)メソッドが返却する値は引数によって決定する

Page 43: Material

言葉遊びは好きですか( ̄ω ̄)?

Page 44: Material

言い換えてみよう

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

Page 45: Material

引数→入力 x

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

Page 46: Material

返却する値→出力 y

二つの変数 x と y がありメソッドの出力 y は

入力 x によって決定する

Page 47: Material

X と y の語順を入れ替える

二つの変数 x と y がありメソッドは、入力 x に対して

出力 y の値を決定する

Page 48: Material

ちょっと補完

二つの変数 x と y がありメソッドは、入力 x に対して

出力 y の値を決定する規則が与えられている

Page 49: Material

倒置法

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

が与えられているメソッド

Page 50: Material

W ikiped ia 「関数(数学)」より

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

が与えられているとき、

変数 y を 「xを独立変数とすると関数」

或いは簡単に「xの関数関数」という。

Page 51: Material

まぁ、あの・・・

半分こじつけなので、細かい定義についてツッコミ入れられると

泣いちゃうしか無いんですが。

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

Page 52: Material

とにかく

引数によって決まった値を返すメソッド(の返却値)は

数学的な意味数学的な意味での関数と表現できる

Page 53: Material

つまり

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

とゆー事です(`・ω・´)

Page 54: Material

関数と言えばラムダ計算

λ ← こんなん出てきました

Page 55: Material

W ikiped ia先生再登場

ラムダ計算(lambda calculus)は、理論計算機科学や数理論理学における、

関数の定義と実行を抽象化した計算体型である。

ラムダ算法とも言う。

Page 56: Material

(´・ω・`)?

よーわからん

Page 57: Material

あの、あれ、

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

Page 58: Material

簡単な例

(λx . 5 + x) 3

Page 59: Material

こいつが関数

(λx . 5 + x) 3

Page 60: Material

これが引数

(λx . 5 + x) 3

Page 61: Material

引数 x に 3 を束縛

(λx . 5 + x) 3

Page 62: Material

結果

5 + 3 = 8

Page 63: Material

この作業を簡約簡約といいます

Page 64: Material

2つの引数を取る関数

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

(λxy . x + y)とか書けます

Page 65: Material

高階関数

(λfx . f (x * 2)) (λn . n + 2) 5

Page 66: Material

高階関数

(λfx . f (x * 2)) (λn . n + 2) 5

Page 67: Material

高階関数

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

= 10 + 2 = 12

Page 68: Material

部分適用

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

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

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

Page 69: Material

部分適用

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

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

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

ここに注目!

Page 70: Material

注:カリー化≠部分適用

Page 71: Material

閑話休題

Page 72: Material

先ほどのJavaプログラム関数の組み合わせでプログラミングしてるなら

ラムダ式で表現できるんじゃなかろーか

Page 73: Material

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のプログラムを線形リストに変換する部分のコード片

Page 74: Material

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ステップなのでセミコロンいらないです

Page 75: Material

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は省略してしまいましょう

Page 76: Material

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)

ラムダ式っぽく体裁

詳しい説明はしませんが推論が可能なので型も省略します

Page 77: Material

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)

副作用が無いのでこれらのラムダ式は展開しても等価です

Page 78: Material

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)))

できたはできたけど

これは酷いこれは酷いどこか間違えてそうな気もする

Page 79: Material

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コンビネータの話をしてくれるに違いないその他色々と説明のために無視してる事あるです

Page 80: Material

と・・・とにかく!

関数的に書いたプログラムは

ラムダ式で表現できる ラムダ式で表現できる というイメージが伝わればOKです

(´・ω・`)わざわざ宣言的に書かれているものを

一行にまとめれば読めなくなるのは当たり前

Page 81: Material

結局何が言いたかったかと言うと

関数型プログラミングの基本的なテクニック 高階閑数 カリー化 部分適用

これらは全て「ラムダ計算」の上に成り立っているという事。

 (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを勉強しようという事だったので、Javaを採用してみたらひどい目にあいました

Page 82: Material

結局何が言いたかったかと言うと

関数型プログラミングの基本的なテクニック 高階閑数 カリー化 部分適用

これらは全て「ラムダ計算」の上に成り立っているという事。

 (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを勉強しようという事だったので、Javaを採用してみたらひどい目にあいました

【注】発表後読み返してみたんですが

カリー化はちょっと違うかもです(´・ω・`)

Page 83: Material

でも実際には副作用が無いと辛い事も多いですよね

計算途中の状態を引数として引き回してる

Page 84: Material

なので・・・

ScalaやLispは、副作用を認めています Haskellにはモナドがあります Haskellにはモナドがあります Haskellにはモナドがあります Haskellにはm(ry

Page 85: Material

でもやっぱり

プログラムに副作用が無く、式として表現できる事によって得られるものは大きいです

(`・ω・´)関数型言語を学ぶ事によって「純粋なプログラム」を心がけるようになれば、スパゲッティなプログラムから解放される

・・・はず!! 何より、関数型言語は面白い!!何より、関数型言語は面白い!!

Page 86: Material

なんか、終止Javaの話ばっかりしてた気もしますが多分それは気のせいです

さぁみなさん、一緒に

関数型脳になりましょう関数型脳になりましょう((`・`・ωω・・´)´)

おしまい