Material

4,279 views

Published on

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

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,279
On SlideShare
0
From Embeds
0
Number of Embeds
2,653
Actions
Shares
0
Downloads
8
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Material

  1. 1. 関数型脳になろう!
  2. 2. どうも!( ̄ω ̄*)ちゅーんでーす
  3. 3. 自己紹介 東京都在住の下っ端プログラマ 7ヶ月ほど前にHaskellと運命の出会いを果たす 誰かに語りたくてしょうがない所にたまたまこの 会を知って何も考えずにノミネート
  4. 4. 自己紹介 東京都在住の下っ端プログラマ 7ヶ月ほど前にHaskellと運命の出会いを果たす 誰かに語りたくてしょうがない所にたまたまこの 会を知って何も考えずにノミネート 初心者です!お手柔らかに><
  5. 5. ちゅーんと関数型言語 Haskell大好きです ちょっとだけLisp書きました Scalaもちょっとだけ書きました モナドって可愛いですね 言ってるほどHaskell使えてません でもHaskell大好きです HaskellハァハァHaskellハァハァHaskellハァハァHaskellハァハァHaskellハァ ハァHaskellハァハァHaskellハァハァクンカクンカ(;´Д`)スーハースハー
  6. 6. と、いうわけで
  7. 7. Javaを書いてきました 皆さんも好きですよね、Java
  8. 8. こんな感じに
  9. 9. こんな感じに
  10. 10. mainメソッドにこんなのを書くと・・・
  11. 11. こんなんが出てきます
  12. 12. ようは  ループ構文禁止  局所変数使用禁止  フィールドは全てfinal  if、switch分岐禁止  三項演算子と再帰処 理と、コンストラクタ のフィールドの初期 化だけでBrainF*ckのイ ンタプリタ作った
  13. 13. BrainF*ckかの有名な難解プログラミング言語+-><[]., の8つの命令で構成されシンプルながらも完全チューリング
  14. 14. ちなみに、参照透明とかなんとかの関係で 入力はここに書きます
  15. 15. 何が言いたいかというと ループなんか無くなって 変数の再代入ができなくなって 手続き的に逐次処理を書かなくたって if/switch文なんか使わなくたって BrainF*ckが実装できる=完全チューリング なのだ!(Java凄い!)
  16. 16. 何が言いたいかというと ループなんか無くなって 変数の再代入ができなくなって 手続き的に逐次処理を書かなくたって if/switch文なんか使わなくたって ( ̄ω ̄*)え?スタックオーバーフロー? なんですかそれwww BrainF*ckが実装できる=完全チューリング なのだ!(Java凄い!)
  17. 17. それにしても・・・
  18. 18. とっても読みにくいです
  19. 19. ファイルもやたら多くなっちゃったし・・・全体で446行あります(´;ω;`)ブワッ (普通に書けば100行以内)
  20. 20. やっぱり変数への再代入やループ構文は必要ですね! 手続き型最高!!
  21. 21. じゃなくて・・・( ̄ω ̄;)
  22. 22. 一生懸命書いたんです! 評価点を探しましょう
  23. 23. 例えばループ  素直に書くとこんな風 に冗長で何をやって いるか解らなくなり やすい。  ネスト数を保持するた めの変数 l が邪魔
  24. 24. 例えばループ  ループの開始や終端 を一文字づつ探して いるのでループ内の ステップ数が多くな ればなるほど高コス ト。  予めプログラムを解 析しておけば問題解 決? →どうやって???
  25. 25. 実は・・・ 最初は同じように1文字づつループの端を検索す る方法を考えてみた でも再帰でそれをやるとスタック領域の消費がハ ンパねー もうちょっと効率よく制御する方法は無いものか →BrainF*ckのコードを 連結リストで表現してみたらどうだろう
  26. 26. 連結リスト car cdr1 2 3 × nil
  27. 27. BrainF*ck のコードを連結リストに++[­­]+ + × - - ×
  28. 28. こうすれば(わりと)低コストで ループを制御できるメモリの値が 0 ならこっち × - 0 以外ならこっちを実行して 戻ってきた返り値を元に 再び実行する - ×
  29. 29. 連結リストとS 式連結リストはS式にして出力すればデバッグに便利! ++[>++<­] ↓ (+ . (+ . ((> . (+ . (+ . (< . (­ . Nil))))) . (+ . (+ . Nil))))) S式は複雑なリストを再帰で簡単に出力できる
  30. 30. 再帰と連結リスト このように、再帰処理と連結リストは相性が良い! というか、再帰的なデータ構造が再帰処理で操 作しやすい。(木構造とか) という事がわかった(`・ω・´)
  31. 31. もう一つ問題が・・・メモリとポインタはどうやって表現しよう? 配列の値の書き換えは禁止してるけど ポインタの指し示す値を書きかえる度に 配列を作り直すのはナンセンス
  32. 32. メモリも連結リストのペア! B A ×C D × ここが現在のポインタ
  33. 33. メモリも連結リストのペア!ポインタを右に移動する場合 C B A ×新しく作ってつなげる D ×C 副作用のある処理は禁止なので cdrを書き換えられないから捨てる
  34. 34. メモリも連結リストのペア!ポインタを左に移動する場合(逆の事をする) B A × 捨てる B C D ×
  35. 35. ここまでのまとめ
  36. 36. ● ループ/副作用なしでもチューリング完全 (スタック上限を無視すればだけど・・・)● 一見難しそうな部分も、連結リストを使って (思ったよりは)簡単に実装できるでも、Javaでは二度と同じことやりたくない● (´・ω・`)面倒くさいし読みにくいし
  37. 37. ところで
  38. 38. ループ/副作用が無いって どういう事なんだろう改めてコードを見なおしてみよう(`・ω・´)
  39. 39. このへんとか
  40. 40. このへんに注目
  41. 41. あとコレは最初に決めたルールだけど とっても大事
  42. 42. 全体の特徴 基本的にJavaなので全体を通してはオブジェクト指向してるけど 全てのメソッドが 必ず何か値を返す必要があり(あるインスタンスの)メソッドが返却する値は 引数によって決定する
  43. 43. 言葉遊びは好きですか( ̄ω ̄)?
  44. 44. 言い換えてみようメソッドが返却する値は引数によって決定する
  45. 45. 引数→入力 x 変数 x がありメソッドが返却する値は入力 x によって決定する
  46. 46. 返却する値→出力 y二つの変数 x と y があり メソッドの出力 y は入力 x によって決定する
  47. 47. X と y の語順を入れ替える 二つの変数 x と y があり メソッドは、入力 x に対して 出力 y の値を決定する
  48. 48. ちょっと補完 二つの変数 x と y があり メソッドは、入力 x に対して出力 y の値を決定する規則が与えられている
  49. 49. 倒置法 二つの変数 x と y があり、入力 x に対して、出力 y の値を決定する規則 が与えられているメソッド
  50. 50. Wikipedia 「関数(数学)」より 二つの変数 x と y があり、入力 x に対して、出力 y の値を決定する規則 が与えられているとき、 変数 y を 「xを独立変数とすると関数」 或いは簡単に「xの 関数」という。
  51. 51. まぁ、あの・・・ 半分こじつけなので、細かい定義についてツッコミ入れられると 泣いちゃうしか無いんですが。 詳しいことはWikipedia見てください
  52. 52. とにかく 引数によって決まった値を返す メソッド(の返却値)は数学的な意味での関数と表現できる
  53. 53. つまり 今回、Javaを使って「関数」の組み合わせによってプログラミングをした とゆー事です(`・ω・´)
  54. 54. 関数と言えばラムダ計算λ ← こんなん出てきました
  55. 55. Wikipedia 先生再登場 ラムダ計算(lambda calculus)は、 理論計算機科学や数理論理学における、関数の定義と実行を抽象化した計算体型である。 ラムダ算法とも言う。
  56. 56. (´ ・ω ・`) ?よーわからん
  57. 57. あの、あれ、 α変換とか、β簡約とかめんどくせー話は置いといて
  58. 58. 簡単な例(λx . 5 + x) 3
  59. 59. こいつが関数(λx . 5 + x) 3
  60. 60. これが引数(λx . 5 + x) 3
  61. 61. 引数 x に 3 を束縛 (λx . 5 + x) 3
  62. 62. 結果5+3=8
  63. 63. この作業を簡約といいます
  64. 64. 2つの引数を取る関数 (λx . (λy . x + y)) は、略記で (λxy . x + y) とか書けます
  65. 65. 高階関数(λfx . f (x * 2)) (λn . n + 2) 5
  66. 66. 高階関数(λfx . f (x * 2)) (λn . n + 2) 5
  67. 67. 高階関数(λn . n + 2) (5 * 2) = 10 + 2 = 12
  68. 68. 部分適用(λf . f 2)((λxy . x * y) 5) = (λf . f 2)(λy . 5 * y) = (λy . 5 * y) 2 = 10
  69. 69. 部分適用(λf . f 2)((λxy . x * y) 5) = (λf . f 2)(λy . 5 * y) ここに注目! = (λy . 5 * y) 2 = 10
  70. 70. 注:カリー化≠部分適用
  71. 71. 閑話休題
  72. 72. 先ほどのJavaプログラム関数の組み合わせでプログラミングしてるなら ラムダ式で表現できるんじゃなかろーか
  73. 73. 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のプログラムを 線形リストに変換する部分のコード片
  74. 74. 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ステップなのでセミコロンいらないです
  75. 75. 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は省略してしまいましょう
  76. 76. 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) ラムダ式っぽく体裁 詳しい説明はしませんが推論が可能なので 型も省略します
  77. 77. 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) 副作用が無いので これらのラムダ式は展開しても等価です
  78. 78. できたはできたけど これは酷い どこか間違えてそうな気もするPARSEPROGRAM := λ idx odr . odr == ; || odr == ] ? Containeridx Nil : odr == [ ? (λ sorce . (λloopin loopout . Containerloopout.idx (ProgramList loopin.state loopout.state)) sorce(PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1)))(PARSEPROGRAM (idx+1) (program.charAt (idx+1)) : packOrderodr (PARSEPROGRAM (idx+1) (program.charAt (idx+1)))
  79. 79. ちなみに、自分自身をこうやって再帰呼び出しするのは 正式なラムダ計算としてはルール違反 です(´・ω・`) 誰かがYコンビネータの話をしてくれるに違いない その他色々と説明のために無視してる事あるですPARSEPROGRAM := λ idx odr . odr == ; || odr == ] ? Containeridx Nil : odr == [ ? (λ sorce . (λloopin loopout . Containerloopout.idx (ProgramList loopin.state loopout.state)) sorce(PARSEPROGRAM (sorce.idx+1) (program.charAt (sorce.idx+1)))(PARSEPROGRAM (idx+1) (program.charAt (idx+1)) : packOrderodr (PARSEPROGRAM (idx+1) (program.charAt (idx+1)))
  80. 80. と・・・とにかく! 関数的に書いたプログラムはラムダ式で表現できるというイメージが伝わればOKです (´・ω・`)わざわざ宣言的に書かれているものを 一行にまとめれば読めなくなるのは当たり前
  81. 81. 結局何が言いたかったかと言うと 関数型プログラミングの基本的なテクニック 高階閑数 カリー化 部分適用 これらは全て「ラムダ計算」の上に成り立ってい るという事。  (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを 勉強しようという事だったので、Javaを採用してみたらひど い目にあいました
  82. 82. 結局何が言いたかったかと言うと 関数型プログラミングの基本的なテクニック 高階閑数 【注】 カリー化 発表後読み返してみたんですが カリー化はちょっと違うかもです(´・ω・`) 部分適用 これらは全て「ラムダ計算」の上に成り立ってい るという事。  (´・ω・`)特定の言語に拘らずに関数型の考え方そのものを 勉強しようという事だったので、Javaを採用してみたらひど い目にあいました
  83. 83. でも実際には副作用が無いと辛い事も多いですよね 計算途中の状態を 引数として引き回してる
  84. 84. なので・・・ ScalaやLispは、副作用を認めています Haskellにはモナドがあります Haskellにはモナドがあります Haskellにはモナドがあります Haskellにはm(ry
  85. 85. でもやっぱり プログラムに副作用が無く、式として表現できる 事によって得られるものは大きいです (`・ω・´)関数型言語を学ぶ事によって「純粋なプ ログラム」を心がけるようになれば、スパゲッティ なプログラムから解放される ・・・はず!! 何より、関数型言語は面白い!!
  86. 86. なんか、終止Javaの話ばっかりしてた気もしますが 多分それは気のせいです さぁみなさん、一緒に 関数型脳になりましょう(`・ω・´) おしまい

×