C言語で書かれたソースコードをC++化する

C言語で書かれたソースコードをC++化する作業に関わったので、メモを残しておく。

前提条件

  • 主にC++やC言語で構成されたアプリケーションにおいて、中間層に位置するコンポーネントの1つとして、C言語で書かれたライブラリがある。
  • このライブラリは今後も積極的に手が加えられることが見込まれるが、さすがにC言語では色々と厳しくなってきたので、C++に移行する。
    • このコンポーネントを挟む上位層も下位層もC言語ないしC++で書かれている。現状ではC/C++以外の言語を選択することは適切ではないと判断した。
  • C++化の初期段階として、まずは公開インタフェースは既存の(C言語の)ままに、内部実装を「Better C」化することにした。
    • つまり、ヘッダファイルには極力手を入れず、ソースファイルを弄っていく。
    • 余裕があれば、公開インタフェースについても多少のC++化を行うかもしれない。
  • C++の言語仕様としてはC++20以降を想定する。

作業の流れ

  1. 最低限のC++化(C言語からC++への置き換え)
  2. 機械的に対応可能な「Better C」化
  3. 必要に応じて:
    • より高度なC++化
    • 公開インタフェースのC++化

最低限のC++化(C言語からC++への置き換え)

まずはBetter C化を行うための土台作りから。

  1. ソースファイルのサフィックス(拡張子)を.cから.cppに変更する。同時に、使用しているビルドシステムに登録されているソースファイル名のサフィックスも変更する。
    • サフィックスについては、対象プロジェクトに応じて.cpp以外の形式も検討する。
    • 興味深いことに、伝統的なmake(1)を用いるプロジェクトにおいては、Makefileの書き方次第では「ソースファイル名の変更に伴う対応」が不要な場合もある。
  2. 公開インタフェースについて、外部にCリンケージとして見せるようにする。
    1. ヘッダファイルで公開している関数・変数の宣言にextern "C"を付与する。
    2. ソースファイル中の公開関数・グローバル変数の定義にextern "C"を付与する。
  3. ソースコード内のインクルードディレクティブについて、必要に応じてextern "C"を付与する。
    • C言語で書かれたライブラリのヘッダファイルの中には、時々「C言語以外で書かれたソースファイルに取り込まれること」を想定していないものがある。
  4. この段階で一度、C++コンパイラでコンパイルしてみる。
    1. コンパイルエラーが発生した場合には、該当する部分を確認して修正する。C言語からC++に移行する場合には、次のような点が問題となりやすい。
      • ポインタ型の型変換。型チェックの強化にともない、C言語ではセーフだった「ポインタ型から、別のポインタ型への変換」まわりでエラーとなることが意外とある。
      • 整数型から列挙型への型変換。型チェックの強化により、明示的なキャストが必要となった。
      • 型定義のネスト。構造体の定義中で別の構造体/共用体/列挙体を定義している場合、C言語とC++では名前のスコープが異なることが原因でコンパイルエラーとなることがある。
    2. エラーにはならないが警告がでることもある。この場合も、該当する部分を確認して修正する。
      • 型チェックまわりで警告がでるようになることが多い、という印象がある。
  5. コンパイルエラーや警告メッセージへの対応を行うことで、最低限の「C言語からC++への置き換え」が完了する。

機械的に対応可能な「Better C」化

以下の対応はソースファイルにたいしてのみ実施する(外部には引き続き「C言語時代のAPI」をそのまま公開するため、ヘッダファイルはBetter C化しない)。

  1. インクルードしている標準ライブラリのヘッダファイル名をC++のものに置き換える。
    • 例えば#include <string.h>#include <cstring>に置き換える、みたいなこと。
  2. NULLマクロからnullptrキーワードに置き換える。
  3. climitscstdintなどに定義されていマクロ定数からstd::numeric_limitsの静的メンバ関数への置き換えを検討する。
  4. 不要なtypedefを削除する。
    • 例えばstruct FooFooとだけ記述するためにtypedef struct Foo Foo;と定義することは、C++では不要である。構造体だけでなく、共用体や列挙体でも同じくtypedefは不要となる。
  5. typedefから「usingを使用した型エイリアス」に移行する。
  6. 構造体と共用体の定義にfinal指定子を付けて派生を禁止する。
    • C言語時代に定義された構造体や共用体は、派生元となることを想定した実装になっていないので、ひとまず明示的に派生を禁止しておく。
    • 後で必要に応じてfinalを止めて派生を解禁していく。
  7. 関数の宣言・定義にnoexceptキーワードを付けて「例外を送出しない」ことを明示する。
    • C言語時代に定義された関数が例外を送出することはない……はず。なのでひとまず明示しておく。
    • C++化を進めていく過程で例外が発生するようになった場合は:
      • コンポーネント内部の非公開関数では、例外を送出する可能性が出てきた関数からnoexceptキーワードを削除する。
      • コンポーネントの外部に公開される関数では、try-catchを使って例外が外に出ないようにしつつ、何かしら問題が発生したことを別の方法(例えば戻り値のエラーコードなど)で通知するようにする。なぜならば、元々C言語で実装されていたコンポーネントを使う側では、例外が送出されることを想定していない(だってC言語だよ?)可能性が高いからである。
  8. 関数の定義にて、ビルド時に未使用の引数についての警告が出力されないように(void) foo;みたいなことをしているケースにおいて、引数リストから仮引数の名前を削除する(型名は残す)方法に置き換えることを検討する。
    • C++では合法である。
  9. 非公開関数やファイルスコープ変数を無名名前空間に移動した上で、static指定子を削除する。
  10. 明示的な型キャストをCスタイルからC++スタイルに置き換える。
  11. 定数の定義をプリプロセッサ(マクロ)からconstexprに置き換える。
  12. 列挙体に基底の整数型を指定することを検討する。
    • enum classじゃない従来式のenumでも型指定できる。
  13. 従来の列挙体からenum class(スコープを持つ列挙体)への置き換えを検討する。
  14. ローカル変数を宣言している部分について、型名からautoへの置き換えを検討する。
  15. 非公開関数の引数について、ポインタから参照への置き換えを検討する。
  16. (C99よりも前のコードベースの場合)独自のブーリアン型からboolへの置き換えを検討する。
  17. (優先度:低)コメントをCスタイルからC++スタイルに置き換える。
    • 1行コメントなどは正規表現で変換しやすい。
    • 装飾を伴うヘッダコメントの類は機械的な変換が難しいので、あえて対応せず既存のままとする。

より高度なC++化

ここから先は、機械的な対応が難しく、少し大掛かりな変更となるので、慎重に進める。引き続きソースファイルにたいしてのみ実施する。

  1. POD (Plain Old Data) でなくても構わない構造体・共用体について:
    1. memset(3)によるゼロ埋めから、コンストラクタ呼び出し時の初期化(非静的メンバ変数の初期化も含む)への置き換えを検討する。
      • メンバ変数の値を初期値に戻すようなメンバ関数(例えばclearresetみたいなやつ)を定義して使用する、でもよい。
    2. 比較演算などの特定の処理について、関数呼び出しから演算子オーバーロードへの置き換えを検討する。
      • algorithmなどの標準ライブラリの機能と組み合わせやすくなる。
  2. 一時的な用途の複合型として構造体を定義して使用している部分について、std::tuplestd::pairなどへの置き換えを検討する。
  3. C言語文字列からstd::stringstd::string_viewへの置き換えを検討する。
    • リテラル構文についても検討する。
  4. 組込み配列からstd::arrayなどのコンテナへの置き換えを検討する。
  5. 従来のmalloc(3)/free(3)でオブジェクトを管理している部分について、スマートポインタの導入を検討する。
  6. スレッド間の同期用プリミティブとして「volatileな整数型」を使っている部分をstd::atomicに置き換える。
    • 必要に応じて「適切なメモリオーダー」を指定することも検討する。
  7. 「任意の型のオブジェクトへの参照」を引き渡すためにvoid *を用いている部分について、std::anystd::variantへの置き換えを検討する。
  8. スコープを超えて「任意の型のオブジェクト」を引き渡すためにvoid *と動的メモリを用いている部分について、std::anystd::variantを用いて実体を引き渡すことを検討する。
  9. 関数ポインタから関数オブジェクトへの置き換えを検討する。
    • 関数ポインタ型からstd::functionに置き換える。
    • 関数を定義するのではなく、ラムダ式を用いてインラインに定義する。
  10. ブロックスコープを用いて「関数内のごく一部で使用する変数」を定義して使用している部分について、状況次第でif・switch・範囲forの初期化式を用いる方法への置き換えを検討する。
  11. 従来のfor文を用いてシーケンスを辿っているコードについて、以下への置き換えを検討する。
    1. 標準ライブラリのalgorithmの機能。
    2. 標準ライブラリのnumericの機能。
    3. 範囲for
  12. 論理的に同じ機能を提供する関数群があり、関数名の衝突を避けるために異なる名前を付けていた場合に:
    1. 関数の多重定義(オーバーロード)を利用して、関数名を統一することを検討する。
    2. 関数内の式・文の論理構造まで同一(異なるのはデータ型だけ)である場合には、関数テンプレートを利用して、関数の実装を含めて統一することを検討する。
  13. 関数形式マクロからインライン関数や関数テンプレートへの置き換えを検討する。
  14. 独自実装の機能から、標準ライブラリで提供されている類似機能への置き換えを検討する。
  15. 環境依存の機能から、標準ライブラリで提供されている類似機能への置き換えを検討する。
    • 例えばスレッド関連、正規表現、疑似乱数など。

公開インタフェースのC++化

C言語のコードから利用されることが無ければ、公開インタフェースをC++化してもよいだろう。

外部への影響が少ない変更
  1. インクルードしている標準ライブラリのヘッダファイル名をC++のものに置き換える。
  2. 公開インタフェースをC++リンケージ化する。
  3. 不要なtypedefを削除する。
  4. typedefから「usingを使用した型エイリアス」に移行する。
  5. 構造体と共用体の定義にfinal指定子を付けて派生を禁止する。
  6. 定数の定義をプリプロセッサ(マクロ)からconstexprに置き換える。
  7. 列挙体に基底の整数型を指定することを検討する。
外部への影響が避けられない小変更
  1. モジュール名をプレフィックスとして用いている場合に、名前空間の利用を検討する。
    • 例えばfoo_Initializeならfoo::Initialize
  2. 従来の列挙体からenum class(スコープを持つ列挙体)への置き換えを検討する。
  3. (C99よりも前のコードベースの場合)独自のブーリアン型からboolへの置き換えを検討する。
  4. POD (Plain Old Data) でなくても構わない構造体・共用体について、コンストラクタの実装を検討する。メンバ変数の値を初期値に戻すようなメンバ関数でもよい。
  5. 関数ポインタから関数オブジェクトへの置き換えを検討する。
  6. 関数の引数について、ポインタから参照への置き換えを検討する。

その他

元々の「C言語で実装した際の基本設計・基本構造」を保ちながらC++化するため、局所的にはC++らしいコードになるものの、全体としてはBetter Cな感じになる。

やはり最初からC++ありきで設計した場合とは違ってくるよね。

山登りプログラマと山下りプログラマ

21世紀になってからプログラミングを始めた口で、世代としては新人研修の言語がJavaだった時分である。しかし何の因果かC言語、それも組込みではなくシステムプログラミング寄りの分野でプログラミングのイロハを学ぶことになった。

