再帰でループします。
好きな数字を心に思ってください。その数字に3を足します。さらに2をかけます。今度は4を足してください。2で割ります。最後に、最初に思った数字を引いてください。
さて。好きな数字を思ってもらいました。さらに計算をしたので、その結果は誰にも分かりません。それでは、最後に出た数字を、しっかりと思い浮かべてください。しっかりと。
分かりました。
その数字は、「5」です。
というわけで、サイキックなid:shunsukです。今日は、再帰ックなお話をします。再帰を使うと、繰り返しの処理を行うことができます。それが、最初は難しい。Schemeの最初のつまずきポイントだと思います。つまずき聡です。
nの階乗を求めるプログラムです。fact関数を定義していますが、その中で自分自身(fact関数)を読んでいます。これを再帰と言います。
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) (fact 5) ;=> 120
(* 5 (* 4 (* 3 (* 2 1))))がイメージできますか?難しいですね。Schemeにはdoという繰り返し構文もあります。
(define (fact n) (do ((m n (- m 1)) (x n (* x (- m 1)))) ((= m 1) x))) (fact 5) ;=> 120
mをnで初期化して、処理ごとに(- m 1)しています。また、xをnで初期化して、(* x (- m 1))しています。そして、(= m 1)になったらxを返しています。doの方が使いづらいでしょ?そこで、Schemeでは再帰が多用されます。
もう一度、再帰のプログラムに戻ってみましょう。
(define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) (fact 5) ;=> 120
関数の中で関数を呼ぶと、関数呼び出しの履歴が積み上げられて(スタックされて)いきます。そうすると、パフォーマンスが低下するのです。ここで、fact関数で最後に評価されるのは、nと(fact (- n 1))との掛け算です。実は、最後に再帰関数呼び出しを行うと、スタックされずに、単純にジャンプするようになる裏ワザがあるのです。と言っても、誰もが知っている裏ワザなので、むしろ表ワザです。
下のコードでは、fact-iter関数の最後がfact-iter関数の呼び出しになっています。
(define (fact n) (fact-iter n n)) (define (fact-iter n x) (if (= n 1) x (let ((next-n (- n 1))) (fact-iter next-n (* x next-n))))) (fact 5) ;=> 120
これにより、効率よく処理を行うことができるようになります。これを、末尾再帰と言います。ラーメン、つけ麺、末尾再帰。
2つの関数に別れてしまいましたが、1つにまとめることもできます。loop構文を使います。mにnの値をセット。xにnの値をセット。
(define (fact n) (let loop((m n) (x n)) (if (= m 1) x (let ((next-m (- m 1))) (loop next-m (* x next-m)))))) (fact 5) ;=> 120
下のような書き方もできます。letrecはletのパワーアップバージョンです。iterの中でiterを呼べるのがポイント。
(define (fact n) (letrec ((iter (lambda (m x) (if (= m 1) x (let ((next-m (- m 1))) (iter next-m (* x next-m))))))) (iter n n))) (fact 5) ;=> 120
というカンジで、再帰のプログラムを載せてみました。解ってる人にはカンタンすぎて、解らない人にはサッパリ解らない。愛しいけれど憎いヤツです。
9LISP 003で、再帰までやるかどうか分かりませんが、もっと解りやすい解説ができるように勉強してきますね。9LISP 003で、再帰までやるかどうか分かりませんが、もっと解りやすい解説ができるように勉強してきますね。繰り返し処理してみました。
letで局所変数を定義します。
iPhoneの裏側に家族のプリクラを貼っています。プリクラを見られるたびに「奥さん、かわいい」と言われるのですが、その妻にプリクラを貼ることを強制されているという事実に気づいて欲しいid:shunsukです。iPhoneにプリクラとか、ジョブズに怒られるよ。。。
今回は、局所変数を定義する方法を勉強します。大域変数は、defineでしたね。
(define x 2) (define y 3) (+ x y) ;=> 5
局所変数は、letで定義します。Let's define local variables!
(let ((x 2)) (+ x 1)) ;=> 3
上の例では、xに2が結びついています。括弧が2重になってる?あわてなすな(熊本弁で「あわてるな」)。xとyの2つを同時に定義してみます。
(let ((x 2) (y 3)) (+ x y)) ;=> 5
xとyは、letの括弧の中でしか使えません。ウソだと思ったら、試してご覧なさいよ。
letは入れ子にすることもできます。
(let ((x 2)) (let ((y 3)) (+ x y))) ;=> 5
そうすると、外側のletで定義したxを、内側のletで使うことができます。下の例では、yにxの値(つまり2ですね)を結びつけてます。
(let ((x 2)) (let ((y x)) (+ x y))) ;=> 4
let*というのもあって、これを使うと、直前に定義した変数を使うことができます。
(let* ((x 2) (y x)) (+ x y)) ;=> 4
上の例では、yを定義するときに、直前のxを使うことができるわけです。
ちなみに、id:shunsukは局所的に変なところがあります。iPhoneにプリクラを貼るとかね。
9LISP 003は、2009/10/31(土)です。
こんにちは。ちょっと風邪気味なid:shunsukです。風邪薬を飲んだら良くなったのですが、飲むのをやめたら悪くなったので、風邪薬を飲んだら良くなったのですが、飲むのをやめたら悪くなりました。こうやって、人生を送っていくのかなと思いながら風邪薬を飲んでいます。
さて、風邪のため参加が危ぶまれる9LISP 003ですが、今週の土曜日です。2009/10/31(土)です。参加表明はこちらからどうぞ。みんな様子見ながらギリギリで登録するよねー。
気になる9LISP 003の内容はこちら!
今回は、国際交流会館でやりますので、気をつけてくださいね。国際交流会館だけに、海外からの参加もお待ちしています。ただし、外国語ができるメンバーはいません。Schemeで筆談しましょう。
さらに今回は、モーニングコーヒーミーティングがあります。勉強会の前の1時間。ゆっくりギークな話をしませんか?ヘンタイ達に囲まれて、自分がフツーだということを実感できます。ウソです。メンバーはみんな、さわやかです。ただし、id:shunsukをのぞく。
いつも通り、課題が出ています。今回からは「チャレンジ問題」も出ています。腕試ししてみてください。問題が解けなくても、勉強会で丁寧に教えてもらえますので安心してくださいね。怖いおにいさんなどいません。絶対にいません。
ああ。頭が痛くなってきました。やっぱり風邪薬を飲んだ方がようさそうです。でも、風邪薬を飲むと、眠くなりますよね。まあ、飲まなくても眠くなるんですけど。。。
条件分岐はifとcondです。
チャ、チャ、チャチャチャ、チャチャチャ、チャチャチャーン。人生には様々な分岐点があります。あの時、チョコではなくバニラを注文していれば。あの時、フォークではなく箸で食べていれば。。そのような経験は誰にでもあるでしょう。今夜もまた奇妙な世界へ足を踏み入れてしまった者たちがいます。もしも人生をやり直せるとしたら。あなたは唐揚げ弁当を選びますか?ハンバーグ弁当を選びますか?
まず紹介するのは、ifです。ifの後に「条件式」「条件を満たすときに評価したい式」「条件を満たさないときに評価したい式」を書きます。
absという絶対値を求める関数がありますが、これを作ってみましょう。my-absという、私のためのabs関数です。abs関数を恋人(二次元を含む)に送りたい人は、your-abs関数にしても構いません。ええ、構いませんとも。
(define (my-abs x) (if (< x 0) (* x -1) x)) (my-abs 5) ;=> 5 (my-abs -5) ;=> 5
他のプログラミング言語と違って、ifは2分岐しかできず、「条件を満たさないときに評価したい式」を省略できないことに注意してください。
では、もっとたくさん分岐したい欲張りなお前は、どうすればいいのでしょうか?そのときは、condを使ってください。cond説明します。cond説明しますが、今説明します。
数字を英語に変換するnum->en関数を書いてみましょう。でも、お前は馬鹿なので、3より大きな数字を知りません。条件に当てはまらないときに評価したい式は、elseに書いてくださいね。
(define (num->en n) (cond ((= n 1) "one") ((= n 2) "two") ((= n 3) "three") (else "I don't know."))) (num->en 3) ;=> "three" (num->en 5) ;=> "I don't know."
これで、条件分岐の基礎はOKですね。では、複雑な条件をかけたいときはどうするのでしょう。40歳以下で、有名大学卒で、年収1,000万円以上で。。。大丈夫、andとorが使えます。ただし、他のプログラミング言語とチョット違うので注意してくださいね。
andは、引数を左から見て行って、#fが出た時点で#fを返します。#fが出なかった場合は、最後の引数を返します。論よりコード。
(and 1 #f 3) ;=> #f (and 1 2 3) ;=> 3
orは、引数を左から見て行って、#f以外が出た時点でそれを返します。#fしか出なかった場合は、#fを返します。論よりコード。
(or #f 2 #f) ;=> 2 (or #f #f #f) ;=> #f
誰です?Scheme爆発しろ!なんて言っているのは、この仕様によってエレガントなコードが書けるんですよ。たぶんね。
奇妙な世界へ通じる扉は、あたなのすぐ傍に開いています。次のその扉を開けるのは、あなたなのかもしれません。。。
9LISPのはてなブックマークが始動しました。
LISPやSchemeを学びたくなる読み物
この記事は今日書いていますが、明日のために書いています。あ、今日というのは、今日からすると昨日のことです。明日というのが今日のことです。つまり、この記事を書いてるのは昨日で、昨日からすると、今日、明日の記事を書いています。えっと。つまりですね。明日(今日のことです)は用事があってブログを更新できないので、今日(昨日のことです)ブログを書いてるわけです。つまり、俺がお前でお前が俺です。
今回は、valvallowさんがまとめてくれた「LISPやSchemeを学びたくなる読み物」のリンク集を掲載します。9LISPの公式サイトにあがってるのをパクってるだけです。
なぜ、LISPなのか?が見えてくる気がしますが、見えてこなくても構わない気もします。
- Beating the Averages
- How To Become A Hacker: Japanese
- 総論 複数のプログラミング言語を学ぶ意義 | 日経 xTECH(クロステック)
- 「計算機プログラムの構造と解釈(SICP)」を読み終えて - higepon blog
- なぜ関数プログラミングは重要か
valvallowさんにはブクマの方もお願いしているので、いろんな記事が集まってくる予定です。かなりマニアックなネタまで拾ってきますよ。
ということで、今日、つまり明日というか昨日は「LISPやSchemeを学びたくなる読み物」を紹介しました。他にこんなのがあるよ、という読み物があれば、ぜひコメントくださいね。
関数を定義してみます。
おとうさんスイッチ。おじいじゃんも可。いきますよぉー。
- おとうさんスイッチ「ら」
- ラクダ本を読む
- おとうさんスイッチ「り」
- LISPの処理系を実装する
- おとうさんスイッチ「る」
- Luaはプロトタイプベースだと説明する
- おとうさんスイッチ「れ」
- レキシカルスコープ
- おとうさんスイッチ「ろ」
- 論よりコード
さあ。遊んでるヒマはないですよ。Schemeで関数を書きます。まずは、Hello, wold!ですね。お約束です。お約束すぎて面白くないので、Hello, work!にしておきます。時事ネタ。時事ネタ。
; hello-l関数を書く (define hello-l (lambda () "Hello, work!")) ; hello-l関数を実行 (hello-l)
DrSchemeを使っている人は、上のエディタに書いて、おもむろに「実行」ボタンをクリックします。下のコンソールに結果が表示されましたね?表示されなかった人は、コードが間違っていないか、これまでの人生に誤りは無かったか確認してください。
セミコロン(;)の行はコメント行です。私のDrScheme on Macでは日本語が入力できなかったので、笑って済ませました。ところで、helloの後ろのlって何?これは、lambda記法(ラムダ記法)のlです。ということは、他の記法があるの?あるんです。
; hello-m関数を書く (define (hello-m) "Hello, work!") ; hello-m関数を実行 (hello-m)
これは、MIT記法です。省略したカンジですね。lambda記法とMIT記法。どちらが一般的に使われるのでしょうか。9LISPではどちらかに統一した方がいいと思いますが、どうですか?
今度は、引数をとる関数を書いてみます。
(define sum-l (lambda (a b) (+ a b))) (sum-l 1 2)
aとbという引数をとって、aとbを合計しています。ちゃんと動きましたか?動かなかった人は、矢田亜希子と八田亜矢子を間違っていないか確認してください。上のコードは、lambda記法。MIT記法だと。。
(define (sum-m a b) (+ a b)) (sum-m 1 2)
カンタンですね。よかったですね。私はドロリッチを冷蔵庫に冷やしているのを忘れてて、カントリーマアムを食べてしまいました。ですから、Schemeで関数を定義するモチベーションなんて持ち合わせておりませぬ。ええ。持ち合わせてなどおりませぬとも。。。I'm not motivated to define a function of Scheme.