見出し画像

関数型プログラミング事始め (16) 繰り返し - Lisp超入門5

関数型プログラミングがはじめての方へ贈る入門の書
前節:配列 次節:代入
参考書:
・五味 弘「はじめてのLisp関数型プログラミング」技術評論社(2016)
・大山口 通夫、五味 弘「プログラミング言語論」コロナ社(2008)
・五味 弘「関数型プログラミングと数学(ITと数学)」技術評論社(2021)

プログラミングの基本構造のひとつに繰り返しがあります。繰り返しは順次、分岐とともにプログラム制御の基本で、プログラムを構造化するために必要です。しかし繰り返しはその中で、状態を持ち、副作用を生じる可能性が高いです。確かに副作用と言っても、その繰り返し構造の中に限定しての副作用であるかもしれません。もちろん、それでも副作用であることには違いありませんがそれでもその影響は限定的で、そんなに心配することではありません。しかし広域的に副作用を生じる危険性もあります。
Lispにもfor文やwhile文などの繰り返しが使えます。副作用に注意して、これらを使いましょう。

(8) プログラム制御

Lispにはプログラム制御として、一般の言語と同様に分岐と繰り返し構造が用意されています。

まずはislispを起動してください。なおISLisp処理系はLisp処理系の導入で紹介していますので参照してください。

> ISLisp Version 0.80 (1999/02/25)
>
ISLisp>

(a) 分岐

ISLispの分岐命令にはifcondcasecase-usingがありますが、基本はifです。ifは以下のように使います。

ISLisp>(if (= 1 1) 10 20)
10
ISLisp>(if (= 1 2) 10 20)
20

上記のように(if 条件 then節 else節)の形式でifを使います。
なおLispで偽を表す値はnilであり、また空リスト()はnilと同値で、こちらも偽となります。真の値はt(true)ですが、偽でない値(non nil)も真として扱います。

ISLisp>(if nil 1 2)
2
ISLisp>(if () 1 2)
2
ISLisp>(if t 1 2)
1
ISLisp>(if 0 1 2) ; 0はnilでないので真
1

(b) 繰り返し

ISLispの繰り返しにはforwhileがあります。ここでは繰り返しで多く使われるforを紹介します。

ISLisp>(for ((x '(1 2 3 4 5) (cdr x))) ((null x 6)) (write x))
(1 2 3 4 5)(2 3 4 5)(3 4 5)(4 5)(5)6

forは(for 繰り返し仕様* (終了判断 形式*) 本体)の形式をしていて、繰り返し仕様は((制御変数1 初期値1 ステップ処理1) …)であり、複数の繰り返し仕様を定義できます。

上記ではxが制御変数であり、初期値は(1 2 3 4 5)になり、繰り返し毎(繰り返し本体の実行後)に制御変数xに(cdr x)を代入します。
最初の繰り返しではxは(1 2 3 4 5)であり、本体(body)の(write x)では(1 2 3 4 5)が表示されます。次の繰り返しではxは(cdr x)が代入され、(2 3 4 5)が格納され、それが(write x)で表示されます。
そしてxが()になったとき、(null x)が真になり、forの繰り返しが終了し、このforは6を返します。

(c) その他の制御構造

分岐と繰り返し構造以外の制御構造には、prognblock return-fromcatch throwtagbody gounwind-protectがあります。prognが単純な順次制御で、その他は途中脱出などの機能を追加した順次制御になります。

またLispは多くの箇所では暗黙的に順次制御が行われています。例えば、関数定義の本体やforの本体には、上記のprognを使うことなく、複数の形式(form)を書くことができます。

(d) 繰り返しは悪魔の所作?

繰り返しは制御変数を使い、繰り返し本体で多くの場合、状態を変化させます。このように考えると、繰り返しでは制御変数に対する破壊的代入が行われ、制御変数に対する副作用が生じます。また本体ではforの外側にある変数に対して破壊的代入を行い、副作用を生じさせる可能性が高いです。

このうち、制御変数に対する副作用はそれほど心配する必要はありません。制御変数を制御するためだけに使っているときは、他に影響を与えることはありません。でも、万が一、制御変数の値を利用して状態変化をする場合には副作用が生じるかもしれません。これは制御変数に対する悪魔的な所業、所作です。注意してください。中止してください。

一方、本体でfor外の変数(外部変数)を使って、その値を状態変化させている場合は注意が必要です。この使い方はforにおける一般的な使い方で、forの多くの場合がこれに該当します。

副作用を防ぐためには、forを実行する前に、外部変数の過去の値をコピーして保存しておき、forの実行が終わったときに、外部変数の値を過去の値に戻す必要があります。

しかしこれには多くのコストが掛かります。このため、副作用が生じていることを念頭に置き、最大限の注意を持って、プログラミングするという逃げも立派な戦略です。なお純粋関数型言語では(裏でこっそりと)コピーしていますので、手動で行う必要はありません。

Lispは自由なので(というよりも、一般の言語と同様に何も対処していないので)手動でコピーするか、最大限の注意をする必要があります。

(次回予告)Lisp超入門6

次回もOK! ISLisp処理系を使って、Lispの超入門の第6回を紹介する予定です。お楽しみに。

参考:プログラミング言語はどれがお得?(前編)|五味弘 (note.com)
参考:プログラミング言語はどれがお得?(後編)|五味弘 (note.com)


いいなと思ったら応援しよう!

五味弘
よろしければサポートをお願いします!