C言語によるクロスプラットフォーム・ライブラリ開発とWindows/Unixシステムプログラミング、あとC++03時代のMFCアプリ開発、この辺がキャリアの出発点である。

Cや古いC++を出発点として、そこからよりモダンで安全で便利な言語を覚えていった。モダンな言語になるにつれて、コーディング自体は楽になる一方で、作るものは高度化していくなあ、という感想を抱いている。

例えるならば、抽象化の山の3合目にてバスから降り立ち、暫くキャンプして過ごした後に、えっちらおっちら山を登り始めて、5合目や7合目を通過していった――そんな感じのキャリアだろうか。

最近、20代前半ぐらいの人と組んで仕事する機会が多いのだが、彼らのキャリアは私とは反対だ。

プログラミングの入り口がWebアプリ開発JavaScriptから入門したとか、あるいは最初から組込み開発に興味があった人でもRaspberry Pi上でPythonでデバイスを制御していたとか、要するにモダンな言語でプログラミング入門している。

そこから、パフォーマンスの問題でC++を使う必要がある開発だとか、フットプリントの都合でC言語を多用している組込みLinux開発とか、そういうプロジェクトに関わるようになった。

先ほどの山の例えを持ち出すなら、8合目や9合目に落下傘降下して、暫くキャンプして過ごした後に、5合目や3合目を目指して山を下り始めた――といった塩梅だろうか?

この違いは意外と大きい。

というのも、山を登ってきたベテラン勢は、上に行くほど作業内容が高度で難しく、下に行くほど作業が「それほど難しくはないが、面倒で泥臭い」代物になる、という風に考えている節がある。

だから「5合目の作業ができるなら、より下層である3合目の作業も大丈夫だろう」とか、「7合目の作業が問題ないなら、5合目もいけるだろう」みたいな判断を無意識のうちにしがちである。

しかし、実のところ若手勢は山を下っている最中で、3合目はおろか5合目ですら未知の世界であったりする訳だ。

だから「7合目の作業は大丈夫だったから、5合目の作業もお願い」といった感じに作業を依頼した時に、散々な出来のアウトプットを出してくることがある。

この事態に遭遇したベテラン勢は戸惑うわけだが、しかし若手勢にヒアリングしてみると、実は依頼した作業のレイヤーは割と未経験だった、ということが判明する。それでは、事前準備なしでは失敗するはずだ。自分自身が同じ立場だったら、やっぱり失敗するだろう。

難しいのは、かつてベテラン勢が経験してきた3合目の物事、我々が基礎教養だろうと思い込んでいたアレコレが、意外と広く大きく遺失技術と化している、という点だ。

抽象化層が重ねられて、下位層への新規アクセスが途絶えたことによって、遺失技術扱いになりつつあるのだが――3合目や5合目の面倒で泥臭い作業を遂行するためには必要不可欠である。

正規表現ライブラリが使えなければ状態遷移かジャクソン法でパースすればいいじゃない。

自分自身が若手の時は、後輩にそう言うだけでも通じたものだ。なぜなら本当に使えないシチュエーションが多々あったからだ。だから私も後輩も自前でパースした。状態遷移というかオートマトンの図を書きながらパーサーを設計してコードに落とし込む、ジャクソン法の「連接・分岐・反復」で正しいテキストの構造を分析して「連接・分岐・反復」の制御構文で書き下す――難しくはない*1、しかし面倒で泥臭い作業だ。

しかし今の若手は状況が異なる。状態遷移やジャクソン法について、知識としては知っていても、それを自前で操る術をしらない。なぜならば、そんなことは不要だったからだ。もしも私自身が最近プログラミングを始めたのだとしたら、やはり知らないままだろう。正規表現ライブラリが使えるのだから、正規表現でパースすればよいのだ。

我々ベテラン勢にできることは、遺失技術を洗い出して、必要な範囲で若手に伝授するぐらいである。

ところで、抽象化の山を登ってきたベテランと山を下りつつある若手の双方に共通することが1つあって、それは、慣れ親しんだ第一の言語を離れて、異なるパラダイムの2つ目の言語を学ぼうとした時に、もれなく七転八倒するということだ。

我々ベテラン勢が「よりモダンな言語」を学ぶ時に体験したアレを、今、若手プログラマはCやC++を目の前にして経験しているのである。

これについてベテランができる助言はたった1つ。大丈夫、3つ目の言語ぐらいから慣れてくるよ :p

*1:かといって易しい訳でもないのだが。

失われたHELLO WORLDをたずねて三万海里

自宅のPCのディスクの奥底からHello Worldを発掘し始めて2週間経った。我ながら色々な言語でHello Worldを書いてきたものだと、つくづく呆れている。

多少なりとも本格的に言語に触れたいならFizzBuzzを書くべきだろう。プログラムの実行環境との関係性を探るならargsなども考えられる。

でも、ほんのちょっとだけ、気の迷いでも構わないなら、Hello Worldは悪くない題材だ。

正統派なHello World

Hello Worldの起源はBCPLらしいが、有名になったのはC言語のバージョンだろう。C99以降にて合法的な版はこんな感じだろうか?

#include <stdio.h>

int main(void)
{
    puts("hello, world");
}

良くも悪くもC言語の影響は大きい。C言語の後継を目指した言語は色々とあるが、割とどれもmain関数から処理が始まる。例えばD言語の場合:

import std.stdio;

void main()
{
    writeln("hello, world");
}

愚考するに、C言語では、プログラムは「関数の塊」として抽象化されている節がある。ごく一部の例外の除き、式や文は関数の中に書く。プログラムは関数の入れ子として階層化される。

ここで、入れ子のもっとも外側の関数について、ホスト環境では便宜上mainという名前を付けることにしよう――といった塩梅だろうか? 今では言語仕様にすらなっている名前である。

時が経ち、モジュール機構やオブジェクト指向プログラミングの機能が言語に組み込まれるようになっても、下記のC++版のように関数で始まるスタイルは続いたように思う。

#include <iostream>

int main()
{
    std::cout << "hello, world" << std::endl;
}

――が、そのうちに、今度はプログラムを「オブジェクトの塊」として抽象化する言語が出てきた。その起源は知らないのだが、有名なのはJavaだろう。

class Hello {
    public static void main(String[] args) {
        System.out.println("hello, world");
    }
}

Javaの後に出てきたC#もそうである。

namespace HelloWorld
{
    using System;

    class Hello
    {
        static void Main(string[] args)
        {
            Console.WriteLine("hello, world");
        }
    }
}

後知恵になるが、いちプログラマとしては、言語仕様のレベルで「オブジェクトの塊」として抽象化してしまうのは、ちょっとやりすぎだったように思う。

プログラムを「オブジェクトの塊」として抽象化する――というスタイルの一貫性の観点では、割とキレイな抽象化だと思うのだ。

が、しかし、そんな言語を使うのは、曖昧で一貫性のかけらもない、薄汚れた我らプログラマである。ちょっとしたスクリプトを書くなら関数化すら不要と嘘吹き、小ツールを実装するには関数による機能分割で十分と豪語する。要するに、作るものの大きさに合わせて、どのレベルで抽象化するのかを自分でコントロールしたいのだ。わがまま極まりない。

――そういう事情があったか否かは定かではないのだが、JavaC#よりも後に出てきた言語では、再びmain関数から始まるスタイルに回帰している。

Dartも:

void main() {
  print('hello, world');
}

Goも:

package main

func main() {
    println("hello, world")
}

Rustも:

fn main() {
    println!("hello, world");
}

全てはmain関数に始まり、main関数で終わる。

――と言いたいところだが、昨今はSwiftなどのようにmain関数すらない(少なくともコードの見た目としては存在しない)汎用のプログラミング言語もある。

print("hello, world")

シンプルかつ正統派なHello World

main関数のようなブツの有無に関して想像するに、スクリプト言語の系譜では「そんなものはない」スタイルが常なように感じる。

まず、言語ではなく、コマンドラインインタプリタの一種であるUnixシェルや:

#!/bin/sh

echo hello, world

Windowsのバッチファイル:

@echo off

echo hello, world

これらの原型というか先祖の世代のバッチ言語やらマクロ言語の潮流から、REXXみたいなスクリプト言語が出てきたのかなあ、と妄想している。REXXの時点でmain関数が無い。

#!/usr/bin/env rexx

say "hello, world"
exit 0

スクリプト言語と言えば、私の世代ではPerl/Python/Rubyの三銃士の印象が強い。なぜか一括りにされているのだが、しかしよく見てみると、Perlと:

#!/usr/bin/env perl

use 5.030;
use strict;
use warnings;

say "hello, world";

Pythonと:

#!/usr/bin/env python3
"""hello, world
"""

print('hello, world')

Ruby

#!/usr/bin/env ruby

puts "hello, world"

Hello World程度ではまだ分からないが、もう少し手の込んだ小スクリプトを書こうとすると、どれも似てない。みんな違って、みんないい。でもmain関数が無いのは共通している。

Webクライアント開発の言語であるJavaScriptや:

#!/usr/bin/env node

console.log('hello, world');

TypeScriptのようなaltJSとして出てきた言語の大半:

#!/usr/bin/env -S deno run

console.log('hello, world');

これらもmain関数が無い。こう考えると、割とWeb開発系はフロントエンドもバックエンドもmain関数が無い言語の割合が多いのかもしれない。

いろんな言語のHello World

C言語の影響でmain関数のフォロワーが続出したと仮定するなら、では、同世代や前世代を起源とする言語はどうだろうか?

例えばPascal――は世代ではないし、Object Pascal(原典の方)も掠ってすらないので、Free Pascalで書いてみた版:

{$CODEPAGE UTF8}
{$MODE OBJFPC}
{$LONGSTRINGS ON}

program Hello;

begin
  WriteLn('hello, world');
end.

programで始まるスタイル。元ネタはFortranだろうか? Fortran 90版もprogramで始まる。

program hello
  print *, 'hello, world'
end program hello

FortranといえばCobolだが(偏見が過ぎる)、Cobolのコードは素人目にも異彩を放っているように感じられる。

       IDENTIFICATION   DIVISION.
       PROGRAM-ID.      HELLO.
       AUTHOR.          EEL3.
       DATE-WRITTEN.    2010-06-29.
      *
       ENVIRONMENT      DIVISION.
       CONFIGURATION    SECTION.
       SOURCE-COMPUTER. PC.
       OBJECT-COMPUTER. PC.
      *
      *DATA             DIVISION.
      *
       PROCEDURE        DIVISION.
       MAIN.
           DISPLAY "hello, world"
           STOP RUN.

Pascalは元々教育用途メインで開発されたらしいが、同じく教育分野で古典的な言語であるLOGOはどうだろうか?

; Worked on UCBLogo (Berkeley Logo)

print [hello, world]

シンプルに1行だけ。スクリプト言語っぽいスタイルである。元々インタプリタ的というかREPLで対話的というか、そういう感じだからなのかなあ?

そして古典の中でも異彩を放つ文法のLisp。今の時代でもCommon Lispや:

#!/usr/bin/env clisp

(format t "hello, world~%")
(quit)

もしくはSchemeの処理系は手に入りやすい。

#!/usr/bin/env gosh

(begin (display "hello, world")
       (newline))

言語処理系じゃないけどHello World

Lispといえば、汎用の処理系ではないが、Emacs Lispも一大勢力を築いている。

; emacs --batch -l hello.el

(message "hello, world")

なぜかよく対比されるviについても、Vimならスクリプトを記述できる。

" vim --cmd ':source hello.vim'

