読者です 読者をやめる 読者になる 読者になる

ファイル名の順序

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

ウィンドウズのエクスプローラもソート機能を持っているが、ファイル名の順序でソートしたときにファイル名に含まれる数値の部分を特別扱いする。 たとえば 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

省略可能引数

プログラミング言語 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