文字列とバイト列

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

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

私が多用しているプログラミング言語 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

省略可能引数

プログラミング言語 Scheme は仕様は小さいが、 SRFI という形で便利な機能が提案されている。 特に SRFI-1 は典型的なリスト操作を多く含むのでよく利用され、また、典型的であるが故にこれを真似るのは練習の題材としても恰好のものである。

SRFI-1 にある等比数列を作る手続きである iota を真似て独自に実装してみようとする記事を見た。

整数のリストを作る | blog.PanicBlanket.com

ここでは数列はともかくとして、省略可能な引数の扱いに手間取っているようだ。 引数もまたリストであるので定型的に書けるが、よくある形ならばそれはひとつの語彙に押込めてしまうのが作法というものだろう。 このような場合のために R6RS/R7RS では case-lambda という構文が用意されている。 case-lambda を用いて iota を書くならばこのような要領になる。

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

しかし、私は Gauche や Chicken などにある let-optionals* の方が好きだ。 上記のコードを let-optionals* を使って書き替えた場合はこうなる。

(define (iota count . restarg)
  (let-optionals* restarg
      ((start 0)
       (step 1))
    ;; 省略
    ))

let-optionals*Scheme の仕様に含まれるものではなく、一部の処理系が独自に提供しているものだが、マクロを使えば以下のように簡単に定義できる。