echo "hello, world"
quit!

秀丸エディタもそうだが、テキストエディタスクリプト機能を組み込むスタイルの起源はどこにあるのだろうか?

// https://hide.maruo.co.jp/software/hidemaru.html
insert "hello, world\n";

テキストエディタ以外にも、Hello Worldを書けるツールは色々ある。例えばmake(1)とか:

.PHONY: hello

hello:
	# hello, world

GDBのようなデバッガも、スクリプト機能を使えばOK。

#!/usr/bin/env -S gdb --batch -x

echo hello, world\n
quit

そして、一応計算機という立て付けのbc(1)でも:

/* bc -q -s hello.bc */

"hello, world
"
quit

計算機のハードの上でOSを動かし、その上で動作する計算機を作っておいて、なお計算機らしくない使い方をする。何とも度し難い。

身も蓋もないHello World

ここまで見てきたHello Worldの大半は式や文(命令?)をともなうものだったが、それすら省いたパターンもある。

モダンかつ一応言語っぽいところだとPowerShellだろうか?

<#
.SYNOPSIS
Hello world.

.INPUTS
None.

.OUTPUTS
hello, world

.EXAMPLE
C:\PS> .\hello.ps1
 #>
Set-StrictMode -Version Latest

'hello, world'

色々と余分なものを書いているが、肝の部分は'hello, world'だけである。

マクロプロセッサの類を含めると、事態は悪化する。例えばm4の版:

hello, world

これだと身も蓋もなさすぎるので、もう少し小細工を労してみると、こんな感じだろうか?

