Scheme における戻り値の未規定

プログラミング言語 Scheme では式を評価することで値が得られる。 そしていくつかの手続きは戻り値が未規定である。 JavaScript などでは「未定義値という値」が存在することと、 Scheme のいくつかの実装でも同様にそのようなオブジェクトが使われていることからしばしば誤解されることがあるようなのだが、 Scheme でいうところの「戻り値が未規定」というのは何も決まっていないという意味である。

未規定なのは処理系の裁量で好きにしていいのだ。 各手続きの処理の中で使えそうな情報を返すような実装でも許されるし、デタラメな数値かもしれないし、とりあえず真偽値でも返しておくというのもかまわない。 実際には REPL で対話的にプログラミングしているときに無関係な出鱈目な値が出てくるのはうんざりするということもあってか、未規定であることを表すオブジェクトを返す処理系は多い。 その他、 '()#f を返すような実装も一般的である。

ちなみに、 R5RS や R7RS では「式の結果は未規定 (the result of the expression is unspecified)」という表現になっているのだが、 R6RS では「未規定値を返す (return unspecified values)」という表現になっている。 私は英語を不得意としているから微妙な意味合いを捉えることが出来ないのだけれども、もし JavaScript などに慣れた人が見たら R6RS は未規定を表すオブジェクトがあるように誤解しやすい表現であるように思う。 一方で、 R5RS や R7RS で用いられている result という語は副作用も含めた結果を連想させてしまうと思う。 言葉の意味するところについて説明している項目はあるのだが、(Scheme の仕様はそう多くもない分量とはいえども) 隅々まで目を通している方が少数派であろうし、知っていても言葉の印象に引き摺られるということはあるかもしれない。

更に、 R6RS で表現が異なっているのは文章表現が異なっているだけではない。 よく見ると values という複数形で書かれているのである。 R6RS で、ある手続きが未規定値を返すといった場合にはそれが多値である可能性がある。 このことは 5.9 Unspecified behavior に明記されている。 実際に多値な実装を見たことはないが、戻り値が未規定の件については R5RS や R7RS より R6RS の方がより未規定というわけだ。

他の言語でもそうだが、仕様で未規定な部分は不意に処理系が挙動を変えてしまったり、処理系ごとに挙動が違ったりする。 意識しておかないと処理系が更新したときに急に動かなくなってしまうこともあるし、移植性に劣るものになってしまうかもしれない。 こういった未規定な値に依存しないように注意が必要だ。

Document ID: a58c1648bb0f2db1ea76d5e7b78a1fa8

僕の考えた最強のデータ交換用フォーマット

データ交換用フォーマットの現状

データ交換用フォーマットとして XML は優秀です。 柔軟な表現が可能であり、一方では厳密な構造を形式的にスキーマで規定することも可能です。 また、考え方は簡潔です。

しかし近頃ではウェブ API では JSON が用いられることが多くなっています。 ウェブを支配している JavaScript との親和性のよさは XML の利点と引き換えにするだけの価値がある場合もあるでしょう。

XML の欠点

XML の難しさのひとつは名前空間にあると私は考えています。 既存の語彙を差し込むことで再利用できる名前空間の有用性はわかりますが (名前空間の機能を持たない) JSON が広く使われている状況を見ると実は無くてもよい場面の方が多いのではないかとも思えます。 また、属性と子要素との区別は HTML (XHTML) などの文章のマークアップには有効に働きますがプログラムがデータ交換するにはあまり意味がありません。

新しいデータ交換用フォーマット SLN の提案

以上を踏まえて、簡単に使えるデータ交換用フォーマットを考えてみました。 人間が読み書きすることを考えず、 XML の面倒な部分を排除したデータ交換用フォーマットです。 XML に似たタグによる構造化を行指向にやることから私はこれに Structured lines notation (略して SLN) と名付けました。 各行が XML で言うところの開始タグ、テキスト要素、終了タグのいずれかであるようなテキストです。

最初に例を示します。 たとえば人物名と年齢の組を XML で表現するとこうなるでしょう。

<members>
 <person>
  <name>James</name>
  <age>16</age>
 </person>
 <person>
  <name>Alice</name>
  <age>10</age>
 </person>
</members>

SLN で表すとこうなります。

+members
+person
+name
.James
-
+age
.16
-
-
+person
+name
.Alice
-
+age
.10
-
-
-

行の最初がプラス記号である場合が XML でいうところの開始タグに相当し、マイナス記号のみの行が終了タグに相当します。 ピリオドで始まる行がテキスト要素です。 プラスの次の要素から改行までがタグ名です。 空白などが含まれる場合はその空白も含めてタグ名として機能します。 同様にピリオドの次の文字から改行までがテキスト要素の内容です。 XML と同じくただひとつのルート要素を持ちます。

