2009-06-24
【第4話】もっとプログラムっぽく!(前編)
少年オッカムル | | ![]()
「ね、そろそろ時間じゃない?」
向かいの席のミラっちが僕に声をかけてくる。15時か、、確かにそろそろリンダさんの来る頃かな…
僕とミラっちは、勉強会に使ってるいつものファミレスにいる。それぞれ本を読んだりノートに落書きをしたりしながらリンダさんを待っていたところだ。ミラっちがどうしても参加させろと言って聞かないのでリンダさんにメールで尋ねてみたところあっさり快諾。曰く「人数多い方が楽しいよね!」だそうで。正直、僕としてはなんかモヤモヤ感があったりなかったりで複雑な気分・・・
「うん。そうだね。 ・・・ところでミラっちはさっきから何やってんの?」
なにやらノートに絵を描いているっぽいのが、実はずっと気になってた僕。
「あー、コレ? マインドマップ」
「マインドマップ??」
「そう。教科書の内容をそのまま暗記しても仕方ないでしょ? 簡単に言えば、どんな事が書いてあったか後で思い出せるように作る絵的なメモって感じかな?まぁ正式に勉強したワケじゃないし、アタシのは自己流だけどね」
ペロっと舌を出すミラっち。うーん、やると決めたら本格的に取り組むところがスゴイよなぁ、、と思う。
「この調子じゃ、僕なんか簡単に追い抜かれちゃいそうだなぁ、、、」
「そうね。そんな事もあるカモね」
シシシと笑うミラっち。ちぇっ。否定しろよー。見てろよ、僕だって負けないぞ!
なんて気合が入ったところでリンダさんが到着した。僕らを見つけて一瞬意外そうな顔をしてたけど、すぐに笑顔で近づいてくる。
「あら。友達って、女の子だったんだ?」
ミラっちの方をチラリと見てリンダさんが言う。
「あ、はい。クラスメートのミラっちです。ほら、挨拶挨拶!」
「どもー」
右手を挙げて陽気に挨拶するミラっち。事前に「余計なコトは言うなよー」ってしっかり頼んでおいて良かった。無難に済みそう・・・とか思ってたら、
「へー、美人さんねー。なに、キミの彼女?」
リンダさんから余計な質問が飛んできた。
「え? いや、そんなんじゃないですよ! ねぇ?ミラっち?!」
慌てて同意を求める僕。
「あはは、違いますよー」
とミラっち。あれれ?なんかちょっとテンション下がってる??
「へぇー。。」
そっか、そうなんだ、と言いながら僕の隣に腰を下ろすリンダさん。
「クリームソーダ、アイス2個入りですね?」
先読みして聞いてみる。
「正解!」
とリンダさんがニッコリ。やっぱりお気に入りメニューらしいぞ。
予想が当たって少し得意げに注文する僕を見ていたミラっちが無言で立ち上がり僕の隣に腰を下ろす。
「?」
「画面」
僕と目を合わせずにミラっちは続ける。
「画面、見えないでしょ?横からじゃないと」
あれ?なんか機嫌悪かったりする。。。?
「ああ、そだね」
気のせいだよな。うん。さぁ、とにかく勉強を始めよう。
・・・・・・
「さて、じゃあ今日は第5章からだったね」
リンダさんはテーブルの上の教科書を開く。
「はい。条件分岐のところです」
「じゃあいつもの通り、条件分岐がどういう時に使うものか?ってトコから説明してくれるかな?」
「はい。えーと、条件分岐は簡単に言うと『場合分けの為の技術』だと思います」
「と言うと?」
「今まで学んできたやり方だけだと、決められた処理を順々にこなすようなプログラムを作る事は出来ても、特定の条件に合わせて処理内容を分けるような事が出来ませんでした。条件分岐を使うと、プログラムの中にいくつかの道筋を作り、状況に応じて処理内容を変えるような事が出来るようになります」
隣ではミラっちが頷いている。大体同じような理解なのかな、多分。
「うんうん。良く出来ました」
と、リンダさんもニッコリ。よしよし。
条件分岐の構文
if 条件 then 式 else 式
「ここでの注意は、then と else の後に来る式それぞれが同じ型じゃないとダメってところね」
と、リンダさんは教科書上に人差し指で丸を描く。
「はい。」
頷く僕とミラっち。
「最初の方は結構楽でしたね。挙動が教科書の内容と違うってコトもあんまりなかったし」
「そうね。じゃあ、気になったところを教えてくれるかな?」
「はい。まずは絶対値を求めるプログラムを書くときの、符号の反転ですね」
そういって僕は次のコードを入力した。
(* 目的:入力された実数Xの絶対値を求める *) (* abs_value : float -> float *) let abs_value x = if x > 0.0 then x else -x (* テスト *) let test1 = abs_value 2.8 = 2.8 let test2 = abs_value (-2.8) = 2.8 let test3 = abs_value 0.0 = 0.0 printfn "test1: %b" test1 printfn "test2: %b" test2 printfn "test3: %b" test3
「これは40ページに出てくるプログラムの最終形なんですけど、この else の後に本来なら『-.』を使います。ただ、F# でそれをすると、、、」
> [Loading C:\Program Files\SharpDevelop\3.0\bin\test6.fs] test6.fs(5,24): error FS0001: The type 'float' does not support any operators named '~-.'
「エラーになっちゃうんですよね。で、初回の話を思い出して『-.』を『-』にしてみますと・・・」
> [Loading C:\Program Files\SharpDevelop\3.0\bin\test6.fs] test1: true test2: true test3: true namespace FSI_0006 val abs_value : float -> float val test1 : bool val test2 : bool val test3 : bool
「この通り動くようになります。という事で、符号を反転させる演算は単に『-』を使うべし!ってのが、第1点目の差分になると思います」
「そうね。良く出来ました!」
笑顔で頷くリンダさん。うーん。今日はなんか調子がイイぞ!
「じゃあ、次はアタシが説明する!」
黙って聞いてたミラっちがキーボードを近づけようとパソコンの向きを変える。おー。ミラっちもヤル気満々だなぁ。
(* 目的:月と日を受け取ってきたら星座を返す *) (* seiza : int -> int -> string *) let seiza tsuki hi = if tsuki = 1 then if 1 <= hi && hi <= 19 then "山羊座" else if 20 <= hi && hi <= 31 then "水瓶座" else "なし" else if tsuki = 2 then if 1 <= hi && hi <= 18 then "水瓶座" else if 19 <= hi && hi <= 29 then "魚座" else "なし" else if tsuki = 3 then if 1 <= hi && hi <= 20 then "魚座" else if 21 <= hi && hi <= 31 then "牡羊座" else "なし" else if tsuki = 4 then if 1 <= hi && hi <= 19 then "牡羊座" else if 20 <= hi && hi <= 30 then "牡牛座" else "なし" else if tsuki = 5 then if 1 <= hi && hi <= 20 then "牡牛座" else if 21 <= hi && hi <= 31 then "双子座" else "なし" else if tsuki = 6 then if 1 <= hi && hi <= 21 then "双子座" else if 22 <= hi && hi <= 30 then "蟹座" else "なし" else if tsuki = 7 then if 1 <= hi && hi <= 22 then "蟹座" else if 23 <= hi && hi <= 31 then "獅子座" else "なし" else if tsuki = 8 then if 1 <= hi && hi <= 22 then "獅子座" else if 23 <= hi && hi <= 31 then "乙女座" else "なし" else if tsuki = 9 then if 1 <= hi && hi <= 22 then "乙女座" else if 23 <= hi && hi <= 30 then "天秤座" else "なし" else if tsuki = 10 then if 1 <= hi && hi <= 23 then "天秤座" else if 24 <= hi && hi <= 31 then "蠍座" else "なし" else if tsuki = 11 then if 1 <= hi && hi <= 21 then "蠍座" else if 22 <= hi && hi <= 30 then "射手座" else "なし" else if tsuki = 12 then if 1 <= hi && hi <= 21 then "射手座" else if 22 <= hi && hi <= 31 then "山羊座" else "なし" else "なし" (* テスト *) let test1 = seiza 6 11 = "双子座" let test2 = seiza 6 30 = "蟹座" let test3 = seiza 9 17 = "乙女座" let test4 = seiza 10 7 = "天秤座"
「OCamlなら解答例として示されてるこのファイルで動くんだけど、F#でやると、、、」
> [Loading C:\Program Files\SharpDevelop\3.0\bin\test7.fs] test7.fs(5,1): error FS0191: TABs are not allowed in #light code
「TABキーは使うな、って出るみたい」
「へーっ!」
思わず僕は感心してしまった。敢えて模範解答のファイルで動作確認するなんて発想、僕にはなかったよ。こういう発見をしたりするんだなぁ。
「で、じゃあ TAB キーを使うのを止めたらドウなるか試してみたの」
そんな僕を無視して続けるミラっち。なんか切ない・・・
test7.fs(7,1): error FS0010: Unexpected keyword 'elif' in implementation file > [Loading C:\Program Files\SharpDevelop\3.0\bin\test7.fs]
「そうすると今度は別の理由で怒られちゃう」
ああ、このエラーは僕も見たぞ。
「で、この『elif』ってのを調べてみたんだけど、どうやら F# の場合、一般的に else if を elif って書く習慣があるみたい」 【→ いげ太さんに頂いたコメントに合わせて修正】
と言ってミラっちは別のファイルを読み込む。
(* 目的:月と日を受け取ってきたら星座を返す *) (* seiza : int -> int -> string *) let seiza tsuki hi = if tsuki = 1 && 1 <= hi && hi <= 19 then "山羊座" elif tsuki = 1 && 20 <= hi && hi <= 31 then "水瓶座" elif tsuki = 2 && 1 <= hi && hi <= 18 then "水瓶座" elif tsuki = 2 && 19 <= hi && hi <= 29 then "魚座" elif tsuki = 3 && 1 <= hi && hi <= 20 then "魚座" elif tsuki = 3 && 21 <= hi && hi <= 31 then "牡羊座" elif tsuki = 4 && 1 <= hi && hi <= 19 then "牡羊座" elif tsuki = 4 && 20 <= hi && hi <= 30 then "牡牛座" elif tsuki = 5 && 1 <= hi && hi <= 20 then "牡牛座" elif tsuki = 5 && 21 <= hi && hi <= 31 then "双子座" elif tsuki = 6 && 1 <= hi && hi <= 21 then "双子座" elif tsuki = 6 && 22 <= hi && hi <= 30 then "蟹座" elif tsuki = 7 && 1 <= hi && hi <= 22 then "蟹座" elif tsuki = 7 && 23 <= hi && hi <= 31 then "獅子座" elif tsuki = 8 && 1 <= hi && hi <= 22 then "獅子座" elif tsuki = 8 && 23 <= hi && hi <= 31 then "乙女座" elif tsuki = 9 && 1 <= hi && hi <= 22 then "乙女座" elif tsuki = 9 && 23 <= hi && hi <= 30 then "天秤座" elif tsuki = 10 && 1 <= hi && hi <= 23 then "天秤座" elif tsuki = 10 && 24 <= hi && hi <= 31 then "蠍座" elif tsuki = 11 && 1 <= hi && hi <= 21 then "蠍座" elif tsuki = 11 && 22 <= hi && hi <= 30 then "射手座" elif tsuki = 12 && 1 <= hi && hi <= 21 then "射手座" elif tsuki = 12 && 22 <= hi && hi <= 31 then "山羊座" else "なし" (* テスト *) let test1 = seiza 6 11 = "双子座" let test2 = seiza 6 30 = "蟹座" let test3 = seiza 9 17 = "乙女座" let test4 = seiza 10 7 = "天秤座" printfn "test1: %b" test1 printfn "test2: %b" test2 printfn "test3: %b" test3 printfn "test4: %b" test4
「こんな感じで直してあげれば、、」
> [Loading C:\Program Files\SharpDevelop\3.0\bin\test8.fs] test1: true test2: true test3: true test4: true namespace FSI_0008 val seiza : int -> int -> string val test1 : bool val test2 : bool val test3 : bool val test4 : bool
「ちゃんと動くようになるみたい」
と、ここまで説明したところでミラっちはじっとリンダさんの目を見つめる。
「良く出来ました」
パチパチと拍手をするリンダさん。それを見て、ようやくちょっとミラっちの顔に笑顔が戻ったような気がする。へー。ミラっちでも緊張してたりしたのかな?
「そうね。5章のポイントは今の2つかな。1つは『符号の反転方法』、そしてもう1つが『elif』って書き方。私がチェックした限りでも、OCamlとの差分はそんなところだと思うよ。ああ『elif』については『else if』って書けないってコトじゃないんだけどね」
第1関門はどうやら合格みたい。やったー!
・・・・・・
「じゃあ、次は6章のエラーの種類について見ていきましょうか」
クリームソーダが届いて更に機嫌が良くなったリンダさんが僕らを促す。
「何か、OCaml との動作の違いに気が付いたところはあったかな?」
「結構ありますね」
今度は僕の番だ。
let x 3 ;;
「まずコレですが、、」
let x 3 ;; --------^^ stdin(14,9): error FS0010: Unexpected incomplete structured construct at or before this point in binding. Expected '=' or other token.
「エラーという意味では同じなんですけど、エラーの種別がちょっと違いますね」
「そうだね。それから?」
アイスを食べながらリンダさんが次を促す。
if 2 > 3 then else 3 ;;
「これについても同じですね」
if 2 > 3 then else 3 ;; --------------^^^^ stdin(15,15): error FS0010: incomplete structured construct at or before this point in if/then/else expression
「教科書に書いてあるのより、詳しいエラーの中身がレポートされるようになってるみたいです」
「うん。そうみたいね。それから?」
3 + 4) * 2 ;;
「これなんかもそうです。」
3 + 4) * 2 ;; -----^ stdin(16,6): error FS0010: Unexpected symbol ')' in interaction. Expected incomplete structured construct at or before this point, ';', ';;' or other token.
「あと、これも」
(3 + 4 ;;
(3 + 4 ;; -------^^ stdin(17,8): error FS0010: Unexpected symbol ';;' in expression. Expected ')' or other token.
「あ、まぁこの最後のは、教科書に書いてある動作も殆ど似たようなもんですけど」
「それだけ?」
「あ、次のページを忘れてた。コレもありましたね。」
新^"横浜" ;;
新^"横浜" ;; ^ stdin(20,1): error FS0039: The value or constructor '新' is not defined.
「そうね。こうして見ると、F# と OCaml って結構違うよね。まぁ『何がエラーになるのか?』ってコト自身は一緒なワケだから、そんなに深刻な話じゃないけどね」
うんうん、と頷きながらリンダさん。
「じゃあ、次はアタシ!」
再びパソコンを奪ってミラっちが入力を始める。
2 + a * 4 ;;
2 + a * 4;; ----^ stdin(21,5): error FS0039: The value or constructor 'a' is not defined.
「未定義の変数に関するエラーについても似たような感じみたい」
話しながら入力を続けるミラっち。
lat x = 3 ;;
lat x = 3 ;; ^^^ stdin(22,1): error FS0039: The value or constructor 'lat' is not defined.
A ;;
A ;; ^ stdin(23,1): error FS0039: The value or constructor 'A' is not defined.
「型エラーについてもやっちゃうよ」
物足りなかったらしくキーを打ち続けるミラっち。結構ハマってきてるのかも?
3 + 2.5 ;;
3 + 2.5 ;; ----^^^ stdin(24,5): error FS0001: The type 'float' does not match the type 'int'
「ただ F# の場合、これはエラーにならなかったのよね」
1.0 + 2.0;;
> > > > > > > > > > > val it : float = 3.0
「そう。実数同士の演算の時につかう『+.』を使わないからね」
リンダさんが答えるより前に僕が頷く。それを見てクスっと笑うリンダさん。えへへ。
「あと、ちょっとここのエラーは不親切な気がする」
構わず続けるミラっち。
sin 3.14 0. ;;
sin 3.14 0. ;; ^^^^^^^^ stdin(26,1): error FS0003: This value is not a function and cannot be applied
「こう言われると、sin って関数は無いみたいに見えるんだけど、、、」
sin 3.14 ;;
「こうすると値が求まっちゃうのよね。。。」
> > val it : float = 0.001592652916
「うーん、確かに」
と僕。
「そうね。エラーのメッセージは細かくて親切だけど、完璧ってコトはないっぽいよね」
同意するリンダさん。
まぁ、アテにするのは良いけど、信じすぎるのは良くないってコトだよな。
「じゃあ、次はパターンマッチの話に進もうか?」
よしよし。なんだかプログラミングの話っぽくなってきたぞー。
【本日使った本】
プログラミングの基礎 (Computer Science Library)
- 作者: 浅井健一
- 出版社/メーカー: サイエンス社
- 発売日: 2007/03
- メディア: 単行本
- 購入: 13人 クリック: 300回
- この商品を含むブログ (90件) を見る
次回につづく。

ちょっと流れ的に「F# では else if と書けない」と誤読を誘いそうな気もしないでもないですが。elif も else if も、どっちも書けますよ。
> error FS0010: Unexpected keyword 'elif' in implementation file
わかりづらいエラーになっちゃってますが、ここでの問題は、外側の tuki による分岐では if と else が同じインデントにあるのに、内側の hi による分岐で if よりも else のインデントが下がっていることです。ちゃんと if と else のインデントを揃えてやれば OK です。
F# では、Lightweight Syntax といって、デフォルトでこれが on になってます。これにより、いわゆるオフサイド ルールと、いくつかの略記法が有効になります。もしこれがイヤなら、#light "off" としてオフサイド ルールをオフることもできます(tab 文字もインデントに有効になります)。
確かに『「F# では else if と書けない」と誤読を誘いそうな気もしないでもない』というご指摘は正しいですね。「F# の場合、一般的に else if を elif って書く習慣があるみたい」くらいにしておくのがイイかな、と今思いついたので修正しておきまっす。
あと「#light "off" 」の件なのですが、そういう事が出来るというところまでは私も検索できていたのですが、それを使うと今度は「printfn "test1: %b" test1」以下の行の実行が上手くいかなくなってしまうっぽい(「っぽい」と言うのは、私が確信を持って言える原因を見つけられなかったため)なぁ、、なんてまた別の壁につきあたり、何だかちょっと深追いしていくのが面倒になってしまいまして、主人公達に現状のような解決策をとらせた次第です。。『初心者がプログラミングを学ぶ』という物語上、「Lightweight Syntax 」がこの先のストーリー展開を考える上で肝になって欲しくないなぁ、、とも思いつつ。。
いずれにしても、私にとっては大変勉強になりました(if と else のインデントの件は初めて認識しました)。ありがとうございます!こういう指摘を頂けてこそブログとして公開している甲斐があるというものですので、今後とも宜しくお願いしますー。
トップレベルに直に式を書くのでなくて、なにかに束縛してみてください。do でもいいし、let _ = ... でもいいです。そして、省略されていた ; をちゃんと書いてあげる。たとえばこんな感じです。
do
printfn "test1: %b" test1;
printfn "test2: %b" test2;
printfn "test3: %b" test3;
printfn "test4: %b" test4
コメント欄に書いたので消えてしまったのですが、、do 束縛は、let 束縛でそうするように、インデントしておいたほうがよいです。インデントしてもしなくても正常に動作はしますが、見やすさのため。
> do とかって「プログラミングの基礎」では取り扱ってないトピック
do 束縛は F# 独自な "はず" ですので。「なるだけ OCaml 互換で」ということなら、機能的にほぼ同じ let () = ... か、あるいは let _ = ... を使うのがよいだろうと思います。
インデントの件はナルホドですね。確かに「Python とかならココでインデントするのになー」 とか思ってました。#light を"off"にしているので動くには動くが、doを使うならインデントすべき、ってコトですね。
それと、2ちゃんスレで「let () = ...」の意味するところを先ほど学びました! 以降はこっちを使っていこうかと思います。アドバイス有難うございました!!