星田さんの記事に対するコメント。
値が返らない!?そ、そうだったのか!!
うん。
実はこれまでも、「Schemeの破壊的変更は値を返さない」とは何度も書いている。
あ、誤解せんように。責めてるわけじゃない。
そうじゃなくって、言わば、プログラミングを勉強してる人ってそういう事を、例えば教科書とかで読んでも実感が沸かないんだよな。
100人中99人くらいはそうなの。たとえ仮に「仕様を完全に読んだ」人でさえ、いざプログラムを書くとすっぽ抜ける人が殆どだ、と言う事だ。
つまり、あることを実感するには、
- 自分で問題を設定する(つまり作りたいプログラムをプログラミングしようとする)
- そこで実際エラーを喰らう
と言うプロセスがどうしても欠かせないんだ。
ここでエラーを喰らってはじめて「仕様/教科書で書いてた事柄の意味」ってのを知るわけだよ。
だから今回のは、「Schemeでは破壊的変更と言う行為そのものは」値を返さない、と言う事を実感する良いチャンスだったと思う。
むしろ、ANSI Common Lispが異常なだけ、で、例えばPythonでも、
def foo(arg):
return arg += 1
なんてコードを書いたら怒られるんで、Schemeの形式の方がフツーなんです。
Gaucheのレコードみたいにmake-eki_tみたいな感じで書く方法無いんかい・・と思ってた
うん、ちと悩みどこではあったんだよね。いや、違う意味なんだけど。
星田さんのプログラミング学習の過程で、最初Racketだったわけだけど、今この発達時期にGaucheに手を出すのが是なのか否なのかちと判断が付かなかった。
もちろん僕が判断する話じゃないんだけど。
一昔前(っつーかもう随分と前か)だったら、プログラミング言語を「買う」必要性があったから「石に齧り付いてでも一つの処理系にこだわりなさい」と言う人が多かったんだけど。
今だといろんなプログラミング言語がタダで入手可能なワケじゃん?だから同時期にいろんなプログラミング言語や同一の言語内での別々の処理系を「ほぼ同時に」試すってのが悪い話とも思えないんだよな。
本音言うと、ANSI Common Lispにも触って欲しいトコもあるし(注釈で良く例として引き合いに出してるし)、むしろPythonも触り続けていて欲しいな、ってトコもあるし(ぶっちゃけ、Paizaやってた時点より「Lispを通したら」見える視点が変わってると思う・・・・・・如何に「C言語的プログラム法」がダメか実感するだろし)。何だかんだ言って、間接的にOCamlにも「事実上」触ってるわけだし。
そんな中で「Gaucheに手を出すのはダメ」とは言えないわな。少なくとも、使ってるPCの事情でオンラインでやらなアカン状況なわけだし。
ただ、構造体/レコード型を扱う、って時点でちとタイミングが悪かったよな、ってのはどのみちあるとは思うんだ。
以前にも書いたけど、元々、RacketがPLT Schemeだった時、構造体は
って言うANSI Common Lispを真似た形式だったんだよ。これがRacketになったら単純にstructになった。
いや、これって、Scheme的な命名規約的なモノから言うと「ホンマそれでエエの?」って改変だったんだよね。結構抵抗覚えた人も多かったんじゃないか。
何らかの定義をする場合、define-何とか、ってのが主に使われる命名規約だったから。
それだけじゃない。define-struct時代での構造体のコンストラクタはmake-何とかだった。だから星田さんの
make-struct-hero とか make-nausika や make-hero なんかもダメだし・・
って戸惑いは正しいんだよ。そうじゃなくなっちゃったRacketの方が、Scheme系だけじゃなくって、Lisp系言語を扱う「直感」に対しては異端の選択をしてるんだよな。
つまり生憎、Racketの場合、構造体のコンストラクタはmake-が無くなっちゃった。make-が無くても構造体のコンストラクタはその構造体に付けた名前自体になってる。
今、仮に、Racketズッポリだったら多分その辺は既に「使えてた」んで、問題は生じなかっただろう。しかし現実では、星田さんはGaucheのdefine-record-typeに引きずられてたんで、ここに於いてはRacketの作法に思い至らなかっただけ、なんだ。
ホント、単にタイミングが悪かったんだよな。Racketの様式とGaucheの(Schemeとしては普遍的な)様式の差で、言っちゃえば、つまらん迂回策を考えなアカン、と言う状況になっちゃってて、別に星田さんのせいではないんだわ。
そしたら例のRealm of Racketのオークバトルのところでこんな感じであったもんですからつい・・
うん、いい例だ(笑)。
いや、それ、リスナーの状態良く見てみて。
> (define player3 player 1) ; ここに返り値が無い! -> 実はdefineも「破壊的設定」だ。> (set-player-agility! player3 666) ; 破壊的変更だから返り値がない> player1 ; これを実行した事ではじめて返り値として中身が見れる(player 33 666 3)
プロンプト(>)が三つ連続に並んでる、って事は前2つには返り値がない、って事なんだ。
つまり、player1と言うデータ型を実行指定しないとそのデータは取り出せない、って事になる。
これがANSI Common Lispなら
と各プロンプトの下に何らかが表示される。
インタプリタ上で結果が表示される、ってのはそれが返り値だ、と言う意味だ。
これはヒント、なんだよ。少なくともこれで分かるのは、繰り返すけど、ANSI Common Lispではどんな作業も返り値を伴う、と言う事だ。インタプリタ上での表示に於いての情報量の差、ってのは明らかに意味があるんだ(※1)。
いずれにせよ、ここでSchemeとANSI Common Lispの差が対比によってハッキリと分かる。
僕と来たらまたREPLの基本忘れてしもてますよ・・
いや全然(笑)。
逆に考えるんだ。この時点でREPLを書ける地点にいるんだ、と。
C言語だろうとPythonだろうと、REPLの概念に到達するのは至難の業だ。延々とforループを書く事にこだわって、ソフトウェアのシステムと言う概念には(ほぼ)永久に辿り着けない。
逆にLispでのプログラミング学習は極めて短い時間でソフトウェアは全て原理的にはインタプリタだと言う概念へと誘う。
今、星田さんはあらゆるソフトウェアを自在に書ける、と言う奥義を獲得する場所に自ら入って来てる。
その第一歩だ。
肯定的に捉えよう。
またしてもCametanさんにお手数をおかけすることになったというね・・
あ〜、この辺はあんま気にせんでエエです。
まぁ、「どんなデータなんだ?」ってのは欲しいけど、写真かどうか、ってのはどうでもいいかなぁ。
少なくとも、codeタグとかpreタグが使えないgoo blogだと、意外にコードをテキストで貼っつけるのはあんま良い手段じゃねぇんだよなぁ・・・・・・。なんかのはずみでコードの一部が消失したりするんで、イライラする事間違いない、ってのは実感してるんで・・・・・・(インデントも消えるしね)。
※1: ちなみに、「ANSI Common Lispでは破壊的操作でも値を返す」と言う性質と、symbol-valueを利用して、ここで書いた実行例をまとめて
と、まるでSchemerが書くようなコードに仕上げる事が出来る。返り値の連鎖で、紛うことない、破壊的変更にも関わらず、スタイルだけは関数型プログラミングのコードだ。
ただし、CLerはこういうScheme臭いコードは気に食わない、って事を申し添えておく。
こういうScheme臭いコードを書くくらいなら、破壊的変更で逐次処理、と言うコードを書く方が一般的にCLerには好まれる。