(define-syntax let-optionals*
  (syntax-rules ()
    ((_ args ((var default) . rest) body* ... body)
     (let* ((temp args)
            (var (if (null? temp) default (car temp))))
       (let-optionals* (if (null? temp) '() (cdr temp)) rest body* ... body)))
    ((_ args () body* ... body)
     (if (null? args)
         (begin body* ... body)
         (error "Too many arguments:" args)))
    ((_ args (var . rest) body* ... body)
     (let-optionals* args ((var #f) . rest) body* ... body))
    ((_ args rest-var body* ... body)
     (let ((rest-var args)) body* ... body))))

Script-fu で省略可能な引数を扱っている記事も読んだ。

Script-fu は画像処理ソフト GIMP に組込まれている Scheme 処理系で、 TinyScheme が基礎になっている。 残念ながら syntax-rules はサポートされていないが、伝統的マクロならば使える。 Script-fu の伝統的マクロを用いて let-optionals* を書くとこうなる。

(define-macro (let-optionals* args opts . body)
  (cond ((and (pair? opts)
              (let ((opt (car opts)))
                (and (list? opt) (= (length opt) 2) opt)))
         => (lambda (opt)
              (let ((var (car opt))
                    (def (cadr opt))
                    (rest (gensym)))
                (if (symbol? var)
                    `(let* ((,rest ,args)
                            (,var (if (null? ,rest) ,def (car ,rest))))
                       (let-optionals* (if (null? ,rest) '() (cdr ,rest))
                           ,(cdr opts)
                         . ,body))
                    (error "Malformed syntax")))))
        ((and (pair? opts)
              (let ((opt (car opts)))
                (and (symbol? opt) opt)))
         => (lambda (opt)
              `(let-optionals* ,args ((,opt #f) ,@(cdr opts)) . ,body)))
        ((symbol? opts)
         `(let ((,opts ,args)) . ,body))
        ((null? opts)
         `(if (null? ,args)
              (begin . ,body)
              (error "Too many argments")))
        (else (error "Malformed syntax"))))

この定義はもちろん TinyScheme でも使える。

私は画像を扱うときは PIXIA を好んで使っていたのだけれど、 OS を Windows7 から Windows10 にアップグレードした際にどういうわけかクラッシュしやすくなってしまったので GIMP 2 を導入した。 折角なので Script-fu を使った自動化、またその下地になるユーティリティ的な手続きやマクロの定義を蓄積していきたいと思う。

Document ID: c80009ca4afc568f85c46f6e89428b6b

サクサク

食パンの広告では「ふんわり」「もっちり」を売り文句にしている場合は多い。 日本でもっちりとしたパンが好まれるのは、これが米のかわりと見做されているからだという説がある。 日本の米は (べっちゃりとしない程度に) 水気を多く含んでやわらかいものが上等とされがちで、その延長線で考えればパンもふんわりしたものが好まれるというのは理解できる。

しかし、私はクッキーの一歩手前くらいのサクサクとした食感が好きだ。 ふんわり食パンを強くトーストしただけではどうにも良いサクサク感にはならない。 サクサク食パンは材料から違うのだろう。 もちろん探せばサクサク食パンも売っているのだが、選択肢が少ないことにはかわりない。

パン以外でもそうだ。 人気があるからどんなものだろうとミスタードーナツが出しているポン・デ・リングを食べてみたらやたらとふわふわで私の好みに合わない。

もっとサクサクしたい。

Document ID: a9a74ad8c073af4a6f452e471bfea23f

こんな夢をみた「エレベータ」

いつも意味不明な夢シリーズだが、今回は毛色の違う夢を見た。

私は高層建築物の中にいた。 滑り台で一階分を下りられるようになっていたので珍しいと思ったのだが、それは下の階へ下りるためのものではなくエレベータホールへと繋がるものであった。 エレベータホールへは階段か滑り台のみで繋がっていた。 つまり、エレベータホールへ行くためには、同時に階段で一階分上がるか滑り台で一階分下がるかしなければならない。 エレベータホールから出るには階段で一階分上がるか階段で一階分下がるかということになる。 (エレベータホールから出るためには滑り台を使えない。) また、エレベータホールとの接続とは別に上下階とは階段で繋がっている。 どの階へ行くにも最低でも一度は階段を上るか下るかする必要があるということだ。

この条件では、ひとつ上の階へ行きたい場合にエレベータを使おうとすると滑り台で一階分下がってエレベータホールに到達し、エレベータで一階分上がってから階段で一階分上がるというのが最短経路になる。 (ここでは階段を上るのと下るのは同じ階数分であれば同じ労力であると仮定している。) それなら階段で一階上った方がマシだということになる。 ひとつ下の階へ行きたい場合も同様である。

二階分下がろうとすると、滑り台でエレベータホールへ行ってからそのまま階段で一階分下りるのが最短になる。 エレベータホールを経由するが、エレベータを使う必要はない。

つまり、この設計だと上一階分の移動と下二階分の移動はエレベータを使わない方が楽なわけで、わずかな移動の場合にはエレベータを使わないように誘導する設計思想なのである。 現実には何かと問題もあるだろうが、夢の中でこの建築物を見た私はとても感心したのだ。

Document ID: 77e457119cc35af24f3e06e793dbc2df

Cの本質

本質というものは状況や立場によって異なる。 例えばどこかの企業で事務を電子化しようとした場合、それをやる技術者にとってはメカニズム (アルゴリズム) が本質だろう。 技術者は技術に対して対価を貰う立場なのだから。 その仕組みを使う事務員はどれだけ作業が効率化できるかが本質だと思うだろう。 経営者にとってはそれの導入がどれだけ収支に影響するかが気にかかるはずだ。

さて、プログラミング言語 C についてしばしば「本質的でない要素が多くて初心者向けではない」という主張を見ることがある。 C は高級アセンブラと揶揄されることもあるように、言語の意味論と機械の意味論が不可分に融合していて、ここで本質的ではないと言われるのは機械の理屈に由来する部分だったりするのだ。 (文法にも型表記などに歴史的負債を引き摺っているわかり難い部分はあるが、ここではそれは脇に置く。)

機械を操ろうというのにその仕組みは本質的ではないのだろうか。 プログラミングを問題解決の道具として見るのならば機械の仕組みは本質的ではないのだろう。 しかし、機械の理屈を理解しようという立場でプログラミング入門するならば、むしろ C こそ本質を捉えているともいえる。

「本質」という言葉を使うときには、それがどのような視点で見たときのものかを意識して擦り合わせをしないと噛み合わない。

Document ID: a0427421a16b8bc485ce026fcc12d1c4

文書構造

いくつかの小説ホスティングサイトから文書を取得して電子書籍 (ePub 形式) を生成するスクリプトを私は以前に作って公開している。

SaitoAtsushi/yomou-publisher at ff3950e

いくつかのサイトに対応していることもあって ePub 生成する部分をひとつのライブラリとしてくくり出しているのだけれど、ひどいインターフェイスでとても汎用的には使えない。 問題のひとつは文章構造の表現のしかただ。

文書と文章構造はリストにまとめて渡すようになっている。 例で言えばおおよそ以下のような形式だ。

("第1章"
 ("001.xhtml" "第1章 第1話 ほにゃらら" "ほにゃらら本文")
 ("002.xhtml" "第1章 第2話 かくかく" "かくかく本文")
 ("003.xhtml" "第1章 第3話 しかじか" "しかじか本文")
 "第2章"
 ("004.xhtml" "第2章 第1話 なんたら" "なんたら本文")
 ("005.xhtml" "第2章 第2話 かんたら" "なんたら本文"))

リストの要素は

  • 章の名前
  • 各話のファイル名と表題と本文をリストでまとめたもの

のいずれかである。

しかしこのような表現では行き詰まりが出てきた。 文章のまとめをネストできないからである。 例えば「第何部」「第何章」「第何節」というようにいくつかの話をまとめてそれを更にまとめるというような構造が表現できないのである。 主要な小説ホスティングサイトではネストする機能がなかったこともあって想定しなかったわけだが、 KADOKAWA が始めたカクヨムというサービスではネストできるようで、実際にそうしている作品もある。 ライブラリとして汎用的にするなら想定して然るべきであった。

単純に考えれば、リストに入れるのだから適当にグループ化すればいいじゃないかと思うだろう。 こんな要領で。

(("001.xhtml" "まえがき" "まえがき本文")
 (section "第1部 導入編"
   ("002.xhtml" "プロローグ" "プロローグ本文")
   (section "第1章"
     ("003.xhtml" "第1章 第1話 最初の話" "ほにゃらら")
     ("004.xhtml" "第1章 第2話 次の話" "ほげほげ")
     ("003.xhtml" "第1章 第3話 一段落" "ふがふが"))
   (section "第2章"
     ("004.xhtml" "第2章 第1話 次の事件" "わはー")
     ("005.xhtml" "第2章 第2話 解決" "むすー")))
 (section "第2部 発展編"
   (section "第3章"
     ("006.xhtml" "第3章 第1話 開始" "かくかく")
     ("007.xhtml" "第3章 第2話 開始" "しかじか")))
 ("008.xhtml" "あとがき" "あとがき本文"))

これならいくらでもネストできる。

とは言っても、そもそも論として文章構造と文章の内容をひとつのリストにまとめるのは妥当だろうか。

各話ごとに書き出してしまえばメモリ上で全体を保持する必要はなく、効率は良くなる。 目次などのメタデータだけは最後に整理して出力すればよい。 これは見掛け上、オブジェクトに対するメッセージとしてインターフェイスを整理できる。

しかし、メッセージは時系列での順序しかない。 ネストをどう表現するかを考えると、グループの開始と終わりを表すメッセージを投げる方法が考えられる。 上の例をメッセージで表すならこうなる。

(let ((book (book-open "ファイル名.epub" "表題" "作者" "概要")))
  (add-html book "001.xhtml" "まえがき" "まえがき本文")
  (start-section "第1部 導入編")
  (add-html book "002.xhtml" "プロローグ" "プロローグ本文")
  (start-section "第1章")
  (add-html "003.xhtml" "第1章 第1話 最初の話" "ほにゃらら")
  (add-html "004.xhtml" "第1章 第2話 次の話" "ほげほげ")
  (add-html "003.xhtml" "第1章 第3話 一段落" "ふがふが")
  (end-section)
  (start-section "第2章")
  (add-html "004.xhtml" "第2章 第1話 次の事件" "わはー")
  (add-html "005.xhtml" "第2章 第2話 解決" "むすー")
  (end-section)
  (end-section)
  (start-section "第2部 発展編")
  (start-section "第3章")
  (add-html "006.xhtml" "第3章 第1話 開始" "かくかく")
  (add-html "007.xhtml" "第3章 第2話 開始" "しかじか")
  (end-section)
  (end-section)
  (add-html "008.xhtml" "あとがき" "あとがき本文")
  (book-close book))

書籍となるとカスタマイズしたい事項は無数に生じる。 縦書き/横書きだとかスタイルシートの指定だとか、そういったものをオプションとして手続きに渡すとなると引数の数が際限なく増えて不格好になるのでそういったオプション要素もオブジェクトに対してメッセージで投げられるようにすれば一貫したインターフェイスに出来るだろう。

この方向でスクリプトを整理しなおすことを考えている。

Document ID: 3067cd99051d86bf097ce5c87f68c459