define(`HELLO', `hello,')dnl
define(`WORLD', `world')dnl
HELLO WORLD

m4ほとではないが、T4(Visual Studio付属のテキストテンプレート変換)の版も身も蓋もない:

<#@ template language="C#" #>
<#@ output extension=".txt" #>
hello, world

もうちょっとだけ特別感を出したいなら、こんな感じだろうか?

<#@ template language="C#" #>
<#@ output extension=".txt" #>
<#= "hello, world" #>

変わり種Hello World

身も蓋もないといえば、私は暇を持て余すあまりに「cat(1)用のHello World」というもはやHello Worldなのかすら怪しい代物を考え出したことがある。

hello, world

あまりに身も蓋もなさすぎて、さすがにrev(1)の方がマシだろうと考えたのだが……:

dlrow ,olleh

この手の一発ネタは、なかなか思いつかない。どうしても手を加えすぎてダメになってしまう。

paste(1)を使う版も割と失敗作である。

#!/bin/sh

paste -d '' - - - <<'END'
hello
, 
world
END

paste(1)版には元ネタがある。元々はxargs(1)とprintf(1)を使った版を考えていたのだが、うまくいかなくてpaste(1)版を作成したのである。

その後にxargs(1)とprintf(1)を使う版を完成させた。

#!/bin/sh

xargs printf %s%s%s\\n <<'END'
hello
', '
world
END

肝は', 'である。xargs(1)の仕様を調べていて「へーそうなんだ」と思った次第である。

思わぬ類似系Hello World

Hello Worldは時に既視感をもたらす。

スタック指向の言語、例えばForthとか:

( https://github.com/philburk/pforth )

s" hello, world"
type

あるいは、特にPostScriptとか:

%!
% ps2txt hello.ps

(hello, world\n) print
quit

この辺のHello Worldを書いた後に、オブジェクト指向言語のIoと:

#!/usr/bin/env io

"hello, world" println

GNU Smalltalkの版を見てみると:

#!/usr/bin/env -S gst -f

'hello, world' displayNl.

……どれも「テキスト → 命令」の順序になっている。だからと言って、特に何もないのだが。

未完のHello World

うまくいかなかったHello Worldも多い。

例えば、sed(1)を使うHello Worldを考えてみたのだが、どう頑張っても何かしらの入力が必要だった。

#!/usr/bin/env -S sed -f

s/^.*$/hello, world/
1q

echo '' | ./hello.sedなら動作するがecho -n '' | ./hello.sedだとダメなのだ。

XSL版も考えたが、こちらもやはり入力が必要だ。何かしらのXMLファイルを与えなくてはならない。

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="utf-8" />
  <xsl:template match="/">
    <xsl:text>hello, world&#x0A;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

まあ、Hello Worldを書いたXSL自体(つまり自分自身)を入力とすることで何とかなるのだが……。

もう少し未完成度が高いのはProlog版だろう。B-Prologで動かす前提で書いてみたのだが、規則の部分はファイルに記述できるものの、質問は別である。対話的に実行するか、もしくはコマンド引数などで与える必要がある。

% bp -g "consult('hello.pl'),hello,halt."

hello :- write('hello, world'),nl.

まとめ

2025年はHello Worldで締めたいと思う。2026年がHello Worldで始まるか否かは定かではない。ワイは鬼に笑われたくないんや……。

Microsoft EditのコピペがGUIアプリと微妙に連動しない問題 今さらconhostかよ編

微妙にタイトルで出落ちである。

Windows 11 Pro 24H2を使っているのだが、コマンドプロンプトを管理者権限で実行して旧来のconhost.exeのウィンドウで立ち上げて、その中でMicrosoft Editを使用した時、他のGUIアプリでコピーしたテキストをEdit側にペーストできないのである。

Edit内部でのコピペは正常に動作しているように見える。何ならEdit側でコピーしたテキストを他のGUIアプリにペーストすることも可能なのだが、その逆だけ動作しない。

そしてこの問題は、Windows Terminal上でEditを使用した場合には発生しない。なので回避策はWindows Terminalを使うことである。

おそらくこの問題はMicrosoft Editではなく「Editを実行している環境」――多分、conhost.exeとOpenConsole.exeの違いに起因しているのではないかと妄想している。両者のコードベースは同じらしいが、Windows Terminal同梱のOpenConsole.exeが頻繁に更新されるのにたいして、システムコンポーネントの一部であるconhost.exeは遅れて追随するようだ。しばらくしたらconhost.exe側でも正常にコピペできるようになっているかもしれない。

(それこそ、もしかしたら25H2では直っているかもしれない……)

この手の問題には稀に遭遇することがある。数年前だが、GnuPGのgpg.exeを使って「ファイルを復号して、その中身のテキストをコンソール画面に垂れ流す」ことをやろうとしたら、conhost.exeのウィンドウでは動作せず、Windows Terminalでは動作した――ということがあった。でも今ではconhost.exeのウィンドウでも動作する。この件もおそらくconhost.exeとOpenConsole.exeの違いに起因していたのだろう。

まあ「今さらconhostかよ」というか「Windows Terminal使えよ」という話でもある。そもそもMicrosoft自体が既定のターミナルをWindows Terminalに変更してしばらく経つ訳で――個人的には両者を併用して、開発作業にはWindows Terminalを使い、システム管理には管理者権限で起動したconhostを使いたいのだが、こういうスタイルは少数派なのかもしれない。

人の手のぬくもりが感じられるソースコード

最近、他人が書いたソースコードを以前よりも多く読んでいる。環境の変化*1によるものだが、とにかく、自分以外の人のコードを読み、理解し、時に問題点を指摘する、という機会が増えた。

同時に、相変わらず自身でもコードを書いている。仕事で書いて、時折プライベートでも書いている。肩ひじ張って開発してプロダクトに組み込んだモジュールもあれば、ちょっとした痛痒を解消するために適当にでっち上げた個人的な小スクリプトもある。

自分がガリガリとコードを書いているなか、世間ではLLMベースの生成AIを用いたソフトウェア開発が産声をあげたようだ。門外漢なので実情は全く分かっていないが、少なくとも産声をあげたといえる段階には到達しているように見える。ここから無事に成長するかどうかは分からないけど。

興味はある。しかし手を出してはいない。何か事情がある訳でもなく、単に興味の矛先が他にあるだけだ*2

つまりは、生成AIについては門外漢な訳だ。


門外漢ながらに思うことに、特にバイブコーディングあたりで顕著だが、現状のAIを用いた開発においては、外部仕様が割と重要な地位を占めているように見える。

LLMが自然言語処理の分野の代物であるため、AIへの指示は自然言語で記述することになる。この特徴に起因してなのかどうかは不明だが、AIと対話して仕様を策定して文書化して、文書化した仕様を元にコードを生成させて、自動化されたテストで検証する――といった感じで、少なくとも「何を作るか」については「自然言語で記述された外部仕様」で規定しているように見える。

単体テストの自動生成についても、例えば自然言語で記述された「関数のインタフェース仕様」に基づいてテストケース・テストコードを生成する、といったアプローチが見られる。テスト対象のコードを読み込ませてテストケースを生成するアプローチも存在するが、この場合、コードを読み込む際にAIが対象を批評することはなく、所与のものとして扱われる。

総じて、AIの比重を大きくする方向に進むにつれて、現状では外部仕様の重要度が高まっていく傾向にあるように見える。


外部仕様の比重が大きい、という私の見方がある程度正しいと仮定するならば、品質の観点では、少なくとも外部品質は一定以上担保されているのだろう。

ここで門外漢の私が少し気になるのは、内部品質の取り扱いがどうなるか、という点である。

C/C++プログラマの職業病かもしれないが、とかく「外部仕様を満たしているバグありプログラム」を目にしてきた身としては、現状において外部仕様の比重が大きく見えるバイブコーディングが行きつく先において、内部品質のあり方がどうなるのか、気になるものである。


私見だが、内部品質に関する本質的な部分は、半世紀以上前にダイクストラが "Testing shows the presence, not the absence, of bugs." と喝破した頃からあまり変化していないように思う。

外部仕様(インタフェース仕様)にもとづく適切なソフトウェア・テストは、バグの存在を燻り出すことはできるが、バグが無いことを保証しない。それは論理として成立しない。*3

この問題にたいするダイクストラの解答は、ソースコードという「プログラムの静的構造」を解析することでバグの不在を証明しよう、というものである。初期の論文では数え上げ・数学的帰納法・抽象によるソースコードの解析を提案している*4

ここで、半世紀前においては、プログラムの静的構造を解析するためにも人間がソースコードを読む必要があったのだが、「人間の知性の限界」という観点より「解析しやすいソースコード」と「解析しにくい/解析不能ソースコード」に分類することができる――という点に着目して、「では静的構造を解析しやすいソースコードとはどのようなものか?」を論じたのが、ダイクストラの構造化プログラミングである。


あれから半世紀以上が経過した。我々は未だに長い時間をコードリーディングに費やしているし、コーディングにおいて「理解しやすさ」という観点で内部品質を担保しようと努めている。

もちろん何も進展がなかった訳ではない。C/C++よりも遥かに安全なプログラミング言語の割合は増えた。人間によるコードリーディング以外にも、コンパイラの警告機能や、Lintのような静的解析ツールを使う「機械による静的構造の検証」というジャンルが登場して、今ではエディタ上でオンザフライで稼働している。ソフトウェア・テスト(動的テスト)においても、サブルーチンやモジュールのようなミクロな単位でのテスト――単体テストが広まっている。

それでも、だ。今でも主力は人間の目なのだ。この傾向は要求品質が高くなるにつれて顕著になる。組み込み開発でホワイトボックステストと網羅基準の話が出てくるのは、単なる偶然ではない。高品質を求めるなら静的構造の検証は必須であり、検証の主力が人間である以上、内部品質を担保してコードを読み易くすることは静的構造の検証の容易化に繋がるのだ。

まあしかし、現代は、単体テストの議論において「プライベート関数を個別にテストするか否か?」を議論できる時代である。そういう議論が成立するほどに、ソフトウェア開発の分野が広がり、結果としてプロダクトごとに要求品質の差が大きくなったと言えるだろう。

外部品質のテストと機械的な静的構造の検証だけでも十分間に合う程度の要求品質のプロダクトと、人間の目でソフトウェア内部に踏み込む必要のある要求品質のプロダクト、これらが混在しているのが2025年の状況である。


さて、仮に外部仕様駆動のバイブコーディングが広まっていくとしたら、これからどうなるだろうか?

要求品質がそれほど高くない分野では、仕様に基づく外部品質のテストと、静的解析ツール、これらの組み合わせだけで要求品質をそこそこクリアできるだろう。どちらの方法も比較的容易に自動化できそうだ。

このような分野ではAIが人間のプログラマにとって代わる可能性は高いだろう。また、プログラムにバグがあった時、小さなパッチを当てる直し方だけでなく、AIに作り直させる余地もあるだろう――自動化されたテストで要求品質をクリアできるからだ。

こうなると、淘汰されずに生き残ったプログラマプレイングマネージャーと化すのかもしれない。外部仕様を考え、文書化してAIに与えて、AIが高速かつ頻繁に提示する開発物をさばきつつ、いざとなったら自分自身も打って出る――というスタイルが目に浮かぶ。

一方で、高品質が求められる分野では、静的構造を検証するためのコードリーディングと、その容易性を高めるための内部品質の担保は、依然として重要であり続けるだろう。

この場合は、人間のプログラマとAIの共存路線が考えられる。例えば、AIに下書きさせてから人間が手を加えて形を整えていく、旧来の「サンプルコードをコピペして改変」の発展版のようなアプローチが考えられる。

どんなにAIが高速かつ頻繁にソースコードを生成したとしても、それを人間が読んで理解する必要があるならば、開発速度は人間に律速したままだろう。また、理解を深めるためにも、単にコードを読むだけでなく、コードに手を入れることも効果的だろう。

どうせ人間がボトルネックとなるならば、プログラマがより静的構造を把握しやすい方法――AIと協調してコードを書く、という戦略は十分に考えられる。

コードリーディングにおいてもGitHub Copilotのコードレビューのような仕組みが出てきている。学習データの出どころを考えると、カバーされるのは一般的な問題点に絞られる気がするが、しかしその分だけプログラマの負担は減るだろう。より重大な、複雑な問題に焦点を当てたコードリーディングに時間を割くことができるはずだ。

まあしかし、この分野のプログラマも幸せにはなれないかもしれない。元より品質確保のために技量が求められる分野ではあるものの、より高い技術力が要求されるようになる気がしてならない。

AIに下書きさせたコードに手を入れるならば、AIよりも一段上の「コードへの審美眼」が求められるだろう。他人のコードを読んで良し悪しを判断できる技術水準に達していなくてはならない。AIが軽微な問題を指摘するならば、AIではカバーしきれない「難しい部分」が人間のプログラマに回ってくることになる。


――ここまでは門外漢の妄想だ。さてはて、一体どうなることやら。

非常に興味深い時代だ。同時に、1人のプログラマとしてキャリアの先が不透明で困惑させられる時代でもある。もっと気楽に、へらへらしながらコードを書きたいのだけどなあ。

*1:新しいコードベースを相手にしている、メンバーが増えてコードレビュー回数も多くなった、等々。

*2:個人的経験に基づけば、中途半端に興味がある状態で手を出しても良い結果にはならない。手を出すには、まだまだ興味ゲージが足りていないようだ。

*3:三段論法「もしPならばQである。Pである。従ってQである」に誤謬はないが、「もしPならばQである。Qである。従ってPである」は後件肯定である。経験的に正しいと思われる「もしこのソフトウェアにバグが無ければ、テスト結果は全てOKである」という命題の下において、「テスト結果は全てOKだった」という事実から「このソフトウェアにはバグが無い」と主張することは、形式的誤謬が過ぎるというものだ。そして我々は、これまた経験的に「もしテスト結果が全てOKならば、このソフトウェアにはバグが無い」という命題が現実世界において成り立たないことを知っている。

*4:後には最弱事前条件も出てきて、ホーア理論などの形式手法や、契約プログラミング(契約による設計)に繋がっていくことになる。

VBAの学び方 for プログラミング経験者

WSH (Windows Script Host) のJScriptVBScriptを触っていた頃からの不満なのだが、MicrosoftEUC (End-User Computing) 向けプログラミング言語の教材は軒並み「プログラミング初心者向け」である。

いやまあEUCなので、幅広い層をターゲットにしようとした結果として、その辺のレベル向けの教材が大勢を占めるに至ったのは分かる。

なのだけど、なんだろう、本当に「プログラミング初心者向け」のものばかりで、そこからステップアップしていく部分の情報が非常に少ないのである。*1

(私は無茶苦茶C言語っぽいスタイルのJScriptでベタ書きされたWindows Server管理用スクリプトのことを決して忘れないだろう)

このことは、他のプログラミング言語の経験者が例えばVBA (Visual Basic for Applications) を学ぼうとした時に、必要とする情報のミスマッチに悩まされる――という事象に繋がる。

基本的にVBAの教材は「このAPIを使えば〇〇できる」とか「××したいなら△△のAPIを使おう」みたいな部分が多い。またサンプルコードはベタに書かれている。これはこれで情報源としては役に立つ。しかし例えば「〇〇と××と□□と▽▽を組み合わせたいが、プログラムが大きくなるので、メンテナンス性や効率性を考慮して構造化したい」というときに高確率で「この教材には参考になりそうな情報が欠けている……」と悲しみに包まれることになる。

では、例えばVisual Basic系以外のプログラミング言語での開発経験者がVBAにキャッチアップするには、どうすればよいだろうか?

先に挙げた「プログラミング初心者向け」の情報に容易にアクセス可能である(リファレンス本が手元にある等)という前提で、開発者が追加で参照すべき情報は、概ね次のように分類可能だろう:

  1. 開発環境およびプロジェクト構成
  2. Visual Basicの言語仕様と組み込み機能(Microsoft Formsを除く)
  3. Microsoft Forms
  4. 開発ターゲットのOfficeアプリ固有の機能
  5. 参考:Officeアプリ共通の機能
  6. 外部連携(特にデータベース)

開発環境およびプロジェクト構成

Visual Basic Editorなるものが存在することと、ステップ実行の方法、この2点は押さえておくとよい(起動方法や詳しい操作方法は都度ググってOK)。例えVisual Basic Editorで開発を行わなくとも、実際に問題が起きているコンピュータ上でVisual Basic Editorを起動してデバッグすることが可能だという点は、いざという時に役に立つかもしれない。

プロジェクト構成については――VBAのコードはOfficeアプリケーション用の各ファイルに格納される、という特殊な事情を念頭におくべきだろう。他の言語で言う「ファイル分割」をしたくなった場合の正攻法は「Visual Basic Editor上でモジュールを追加する」だろう。

今となってはVisual Basic Editorは使いにくいのだが、諸々の特異性より、なかなか他のエディタ等にスイッチしにくいという難しさがある(どうしてもインポート・エクスポート的な処理が間に挟まってしまう)。もちろん、例えばVisual Studio Codeで開発するためのサードパーティによる仕組みもあるのだが、その内部で「インポート・エクスポート的な処理」をやっている旨を把握しておかないと、何か問題が起きた時に場当たり的なアプローチに終始してしまいかねないだろう。

Visual Basicの言語仕様と組み込み機能(Microsoft Formsを除く)

Microsoft公式のVBAのリファレンスは、Visual Basicという言語に取り組む際に色々と参考になる。

言語の雰囲気が分かるし、組み込み関数などのリファレンスを見ることで意外な便利機能が見つかったりする。

公式の情報だということは、すなわち「VBAの言語開発者が考える『VBらしいコード』の書き方」がサンプルコード等に表れていると言っても過言ではないだろう。

なお、モダンな言語に慣れた身からすると、Visual Basicの各機能はプリミティブ気味に感じる上に、抽象化に使える手札が少ないので、コードを書いていて時々「ちょっと面倒だなあ」と感じる時がある。

Microsoft Forms

こちらもMicrosoftの公式情報を参照するとよいだろう。

GUIプログラミングの流儀はフレームワーク等によって随分と異なる。VBAのそれは、.NET以前のWindowsアプリ開発の経験があれば、何となく方向性が分かるのだが……。

開発ターゲットのOfficeアプリ固有の機能

VBAといったら十中八九Excel VBAなので*2、その手のリファレンス本との抱き合わせで、これまた公式情報も押さえるとよいだろう。

大まかな使い方はリファレンス本で十分だが、そこから先の細かい部分は公式情報でチェックすることになる。

参考:Officeアプリ共通の機能

ところでVBAはOfficeの各種アプリに組み込まれているのだが、その際の抽象化のアプローチとして「Officeアプリ共通の部分」と「特定のOfficeアプリ(例えばExcel)に固有の部分」みたいな感じに分けられている。

そのためか、Officeアプリ共通のカスタマイズ・ポイントに関する話題は、公式情報の中で独立した章立てとなっている。

ここまで手を出すことって、どのくらいあるのだろうか……?

外部連携(特にデータベース)

この辺もリファレンス本 + 公式情報の組み合わせで進める部分だろう。問題は、個々の技術(例えばActiveX Data Objectとか)の公式リファレンスを探すのが面倒なことだが……。

まあしかし、例えばMicrosoft SQL Serverとの連携云々とかそういう次元になってくると、クライアント側でVBAで頑張るのではなくて、サーバ側も含めた全体でのアプローチの見直しを検討してもよいかもしれない……ケースバイケースだけど。

足りないもの:VBAでのコーディング・ベストプラクティス

ここまでの情報で、言語の細かい部分や、具体的な「やりたいこと」実現のための情報などは網羅できるのだが、唯一知ることができないのはVBAでコーディングする際のベストプラクティスだと思う。

VBAだけでなく、VBScriptVisual Basic .NETを含めて、VB一族のベストプラクティスに関する情報は非常に少ない。プロプライエタリな言語だからか、それともVBが「クールな言語」だった時代よりも後にインターネット普及期が来たからか……。

プログラマには、後発の言語に飛びついて「こいつはクールだ。それに比べて――」と今まで使ってきた言語を腐す人も多く含まれているものだ)

一応Visual Basic .NETには公式のコーディング・ガイドラインがあるのだが:

VBAになると、個人レベルの情報に限られてしまいがちな印象がある。

まとめ

なお、このエントリを書いている人はVBAでの開発経験が無いことに留意すること。Excel VBAで書かれたツールを解析した経験はあるが、コードを書いたことはない。

私のVisual Basic系言語での開発経験は、VBScriptでサーバ管理用ツールを書いたことと、初期のVisual Basic .NETで開発されたASP.NETアプリケーションに後から機能追加を行ったことぐらいである。

*1:この点について、PowerShellでは幾分と改善されているように思うのだが、如何せんPowerShellそのものが割と独特である……。

*2:さすがに言い過ぎである。案件としてはAccess VBAもありうるだろう。それ以外のOfficeアプリのVBA案件は知らない。

書籍購入:『C++ソフトウェア設計』

買った。ソフトウェア設計の本なのだが、最近のC++の機能(言語機能と標準ライブラリの両方)の使い方が目当てである。

本書はデザインパターンを核に据えたソフトウェア設計の本なのだが、同時に、デザインパターンをModern C++で実装したらどうなるかを解説している本でもある。

すなわち、現実のプロダクション・レベルのプログラミングにおいてModern C++の機能をどのように使うことが可能であるか、説明付きのコードが提示されている訳だ。読み進めているうちに「標準ライブラリにこんな機能も用意されているのか」とか「あの機能は、こう使うこともできるのか」といった風に、設計以外の面でも思わぬ知見が得られるものである。

それと同時に、後世になってデザインパターンを学ぼうとして、しかし肝心のサンプルコードが古き良きクラスベースのオブジェクト指向プログラミング版しか見当たらず、思わず「これはつらい……」と嘆息してしまった人にとって、Modern C++前提のサンプルコード付きの本書は福音となるかもしれない。

非常に興味深い本である。Modern C++目当てで買ったものの、私自身はソフトウェア設計についてろくに学んでこなかった人なので、デザインパターンの解説の部分にも目を通している。

Makefileを便利コマンドメモとして使う時の一捻り

ふーん……。

ほな、便利コマンドメモとして使う時に一捻り加えるために、黒魔術のさわりのさの字だけ書いとこかな……。

レベル0:疑似ターゲットを使う

疑似ターゲットを使って、例えばmake initと実行すると何らかの初期化処理を実行する、というのは分かりやすいし、一見して便利そうである。

だけど、単に「疑似ターゲットに記述した処理を実行する」だけなら、例えば「シェルスクリプトcase "$1" in」しても良いのではないか(ちょっと面倒だけど)、という話もある。

case "$1" in
help)   usage 0 ;;
init)   do_init ;;
task1)  do_task_1 ;;
task2)  do_task_2 ;;
task3)  do_task_3 ;;
*)      usage 1 ;;
esac