SLN では文字コードとしては原則として UTF-8 を採用します。 文字列のエスケープはありません。 特別な解釈をされてしまう文字を普通の文字とした解釈させるためのものがエスケープと考えると、 SLN には行の最初の文字しか特殊な解釈をされる文字がないのでエスケープの必要がないからです。 ただ、以上の仕様だけではタグ名前にもテキスト要素にも改行を含めることができません。 そこでスラッシュで始まる要素は改行を狭んで前要素と繋げるという仕様も追加します。

例として掲示板のデータを考えてみましょう。 XML でいえば以下のようなデータです。

<bbs>
 <message>
  <name>James</name>
  <content>Ladies and gentleman!
How are you?
</content>
 </message>
 <message>
  <name>Alice</name>
  <content>I'm fine.
Thank you.</content>
 </message>
</bbs>

これは SLN では以下のように表現できます。

+bbs
+message
+name
.James
-
+content
.Ladies and gentleman!
/How are you?
-
-
+message
+name
.Alice
-
+content
.I'm fine.
/Thank you.
-
-
-

スラッシュで始まる前の行が開始タグだった場合にはタグ名に改行を含んだものとして処理することも出来ます。

SLN の表現としての改行は 0x0A の 1 バイトのみとしますが、構文解析した結果の改行をどう解釈するかはアプリケーションの裁量とします。 それと、最後の終了タグの行も改行で終わるのは必須とします。

検討を要す事項

終了タグ

マイナスで始まる行は終了タグであると決めましたが、マイナスと改行の間に文字列が有った場合にどのように解釈するべきか決めかねています。 考えられる候補としては以下がありますが、どれも一長一短があるように思います。

  • 認めない。 余計な文字列はエラーである。
  • 開始タグと同じ文字列があるべきである。
  • 開始タグと同じ文字列があるべきであるが、省略することも許す。
  • 単に無視する。

プログラムが読み書きすることだけを想定しているので、エラー検出を目的とした冗長な文字列はあまり意味がないでしょう。 余計な文字列はエラーとするのが望ましいのではないかというのが私なりの考えです。

記号

各行の最初の文字列をプラス、マイナス、ピリオド、スラッシュとしました。 これらは感覚的に妥当でしょうか?

空行

ルート要素の開始タグの前の空行、終了タグの後の空行を許すべきでしょうか?

運用

ルート要素

ルート要素の名前は実質的にフォーマットを表わす名前として機能します。 汎用的なフォーマットはバージョンナンバーまで含めた詳細な名前をルート要素のタグ名にするのが望ましいと考えます。

順序

SLN では連続するテキストを別の行にするだけで複数のテキスト要素とすることが出来ます。 出現順序に意味付けすることでタグを付けずに済ますという方法は考えられます。

つまり、最初の例をこのように表現するというのもひとつの案です。

+members
+person
.James
.16
-
+person
.Alice
.10
-
-

person 要素の子要素として最初に現れるテキスト要素を名前、次に現れるテキスト要素を年齢ということにすればそれぞれをタグで囲わずに済ませることが出来ます。 データ量が多少節約できるかもしれません。

拡張の余地

行の最初の文字でその行が意味するところを表わすという設計なので、適当な文字に意味付けすれば様々なものを入れられるでしょう。 たとえば $ で始まる行に入っているのは数値型とするというようなことも可能です。 私としては (XML がそうであるように) 要素の意味付けはアプリケーションでやるべきだという考えですが、拡張できる余地は有るに越したことはないでしょう。

Document ID: 4326062d3d9bca78c0a03fc9c1d0f19c

字面と意味と

私はラテン文字の単語を文章中に書くのが好きではない。 不自然でない程度に伝統的な日本語の単語に置き換えれないか考えるし、それが出来ないならカタカナ語で書く。 とはいっても固有名詞はそのまま書くこともあるし、プログラミングの話題も多いので具体的な字面に意味がある言葉はそのまま書くしかない。 たとえば Scheme の syntax-rules という構文のことをシンタックス・ルールなんて書かないし、もちろん構文規則という言葉に置き換えることもない。

さて、私はこの考え方に基いて Scheme で手続き (関数) を生成する式という概念をいうときは「ラムダ式」と書き、具体的に lambda という構文を用いて書かれている式のことは「lambda 式」と書いたりする。 Scheme という言語の中ではそれは実質的に同じことを指しているのだから区別する意味などないのかもしれないが、私なりの美学だ。

私は日本人 (更に言えば英語を不得意としている) であるから、日本語に置き換えるときに字面と意味とを区別して考えているが、英語をよく理解して英語の文章で読み書きしている場合にはこの区別は曖昧になりそうだということをふと思った。 (Scheme の仕様書では字面に意味がある言葉は等幅書体で書かれているので区別はされている。)

Document ID: 2f56765df19a4de2b2a1e3088b727e69

