LoginSignup
10
1

More than 3 years have passed since last update.

私を苦しませた Scheme の話

Last updated at Posted at 2020-12-13

こんにちは!ちょうど1ヶ月後が誕生日のハルークスです。今日は大学初めて本格的に学んだプログラミング言語 Scheme を紹介していきます。

Scheme

Schemeは関数型プログラミング言語の一つで、あまり実用的じゃないですがプログラミング初心者がアルゴリズムを学ぶのには適した言語だと言われています。

関数型言語とはなんぞや?

そもそもプログラミングにはいくつかの型があってその中にswiftのようなオブジェクト指向やSchemeのような関数型がある訳です。関数型言語は関数を組み合わせて問題を解いていくプログラミングの手法です。

プログラミングの呼吸 関数型 スキーム!

Recursion

次にオブジェクト指向の java と関数型の scheme でそれぞれの反復処理を書いて比べてみよう!

なんとなんと Scheme には for や while などのループの概念がありません!
じゃあどうやって繰り返しの処理をするの!?

簡単な具体例として 配列/List の要素の合計を求めるプログラムを見てみよう!

まずは Java で配列[12, 14, 79, 9] の合計を求めましょう。

java

int[] numArray = { 12, 14, 79, 9 };
int sum = 0;
for (int i = 0; i < numArray.length; i++) {
    sum += numArray[i];
}

このようにfor ループが使えるとシンプルに 配列 の中の数字の合計を計算しています。
しかし使えないとどうなるでしょう?

scheme

(define sum -----------|
  (lambda (foo)        |
    (if (null? foo)    |
        0              |
        (+ (car foo) (sum (cdr foo))))))     

sum という関数は foo という値を受け取り、 if 文以降を実行します。
それでは本題の「どうやって繰り返しの処理をするの!?」に戻りましょう。 

ループの概念がない Scheme では 再帰呼び出しという手法を使います。

ある関数𝑓が𝑓の中で自分自身である𝑓を呼び出すことを再帰呼び出しといいます

この例での 𝑓 は sum ですね。
sum の定義文の中に 再び sum が使われている部分を「再帰呼び出し」と呼びます。

    (if (null? foo)
        0
        (+ (car foo) (sum (cdr foo)))) 

car は List の1番目の要素を返し、cdr は List の car 以外の要素を返します。
実際に数字を入れて見ます。

(car (list 12 14 79 9))
→ 12
(cdr (list 12 14 79 9))
→ '(14 79 9)

foo に (list 12 14 79 9) を代入します。


foo には何が入る?
インプットする値はList型でなくてはいけません。今は配列のように扱っていますが、本当は配列と List はデータの構造などが違うので興味があったら調べてみてね!
コマンドラインに (sum (list 12, 14, 79, 9))と書くとコードが実行されます。もし (sum 3)(sum "hello world")など List じゃないものがインプットされるとエラーが出ます。

    (if (null? (list 12 14 79 9))
        0
        (+ (car (list 12 14 79 9)) (sum (cdr (list 12 14 79 9)))))

List は null (空)ではないので (+ (car foo) (sum (cdr (list 12 14 79 9))) を実行します。 car は List の1番目の要素を返すので 12, cdr はそれ以外を返すので (list 13 79 9) を返します。

(+ (car (list 12 14 79 9)) (sum (cdr (list 12 14 79 9)))
→ (+ 12 (sum (list 13 79 9)))

Scheme では a + b を (+ a b) と書くのでこれは (sum (list 13 79 9))に12を足すということになります。どんどん砕いていきましょう!

→ (+ 12 (sum (list 13 79 9)))
→ (+ 12 (+ 13 (sum (list 79 9))))
→ (+ 12 (+ 13 (+ 79 (sum (list 9)))))
→ (+ 12 (+ 13 (+ 79 (+ 9 (sum null))))))

List が空になりました!そうしたら if 文の最初に戻り(if (null? foo) 0 ) なので

→ (+ 12 (+ 13 (+ 79 (+ 9 (0)))))
→ 114

とやっと計算できる形になりました!
再帰呼び出し (Recursion) なんとなく分かったでしょうか?
ちなみに配列の要素の合計の114は私の誕生日です :yum:

Scheme でお絵かき

再帰呼び出しを使いこなせるようになるとこんなのも作れちゃいます!
Image from Gyazo
このとびすけ的なものは色んなサイズの四角と丸で作りました。実際のコードは300行以上あるのでここには載せないですがどんな感じにやったかをざっくり説明します。

scheme
(map list (map (section + <> 26) (iota 74)) (make-list 74 0))

output: (26 0)(27 0) (28 0) (29 0) ... (98 0)(99 0)

一つ一つの List が座標になって、最後に各点の色や形などを指定していきます。
再帰呼び出しはどこに使ってるのかというとiotasection で使いました。
section は激なが激むずなので iota だけ書いときます。

scheme
(define iota
  (lambda (n)
    (let kernel ([i 0])
      (if (= i n)
          null
          (cons i (kernel (+ i 1)))))))

(iota 5)
→ (0 1 2 3 4 5)

最後に

個人的には Scheme を大学で学んだとき、全然理解できなくてプログラミングが嫌いになりました:sweat: 今でも Scheme のことはそんなに好きではありませんが、他の言語を理解する際に役に立ちました。また、違う言語を学んだ後に scheme に戻ってみると前までわからなかったことが理解できるようになってたりしました。論理的思考力が鍛えられる言語なので、興味あったらトライしてみてね!

また、この記事を書くにあたり関数型についての記事をいろいろ読んできましたが、最終的に全部「関数型言語を知るためには実際に書くしかない!」と書いてありました。関数型だけではなくプログラミング言語全部がそうだと思います。自分で書かなきゃ完全に理解できないよね、っということで実践大事!

scheme

(display "今日の日にちは?: ")
(define date (read))
(display (string-append (number->string(- 25 date)) " days till Chirstmas!!"))

今日の日にちは?: 14
11 days till Chirstmas!!

明日はモンスターが何か書いてくれます!お楽しみに!

参考文献

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1