なにこれ
- プログラム言語論対策。
LISP
関数型言語ってそもそも何
関数がプログラムの主役になっている言語。
- ここでいう関数というのは何かを受け取ってそれをゴニョゴニョして何かを返すもの。
- 数学の関数の定義に数字以外も扱えるように付け足した感じがイメージとしては近い。
C言語にも関数あるじゃん
- C言語の関数は「これこれしてあれあれしてそれそれしなさい」という命令の列を並べたもの。
- 引数を受け取って返り値を返すので、関数っぽいということから関数と名付けられた。
関数型言語の関数はいろいろすごい
関数に何か値を渡してゴニョゴニョすると何か値が出てくる(この作業を評価という)。
- 関数を評価すれば必ず何か値が出てくるので、関数=値と考えちゃってもいい。
- 実際関数型言語では、関数と値は全く等価に扱われてる。
- 関数=値なんだから、関数の引数に関数渡したりもできるよね!!!!
- 関数=値なんだから、データ構造(リスト構造)とかに直接関数突っ込んだりもできるよね!!!!!
それができて何が嬉しいの?
- いずれわかる。
副作用って何
- 同じ引数を与えたら必ず同じ結果が返ってくる
- 周りの状態(変数とか)に影響を与えない
上の2つのどちらかでも崩してしまうような操作を副作用がある操作という。
副作用は悪
- 副作用はいろんな所に溢れてる。
- 入出力は外部に影響を与えるので、副作用がある操作。
- グローバル変数を変更するような関数も、副作用があるといえる。
- しかし副作用はプログラムの見通しを悪くする原因。
- どこで何が起きているのか管理できなくなってしまったり。
- 入出力などどうしても副作用を回避できないもの以外は副作用が出ないようにしたいよねー
- 関数型言語だとそれができます。
副作用がないとどういう変化が起きるのか
- 同じ引数を渡せば必ず同じ結果が返ってくるようになる
- その関数だけにらめっこすればいいことになって、プログラムの見通しが良くなる。
- 周りの環境に不必要な変化を与えないので、周りの環境に気を遣う必要がなくなる。
LISPって何
- 関数型言語の一つ。
リスト記法と呼ばれる手法を使って、プログラムとデータを全く同じ形でかけるようにした。
- どうやってるのかはあとで。
- 完全に副作用を排除しているわけではない。
- これについてもあとで。
- 今回の講義では多分LISPの変種のSchemeをやった。
LISPの構文要素
アトム
- C言語などでの変数、定数、文字列にあたるもの。
- アトムとアトムはスペースを使って区切る。
- 文字列はC言語と同じでダブルクオートで囲む。
- リスト
- これについては後で。
リスト構造
アトムを並べて丸括弧で囲ったものをLISPではリスト構造と呼ぶ。
- (a b c)
- (a)
- ( + 10 5)
- リストの中にリストを入れてもいい。
- ((a) b c)
- ((((a))))
S式
- リストとアトムを併せてS式という。
- S式はLISPのプログラムを書く時の基本の書き方。
式の評価
LISPではインタプリタに式を与えて、インタプリタがその式を評価して返すという形になっている。
式の評価の例
変数xがあったとする。その状態でLISPのプログラムにxとかくと、xが評価されて、xの値が返ってくる。
- S式に'をつけると、評価せずにそのままのものを返す。例えば'(+ 1 3)とかくと、(+ 1 3)が返ってくる。
- 定数や文字列をプログラムに書くと、評価されて、それが表しているものそのものを返す。例えば8901016とかくと、8901016が返ってくる。
もうちょっと複雑な式の評価
- LISPに足し算させたい。
- LISPには+という名前の足し算をするための関数が用意されている。
- この+関数を使って1+3を計算したい時は下のように書く。
1 (+ 1 3)
- リストの先頭に使いたい関数の名前、その後ろにずらずらと引数を書けばいい。
- リストは入れ子にできるんでこんな書き方もできる。
1 (+ (* 1 3) (* 2 -4))
- *は説明してないが、掛け算のための関数。
- LISPではデータとプログラムが同じように扱われているというのはこういうこと。つまり
- 上の式はLISPに計算させているのでプログラムと言えるし、下の式は単に数を並べただけなのでデータの並びと言ってしまえる。
評価の順番
- 次のS式はどういう手順で評価されるのか。
1 (+ (* 1 3) 3)
- LISPは最内評価という方法を取る。
- 引数を評価してから手続きを評価する。
- だからまずS式はつぎのようになる。
1 (+ 3 3) ;(* 1 3)を評価した
- さらにこうなる。
1 6 ;(+ 3 3)を評価した
number?
- 引数が数のときにt(真という意味)というアトムを返す関数number?というものがある。
- 次のS式はどういう結果を返すのだろうか。
関数を自分で作りたい
- 関数を自分で作るためにdefineというものをつかう。
- LISP演習ではdefunとやったと思うが、Schemeはdefine。
- 関数は次のように書く。
1 (define (関数名 関数の引数) 関数の中身のS式)
- こんな関数が例えば考えられる。
1 (define (square x) (* x x))
変数を''束縛''する
変数に値を与えることを変数を値で束縛するという。
- なんでこんな妙な言い方をするのかはあとで。
- 一旦束縛したら基本的に変数と値の対応は変えられないと考える。
- C言語みたいに何度も何度も変数に別の値を入れることはできない。
局所変数とlet
- いわゆる局所変数(ローカル変数みたいなもの)の束縛にはletを使う。
- 使い方は
1 (let (束縛したい変数とその値のリスト) 束縛した後に評価させたい式)
- 評価させたい式を一杯並べると、前から順番に評価されて、最後の式の評価結果が値として返ってくる。
1 (let ((x 1)) x) ; xを1で束縛したあとxを評価
- 一杯並べることもできる。
- letの束縛は全部が一気に行われる。
1 (let ((x 1) (y 2)) x) ; xを1で束縛すると同時にyを2で束縛した、そのあとxを評価
大域変数とdefine
- 大域変数(いわゆるグローバル変数)の束縛には関数定義と同じdefineをつかう。
- 例えば
1 (define hoge 0)
大域変数と局所変数の違いをみる
- ちょっと実験してみる。
- 評価は2回行われるので、2回値が返ってくる。
- 1回目の値は2行目のyが評価された時のやつで
1 0
- なぜなら、letでは同時に束縛が行われるのでyをhogeで束縛した時にはhogeを0で束縛した状態になってるから。
- 結局yも0で束縛される
- 2回目の値は3行目のhogeが評価された時のやつで
1 0
- なぜなら、2行目で出てくるhogeは局所変数なので、letが終わった瞬間に虚空の彼方へ消えていってしまうから。
- 3行目で見てるのは1行目でつくった大域変数のhoge。