make task1 task2 task3みたいな挙動も、シェルスクリプトforcaseを組み合わせて実現できるからなあ……実現は簡単だけどちょっとだけ面倒、というレベルで。

for i; do
    case "$i" in
    help)   usage 0 ;;
    init)   do_init ;;
    task1)  do_task_1 ;;
    task2)  do_task_2 ;;
    task3)  do_task_3 ;;
    *)      usage 1 ;;
    esac
done

レベル1:疑似ターゲット間の依存関係を指定する

make(1)には「『依存関係』指向のルール記述言語」みたいな側面がある。これを利用して、例えば「疑似ターゲットinitに初期化処理をまとめておいて、他の疑似ターゲットの依存関係に指定しておく」という記述してみよう:

.PHONY: all init task1 task2 task3

all: task1 task2 task3
	# all tasks done.

init:
	# initialize for tasks.

task1: init
	# exec task 1.

task2: init
	# exec task 2.

task3: init
	# exec task 3.

ここで興味深いことに、例えばmake allと実行した時に、各タスクの依存関係にinitが指定されているにも関わらず、initの初期化処理は1回しか実行されない。

$ make all
# initialize for tasks.
# exec task 1.
# exec task 2.
# exec task 3.
# all tasks done.
$ _

このような振る舞いをシェルスクリプトあたりで実現するのは面倒である。多分、有向グラフのトポロジカルソートとかを使う必要があるんじゃないかなあ、知らんけど。素直にRakefileやTaskfileなどの依存関係をサポートした専用ツールを使うべきだろう。

各タスクごとに毎回初期化処理を実行したいなら、初期化処理をシェルスクリプト等に独立させて、素直にタスク実行前に呼び出せばよい。GNU Makeなら、シェルスクリプト等の代わりにdefineディレクティブを使ってもよいかもしれない。

レベル2:ファイル間の依存関係を指定する

さて、make(1)の「依存関係」指向な側面は疑似ターゲットにおいても有用であるのだが、疑似ではないターゲット――ファイルに適用することで、より効率的なタスクランナーを実現できるようになる。

そもそもmake(1)は「コマンドにファイルAを入力して、出力としてファイルBを得る」というタスクの自動化ツールである。

make(1)がC/C++のビルドで用いられるのは、まさに「コンパイラにソースファイルを入力して、出力としてオブジェクトファイルを得る」というタスクの集合だからだ。とはいえGNU Makeの中をのぞき見すると、C/C++以外のプログラミング言語Fortran・Modula-2・Pascalアセンブラなど)のビルド、LexやYaccによるC言語のソースファイルの生成、Texinfoでのドキュメントの生成――など組み込みルールが含まれている。単にプログラムのビルドに留まらない、意外と間口が広いツールなのである。

ファイル間の依存関係において興味深いのは、ファイルの更新時刻を比較してタスクを実行するか否かを決定することだ。入力ファイルAの内容によって一意に出力ファイルBの内容が決定されると仮定するならば、一度ファイルAからファイルBを生成した後、ファイルAの内容が更新されない限りファイルBを生成し直す必要はない。このような挙動をリーズナブルに実現するために、make(1)は依存関係にあるファイルの更新時刻を比較する。もしもファイルAの内容が更新されたならば、ファイルAの更新時刻はファイルBよりも新しくなっているはずだ、という考え方だ。この方法は万能ではないし穴もあるが、それでも大抵のシチュエーションではうまく機能する。

例えばGoogleTestを使っていて、「CMakeを使ってMakefileを生成 → 生成されたMakefileを使って単体テスト用のプログラムをビルド」という作業を毎回コマンド実行するのが面倒なので整理してみよう:

WORKDIR    ?= work

cmakelists := ./CMakeLists.txt
makefile   := $(WORKDIR)/Makefile

do := cd '$(WORKDIR)' &&

.PHONY: build clean configure distclean run

build: $(makefile)
	$(do) make

clean:
	$(do) make clean

configure: $(makefile)

distclean: clean
	$(RM) '$(WORKDIR)'

run: build
	- '$(WORKDIR)/unittest'

$(makefile): $(cmakelists)
	[ -d '$(WORKDIR)' ] || mkdir -p '$(WORKDIR)'
	$(do) cmake ..

CMakeはCMakeLists.txtを元にプロジェクトファイル(ここではMakefile)を生成する。一度プロジェクトファイルを生成したら、次にCMakeLists.txtが更新されるまでの間はプロジェクトファイルの再生成は不要だ。で、CMakeLists.txtが更新されたら、CMakeを実行してから単体テスト用プログラムをビルドし直す必要がある。

――ということを依存関係を用いて解決している。make runでプロジェクトファイルの生成から単体テスト用プログラムの実行までワンストップで実行するが、プロジェクトファイルの生成は必要な時にしか実行されない。

ここではUnix環境を想定しているため、プロジェクトファイルはMakefileであるが、Visual Studioのソリューションファイルを生成するケースでも同じようなことを実現できる。

ここで挙げた例のようなアプローチは、自動化したいタスクを注意深く分析することで、意外と適用できそうなシチュエーションを見つけられるものだ。個人的な話となるが、過去に依存関係を用いて「Dockerfileが更新されたらDockerイメージを作成し直す」というタスクを実現したことがある。まあDockerイメージそのものをファイルとして参照できなかったのでtouch(1)とダミーファイルを使ったのだが……。

レベル3:パターンルール/サフィックスルールを追加する

make(1)にはパターンルール(GNU Makeだとサフィックスルールも)が組み込まれている。これによって拡張子だけ異なるファイル間の変換(例えばfoo.cからfoo.oへの変換)を毎回Makefileに記述しなくても実行できるようになっている。

パターンルールやサフィックスルールは、よくあるパターンはmake(1)に組み込まれている(だからこそ、C/C++アプリのビルド時にMakefileに記述する内容を簡素化できるのだが)。この組み込みルールは、ユーザ側で一時的に上書きすることが可能だ。また、新たなルールを追加することもできる。

例えば、GNU Make向けに、以下の内容だけ記述したMakefileを用意してみよう:

%.html: %.md
	# pandoc -f markdown -t html -o $@ $<

以下のような風に遊ぶことができる。

$ ls
Makefile  foo.md
$ cat Makefile
%.html: %.md
	# pandoc -f markdown -t html -o $@ $<
$ make foo.html
# pandoc -f markdown -t html -o foo.html foo.md
$ _

コメントアウトを止めてpandoc -f markdown -t html -o $@ $<にすると、pandoc(1)を利用可能な環境ではMarkdownからHTMLへの変換が可能となる。

まとめ

Makefileをタスクランナーや便利コマンドメモとして使うならば、最低でもレベル1に到達しないと「make(1)を選んだ旨味」が無いよねって思ってしまう。

まあ、実のところ後発のツールでもレベル2までは同じことを実現できるはず(レベル3については調べてないので分からない。Rakefileでは可能なはず)。

可読性その他に優れた後発ツールがあるにも関わらず、それでもMakefileを使うのは、単に「大抵の開発者の環境にmake(1)がインストールされているから」だ。

その、自分と他の開発者とで、開発環境の同質性が高い環境ならば、例えばTaskfileを使っても何の問題もないはずなのだ。何ならみんなbrew(1)を使える環境だからbrew install go-taskでインストールできるよね――といった具合。Rakefileだって、みんなRailsで開発してるからgem install rakeできるはずだよねって。

でも世の中そんな場所だけではない訳で、私なんて左右の席の開発者とは開発環境が結構異なるし、何なら左の席と開発者と右の席の開発者の間でも相応の差異がある。そもそも開発しているソフトウェアのターゲット環境が全く異なるのだ。

このような、開発環境の同質性に疑義が生じる環境においては、自分以外の開発者のPCにもデフォルトで入っていそうなツールは「ベストではないがベター」な選択肢となる。

まあでもLinuxmacOSならGNU Makeを前提としても問題ないけど、Windowsだと「Visual StudioかQt Creatorが入ってるだろうから、NMAKE向けの文法で書こうか」となるのが辛いところだ。流石にMicrosoftが公式にGNU Makeを移植してくれるとは思えない……どちらかと言えば「NMAKEは止めてDevenvかMSBuildを使え」ってスタンスだよなあ。

今までどのくらいプログラミング言語を触ってきたか(3秒で挫折したものものも含む) Ver.17

2025-06-30現在のステータス。昨年(2024-06-22)から1年経て、こうなっている。

eel3.hatenablog.com

なおCSS、HTML、XMLはひとまず除外する。人工言語ではあるけれども「プログラミング言語」という括りに含められるか否かは議論が分かれる気がする。*1

まあでも、XMLについてはメタ言語だから――XSLTみたいに、XML上に構築されたものがプログラミング言語的なことはあるよね。MSBuildXMLスキーマプログラミング言語っぽい部分があったりするし。

よく使っている

