区切れ

ある日、母が新聞を見ながら「これがどうしても理解できん。 日本語として成り立ってないようにしか思えん」と言って示したのはテレビ番組表であった。 具体的には 2017 年 7 月 5 日の朝日新聞香川版のテレビ番組表、 OHK テレビの 7 時から 9 時の番組の箇所である。

おじゃMAP‼ 復活
結婚式企画2時間SP
「私、幸せになっても
いいのでしょうか?」
幼い子残し他界した夫
妻の再出発…結婚式を
香取ザキヤマが初司会
&プロデュースで感涙
▽意外と知られてない
穴場⁉香港ディズニー
3万円台で行けちゃう
衝撃のツアー‼◇N天

母は「他界した夫妻が再出発ってどういうこと?」と頭をひねっていた。 もちろんこれは読点を入れれば何の疑問もなく解釈できる。 「他界した夫、妻の再出発」である。

番組表は書ける文字数が少ないせいか無理に詰め込む傾向があり、ときにはこういったことが起こる。 興味深い事例であった。

Document ID: 2f2080a84d17e1d6f8f20a0f6a4fd862

こんな夢をみた「太鼓折半」

こんな夢を見た。 私は辞書をひいていた。 「太鼓(たいこ)折半(せっぱん)」という語を調べようとしていた。

そして見付けた項目には「ひとつひとつは不幸な事例が重なって結果的に利益を得ること」とあった。

目が覚めてから調べてみたが、実際にはそんな言葉は存在しなかった。

私の夢に現れたからには私の脳で生み出された言葉なのだろう。 どういうメカニズムでこういう造語が、しかも合成元の言葉 (太鼓と折半) から全く想像できないような意味で作り出されるのか興味深いことだ。

Document ID: b05792fb9115aad482b40443f5415625

型を隠せるか

C++ ではクラス定義時に private としたメンバには外からはアクセスできない。 そのメンバへの参照やポインタを作って外へ漏らしたりしない限りあくまで内部的なデータである。 (メモリレイアウトを知っていれば無理矢理にアクセスすることは出来なくもないが、それは言語の範囲外の話ということでとりあえず置く。)

そして型についてもまたクラスの private 要素として定義することが出来る。 たとえば

#include <iostream>

class foo {
private:
  class bar {};
public:
  static bar make_bar(void) {
    return bar();
  }
  static void print_bar(bar) {
    std::cout << "bar" << std::endl;
  }
};

という定義を作ったときは foo::bar という型を隠したいという意図がある。 foo::bar という型に外からアクセスできないのだから、 foo::bar 型の変数を foo の外で作ることは出来ない。 foo::make_bar が返した値は捨てるか foo::print_bar が受け取る以外の使い方は出来ない。 …… C++03 までは。

C++11 以降では autodecltypeprivate な型も使える。

int main(void) {
  foo::make_bar(); // OK
  foo::print_bar(foo::make_bar()); // OK
  foo::bar x = foo::make_bar(); // foo::bar は private なのでエラー
  auto x = foo::make_bar(); // C++11 からは auto が使える
  decltype(foo::make_bar()) y = foo::make_bar(); // C++11 からは decltype が使える

  return 0;
}

autodecltype を通じてでも foo::bar という型を外で利用できなくする方法というのはあるだろうか?

Document ID: 10255bcb2ece0cf1d51420c8189eb2a2

Scheme でローカル定数

Common Lisp でローカルな定数を導入する構文を作ろうとする記事を読んだ。

Common Lispでローカル定数の構文 — #:g1

私は以前に似たようなことをやっていたことを思い出した。

以前に作ったマクロ define-constant は最終的に define に展開されるので、そのまま internal define に使っても機能はするのだが、あらためて let 系の形式でローカル定数を定義するマクロを書いてみることにした。

#!r6rs
(import (rnrs))

