Upgrade to Pro — share decks privately, control downloads, hide ads and more …

関数型プログラミングの考え方について / Scheme入門

24f2b4b72c52a14b1d863a6731734f69?s=47 soukouki
September 14, 2021

関数型プログラミングの考え方について / Scheme入門

2021-09-12に開催された低レベル勉強会で使用したスライドに、追記したものです。

Scheme入門はLambda Chipを触れるレベルまで解説することを目標としており、基礎的な内容の多くを割愛しています。

24f2b4b72c52a14b1d863a6731734f69?s=128

soukouki

September 14, 2021
Tweet

Transcript

  1. 低レベル勉強会 前半 関数型プログラミングの考え方について 後半 Scheme入門 2021-09-14 追記版

  2. 自己紹介 sou7です。会津大学の学部1年生です これまでに触れた関数型言語(と、それを含むマルチパラダイム言語)は Elm Scheme Scala Rust(ほんのちょっと) です。 そこまで関数型バリバリな人ではないので、理解が曖昧な箇所も多いですが、できる 限りやっていければと思います

    ちなみにSchemeも一部理解した程度です・・
  3. 各種SNSアカウントなど Twitter GitHub Discord @sou7_ _ _ @soukouki sou7#0094

  4. 大まかな流れ 関数型言語の考え方について ファーストクラスクロージャや、参照透明性などの考え方について触れます Scheme入門 SchemeインタプリタのGaucheを用いて、実際にSchemeのコードを書いて、Lispの構 文にある程度慣れるところまで行きたいです

  5. 関数型言語の考え方について

  6. 他のプログラミングパラダイムとの比較 手続き型プログラミング オブジェクト指向プログラミング マルチパラダイム

  7. 手続き型プログラミング 状態を書き換えて計算を進める 変数への代入 ループ 手続き呼び出し 例:C, brainfuckなど

  8. オブジェクト指向プログラミング 手続きを抽象化する手法の一つ データと手続きを一塊として扱う 例:C++, Javaなど

  9. マルチパラダイム 関数型とオブジェクト指向の融合を目指す 例:Scala オブジェクト指向言語への関数型の導入 例:Java, Pythonなど マルチパラダイム言語が増えていて、パラダイムの違いが曖昧になってきている

  10. 関数型プログラミングの特徴 (数学的な)関数を評価することで計算を進める クロージャを値として扱える(ファーストクラスクロージャ) 関数を値のように扱える 関数ポインタとの違い クロージャの式の中にクロージャの式を書ける (C言語では、関数の中で関数を定義することは出来ない) クロージャを生成する際に、外側の変数の値を使用できる 参照透明である 同じ引数で関数を呼び出すと、必ず同じ結果が帰ってくる

    関数が状態を持たないことを保証できる 並列処理や自動テストに都合が良い 例:Lisp, Haskell, Elmなど
  11. 参照透過でない例 呼び出す度に状態が変化する 状態の変化(副作用という)がある int count() { static int cur =

    0; cur++; return cur; }
  12. ループについて ループはない ループは副作用を前提としているため int sum = 0; for(int i=0; i<100;

    i++){ sum += i; }
  13. それでも、副作用が必要なときもある 入出力など、どうにかして副作用を扱う必要があります なので、関数型言語は様々な手法でそれを乗り越えようとしています 諦めて、参照透明でない書き方ができるようにする Scheme, Common Lisp, ML モナド 例:Haskell

    一意型 1度しか参照できない型を用意して、過去の時点のインスタンスに触れないようにする 例:Clean The Elm Architecture(TEA) 例:Elm FRP, 遅延ストリーム, etc...
  14. 前半まとめ 関数型プログラミングの特徴として、ファーストクラスクロージャや参照透明な どがある 副作用が必要なときのために、参照透明でない描き方ができる言語もある 避けられるなら避けるべき

  15. 休憩 質問などもどうぞ 後半はSchemeについて話します。Gaucheを用いるので、暇な人は http://practical-scheme.net/gauche/download-j.html sudo apt-get install gauche などでインストールしたり、適当に遊んでみてください。

  16. Schemeの入門 Lispの構文に慣れるところまでを目指します

  17. インストール Schemeインタプリタの一つ、Gauche(ゴーシュ)を使います。 http://practical-scheme.net/gauche/download-j.html sudo apt-get install gauche gosh コマンドで起動できます 環境変数

    GAUCHE_READ_EDIT をセットしておくと、後々の作業が楽になるかもしれませ ん export GAUCHE_READ_EDIT=1
  18. Hello world! (print "Hello world!") ; セミコロンより後ろはコメントです はい、これであなたもLisperの仲間入りです!

  19. 値 123 3.14 "str" 今回使う値のリテラルです。これ以外にもいろいろなリテラルがあります #t #f これらはそれぞれ真と偽を表す真偽値です

  20. 計算 (+ 12 34) (- 1000 334) (* 10.0 -3)

    Lispの構文, 関数呼び出しは、このように丸括弧で囲われ、空白で区切る形で書きます 一番左の要素( + , * )が演算子、それ以外の要素は引数です (+ 12 34 56) このように、一度に複数の値を計算することもできます
  21. クイズ (+) (*) ところで、この2つの式の結果はどんなものになるでしょうか? (ヒント: 和と積の単位元)

  22. 変数 (define x 123) x (define x+1 (+ x 1))

    x+1 変数は define を使って定義します 2つ目の要素が変数名、3つめの要素が代入する値になります(正確には束縛する名前と 値、のほうがいいのでしょうか) 変数名には英数字だけでなく、様々な記号も使えます
  23. ラムダ式 次に、クロージャを生成するラムダ式について触れます (lambda (left right) (+ left right)) このように lambda

    を使うと、2つの引数 left と right を取り、その和を返すクロージ ャーが返ります ((lambda (left right) (+ left right)) 12 34) これを呼び出すときには、丸括弧で囲った上で引数を渡します (lambda () 123) 引数を取らないクロージャも作れます
  24. (lambda (a) (lambda (b) (+ a b))) ラムダ式は入れ子にでき、また外側のラムダ式の変数を内側のラムダ式で使えます (lambda ()

    (print "Hey!") (print "Heyhey!")) lambda の後ろ側にはいくつも要素を増やすことができ、それらは前から順番に実行さ れます (lambda () (define x 123) (print x) x) ; replでは123が2回見られますが、1つ目はprint、2つ目は返り値のものです
  25. クイズ 引数が1つで、返り値が「引数が1つで、渡された2つの値の和を表示するクロージャ」 であるクロージャ を書いてみましょう

  26. 模範解答 (lambda (x) (lambda (y) (print (+ x y))))

  27. 関数定義 さて、ようやく待ちに待った関数定義です。ですが、実は define と lambda で出来て しまいます (define square (lambda

    (x) (* x x))) (square 2) ファーストクラスクロージャーを持つ言語ならではです (define (square x) (* x x)) 上の式では define と lambda で入れ子になっていて分かりづらいため、このようなシ ンタックスシュガーが用意されています 実際のコードではこちらを使うことが多い印象です
  28. 関数-等価判定の方法 (equal? 1 2) (equal? "str" "str") equal? 関数を用いると、オブジェクトが等しいかどうかを判定できます 他にも

    eq? , eqv? などありますが、今回は省略します(気になる方はGaucheユーザー リファレンスマニュアルを見てみてください。)
  29. 関数-数値の比較 (= 1.0 1.0) (>= 12 45) (< 1 4

    9 16) 比較の演算子は、他のプログラミング言語でよくあるようなものが使えます。また、 幾つも引数を渡してあげると、引数の順に並んでいるときのみ真を返します。ポーラ ンド記法の強いところです
  30. 条件分岐 (if (equal? 3 3) "true" "false") (if #f (print

    "true") (print "false")) (if "objectは真と評価されます" "3つ目の要素はなくても動きます") if は2つか3つの要素を受け取ります。1つ目は真偽値(真偽値を返す式)、2つ目は真の ときに実行される式、3つ目は偽のときに実行される式です 2つ目の例では、 print 関数が呼ばれるのは if で条件分岐をした後のため、 (print "true") は実行されません 他にも cond , unless , case などの条件分岐がありますが、今回は省略します(気にな る方はGaucheユーザーリファレンスマニュアルを見てみてください。)
  31. クイズ 絶対値を求める関数 abs を定義してみましょう ; 組み合わせる関数や書き方 (define square (lambda (x)

    (* x x))) (<= 2 4) (if (equal? 1 1) (print "true") (print "false"))
  32. 模範解答 (define (abs x) (if (< x 0) (- x)

    ; xが0より小さいときは符号を反転 x)) ; xが0以上のときはxを返す
  33. 再帰呼び出し 関数の中で、(結果的に)自身と同じ関数を呼び出すことを再帰呼び出し、と言います これを使うことで、ループ構文などを使わずに繰り返しの処理を行えます (define (factorial n) (if (= n 0)

    1 (* n (factorial (- n 1))))) 階乗を計算するプログラムです。少し複雑ですが、例えば (factorial 4) が呼ばれた 際にどういうふうに処理されるかを追っていくといいかもしれません
  34. クイズ 4 3 2 1 のように与えられた値から順番に表示する関数 print-numbers を定義してみましょう

  35. 模範解答 (define (print-numbers n) (print n) (if (> n 1)

    (print-numbers (- n 1)))) ; nが1以上なら、n-1を引数に再帰呼び出しを行う
  36. 今回は取り扱わなかった基礎的な内容 他に覚えると良い構文 set! , cond , begin , let リスト操作に関する関数

    car , cdr , cons , list , take , drop , map , for-each 今回は最重要な構文のみに絞ったため割愛しましたが、他にも沢山の関数や機能があ ります。それらはGaucheユーザーリファレンスマニュアルに載っているので、ぜひ眺 めてみてください 今回はこのスライドを見ていただきありがとうございました!
  37. 参考文献 1. 関数プログラミング入門 前半部分の多くで参考にさせていただきました https://www.slideshare.net/tanakh/ss-3580292 2. 関数型プログラミングはまずは純粋関数型言語を用いて、考え方から理解しよう 関数型言語プログラミングと、その他のパラダイムの違いが気になる方は読むといい かもしれません https://zenn.dev/ababup1192/articles/fb25358a570763

    3. The Elm Architecture(公式ドキュメントの日本語訳) https://guide.elm-lang.jp/architecture/ 4. Gaucheユーザーリファレンスマニュアル https://practical-scheme.net/gauche/man/gauche-refj/index.html 5. 計算機プログラムの構造と解釈(SICP) https://www.vocrf.net/docs_ja/jsicp.pdf