AWK (Gawk)
単純なテキストレコードの処理はAWKで十分間に合う。今の時代、自作ツールをnawkやGNU awk単体で実装するのは苦行すぎて*2皆無なものの、シェルスクリプトMakefileAWKのコードを埋め込むなどして他のコマンドと組み合わせて使う機会は依然として多い。シェル上でワンライナーでテキスト処理する時にも重宝している。これはこれで十分AWKらしい使い方ではないだろうか? ところで、はやくオプション--csvが使える処理系が広まってくれないかなあ……。
C++
最近のお仕事の主力言語で、C言語のコードをC++に移行する作業も多いのだが、しかし未だに本職のC++使いのレベルに到達できていない。もともと「C++11以降は非常に便利で、ホスト環境ではbetter Cでも使う価値がある」と思っていたのだが、最近はそこそこ新しいC++仕様に準拠したコンパイラを使える環境にいることもあり、より便利な機能を積極的に使えるようになった。嬉しい反面、旧来のアプローチとどう併用したらよいのか、試行錯誤が続いている。正規表現とスレッドとファイルシステムが標準ライブラリに加わったので、あとはソケット(低水準ネットワークAPI)をサポートしてくれないだろうか。低水準の処理を行いつつも便利な機能で実装時間を短縮できる点は便利で、少なくともシステムプログラム言語としての利点だと思う。だけど機能多すぎ/複雑すぎなところはなんとかならないものか。強力な反面、使い手を選ぶ言語だ。
C言語
お仕事での主力言語だった――ここ最近は仕事では使っていない(C++のコードの一部にC言語寄りのコードを埋め込むことはあるけど)。時々、仕事以外で触れる機会があり、その際にはなるべく最近の仕様に触れるようにしている。シンプルかつ低水準の世界が垣間見れるところが割と好きだが、同時にどうしようもなく面倒にも感じる。最近の他の言語と比較すると、シンプルすぎて安全機構が欠けているし、標準の便利機能も少ないので、入門用の言語としては薦められないが……一方でモダンな言語とは「変数とメモリ」まわりの概念モデルが全く異なるために、モダンな言語でプログラミング入門した人が後からC言語を学ぼうとした時に「変数とメモリ」についてのメンタルモデルと概念モデルの食い違いに悩む姿も見ているので、何というか、C言語から入門するのも、C言語以外で入門してからC言語を学ぶのも、どちらも大変ですな。
make (Makefile)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで……いやGNU Makeはそこそこプログラミング言語的か。GNU Make 4.0はさらにプログラミング言語的だな、特にGNU Guileなところが。GNU MakeとNMAKEが主力。昔は稀にNetBSD Make(pmake)を使うこともあった。3者いずれも独自拡張アリアリで使っている。もう素のmakeではMakefileを書けない :) 最近はLinux向けツールのインストール・スクリプトを書く時にシェルスクリプト(特にBashアリアリとか)や高水準なスクリプト言語Pythonとか)が選ばれがちで、なかなかMakefileを見かけることが少なくて、ちょっとだけ寂しさを感じている。いやでも「ファイルを入力として、別のファイルを生成する」系の作業のタスクランナーとしては、makeはまだまだ使えると思うのだ。
Objective-C, Objective-C++
時代はSwiftだと言われて久しい――どころか場所によっては「Flutter + Dartがメインで、Swiftはサブ」みたいなこともありそうだけど、どっこいObjective-CObjective-C++は生きている。というかSwiftのコードにC++で書かれたライブラリを直接組み込むことができない以上、両者を繋げるグルー言語として生き残ることになるよね。一定以上のリアルタイム性が求められるアプリをSwiftだけで書くのは厳しくて、どうしても部分的にC言語C++を使うことになり、グルー言語としてObjective-Cが召喚されることになる。最近流行の言語と比べると良くも悪くも80年代的だが、アプリケーションプログラミング用としてはC言語よりマシだし、C++ほど複雑怪奇*3ではない。そしてC言語C++で書かれた既存のライブラリをそのまま使える。Objective-Cのハイブリッドな所は好きだが、Objective-C++はハイブリッドすぎて――C++のクラスとObjective-Cのクラスを、C++ラムダ式Objective-Cのブロック構文を同時に使うのは大変だ。便利ではあるんだけどね。
Python
CLIの小ツールを作る際の主要言語となりつつある。コードは型ヒントありありである。Pythonではlazyなスタイルでのコーディングが許されず、整然とコードを記述する必要がある。その辺は、Perl 5やRubyとは随分と雰囲気が異なるように思う。特に、インデントが必須な言語仕様であるために、シェルスクリプトに埋め込んで使うのが苦痛だ。Pythonだけでコードを書く分には気にならないのだが……。
シェルスクリプト (/bin/sh)
プログラミング言語に含まれるかどうか不明だが……いや、私的にはシェルスクリプトは立派なプログラミング言語だ。基本的な用途は、バッチファイルと同じくちょっとした自動化や複数コマンドを組み合わせて使うときのラッパーだが、実現できる内容は遥かに多い。言語本体(?)がバッチファイルよりも高機能だし、Unixユーザランドはコマンドが充実している。その意味では、WindowsではMSYSよりもCygwinで――いやむしろWSL(Windows Subsystem for Linux)で環境構築すべきだろう。Cygwinでは、主要な処理をシェルスクリプトで記述しておき、bashからはシェルスクリプトを利用し、コマンドプロンプトではラッパーのバッチファイル経由でシェルスクリプトを叩く使い方をしている。ただWindows上では処理速度が妙に遅くなる点が不満だ。まあしかし、Unixのシェルは言語設計もシステム開発技法も未成熟だった大昔に「プアな環境でも問題なく動作する、プログラマブルな対話型コマンドインタプリタ」として開発された代物なので、言語設計の研究が進んでから作られたプログラミング言語と比較してはならない。なお自分自身が落とし穴に嵌らないようにShellCheckを活用すべし。

あまり使っていない

DOSバッチファイル
プログラミング言語に含まれるかどうか不明だが、含めてしまう。ちょっとした自動化や、複数ツールを組み合わせて使うときのラッパーとして、今でもよく使う。コマンドプロンプトはシバン(shebang)に対応していないので、スクリプト言語で書いたツールを起動するラッパーとしても多用している。意外と色々なコマンドが用意されているので、単純にそれらを叩く分には十分だが――言語機能がショボいので、バッチファイルでifやforのような制御構文系コマンドが必要になってきたら、何か決定的に間違えていないか、考え直すようにしている。
Ruby
自作ツールを実装する時、最近はPythonを使うことが多いのだが、時々Rubyを選択することもある。多言語化(文字エンコーディング)が絡むテキスト処理や、処理速度は考慮しなくてよいが桁あふれが気になる数値計算を行う場合だ。あとirb(1)も時々使っている。to_s(16)to_s(2)で基数変換して表示できるところが割と好き。
Scheme
GaucheWindowsネイティブ環境用バイナリは実験版だが、私が触る分には何の支障もない*4ことに気づいて久しい今日この頃。REPLを常時3つぐらい立ち上げているのだが、いずれも電卓代わりにしかしていない。REPLに貼り付けるコードもSchemeCommon LispのどちらでもOKな四則演算に毛が生えた代物である。より本格的にUTF-8な環境に移行したら、大手を振ってGaucheでフィルタを書きたいなあ。
sed
プログラミング言語に含まれるかどうか不明だが、DSL扱いで*5。テキスト処理用。シェルスクリプトMakefileにて他のコマンドと組み合わせて使う。というか正規表現でのテキスト置換以外の機能を使った記憶が……あったな、dとiとpと=とブレースによるグループ化ぐらいだが。私の技術レベルではsedFizzBuzzを書けないので、sedで難しい処理を記述しないようにしている。
Swift
コンパイラによる強力な型推論と型安全性のチェック」がお仕事用のメジャーな言語にまで降りてきたという点で、Swiftは静的型付け言語界のJavaScript*6だと思っている。でもユーザ数的には、Kotlinが「静的型付け言語界のJavaScript」ポジションなのかもしれない。割と好感が持てる言語だが、知識が中途半端にKotlinとごった煮になっているので、ついうっかりif式を書こうとしてコンパイルエラーになったり、「varval」と「varlet」の振る舞いの差異につまづいたりしてしまう*7
Windows PowerShell
Windows 11に移行したこともあり、少しずつPowerShell Coreを触り始めている。しかし今までに書いたスクリプトは以前としてv5.1で動かしている。スクリプト言語としてのPowerShellは、オブジェクト指向.NET Frameworkを叩けてダイナミックスコープでスクリプトブロック(という名の無名関数)と、無茶でピーキーで完全にプログラマ向けな代物だ。Microsoftもよくもこんなエライ代物を出したよね。コマンドプロンプトの代替という観点では、外部ツールとの親和性が微妙にイマイチな気がする(特に文字コードとか)。でもPowerShell内で閉じている分には問題ない。私の手元では「Windows専用のGUI付き小ツールを作るためのスクリプト言語」か「Excel COMとか叩く用のスクリプト言語」か「Windows Serverの管理スクリプトを書くためのスクリプト言語」扱いしている。ところで、いい加減『Windows PowerShell イン アクション』並みの言語解説書の最新バージョン対応版を出版してくれないだろうか。

最近使ってないが、縁は切れてない

bash
最近はデフォルトシェルがbashな環境も多いので、自分用のツールぐらいは素の/bin/shではなくbashで書いても大丈夫な気がしてきた。shよりbashの方が遥かに便利だからなあ――PerlRuby等には負けるけど。bashスクリプトを書くときの唯一の欠点は、メジャーバージョンごとの差異や各ディストリでのビルドオプションの違いにより、同じbashという名前でも実は千差万別なところだと思う。PerlRubyのバージョンは気にするけど、これがシェルになると意外とバージョンに無頓着になってしまう。なんでだろう?
Go
寡作ながらもいくつか小ツールを書いてみたが、標準ライブラリが充実しているコンパイラ型言語っていいっすね。C言語に比べればC++の標準ライブラリも充実しているが、どちらかといえばプリミティブな機能が中心だ。PythonRubyばりの標準ライブラリを抱えているGoには及ばない。その辺は、やはりCプログラマ(特にCでフィルタやデーモンの類を書く層)には受けそうな言語だと思う。並列処理周り(goroutines)とかARM対応とかが気になる。ソフトリアルタイム限定だが「組み込みLinux + Goで書いたデーモン」とかどうだろう? ただメモリを食うらしいという噂がどうなったか気になる――64bit環境では解消されるという話だったようだが、32bit環境でも解消されるようになったのだろうか? 組み込みでは現時点では逆立ちしたって64bit CPUはありえないからなあ、スマホタブレット以外では。
Java
生まれて初めて触れたプログラミング言語その2。実のところ、職業プログラマとして本格的に使用することは一生ないと思っていた。Androidアプリ開発も、Kotlin採用後に本腰入れて関わるようになったので、Kotlinメインだ。だが、なぜかぬるい感じに時々Javaのコードを触っている。先にコレクションの操作方法が充実した他の言語を学んでからJavaを本格的に触るようになったので、Java 8以降のStream APIが使えないと身体が拒否反応を示す。少なくとも、構文の見た目こそ保守的なオブジェクト指向プログラミング・スタイルで書かれたC++に似ているけど、中身はC++とは似ても似つかない代物だということは体感している。
JavaScript(クライアントサイド)
ものすごく久しぶりにクライアントサイドJavaScriptのコード触った――いまどき珍しい、DOM直叩きスタイルだけど。収穫は、最近のECMAScriptのスタイルに触れたぐらいだろうか? フレームワークもTypeScriptも触っていないので、クライアントサイド開発のスキルは依然として賞味期限切れのままだ。
JavaScript(サーバサイド?)
初めてお仕事でNode.js向けのJavaScriptのコードを触った。Web開発の外の人からみた印象としては、ブラウザ以外のJavaScript処理系はNode.jsに収斂しちゃった感があるなあ――Denoもあるけど、エコシステム的に今後どうなるんだろう?
Kotlin
本格的にAndroidアプリ開発に関わるようになったのがGoogle I/O 2017直後の過渡期なので、JavaよりもKotlinでの経験値の方が多い。モダンな「強い静的型付け」の、割とええ感じの言語やね。ただ、使い始めが「Swift 3をつまみ食いして半年以上経ってからKotlinをつまみ食いした」みたいな経緯だったこともあり、未だに両者の概念・機能が頭の中でごった煮になっている。それと、NDK絡みの作業が多いので、C++17・Java 7/8・Kotlinを行ったり来たり。泣けるぜ。Swiftもそうだが、最近のメジャーな「強い静的型付け」の言語は「開発環境込み」で高い生産性とコードの安全性を両立させる方向に進んでいる気がする。
Lua
Wiresharkのパケット解析スクリプトを書いたことも、C言語で書かれたUnixデーモンの設定ファイル用に処理系を組み込んだこともあった*8。あれから数年経ったが、今はどんな感じなんだろう?
Perl 5
時々、やむをえない事情で触ることがある。だが基本的によく分からない。何というか、あの記号の羅列っぽさに中々慣れないというか、自分は余りに自由度が高すぎる言語は苦手だと気づいたというか。(言語仕様に慣れているなら)半ば使い捨てなテキストフィルタとかをさっと書くに分には悪くない言語だと思うのだけど。
SQL
生まれて初めて触れたプログラミング言語その3ぐらいに位置する。組み込みの人なのでSQLとは無縁だと思っていたが、まさかTransact-SQLを少しだけ触ることになるとは。最近はAndroidアプリ絡みでSQLiteに触れることもあるが、AndroidXのRoom経由だったり、ContentResolverのqueryだったりと、フルセットのSQL文ではなく局所局所でDSL的に使う感じである。
Tcl/Tk
Tclは書き方を忘れた頃にテキスト処理ツールを書いている気がする。Tclは結構独特な言語だ。構文がシェルスクリプトばりに全てコマンドだったり、値が全て文字列だったり、実はリスト構造だったり、意外とTCPソケット通信が得意だったり……。それでも慣れれば結構使いやすい。意外とプロトタイピングに向いている気がする。8.6以降ではオブジェクト指向プログラミングもOKだが、それよりも例外処理用のtry末尾呼び出しの最適化用のtailcallの方が興味深い。しかし、これからメジャーになる可能性は低そうだ。Tkは……小規模なGUIツールをさくっと構築できるところは便利だが、Webアプリ全盛の時代にどれだけ訴求力があるのやら。
Visual Basic .NET
Visual Basic .NET 2003で書かれたコードを時々メンテ中。流石に開発環境はVisual Studio 2013移行したけど。
XSLT
よく考えてみたら生まれて初めて触れたプログラミング言語その4ぐらいに位置する言語だった。縁が切れたと思いきや、仕事でXHTMLから特定要素を抜き出す作業に使うことがあったり……。XMLからテキストレコードに変換してしまえば、後はUnix流テキストフィルタの世界が待っている。餅は餅屋というもので、定型的なXMLの変換はXSLTで記述するべきか。唯一気に入らないのは、xsl:sortでアルファベットの大文字と小文字を区別してソートすることができないこと。ぐぬぬぬ。

