星田さんの記事に対するコメント。
まず前提から。ANSI Common Lispの話から始めよう。
ANSI Common Lispは"Common"と言うのが付いてるトコから分かる通り、それまで存在した数々のLisp方言を「まとめよう」とした規格。
ここで言う「まとめる」と言うのは、方針としては、それまでに作られたLisp方言で書かれたコードを「そのまま」ANSI Common Lispで動かそうとした時、問題なく動くか、あるいは最小限の手直しで動かせるように、と言う結構壮大な(かつ不可能だろ、と言う・笑)計画の下で作られたプログラミング言語です。
だから命名法がメチャクチャだわ、似たような関数で似たような引数を要するのに引数の順序が違ったり、とか調べるのも使うのも結構メンドい、ある種大欠陥を引きずったような言語になってるのね。
まぁ、それでもメイン、っつーかルーツはMITで作られたMacLisp(Macintosh用Lisp、って言う意味じゃなくって単なるそういう名前)とXeroxで開発されたInterlispって二つが基本ではあるんだけれども。
いずれにせよ、「過去の資産」と言うのを重要視してる。
一方、Schemeは。1975年に初版が作られて、これはオリジナルのマッカーシー博士が絡んでたLispを「再定義」しようとした。つまり、それまでアッチコッチにあったLisp方言の特徴やら言い回しを排除して、なるたけモダンな命名法を用いてシンプルな設計にしよう、として作られた言語です。
だから命名法は統一されている。過去のLispの影響は・・・carとかcdrとかconsを除くとそれらの影響を排するように関数名が付けられてるのが特徴。
しかも、シンボル表記は、それまでのプログラミング言語に比べると豊富に使える、ってのがLispの特徴なんだけど、それを最大限に活かすような命名法を採用してる。
例えばね。意外と古めかしいのがPythonなんだけど。
Pythonの場合、例えば関数等の識別子はASCII文字(実際は、3.0からはUnicord文字も使えるようになって、例えば日本の漢字、平仮名、片仮名も使えるようになってる)、アンダースコア(_)しか使えない。
数字も使えるけど、先頭には使えない、とリテラル上の識別子として見ると、「フツーの言語」以上の許容はされてない言語なんです(Rubyの方がまだ許容度は高い)。
だからPythonだと、
- string? -> クエスチョンマークは使えない。Pythonで良くある代替的な命名法はis_stringとかisStringとか。
- 1+ -> 数字が識別子の先頭にあるからダメ。かつ+記号も使えない。
- string-append -> ハイフンが使えない。アンダースコア(_)で代用するケース。
とかLispを知ってるとイライラする命名制限に良くぶつかる・・・まぁ、Lisp以外だと大体この辺はイライラするよな(笑)。1990年代に現れた言語とは思えない。
これらはANSI Common LispだろうとSchemeだろうと「全部受け入れる」命名法です。
SchemeはLispの、これらの制限を取っ払った環境で、モダンで分かりやすい斬新な命名法を用いてます。
以前にも書いたけど、
- ! が付く関数及びマクロ -> 「破壊的変更をしますよ」と明示してる。
- ? が付く関数 -> 「述語」と言われる関数で、真偽値を返す動作をする、と明示してる。
とか。フツーの言語にないフレキシビリティをもって、明解な関数名を付けるようにしてる。
この辺は、だから「過去の資産」に引きずられているANSI Common Lispよりも先進的な命名法になっています。
それでだ。
ハイフンを使えない言語は当然>とか<とか言う文字もリテラルとして使えない。
一方、Schemeでは、データ変換用の関数は->と言う表示が含まれてる、と言う命名規約がある。
そう、list->stringもそうだね。まさしく「名は体を表す」如く、一発で「リストから文字列に変換する」作用だ、と分かる。
そして重要なのは、「データ変換が出来る」と言うのは当然「逆変換もある」って事だ。
つまり、list->stringがあるなら逆変換のstring->listがある、と言う事。
要するに、なんかの変換効果が良く分からないのなら、Schemeだと直球的にその逆変換を想定して、関数の動作結果を知る事が出来る、と言う事なんだ。
それはSchemeの命名法には対称性があり、そしてANSI Common Lispや他のプログラミング言語じゃ見られないメリットを示してる、と言う事でもある。
今回悩んでたのは恐らく。
list->stringが「リストを文字列に変換する」ってのは完全に分かってたんだろうとは思う。ただし、引数として「どんなリストを想定してたのか」が分からん、って事なんだよな、多分。
言い換えると、string->listを使えば「どんなリストを想定してるのか」すぐ分かる、って事だよね。
> (string->list "This is a string")
'(#\T #\h #\i #\s #\space #\i #\s #\space #\a #\space #\s #\t #\r #\i #\n #\g)
>
そう、list->stringが想定してるリスト、ってのは「文字のリスト」なの。
だから、
> (list->string (string->list "This is a string"))
"This is a string"
>
string->listとlist->stringを「重ねがけ」すると必ず「元に戻る」。
当たり前のようなんだけど、これは至極重要だ。ここで「元に戻らなかったら」命名規約の対称性が崩れる、って事を意味するからだ。
ポイント: 関数名に -> を含むデータ変換関数は必ず逆変換の関数が存在する。
そして、Pythonなんかの
>>> "".join(list("This is a string"))
'This is a string'
>>>
ってのが命名規約的に言うと「メチャクチャに見える」ってのはSchemeに慣れると強化されていくと思う(笑)。全くこういう「対称性がない」命名、ってのはいただけないのだ。
いや、なんでcharじゃないよと怒られるのか・・youってcharじゃないの?
そうだ、ここまで見てくれば分かると思うんだけど、youはcharじゃない。
youはシンボルだ。それを含んでるリストは文字のリストじゃなくってシンボルのリストだ。
だから、list->stringの想定してるリストが渡されていないんで、怒ってるわけだ(笑)。
char?って・・文字列なんだからcharでしょ?
charは文字、stringが文字列(※1)。
これね、多分Pythonから始めたから混乱してんだよな。何故ならPythonには文字ってデータ型がない。
Pythonには徹頭徹尾文字列、ってデータ型しかないんだ。
もしかしてシンボルのリストとして作られているとか!?
ここはちと誤用かな。「文字のリストとして作られて」いて、文字≠シンボル、で全く別のデータ型です。
この文字のリストに変換するのにRacketだとformatを使うのが手っ取り早いということなのか(多分)
いや、そこはリストを全部そのまま文字列にしちまうのが、formatがやってる事です。
> (format "~a" '(This is a list.))
"(This is a list.)"
>
多分ね、"~a"ってのにビックリしてると思うんだけど、そこは「女」です(謎
「ハメてくれ」って言ってる(爆
ええとね、~aってのはプログラミング言語で言うと書式指定子とかフォーマット指定子って言われるもので、文字列中に通常、型を指定して、その型に見合ったデータをハメてくれる機能です。
ただ、フツーの言語だと「データ型を指定しなきゃならない」し、formatでもそういう使い方も当然出来るんだけど、一方、~aってのは「何来てもどんと来い」な書式指定子なのね。っつーか指定してない(笑)。なんでもござれ、のアバズレなんだ。アバズレのaだな(笑)。
だからどんなデータ型が来ようとハメさせるさせ子、なんでLisperには「都合のいい女」もとい「都合が良い」書式指定子なんだよ。
> (format "~a" 1) ;; 数値でもO.K.
"1"
> (format "~a" 'foo) ;; シンボルでもO.K.
"foo"
> (format "~a" '(+ 1 2)) ;; リストでもO.K.
"(+ 1 2)"
> (format "~a" (vector 1 2)) ;; ベクタでもO.K.
"#(1 2)"
> (format "~a" #\a) ;; 文字でもO.K.
"a"
> (format "My name is ~a" "Hirokazu Tawa") ;; 当然、文字列でもO.K.
"My name is Hirokazu Tawa"
>
そう節操がないの(笑)。どんな男でも受け入れて文字列を出産する。ひゃあ!
最近のPythonで言うと{}に近い。それが~aです。
んで、前、疑問に思ってたみたいですが、formatはANSI Common Lispのformatと半分くらいは一致しています(※2)。
ANSI Common Lispのformatの場合、第一引数にはTかNILを指定し、Tの場合は標準出力に書き出して、print関数代わりに使えるんだけど、生憎、RacketのformatはCL版の第一引数がNILの場合に対応してて、あくまで文字列整形の役目しか引き受けません。
;; Racket のformat(format "文字列のテンプレート" 第一引数の文字列にハメたいデータ ...)
ってのが形式です。
いやまあ、別にそこまで調べなくても動く書き方が分かればエエやんって話なんですけどね(^_^;)
いやいや、実験は大事ですよ。
※1: C言語やPascal、Javaでも文字列と文字は違う。
例えば、厳密に言うと、逆に、C言語には文字列がない。
C言語には「文字を詰め込んだ配列」、要するに「文字配列」と呼ばれるものしかなく、それが通常の言語で言う文字列の代替である。
ちなみに、もっと言っちゃうと、C言語には文字もなく、数値しかないので、「型」で状況によって、数値を数値と解釈したり、文字と解釈したりする、と言う極めて低レベルな仕組みになっている。
※2: 正確に言うと半分以下。ANSI Common Lispのformatから文字列整形のシンプルな部分を抽出したに過ぎない。変態的な部分はオミットされている。