(define-syntax let-constant
  (lambda(stx)
    (syntax-case stx ()
      ((k ((var form) ...) body body* ...)
       (with-syntax
           (((t ...) (generate-temporaries #'(var ...))))
         #'(let ((t form) ...)
             (let-syntax
                 ((var
                   (make-variable-transformer
                    (lambda(stx2)
                      (syntax-case stx2 ()
                        ((set! v _)
                         (syntax-violation 'set!
                                           "attempt to assign constant"
                                           stx2
                                           #'v))
                        (_ #'t))))) ...)
               body body* ...)))))))

(let-constant ((x 1))
  ;;(set! x 2) ;; ← もし set! を試みたらエラーになる
  (display x)))

例として書いた定数 x は識別子マクロで、展開後には隠れた変数に置き換えられる。 識別子マクロは set! で代入されるときの展開形を特別に定義することも出来て、ここではそういう形になっているときは syntax-violation を使って構文エラーになるように定義した。

Document ID: d5f61355b4e55961f2af8517178d2c53

その心は

技術系の質問サイトや SNS(ソーシャルネットワークサービス) での技術コミュニティを見ている中で、技術的な困難さとは別に応え難いなと思う質問を見ることがある。 それは「A とは B のようなものですよね?」というような質問だ。 既に知っていることに当て嵌めて理解しようとするのが悪いわけではないが、どのくらい近ければ「ようなもの」と言ってしまってよいのかというのは微妙だ。

こういった質問の裏には「A を B と同一視してやってみたら上手くいかなかった」という背景が隠れていることがある。 仮にほとんどの部分で同じものであっても、だからこそ僅かな差の部分が躓きの原因になるので、そういったときに大雑把に「ようなもの」であるというのは何の(なぐさ)めにもならず、話が噛み合わない原因になると思う。

こういった比較のときには「ようなもの」ではなく「A は B と同じように C ですよね?」という風に、ふたつが近いと考える理由を提示するのが望ましいのではないだろうか。 もっとも、そこまで整理して考えることが出来るくらいならわざわざ質問しなくても解決できるかもしれないが。

Document ID: 98fa74bb0b34e6901c6ba4f119a68394

パイプでメモリをストリームにする

メモリ上にあるデータをあたかもファイルのように読みたいというのを以前に考えて、C++ のストリームクラスとして定義したことがある。

しかし、これはもちろん C++ でなければ使えない。 もう少し低水準の API を用いて実現できないかと考えたとき、 Windows API のパイプはほぼファイルと同じインターフェイスで使えることを思い出した。 シークが出来ないという制限はついてしまうが、それを除けば見掛け上はファイルと同じ形で処理できるのではないかと考え、実装してみた。

// memstream.h
HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size);
// memstream.c
#include <windows.h>
#include <process.h>
#include "memstream.h"

struct writer_args {
  HANDLE hWritePipe;
  LPVOID data;
  DWORD data_size;
};

static const int perround = 1024*64;

static void writer(void* args) {
  struct writer_args* wargs = args;
  char* data = wargs->data;
  DWORD data_size = wargs->data_size;
  HANDLE hWritePipe = wargs->hWritePipe;
  free(wargs);
  DWORD written_size;
  DWORD writing_size;
  
  for(DWORD rest=data_size; rest>0; data+=written_size, rest-=written_size) {
    writing_size = rest<perround ? rest : perround;
    if(!WriteFile(hWritePipe, data, writing_size, &written_size, NULL)) break;
  }

  CloseHandle(hWritePipe);
  _endthread();
}

HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size) {
  HANDLE rp, wp;
  BOOL result = CreatePipe(&rp, &wp, NULL, 0);
  if(!result) return INVALID_HANDLE_VALUE;
  struct writer_args* wargs = malloc(sizeof(struct writer_args));
  wargs->hWritePipe = wp;
  wargs->data = data;
  wargs->data_size = data_size;
  _beginthread(writer, 0, wargs);
  return rp;
}

これは以下のような要領で使うことが出来る。

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include "memstream.h"

int main(void) {
  char* sample_str="Hello, this is file mapped memory sample";
  HANDLE mfh = CreateMemoryFile(sample_str, strlen(sample_str));
  int fd = _open_osfhandle((int)mfh, _O_RDONLY);
  FILE *fp = _fdopen(fd, "r");
  int ch;
  while((ch=getc(fp)) != EOF) putchar(ch);
  fclose(fp);
  return 0;
}

ウェブ上を検索してみるとスレッドを使わずに一気にパイプに書き込んでしまう方法を紹介しているものもある。 しかし、データ量が充分に小さいときを除いてはパイプのバッファサイズを越えてしまう可能性がある。 そのとき、少しづつでもパイプから読み出してデータを消費するのならばいずれは書込みも完了するのだが、シングルスレッドでは永遠に待つようになってしまう。 CreatePipe API の第四引数でパイプのバッファサイズを指定しても、あくまでヒントとして活用されるに過ぎず、その大きさのバッファを確保することを保証していないということに注意して欲しい。 パイプのバッファサイズ (としてヒントを与えた数値) より少ないデータ量しか書込まないからロックしないとは言い切れず、シングルスレッドで確実にロックしないようにする方法は (MSDN における CreatePipe に関する記述の範囲内では) 読み取れなかった。 そんなわけで、パイプを書き込む側はスレッドにするのが安全であると判断して上記のように実装した次第である。

Document ID: ecd99cc5c21bd06439374c829b044f4b

Chez Scheme でコンソールに日本語を出力する

プログラミング言語 Scheme の処理系のひとつである Chez Scheme仕様 (R6RS) が定める三種類の符号での入出力をサポートしています。

更に、独自拡張として iconv による変換も可能ですので、ほとんどどんな文字コードでも使えると思ってもいいでしょう。 もちろん、日本語の文字コードとしてよく使われる Shift_JIS やら CP932 やらにも対応しています。

さて、 R6RS では標準入出力用のポートにどんな文字コード変換器が結びついているか、あるいは結びついていないかは未定義ですが、 Chez Scheme では UTF-8 を採用しています。 しかし、 Windows の標準出力に UTF-8 のテキストを流し込んでもコンソール上では化けてしまいます。 それを解決するために CP932 に変換してからの出力を試みている事例を見掛けました。

ですが、これでは CP932 の文字セットに含まれない文字の情報が消えてしまいます。

WindowsUnicode 対応に積極的で、文字列のやりとりが必要な API はほぼ全て Unicode 版が存在しますし、コンソール関連の API についてもまたそうであるのですが、互換性の制約からか、標準出力経由でコンソールに表示する文字は CP932 と解釈してしまうようになっているのであって、コンソール関連の API を直接使えば Unicode のままで利用できます。

そこで、 Chez SchemeForeign Interface を用いて Unicode の文字をコンソールに表示するライブラリを作ってみました。 カスタムポートとして定義しており、このポートに対して出力することでコンソールに表示されます。 標準出力をリダイレクトしていてもコンソールに出力します。

(library (console-port)
  (export open-console-output-port)
  (import (chezscheme))

  (define dummy (begin (load-shared-object "kernel32.dll") 1))

  (define-ftype handle void*)

  (define open-existing 3)

  (define create-file
    (foreign-procedure __stdcall "CreateFileW"
      (wstring unsigned-32 unsigned-32 void* unsigned-32 unsigned-32 void*)
      void*))

  (define file-share-write 2)

  (define generic-write #x40000000)
  
  (define (get-active-console-buffer)
    (create-file "CONOUT$" generic-write file-share-write 0 open-existing 0 0))

  (define write-console
    (foreign-procedure __stdcall "WriteConsoleW"
      (void* wstring unsigned-32 u32* void*)
      boolean))

  (define (open-console-output-port)
    (let ((output-handle (get-active-console-buffer))
          (vsize (make-bytevector 4)))
      (define (write-to-console string start count)
        (let ((str (substring string start (+ start count))))
          (write-console output-handle str count vsize 0)
          count))
      (make-custom-textual-output-port "console" write-to-console #f #f #f)))
  )

使い方としては、 open-console-output-port 手続きが返すポートに書き込むだけです。

(import (rnrs)
        (console-port))

(let ((port (open-console-output-port)))
  (display "あいうえお\nかきくけこ\n" port)
  (display "♘♞♙♕♟♝♜♗♛♚♖♔" port)
  (flush-output-port port)) ;; フラッシュを忘れずに!

f:id:SaitoAtsushi:20170110051212p:plain

CP932 の範囲外の文字もきちんと表示できています。 ただし、コンソールに適用しているフォントがグリフを持っていない場合もあるので Unicode にある文字を全て確実に表示できるというわけではありません。

Document ID: fb260a71b629c76209cdc797c43c9bdd