これから(また)使うかもしれない

Alloy
形式手法の中では比較的カジュアルに使えそうなので期待中。入門書も処理系も入手した。私の場合、先に何か論理型の言語をかじった方がよいのかも。
bison (yacc)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。やっぱり構文解析系統のコードを自作するのは割に合わない――だなんてうそぶきつつ、LALR法とか全く知らないままに、既存のyaccのコードを切り貼りして遊んでみた。簡易電卓レベルだが便利さを体感しつつ、さっそくtypo 1文字で痛い目(shift/reduce)に遭った。とりあえず、flexと組み合わせた上でのエラー処理(エラーメッセージの改善)が課題だ。
C#
かつて、勉強を兼ねてC# 2.0を少し触ろうとするも未完に終わり、数年後にあらためてVisual Studio 2013をインストールして少しだけ触った*9けどほんの少しだけで終わった過去をもつ私。変数の型推論ラムダ式LINQ・デフォルト引数は便利だなあと思っていたら、いつの間にかC# 8.0になってKotlinやSwiftに見られる流行を取り入れてますな。おっちゃん、付いてくのが大変だよ。.NET Frameworkの機能数は反則ものだが、所々に微妙に抽象化が行き過ぎたAPIが見られるのは気のせいだろうか? それにしても、クラスが必須ではないC言語C++に慣れてしまった弊害か、アプリケーション・メインエントリすらclass内に定義しなくてはならないC#には、なかなか慣れない。
Common Lisp
2009年に勉強しようと思い立ったものの、未だに進んでいない。階乗とかハノイの塔とかiotaぐらいは書いたが、目標は「ちょっとしたツールを自作する」だ。まだ道は遠い。最近は時々CLISPを簡易電卓代わりにしている。
Coq
ソフトウェアの基礎が気になるので、処理系だけ入手。
F#
OCamlは「Windows上で日本語を扱う」という視点では処理系がちょっと微妙なので、いっそのことF#に乗り換えようかと……。『実践F#』が積読状態になっている。
flex (lex)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。字句解析用のツールという印象が強かったのだが、よく考えてみたら、flexは「sed(1)のよくある使い方」と同様に「正規表現でパターンマッチング --> 何らかのアクション」という内容を記述するためのツールだった。ただ単に、「何らかのアクション」をC言語で書けることと、flex自体ではパターンマッチングをせずに「パターンマッチングするC言語のコード」を生成することが少々風変わりなだけ。grep(1)やsed(1)その他で小ツールを実装して運用しつつ、性能が求められたらflexで専用ツール化する――とか考えたけど、普通にgrep(1)やsed(1)を使う方が高速だった。
Forth
pForthをMinGWでビルドしたので処理系は手元にある。スタック指向の言語はいつか勉強したい。
Free Pascal
お試しで触っているのだが、微妙にDelphi/Free Pascal初心者(ただし他言語の経験者)向けの良い資料が少なくて難儀している。玉石混交なのだ。いっそのこと『OBJECT PASCAL HANDBOOK: マルチデバイス開発ツ-ルDelphiのためのプログラミング言語完全ガイド』を買ってしまおうかしら……と思っていたら絶版っぽい。
Io
プロトタイプベースである点を除けば、何となくSmalltalk的であるような――公式ドキュメントらしきIo Programming Guideでも影響を受けた言語として真っ先にSmalltalkが挙げられているから、あながち思い違いでもないだろう。今更ながら『7つの言語 7つの世界』のIoの章を読み終えたので、ちょっとしたコード片を書いているが……Windows版のバイナリが古いためか、リファレンス通りなのに動作しないコードに直面している。
LOGO
そういえばLOGOを触ったことがない。とりあえずUCBLogo(Berkeley Logo)だろうか? Windows上でUCBLogoばりにGUI無しで動作する処理系はないだろうか?
Object REXX
思うところがあって処理系とIBM謹製のドキュメントを入手したものの、そこから先の進展は無いまま。ReginaでClassic REXXっぽい感じで触っているからなあ。
OCaml
Common Lispを勉強するはずが、いつの間にか触っていた言語。一応、階乗ぐらいは書いた。時間が取れたらもうちょっとしっかりと勉強したいが、面倒なのでF#に移行しようか検討中。
Oz
Scheme手習い』の次はCTMCP片手にOzで勉強かなあ。道は遠いな……。
PostScript
これかForthか、どちらに手を出すべきか? 悩ましい。
Processing
入門書も処理系も入手して、あとは弄る時間をつくるだけ。
Prolog
『7つの言語、7つの世界』の地図の色分けプログラムには衝撃を受けた。何というか「正しい問い」を見つけられるか否かが肝なのか。この辺は、根底の部分でAlloyに通じる何かがあるように思う。ひとまず、Prologで論理プログラミングと宣言的なスタイルに慣れておけば、形式手法にて「論理で宣言的に」記述するときに戸惑いが減るのではないかと期待している。
Rust
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、今のお仕事的にはGo言語やD言語よりもRustがド直球の本命に近いはず……まあ、本筋とは外れたところでGo言語を使うことになるとか、そういうパターンはありそうだけど。ちなみに、これら3言語と同列にSwiftが挙げられることもあるようだが、個人的見解としては、システムプログラミング言語としてのSwiftには全く期待していない。あれは、Appleというしがらみからは逃れられないでしょうな。
VBA (Visual Basic for Applications)
今までVBAから逃げ回っていて、今のところ追いつかれてはいないのだが、どこかで使う(というか解読する)ことになりそうな予感。たぶん、Excel VBA 8割にAccess VBA 2割ぐらいかなあ。

今は全く使っていない