Scheme の begin と継ぎ合わせ

プログラミング言語 Scheme の構文である begin は直列化のための機能としてよく理解されている。 要するに複数の式を順番に評価させる (そして最後の式の評価結果を全体の評価結果として返す) ための構文である。 Common Lispprogn に相当するものと説明されることもある。

だが、 begin は状況によって別の解釈をされることがある。 R5RS の「5.1.プログラム」の項目で定義されているのだが、トップレベルに現れる begin フォームに限っては、その内側に書かれている式、定義、構文定義と等価であるとされている。 つまり、 (begin a b c) と書かれていたら、それはトップレベルに a b c と書かれたのと同じになるということだ。

具体例で表してみよう。

(begin
  (define (plus-one x)
    (+ x 1))

  (define (plus-two x)
    (+ x 2)))

(write (+ (plus-one 1) (plus-two 1)))

このようにトップレベルの begin の内側に書かれたものは以下と同等に解釈される。

(define (plus-one x)
  (+ x 1))

(define (plus-two x)
  (+ x 2))

(write (+ (plus-one 1) (plus-two 1)))

要するにトップレベルで begin に囲まれている個所は囲まれていないのと同じであるということだ。 この操作を begin の外側に「継ぎ合わせ (splicing)」られるという。 このことを知らないと begin の内側での定義は一見してローカルな定義に見えてしまいがちなので注意が必要である。

囲んでも囲まなくても同じならわざわざ begin で囲む意味などないだろうと思うかもしれないが、これは主にマクロへの配慮だと考えられる。 マクロはひとつのフォームを別のひとつのフォームに変換するものだが、複数のフォームへ変換したい場合もあるので見掛け上のひとつのフォームにするために begin が必要なのだ。

ちなみに、 R6RS や R7RS ではこの継ぎ合わせはトップレベルだけでなく <body> 部で機能するように拡大されているのと、意味の異なる二種類の構文であることがより明確に書かれている。 また、 R7RS には define-library の補助構文としての begin もある (つまりは三種類の begin がある)。 それらは同じ begin という名前でありながらも現れる場所によって違う解釈されることには充分に注意が必要である。

Document ID: 0a54a00152f1c3e16c15ea8e11b1ea7b

ファイル名の順序

パソコンを使っているとファイル名一覧を見る機会は頻繁にある。 ファイル名一覧を出力する多くのソフトは見易いように項目の内容でソートする機能を持っている。 ファイル名の順序で並び換える場合は一般的には「辞書順」である。 辞書順というのはふたつの文字列の先頭から比較していって異なるところの文字を比較することで文字列の大小が決定される方法である。

ウィンドウズのエクスプローラもソート機能を持っているが、ファイル名の順序でソートしたときにファイル名に含まれる数値の部分を特別扱いする。 たとえば abc5.txt というファイルと abc40.txt というふたつのファイルが有ったとき、伝統的な辞書順では abc40.txt の方が小さいが、エクスプローラabc5.txt の方を小さいと判断する。 一般的な人間の感覚としてはその方が感覚に一致するのだろう。 私は古い世代の人間であるから、いまだにゼロパディングして桁数を合わせてしまうのだが。

なんとなく思い立ってこのルールを Scheme で実装してみた。 R7RS の形式にしてある。

