$30 off During Our Annual Pro Sale. View Details »

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

soukouki
September 14, 2021

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

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

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

soukouki

September 14, 2021
Tweet

More Decks by soukouki

Other Decks in Programming

Transcript

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

    View Slide

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

    ちなみにSchemeも一部理解した程度です・・

    View Slide

  3. 各種SNSアカウントなど
    Twitter GitHub Discord
    @sou7_ _ _ @soukouki sou7#0094

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. 関数型プログラミングの特徴
    (数学的な)関数を評価することで計算を進める
    クロージャを値として扱える(ファーストクラスクロージャ)
    関数を値のように扱える
    関数ポインタとの違い
    クロージャの式の中にクロージャの式を書ける

    (C言語では、関数の中で関数を定義することは出来ない)
    クロージャを生成する際に、外側の変数の値を使用できる
    参照透明である
    同じ引数で関数を呼び出すと、必ず同じ結果が帰ってくる
    関数が状態を持たないことを保証できる
    並列処理や自動テストに都合が良い
    例:Lisp, Haskell, Elmなど

    View Slide

  11. 参照透過でない例
    呼び出す度に状態が変化する
    状態の変化(副作用という)がある
    int count() {

    static int cur = 0;

    cur++;

    return cur;

    }

    View Slide

  12. ループについて
    ループはない
    ループは副作用を前提としているため
    int sum = 0;

    for(int i=0; i<100; i++){

    sum += i;

    }

    View Slide

  13. それでも、副作用が必要なときもある
    入出力など、どうにかして副作用を扱う必要があります

    なので、関数型言語は様々な手法でそれを乗り越えようとしています
    諦めて、参照透明でない書き方ができるようにする
    Scheme, Common Lisp, ML
    モナド
    例:Haskell
    一意型
    1度しか参照できない型を用意して、過去の時点のインスタンスに触れないようにする
    例:Clean
    The Elm Architecture(TEA)
    例:Elm
    FRP, 遅延ストリーム, etc...

    View Slide

  14. 前半まとめ
    関数型プログラミングの特徴として、ファーストクラスクロージャや参照透明な
    どがある
    副作用が必要なときのために、参照透明でない描き方ができる言語もある
    避けられるなら避けるべき

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  18. Hello world!
    (print "Hello world!")

    ; セミコロンより後ろはコメントです

    はい、これであなたもLisperの仲間入りです!

    View Slide


  19. 123

    3.14

    "str"

    今回使う値のリテラルです。これ以外にもいろいろなリテラルがあります
    #t

    #f

    これらはそれぞれ真と偽を表す真偽値です

    View Slide

  20. 計算
    (+ 12 34)

    (- 1000 334)

    (* 10.0 -3)

    Lispの構文, 関数呼び出しは、このように丸括弧で囲われ、空白で区切る形で書きます
    一番左の要素( + , * )が演算子、それ以外の要素は引数です
    (+ 12 34 56)

    このように、一度に複数の値を計算することもできます

    View Slide

  21. クイズ
    (+)

    (*)

    ところで、この2つの式の結果はどんなものになるでしょうか?
    (ヒント: 和と積の単位元)

    View Slide

  22. 変数
    (define x 123)

    x

    (define x+1 (+ x 1))

    x+1

    変数は define を使って定義します
    2つ目の要素が変数名、3つめの要素が代入する値になります(正確には束縛する名前と
    値、のほうがいいのでしょうか)
    変数名には英数字だけでなく、様々な記号も使えます

    View Slide

  23. ラムダ式
    次に、クロージャを生成するラムダ式について触れます
    (lambda (left right) (+ left right))

    このように lambda を使うと、2つの引数 left と right を取り、その和を返すクロージ
    ャーが返ります
    ((lambda (left right) (+ left right)) 12 34)

    これを呼び出すときには、丸括弧で囲った上で引数を渡します
    (lambda () 123)

    引数を取らないクロージャも作れます

    View Slide

  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つ目は返り値のものです

    View Slide

  25. クイズ
    引数が1つで、返り値が「引数が1つで、渡された2つの値の和を表示するクロージャ」
    であるクロージャ
    を書いてみましょう

    View Slide

  26. 模範解答
    (lambda (x)

    (lambda (y)

    (print (+ x y))))

    View Slide

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

    (square 2)

    ファーストクラスクロージャーを持つ言語ならではです
    (define (square x) (* x x))

    上の式では define と lambda で入れ子になっていて分かりづらいため、このようなシ
    ンタックスシュガーが用意されています

    実際のコードではこちらを使うことが多い印象です

    View Slide

  28. 関数-等価判定の方法
    (equal? 1 2)

    (equal? "str" "str")
    equal? 関数を用いると、オブジェクトが等しいかどうかを判定できます
    他にも eq? , eqv? などありますが、今回は省略します(気になる方はGaucheユーザー
    リファレンスマニュアルを見てみてください。)

    View Slide

  29. 関数-数値の比較
    (= 1.0 1.0)

    (>= 12 45)

    (< 1 4 9 16)

    比較の演算子は、他のプログラミング言語でよくあるようなものが使えます。また、
    幾つも引数を渡してあげると、引数の順に並んでいるときのみ真を返します。ポーラ
    ンド記法の強いところです

    View Slide

  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ユーザーリファレンスマニュアルを見てみてください。)

    View Slide

  31. クイズ
    絶対値を求める関数 abs を定義してみましょう
    ; 組み合わせる関数や書き方

    (define square (lambda (x) (* x x)))

    (<= 2 4)

    (if (equal? 1 1) (print "true") (print "false"))

    View Slide

  32. 模範解答
    (define (abs x)

    (if (< x 0)

    (- x) ; xが0より小さいときは符号を反転

    x)) ; xが0以上のときはxを返す

    View Slide

  33. 再帰呼び出し
    関数の中で、(結果的に)自身と同じ関数を呼び出すことを再帰呼び出し、と言います
    これを使うことで、ループ構文などを使わずに繰り返しの処理を行えます
    (define (factorial n)

    (if (= n 0)

    1

    (* n (factorial (- n 1)))))

    階乗を計算するプログラムです。少し複雑ですが、例えば (factorial 4) が呼ばれた
    際にどういうふうに処理されるかを追っていくといいかもしれません

    View Slide

  34. クイズ
    4

    3

    2

    1

    のように与えられた値から順番に表示する関数 print-numbers を定義してみましょう

    View Slide

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

    (print n)

    (if (> n 1)

    (print-numbers (- n 1)))) ; nが1以上なら、n-1を引数に再帰呼び出しを行う

    View Slide

  36. 今回は取り扱わなかった基礎的な内容
    他に覚えると良い構文

    set! , cond , begin , let
    リスト操作に関する関数

    car , cdr , cons , list , take , drop , map , for-each
    今回は最重要な構文のみに絞ったため割愛しましたが、他にも沢山の関数や機能があ
    ります。それらはGaucheユーザーリファレンスマニュアルに載っているので、ぜひ眺
    めてみてください
    今回はこのスライドを見ていただきありがとうございました!

    View Slide

  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

    View Slide