Active Basic
VBScripを触りだした影響で、時々思い出しては弄くっていた。ほんの少しだけ触って放置して、すっかり忘れてからまた触る――これを繰り返していた気がする。なので毎度初めて触るのと同じ状態だった。String型をバシバシ使用 :)
bc
その昔、Windows標準の電卓アプリの代わりに使おうとして色々あって挫折した。今はirbclisp/goshで計算しているからなあ。
CASL II
生まれて初めて触れたプログラミング言語その1。何だかんだで、後でCプログラマになってからも低水準での思考ツールとして微妙に役に立っている。まあ考えるための言語であって実用言語ではない。仮に実用的な処理系*10があったとしても余りに命令がシンプル過ぎて悶絶するなあ、なんてFizzBuzzしてみて思った。
Clojure, Scala
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。Scala型推論とか、便利っすね。言語仕様はJavaよりも好みだ。とはいえ、IoT時代にJava VMベースでどこまでメインストリームに居残ることができるのか? ちょっと興味深い。サーバサイドに活路を見出すのだろうか?
COBOL
FizzBuzzするためだけにOpenCOBOL 1.0をWindows上に用意して触ってみた。なんというか、COBOLの名前と生まれた時代が示すように基幹業務(というかお金や帳簿が絡んでくるところ)向けの言語だよなあ、といった感じ。COBOL 2002のフリーフォーマットを採用するだけでも使い勝手が変わる気がしたが、世の中にはまだ広まらないのだろうか。
CoffeeScript
仕事で使う予定はない。RubyPythonその他の影響を受けているだけあり、その手のスクリプト言語っぽい感じでコードを書けるので、慣れれば素のJavaScriptで直接コーディングするよりは楽だ。しかし標準ライブラリ回りや処理系絡みの機能やサードパーティのライブラリなど、結局はJavaScriptを知らないとCoffeeScriptでコードを書けないと思う。それに生成されたJavaScriptのコードを見て「うわぁ、これあまり効率的でないなあ」と感じる時もあって、高速化が必要な部分では生成されるコードを気にしながら記述したりCoffeeScriptを諦めてJavaScriptで書くことになるので、やはりJavaScriptを知らないとマズイ。とはいえ便利なのは確かだ。CoffeeScriptのコードは即Node.jsで実行できるので、その辺りから「CoffeeScriptでテキストフィルタ」的な文化が生まれると面白いかも。気になるのはECMAScript 6の存在で、今までCoffeeScript独自の機能だった部分の多くがES6で取り込まれるので、今後ES6対応が進むにつれてCoffeeScriptの立場がどうなっていくのか、少々興味深い。
D言語 2.x
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、Go言語ほどではないが、D言語も気になる存在だ。D言語シンタックスがC・C++に近いだけでなく、コーディングしている時のアプローチ・判断についても、CやC++での流儀がそこそこ通用しやすい気がする。少なくとも、Go言語でコーディングするよりは、文化的背景の違いによるモヤモヤは感じにくい。あと、標準ライブラリを使ってテキストフィルタを書いたところ、エラー処理を1~2ヶ所のtry - catchにスッキリまとめることができて、ちょっと驚いた。throwされる例外のメッセージ文字列が、ちょうどよい塩梅の内容だったため、メッセージを変更する(いったんcatchして、再throwする)必要がなかった。ちょっと残念なのは、マルチバイト対応だが……。
Emacs Lisp
.emacsにコピペ」限定で。Common LispSchemeを触ったためか、何となく内容を追えるようになってきた気がしていたが、勘違いだった。
Fortran
Fortran 90やFortran 95あたりは結構近代的な言語だと思う。用途次第ではC言語よりもFortranの方が遥かにマシな選択だろう。配列がらみの処理はFortranの方が得意だし、言語機能としてのモジュール化の方法はC言語には存在しない。可変長な文字列の扱いに微妙な制限がある点はマイナスな気もするが、まあ基本的に数値計算プログラム用の言語だからなあ。
GDB (GNU Debugger)
……いやGDBはデバッガとして使っているが、GDBスクリプトを書く機会は(FizzBuzz以外に)ない。勉強不足なだけかもしれない。
Groovy
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。動的型付け言語っぽくいくもよし、@CompileStaticや@TypeCheckedで型推論するもよし。言語仕様はJavaよりも好みだ。コンソールアプリを書く人としては、オプション引数解析用の機能を標準で持っている点で、GroovyはClojureScalaよりもポイントが高い*11。個人的には、IoT時代に「Java VMベース」の言語としてどこに活路を見出すのが、興味深く見守りたいところ。やはりサーバサイドだろうか?
HSP (Hot Soup Processor)
FizzBuzzで楽しんでみたが、何というか他言語経験者には受けが悪そうな命令体系だと思う。もっとも初心者がプログラミングという行為に深入りせずにWindows用のGUIな何かを作る分には、あの命令体系でも十分な気がしないでもない。ところで元々は「HSPで職業プログラマ的な良いコードを書くと、どんな感じになるか?」というネタを思いついて処理系を用意したのだけど、そちらは全く進展がないまま。
JScript on WSH
他人が使うテキスト処理ツールの実装に使って以来、時々触ってきた。Windows用の配布可能な小ツールを実装する時の定番言語だった。でもそろそろ潮時だろう。HTAと組み合わせてクライアントサイドJavaScriptなノリで簡易なGUIツールを実装できる点も、PowerShell + WPF + XAMLで代替できそうだ。他のメリットは「JavaScriptECMAScript)でフィルタを書ける」だったが、WSHのなかなか目的にたどり着けないオブジェクト階層にイライラするよりも、Node.jsやPhantomJSを使ったほうが精神衛生的にマシだ。
m4
その昔テキスト処理用に触ろうとして、Windows用のどの処理系も日本語の置換に何かしらの問題を抱えていたので泣く泣く諦めた。思うところがあって改めて少し触ってみたが――なるほど、確かに中毒性のある言語*12だ。
QML
宣伝文句のとおり、QMLはGUIの記述に非常に向いている。それも、単に標準のUI部品(エレメント)を使うだけでなく、少し改造して使うとか、オリジナルのUI部品を作って使うとか、それらを別のアプリケーションに使いまわすとか、そういう時に威力を発揮する。あと、プロパティバインディングやレイアウトのアンカー指定など、画面サイズの変更に追随するUIを作りやすい機能も揃っている。JavaScriptでちょっとした処理も記述できる――とはいえ、やりすぎるとパフォーマンスの罠が……。少なくとも、JavaScriptでゴリゴリコードを書くのはQML的ではない。QMLは宣言的に、シンプルに書くものだ。力技でロジックでゴリ押しすると、色々と罠に嵌る言語だ。
REXX
Open Object REXXの処理系を入手したのに、何故かReginaを入れてClassic REXXっぽい方向に走っていた。何というか、COMコンポーネント.NET Frameworkと無関係でいられるのなら、バッチファイルの代替としてはREXXあたりがほどよい塩梅だと感じる。しかし最近流行の言語とは随分と勝手が違うし、日本語の情報も少ない。メインフレーム以外の世界で流行る可能性は少ないだろう。
Smalltalk (Squeak, Pharo)
Smalltalkは有名な古典的プログラミング言語だというのに、触ったことがない。ということでSqueakとPharoの処理系のみ準備完了。うーん、「環境」付きなのが気になる――言語を弄くる基準が「コンソール上でテキストフィルタ」という変な人種な私だからなあ。
Smalltalk (GNU Smalltalk)
個人の思想信条による理由よりSqueakとPharoにわだかまりを感じてしまう変人なので、邪道だと思いつつもコンソールでテキスト処理もOKなGNU Smalltalkも用意してみた。これで言語としてのSmalltalkの勉強に集中できる……か?
T4 Text Template
「へえ、こんなものがVisual Studioに入っていたのか。機能多すぎで色々と便利なツールを見逃しているんだな、やっぱり」と思いつつ触ってみた。テンプレート変換の用途ではピカ一だと思う。ただ処理系を手に入れる方法が「Visual Studioをインストールする」or「MonoDevelopをインストールする」なので、何となく「単体で手軽に使えるツール」ではないというイメージが……。まあC#VBで処理を記述するので、それらの処理系が必要だという面での制約なのだろう。
VBScript on WSH
JScriptほどではないが「Windows上で他人も使えるツールを書くためのLL」扱いしていた言語。Windows Server管理の関係で触っていた。というかWebで入手可能なWSHのサンプルの大半がVBScriptで書かれていたり、ADSI関連のコレクションをJScriptで舐めれなかったりして、結局は必要に駆られて使用することに。明快に記述できる文法は評価に値するが、スクリプト言語としては少々冗長だ。配列は自動拡張しないし、組み込み関数はプリミティブ気味だし、冗長気味な文法との合わせ技でコードがさらに冗長になっていく……。文法や言語仕様の詳細なドキュメントが見つからないのだが、どこにあるのだろうか?*13
Vim script
少し触ってみた分には、exコマンドの拡張(=コマンドの羅列)とは気づかない程度にはプログラミング言語らしいと思う。とはいえ妙なところで嵌ったり微妙に一貫性のない部分があったりするので、その辺りで好き嫌いが別れる気がする。
秀丸マクロ
7年ほど秀丸エディタを使っていたが、マクロを書く機会はなかった。一念発起してFizzBuzzしてみて感じたのは、最近の便利な言語に慣れた身としては色々とモヤモヤ感がある言語仕様だということ(歴史的経緯的に仕方ないのだが)。とはいえちょっとした拡張ツール的なものを手軽に作れそうではあった。

*1:HTML5 + CSS」の組み合わせなら、チューリング完全の疑惑があったり、JavaScript使わずにCSSでWebチャットを作った猛者がいたりと、色々と怪しいのだけど。

*2:「独立性の高い単体のツールを実装する」という視点では、現代ではAWKよりも便利な言語が山ほどある。

*3:少なくともC++の言語/ライブラリ仕様は私の手には余る。自分が把握している範囲の機能でコードを書くのは好きなのだけど。

*4:支障がある部分を触るほど深入りするには、あと20年ぐらい掛かるのではないか?

*5:これでもsedチューリング完全な言語だと証明されているらしい。

*6:私の認識では、JavaScriptは、第一級関数やクロージャがお仕事用のメジャーな言語に組み込まれて、少なくない人が使う契機となった言語だ。

*7:Kotlinのvalは「再代入不可の変数」だ(定数はconstで定義する)。Kotlinのプリミティブ型以外のデータ型はclass(つまり参照型)なので、valで定義した変数を破壊的操作する行為は割と普通だと思う。一方でSwiftのletは定数であるし、値型が中心の言語仕様である影響かstructやenum(つまり値型として振る舞うデータ型)が多用されるので、letで定義した変数を破壊的操作できるケースとできないケースが生じる。

*8:Windowsのことを考えなければ、自前でライブラリをビルドしてアプリに組み込むのは結構簡単だった。

*9:言語仕様的にはC# 5.0の環境だが、ライブラビまわりはC# 4.0相当だったはず。

*10:――といってもシミュレータだけど。

*11:ClojureScalaに関しては、同様の機能を私が見逃している可能性も高いのだが。

*12:m4はマクロプロセッサなのでプログラミング言語ではないはずだけど……。

*13:MSDNの資料では物足りない。もうちょっと掘り下げた内容のものが欲しい。

GitHubのプライベートリポジトリにSSHアクセスできない 混迷編

GitHub Organizationに作成したプライベートリポジトリにSSHでアクセスできなくて悩んでいたのである。HTTPS(2FAあり)ではcloneできるけど、SSHではcloneできない。Could not read from remote repositoryとなる。

問題を切り分けるために、GitHubの自分のアカウントにてプライベートリポジトリを作成してみたのだが、やはりSSHではcloneできない。HTTPSではcloneできる。Organization固有の問題では無いようだ。

ついでにパブリックリポジトリならSSHでもcloneできる。という訳で、GitHubへの公開鍵の登録や、Gitクライアントでの秘密鍵の利用についても、特段の問題は無いはず。

ところで私の環境ではGitクライアントとしてGit for Windowsを使用していて、PuTTY付属のPlinkSSH通信を行い、Pageantを利用して複数の鍵を管理しているのだが、PageantGitHubアクセス用の秘密鍵1つだけを登録した状態ならSSHでプライベートリポジトリをcloneできるのだ。しかし複数の秘密鍵が登録されている状態では、プライベートリポジトリだけcloneできなくなる。

さらに条件を絞り込んだところ、PageantGitHubアクセス用の秘密鍵が複数登録されている時に発生することが分かった。GitHubのアカウントA用の秘密鍵とアカウントB用の秘密鍵が登録されている、みたいな状況だ。GitHubのアカウントA用の秘密鍵と、GitHubではないホスト用の秘密鍵、みたいな組み合わせでは発生しない。

PuTTYないし付属のツール群でSSHで通信する際、Pageantに複数の秘密鍵が登録されている場合には、SSH接続時に何らかの順番で秘密鍵を試してみて、成功したらその鍵で通信する、ということを行っている。

おそらく、例えばアカウントBのプライベートリポジトリにアクセスしようとした際に、「アカウントA → アカウントB」の順に秘密鍵を試す、みたいな現象が起きているのだろう。どちらの秘密鍵GitHubに登録された正規のものなので、アカウントAの秘密鍵の時点でSSHでの通信は確立する。しかしGitHub側の設定では、アカウントAはアカウントBのプライベートリポジトリへのアクセス権限を持たない。だからGitHubのアクセス制限でNGとなってしまう。cloneの失敗は接続後の出来事で、SSH接続自体は成功しているために、残りの鍵で接続が試行されることはない。

症状から考えるに、LinuxmacOSなどで素のOpenSSHを使っている環境ならば、.ssh/configを使って秘密鍵を切り替えてアクセスできるようにする案件だと思われる。IdentitiesOnly yesを忘れると妙なことになるアレである。

問題は、それをGit for Windows + Plink + Pageantの環境でどう実現するか、なのだが……。PlinkPageantの設定でどうにかなるのか、それともPlinkを捨ててOpenSSHに移行するのか? 仮にOpenSSHに切り替えるとして、Git for Windowsではどのバイナリが良いのか(同梱のものもあれば、Windows付属のものもあるし……)? またOpenSSHへの移行に際してPageantからssh-agent(1)への切り替えも必要となるのか? 色々と悩ましい。

この件の優先度は低いので、とりあえずSSHではなくHTTPSで接続することで誤魔化している。