(define-library (filename-compare)
  (export filename<?
          filename>?
          filename=?
          filename<=?
          filename>=?)
  (import (scheme base)
          (scheme char))
  (begin
    (define (read-integer port)
      (do ((ch (peek-char port) (peek-char port))
           (acc 0 (+ (* acc 10) (digit-value ch))))
          ((or (eof-object? ch) (not (char-numeric? ch))) acc)
        (read-char port)))

    (define (filename-compare str1 str2)
      (let ((port1 (open-input-string str1))
            (port2 (open-input-string str2)))
        (let loop ((ch1 (peek-char port1))
                   (ch2 (peek-char port2)))
          (cond ((eof-object? ch1)
                 (if (eof-object? ch2) 'eq 'lt))
                ((eof-object? ch2)
                 'gt)
                ((and (char-numeric? ch1) (char-numeric? ch2))
                 (let ((num1 (read-integer port1))
                       (num2 (read-integer port2)))
                   (if (= num1 num2)
                       (loop (peek-char port1) (peek-char port2))
                       (if (< num1 num2) 'lt 'gt))))
                ((char-ci=? ch1 ch2)
                 (read-char port1) (read-char port2)
                 (loop (peek-char port1) (peek-char port2)))
                (else
                 (if (char-ci<? ch1 ch2) 'lt 'gt))))))

    (define (filename<? x y) (eqv? (filename-compare x y) 'lt))

    (define (filename>? x y) (eqv? (filename-compare x y) 'gt))

    (define (filename=? x y) (eqv? (filename-compare x y) 'eq))

    (define (filename<=? x y)
      (let ((r (filename-compare x y)))
        (or (eqv? r 'lt) (eqv? r 'eq))))

    (define (filename>=? x y)
      (let ((r (filename-compare x y)))
        (or (eqv? r 'gt) (eqv? r 'eq))))
    ))

これは私の理解を形にしたものであって、ウィンドウズの実装と一致することを検証しているわけではない。

Document ID: 7d30c7ea6aad074df0def7719562b113

文字列とバイト列

プログラミング言語処理系における文字列の扱いというものには様々な戦略がある。 主要なものを挙げるなら以下のような種類が考えられる。

  • 文字列は一貫した文字コードを持つ。 他の文字コードが必要な場合には入出力の段階で変換する。 また、バイト列として扱いたい場合はバイト列を表す型との変換を必要とする。
  • それぞれの文字列は自分がどのような文字コードを使っているかの情報を持っている。
  • 文字列を表現する型はなく、バイト列で代用する。 どのような文字コードで表されるかはライブラリ、またはプログラマの裁量とする。

私が多用しているプログラミング言語 Scheme では R6RSUnicode サポートが必須とされ、文字型のオブジェクトは常に Unicode のコードポイントと相互変換可能なものであるということになった。 内部的にどのような符号で表現されるかは実装の裁量だが、いずれにしても文字コードとしては一貫した形で持つ戦略を採用しているわけだ。 (R7RS では ASCII の範囲外の文字は必須ではなくオプショナルな扱いになったが、 Unicode を想定していることに変更はない。)

ところが、古い時代には文字列がバイト列の代用品として使われている場合もあり、そのあたりの扱いは処理系によって柔軟に許容していることもある。 たとえば Gauche は内部的に UTF-8 を用いているのだが、文字コードとして不正な場合に「不完全文字列」としてフラグを立てつつも文字列として使えるようになっている。 Gauche では不完全文字列は将来的に削除される計画とのことで、不完全文字列を使ういくつかの手続きは非推奨という扱いになっている。 私が書いたスクリプトの中には不完全文字列を前提として書いているものもあるので、気付いたときには徐々に修正していこうと考えている。

Document ID: 1935dc817b1294b7dff363be85d8815d

case-lambda と let-optionals*

先日の記事ではプログラミング言語 Scheme における手続きの引数を省略できるようにする構文として case-lambdalet-optionals* を紹介した。 しかし、このふたつの構文は意味が異なる。 case-lambda は「手続きの数に応じて分岐する」ためのものであり、 let-optionals* は「省略された引数をデフォルト値で補う」ためのものだ。 引数の数に応じて分岐して不足する引数を補うことは出来るのだから case-lambda も引数の省略を実現する部品として見ることは出来るが、省略可能な引数をより直接的に表現しているのは let-optionals* だ。 私が let-optionals* を好んで使っているのはそういった理由からだ。

また、引数の省略を表現するために case-lambda を使おうとすると似たような表記の繰り返しがあり、冗長に思われる。 先日に例として出した iota を再度見てみよう。

(define iota
  (case-lambda
   ((count)       (iota count 0 1))
   ((count start) (iota count start 1))
   ((count start step)
    ;; 省略
    )))

iota と何度も書いているのが冗長に見えないだろうか。

もう少し引数の数が多い例を考えてみよう。 六個の引数があり、その内の五個が省略可能であるような手続き foo を書こうとするとこうなる。

(define foo
  (case-lambda
   ((a) (foo a 2))
   ((a b) (foo a b 3))
   ((a b c) (foo a b c 4))
   ((a b c d) (foo a b c d 5))
   ((a b c d e) (foo a b c d e 6))
   ((a b c d e f) (list a b c d e f))))
(define (foo a . rest-args)
  (let-optionals* rest-args
      ((b 2)
       (c 3)
       (d 4)
       (e 5)
       (f 6))
    (list a b c d e f)))

case-lambda を使った場合に同じことを何度も書いているのはいかにも不格好なのが目立つ。

繰り返すが、 case-lambdalet-optionals* は意味が異なり、引数の省略をより直接的に表現しているのは let-optionals* だ。 しかし、省略以外の理由で引数の数によって挙動を変える手続きというものはそう多くはない。 つまり、 case-lambda が (let-optionals* よりも) 妥当な場合というのは少ないのではないだろうか。 強いて言うならば、割り算をする手続きである / や、引き算の手続きである - が引数一個で呼ばれたときに変則的な振舞いがあるというのが思い付くくらいだ。 そういった変則的な挙動は基本的には悪い設計なので let-optionals* よりも case-lambda が使いたくなる状況があったとしたらそれは悪いサインである可能性がある。

そんなわけで、私は case-lambda の有用性について懐疑的だ。

Document ID: c894eeb80710ca84a1dabc448f3e2952