Emacs における C, C++ 系モードのインデント設定

C, C++ などのコードを Emacs で書いていて、 "インデントを自動でやってくれるのはいいけど、 自分が望んでいるものと違う" ということはないでしょうか?
C, C++ を始め、 Java 、 JavaScript 、 C# などのモードは cc-mode を元に作られています。 この cc-mode ではインデントを細やかに設定できる分、やり方が少しわかりづらいです。
今回はこの C, C++ 言語系のモードでのインデントの設定方法について説明したいと思います。

また、インデントの設定が適切にできていれば、 {, ; などのキーを入力した時に自動で改行、インデントしてくれるという便利な機能もあります。

設定方法とバッファーローカル変数

最初に cc-mode 関連の設定を行う方法について説明します。 Emacs で設定を行う場合には 2 通りの方法があります。

カスタマイズ

cc-mode 関連の設定項目はカスタマイズでは C グループに属するので、次の手順で設定します。
  1. M-x customize-group [RET] c で C group のカスタマイズ画面を開く
  2. 設定したい項目を変更
  3. 設定([Set ..])、保存([Sava ..])

設定ファイルの記述とバッファーローカル

カスタマイズを使わない場合は設定ファイルに記述するのですが、 ここで注意点があります。
それは設定する変数がバッファーローカルかどうかという点です。 通常、変数に設定した値は emacs 全体で共通に使われますが、 バッファーローカルな変数はバッファーごとに固有の値を持ちます。
次章で紹介する機能の ON/OFF 的な設定値は通常の変数ですが、 インデントの設定値の多くはバッファーローカルな変数となっています。

バッファーローカルな変数を設定ファイルで設定する場合には、モードのホックを使って バッファーを開いた時に設定する必要があります。
;; init.el だけに設定されてしまう
(setq c-basic-offset 4)

;; ホックを使った設定
(defun my-c-c++-mode-init ()
  (setq c-basic-offset 4)
  )
(add-hook 'c-mode-hook 'my-c-c++-mode-init)
(add-hook 'c++-mode-hook 'my-c-c++-mode-init)
面倒かもしれませんが、インデント量のような値はモードごとに変えたいため、 このようになっています。


また、カスタマイズで設定した値はバッファーローカルにならないということも注意する必要があります。
これはバッファーローカルな変数でも全てのモードで共通でも構わないという時には楽ですが、 逆にモードで個別に設定したいという場合にはカスタマイズからは設定できないようになっています。


この記事では基本的にカスタマイズ、設定ファイルの両方を説明していますが、 設定する場合は次のような方針で行って下さい。
  • バッファーローカルではない変数(機能の ON/OFF など)
    • カスタマイズ、設定ファイルのどちらでも可
  • バッファーローカル(インデント関連の設定など)
    • 共通した値を使用 → カスタマイズ
    • モードごとに設定 → 設定ファイル
ただし、設定ファイルからであればどの変数でもバッファーローカルにすることは可能です。
(defun my-c-c++-mode-init ()
  ;; 変数をバッファーローカルに
  (make-local-variable 'c-tab-always-indent)
  (setq c-tab-always-indent nil)
  )

インデントの設定を使う Emacs の機能

インデントのスタイルを使う機能を大きく分けると 2 つです。
  1. オフセット(インデント量)を使う機能
  2. 自動改行、インデント

オフセット(インデント量)を使う機能

[Tab] によるインデント、 C-M-\ のインデント整形、 C-j の改行 + インデントなどオフセット(インデント量)を使う機能です。 オフセットはコーディングする上で最低限していないといけない設定でしょう。

[Tab] キーの挙動の変更

[Tab] キーでインデントですが、これは C Tab Always Indent(c-tab-always-indent) の設定を変えると、 挙動を変えることができます。

動作
t 常にインデント
nil 左端(文字の前)ではインデント、それ以外はタブの挿入
t, nil 以外 コメント、文字列などではタブ、それ以外はインデント

この値が t の場合、 Tab キーを押しても インデントをあわせるだけで、タブは挿入できません。 t, nil 以外( 1 など)の場合もコメントなど特定の領域以外はタブを挿入できません。
それらの設定でタブを挿入したい場合には C-q [Tab]などを使います。

c-tab-always-indent はバッファーローカルではないため、カスタマイズ、設定ファイルのどちらでも設定しても同じです。

カスタマイズ :
emacs_indent_tab_always_indent.png

設定ファイル :
(setq c-tab-always-indent t)

自動改行、インデント

{, ; などのキーを入力すると自動で改行、インデントをしてくれる機能です。 カスタマイズで設定するか、設定ファイルに以下を追記すると有効になります。
(setq c-auto-newline t)
これが有効になっているとモード行のモードを表すところに "/la" が付きます。 正確には "/a" の自動改行と "/l" の electric-state (インデント)は別なのですが、 ほぼセットだと思って下さい。
このモードで改行やインデントをさせずに { などを入力したい場合は C-q { といったように入力します。 特に : はネームスペース等でも使うので、"::"(コロン 2 つ)を入力する C-c : も用意されています。

この機能は { 等で改行するので、スタイルは改行位置などもちゃんと設定する必要があります。 自動改行を有効にしなければ、オフセット量だけでも十分ですが、 コーディングが非常に楽になる機能なので、ぜひ使ってみてください。

空白を一度に削除

ついでなので、 hungry-delete の機能も紹介します。
これは [Back Space] などで空白を削除すると 空白以外が出てくるところまで一度に削除してくれます。

以下の記述を追記していると有効になります。
(setq c-hungry-delete-key t)
有効になっているとモード行に "/h" が付きます。

スタイルの設定

インデントの設定では、まずベースとするスタイルを指定します。

スタイルの種類

選択可能なスタイルを次の表にあげます。 ただし、バージョンによって変わるかも知れないので、 正確には C-h v c-style-alist で確認して下さい。

スタイル 説明
gnu GNU スタイル
k&r C の古典 『 K&R 』のスタイル。
標準の ANSI C と文法自体少し違うので、最近ではほとんど使われてない。 本自体も今の版はこのスタイルではない。
bsd BSD/Allman スタイル。
Visual Studio のスタイルはこれがベース。
whitesmith 古くからある商用コンパイラー Whitesmiths のスタイル
stroustrup C++の作者であるストロヴストルップ『プログラミング言語C++』のスタイル
ellemtel 『Programming in C++, Rules and Recommendations』で規定されているスタイル
linux Linux カーネルのコードで使われているスタイル
java java-mode で使われる Java のコーディングスタイル
awk awk-mode で使われる AWK のコーディングスタイル。
python Python モードのスタイルではなく、 Python の C 拡張モジュールを書く際のスタイル
user ユーザー定義用の特殊なスタイル。(後述)

ここで挙げたもののうち、主要なものは Wikipedia に説明があります。

スタイルの設定方法

スタイルの設定はカスタマイズを使った方法で説明します。


C グループの C Default Style でモードごとのスタイルを設定します。 これはモードごとに対応するスタイルを設定する変数ですので、バッファーローカルではありません。
emacs_indent_style.png

C#、 JavaScript など後からパッケージで追加するようなモードは出てませんが、 各 lisp ファイル内で定義されているはずですので、ここで設定する必要はありません。


各モードに指定するスタイル名は前節のものを指定します。 インデントなど指定したスタイルで問題がなければ、それで設定は完了です。
そうでなければ、個別に設定していく必要があります。 例えば、 Visual Studio 風のスタイルにしたい場合、 "bsd" が近いのですが、他の設定もしないと綺麗に一致はしません。

基本オフセット量の設定

基本オフセットというのは、 1 段インデントをする際のサイズです。

カスタマイズの場合、C Basic Offset のパラメーターで設定します。
emacs_indent_basic_offset.png


c-basic-offset はバッファーローカルな変数ですが、カスタマイズで設定した場合には、 cc-mode を元にした全てのモードで同じ値が使用されます。
モード別で指定するためには設定ファイルでロード時に設定にします。
(defun my-c-c++-mode-init ()
  (setq c-basic-offset 4)
  )

(add-hook 'c-mode-hook 'my-c-c++-mode-init)
(add-hook 'c++-mode-hook 'my-c-c++-mode-init)

タブ幅

先ほどの基本オフセットというのはタブのサイズとは別ものです。 必ずしもタブ一つがインデントの一段というわけではありません。
タブ幅が 8 、 基本オフセットも 8 であれば 1 段でタブ1つですが、 タブ幅が 8 、 基本オフセットが 4 ならば 2 段でタブ 1 つとなります。

タブ幅を変更したいという場合は以前の記事を見て下さい。 なお、タブ幅(tab-width)もバッファーローカル変数です。

タブの代わりに空白

タブとインデントの関連でもう一つ補足しておきます。
indent-tabs-mode の値に nil を設定するとインデントの際にタブを使わずに、 空白(スペース)のみ使用するようにできます。 たまに "タブは使うな" といったコーディングルールがあったりするので、そういった場合に設定します。

カスタマイズでも設定できるのですが、 全モードで使われると影響が大きいので、 設定ファイルで設定した方がいいでしょう。
(defun my-c-c++-mode-init ()
  (setq indent-tabs-mode nil)
  )

;; 略 

インデント量の設定

インデントの状況別のオフセット量は C Offsets Alist(c-offsets-alist)で設定します。

emacs_indent_offsets.png

オフセット値

オフセットとして設定する値によく使うのは 3 つです。
意味
0 インデントなし
+ 1 段インデント
- 1 段インデントを戻す

この +, - で増減する単位が先ほどの c-basic-offset のサイズです。

これ以外にも数値、記号、関数など色々使えます。 その他の値については C-h v [RET] c-offsets-alist で確認して下さい。

Syntax

オフセット値は状況ごとに設定できるようになっています。 そのうちの幾つかを紹介します。
if, while ブロック
制御文のブロックです。スタイルごとに違いがでやすいところだと思います。
emacs_indent_offset_if.png
switch ブロック
制御文の中でも switch 文には case ラベルの設定もあります。
emacs_indent_offset_switch.png
クラス内関数
関数の定義のブロックは defun-open なのですが、 クラス内のインライン定義の場合は inline-open になります。
emacs_indent_offset_inline.png
上記以外の項目についても、以下のサイトに説明があります。

スタイルの構成

ここで、スタイルの内容について説明します。

スタイルは継承することができ、 構成は継承元のスタイル名と 変数-値 ペアのリストからなっています。
`("bsd" ;; <- 継承元スタイル
  ;; (変数 . 値) のリスト
  (c-basic-offset . 4)
  (tab-width . 4)
  (c-offsets-alist . ((inline-open . 0)
                      (substatement-open . 0)
                      (case-label . 0)))
  (c-hanging-braces-alist . ((inline-open         before after)
                             (block-open          before after)
                             (substatement-open   before after)))
  (c-cleanup-list . (defun-close-semi
                      list-close-comma
                      scope-operator
                      compact-empty-funcall)))
変数-値のリストを継承元から設定していき、同じ変数の場合は対象のスタイルの値で上書きされます。
例えば、 次節の c-hanging-braces-alist などは継承元で設定されていても、 それは全く使われず、今のスタイルのリストが使用されます。

ただし、 c-offsets-alist は特別でリストの中身も継承されます。
defun-open の値が継承元で設定されていて、今のスタイルで設定されていないとすると、 継承元の値が使われます。


個々のスタイルでどのような値が設定されていかについても C-h v c-style-alist で見ることができます。

user スタイル

c-offsets-alist にはもう一つ特別な点があり、 カスタマイズで設定した値は user スタイルの値として設定されます。

user スタイルはすべてのスタイルの継承元です。
そのため、カスタマイズで設定された値がすべてのモードにそのまま適用されることはありませんが、 設定値はスタイルが優先されます。 スタイルの設定から漏れているものをここで設定する感じです。


なお、設定ファイルからであれば、ロード時に値を変更することもできます。
(defun my-c-c++-mode-init ()
  (setq c-offsets-alist '((statement-case-open . +)
                          (case-label . 0)))
  )
また、自作のスタイルを作成することもできます。 自作のスタイルのサンプルはこの記事の最後にあげていますので、 そちらをご覧ください。

自動改行、インデントのための設定

機能で紹介した自動改行、インデントを使う場合、 改行位置などの設定も必要となります。

改行位置

{ などのキーを押した時に改行を入れるかどうかを指定するため 3 つの変数があります。
キー 変数名
{ } (ブレース) c-hanging-braces-alist
: (コロン) c-hanging-colons-alist
, ; (カンマ、セミコロン) c-hanging-semi&comma-criteria

emacs_indent_hanging.png


これらは対象ごとに before, after を指定したり、複雑な場合は関数で指定します。
substatement-open を例にすると次のようになります。
after
if (a == 0) {
    foo();
}
befor
if (a == 0) 
{   foo();
    bar(); }
befor after
if (a == 0) 
{
    foo();
}
設定する対象の名前は c-offsets-alist の対象の名前とほとんど同じなので、 そちらの説明をご覧下さい。


3 つの変数はどれもバッファーローカルなので、設定ファイルの場合はロード時に設定するようにします。
(defun my-c-c++-mode-init ()
  (setq c-hanging-braces-alist '((class-open before after)
                                 (class-close before)))
  )

改行位置(C#)

C# モード (csharp-mode) の場合、 { を押してインデントが上手くいかないことがあります。
これはインデントの設定があっていないのではなく、 csharp-mode では { のキー割り当てを拡張したコマンドにしていて、 それが上手く動いていないためです。
てっとり速く治す場合、 { の割り当てを他のモードと同じ c-electric-brace にすると治ります。
(defun my-csharp-mode-init ()
  (local-set-key "{" 'c-electric-brace)
  )
(add-hook 'csharp-mode-hook 'my-csharp-mode-init)

Clean up

c-cleanup-list は "} else {" のようにつなげるかどうかや "foo ()" のように関数の前の空白を入れるかどうかなどの設定です。 これを設定していると自動インデントの際に空白、改行を削除(追加)してくれます。
emacs_indent_cleanup.png

Clean up の説明は、英語ですが Emacs のマニュアルがサンプル付きで分かりやすいです。 こちらの変数もバッファーローカルです。
(defun my-c-c++-mode-init ()
  (setq c-cleanup-list '(scope-operator
                         compact-empty-funcall))
  )

カスタムスタイル

デフォルトで用意されたスタイルに合わない場合、個別に設定していきます。 これを自分でスタイルを作ってまとめて設定することも可能です。
開発しているプロジェクトのコーディングスタイルがある場合は、 "カスタムスタイルを用意して、メンバーにそれを使ってもらう" といった際に使えると思います。

サンプルを兼ねて、使いそうなカスタムスタイルを 2 つ挙げたいと思います。

Google コーディングスタイル

Google は C++ のコーディングガイドを公開しており、 そこの「書式」の章でインデントのスタイルも規定しています。 emacs_indent_google.png

そこでは Emacs 用の設定ファイル(google-c-style.el)も公開されています。 使いたい場合はリンクを [名前を付けてリンク先を保存] でダウンロードして下さい。

取得した lisp ファイルは load-path の通ったフォルダーに置き、 ~/.emacs.d/init.el に以下の記述を行います。
(defun my-c-c++-mode-init ()
  (require 'google-style)
  (google-set-c-style)
  ;; (google-make-newline-indent)
  )

(add-hook 'c-mode-hook 'my-c-c++-mode-init)
(add-hook 'c++-mode-hook 'my-c-c++-mode-init)
因みに google-make-newline-indent を記述すると [Ente]r キーで改行とインデントを一度に行うようになります。
ただ、改行するときに C-j を押せばいいだけなので、 設定する必要はないと思います。

Visual Studio のスタイル

Windows で開発していると、自分以外のプロジェクトメンバーが Visual Studio を使っているという驚きの状況に陥ることがあります。 どうやら IDE でコード書いている人がいるというウワサは本当みたいです。

仕方がないので、私はインデントを Visual Studio 風に設定しています。
しかし、スタイルを "bsd" しても、他の設定もしないと一致しません。 そこで、 Google スタイルのようにまとめて設定するファイルを作成してみました。 リンク先をダウンロード([名前を付けてリンク先を保存])して、 パスの通ったフォルダーにおいて、以下の記述を ~/.emacs.d/init.el に記述して下さい。
(autoload 'vs-set-c-style "vs-set-c-style")
(add-hook 'c-mode-hook 'vs-set-c-style)
(add-hook 'c++-mode-hook 'vs-set-c-style)


FC2 Management
 

C# の delegate と event

オブジェクト指向言語では delegate という用語が使われます。 C# でも delegate と名付けられた機能があるのですが、よく使われる意味での delegate を知っていると "なんでこれが delegate" と感じるのではないでしょうか。
今回はそんな C# の delegate について解説したいと思います。 また、 ついでに delegate に似た event の機能についても説明しています。

一般的な Delegate

まずは、他のオブジェクト指向言語でよく使われている意味での delegate から見てみます。
delegate は一般的には 委譲と訳され、 次のような意味で使われることが多いです。
クラスのメソッドを実行する際に、メンバー変数のクラスのメソッドを代わりに実行する

例えば、次のような Bell クラスがあったとします。
class Bell
{
public:
    Bell() {}
    void ring()
    {
        cout << "Ring! Ring!" << endl;
    }
};      
目覚まし時計(AlarmClock)クラスでは、 ring() メソッドでメンバーの Bell のメソッドを呼び出します。
class AlarmClock
{
    Bell m_bell;
public:
    AlarmClock() {}
    void ring()
    {
        m_bell.ring();
    }
};

void main()
{
    AlarmClock aclock;
    aclock.ring();
}
~/lang/cpp $ ./delegate.exe 
Ring! Ring!
このようにメソッドの処理を実際には他のクラスのメソッドにやってもらうため、 委譲(delegate) と呼ばれています。

イベントとコールバック

ここで GUI におけるイベント駆動についても見てみます。

"ボタンを押す" などのイベントが起こった時、 イベントに割り当てられたコールバックを実行します。
cs_delgate_event.png


C や C++ における多くのツールキットでは、 コールバックとして呼ばれるのは通常の(静的な)関数であり、 クラスのメンバー関数ではありません。
通常の関数が使われるのは、それが使いやすいからです。 C, C++ では通常の関数は関数ポインターとして変数に格納でき、 変数に () をつければ格納した関数を呼び出すことができます。
void hello(const string &name)
{
    cout << "Hello " << name << endl;
}

void main()
{
    void (*cb)(const string &name) = hello;
    cb("world");
}
関数ポインターも型として typedef で別名をつけておくことができます。 これを使えば、より変数っぽく扱えます。
typedef void CallBack(const string &name);
void main()
{
    CallBack cb = hello;
    cb("world");
}

Delegate パターン

コールバックとして呼び出す関数ですが、 C++ のようなオブジェクト指向言語では、 静的な関数よりもクラスのメソッドを呼び出したいことの方が多いでしょう。 しかし、 C++ では関数ポインターの変数にメソッドを格納することはできません。
    Bell bell;
    void (*cb)() = bell.ring;   // コンパイルエラー
それでも、コールバックとしてはメソッドを登録したいです。 もっと言えば、複数登録して複数のメソッドを実行したいし、 登録したメソッドを自由に着脱したいです。
これは、頑張れば C++ で実現するやり方もなくはありません。 『 GoF 本』には入っていませんが、 Delegate パターン と呼ばれているやり方があります。 Delegate パターンの中身は説明しませんが、 処理を実行する際に登録しておいた他のメソッドが実行されます。 "他のメソッドが実行する" という点において、これも Delegate と呼ばれています。

C# の delegate

C# の delegate は一般的な Delegate よりも先ほどの Delegate パターンに近いです。 その C++ の Delegate パターンですが、実はかなり実装が面倒くさいです。 これを言語の機能として簡単に使えるようにしたものが C# の delegate です。


C# の delegate は C, C++ の関数ポインター変数のようなものですが、 次のような点が拡張されています。
  • 通常の関数だけでなく、クラスのメソッドも格納(アタッチ)できる
  • 複数の関数を格納可能
    • 呼び出し時には格納した順に逐次実行される
  • 関数の着脱(アタッチ、デタッチ)も自由
namespace DelegateSample
{
    // Delegate の型を宣言
    delegate void HelloDelegate(string name);
    
    class Foo
    {
        // メソッド
        public void Hello(string name)
        {
            Console.WriteLine("(method) : Hello {0}!", name);
        }
    }
    
    class Program
    {
        // static な関数
        static void StaticHello(string name)
        {
            Console.WriteLine("(static) : Hello {0}!", name);
        }

        static void Main()
        {
            Foo foo = new Foo();

            // delegate の変数
            HelloDelegate funcs;
            
            funcs = foo.Hello;          // メソッドの格納
            funcs += StaticHello;       // static 関数の格納
            // 無名関数の格納
            funcs += (string name) => {
                Console.WriteLine("(lambda) : Hello {0}!", name);
            };

            // 格納した関数の呼び出し
            funcs("world");
        }
    }    
}
~/cs/delegate $ ./DelegateSample.exe 
(method) : Hello world!
(static) : Hello world!
(lambda) : Hello world!
このように C# の delegate は通常の関数だけでなく、メソッド、無名関数なども格納できます。

無名関数の delegate

先ほどの例では無名関数に => を使う方法(ラムダ式)を取りましたが、 delegate を使った無名関数の定義(匿名メソッド)の記述もあります。
            // 無名関数の格納 (delegete キーワード版)
            funcs += delegate(string name) {
                Console.WriteLine("(delegate) : Hello {0}!", name);
            };
これは他の言語では function などのキーワードが使われるのですが、 C# では delegate と記述します。
ここでの delegate キーワードは先ほどの関数格納のための delegete とは別物と考えてください。

delegate の注意点

実はサンプルでの delegate の使用方法は少し手を抜いています。

delegate の変数も初期化されていない場合は null です。 null の入った変数に対して呼び出しを行うと例外が発生します。
これを避けるために次の 2 つの方法のどちらかを使うことが多いです。

使用前に null のチェックを行う :
if (funcs != null)
{
    funcs("world");
}
何もしない関数で初期化する :
HelloDelegate funcs = delegate(string name){};

funcs += foo.Hello;
初期化した後に実際に呼び出したい関数を格納します。 呼び出し時には何もしない関数 delegate(string name){} も実行されますが、 何もしないので問題にはなりません。

サンプルコード

説明で使用したサンプルのコードは以下のリンクからダウンロード(リンク先を保存)できます。 コンパイルする場合は以下のコマンドを実行します。
 > csc DelegateSample.cs
csc.exe を使用したコンパイル方法については以前の記事を見て下さい。

C# のイベント

C# の event は一般的な "イベント-コールバック" を C# の delegate の機能を使って実現しているようなものです。
event は当然 GUI で使われることが多いのですが、 ここではわかりやすいようにシンプルにしたサンプルで説明したいと思います。


C# の event には delegate のようにメソッドを登録することができます。
ただし、以下の点が違います。
  • ローカルな変数には使えず、必ずクラスのメンバー
  • 同じクラスのメソッドのみ呼び出し可能
EventSample.cs :
namespace EventSample
{
    delegate void HelloDelegate(string name);
    
    class Button
    {
        // イベント
        // (クラス定義内でしか event は使えない)
        public event HelloDelegate SomeEvent = delegate(string name){};

        // イベントの発生
        public void Clicked(string name)
        {
            Console.WriteLine("<Button Clicked>");
            // 登録した関数を呼び出す
            // (直接には Button クラス内でしか実行できない)
            SomeEvent(name);
        }
    }

    class Foo
    {
        public void Hello(string name)
        {
            Console.WriteLine("(method) : Hello {0}!", name);
        }
    }

    class Program
    {
        static void StaticHello(string name)
        {
            Console.WriteLine("(static) : Hello {0}!", name);
        }

        static void Main(string[] args)
        {
            Foo foo = new Foo();

            Button btn = new Button();

            // 関数の登録(アタッチ)
            btn.SomeEvent += foo.Hello;
            btn.SomeEvent += StaticHello;
            btn.SomeEvent += (string name) => {
                Console.WriteLine("(lambda) : Hello {0}!", name);
            };

            // イベントの発生
            btn.Clicked("world");
        }
    }
}
~/cs/delegate $ ./EventSample.exe 
<Button Clicked>
(method) : Hello world!
(static) : Hello world!
(lambda) : Hello world!
event は delegate の変数のように関数を登録できますが、 直接呼び出すことはできません。 この制限により、"イベントに関連付けたコールバック(関数)はイベントが発生した時に呼び出される" という形式になります。
実際、サンプルは次のように解釈できます。
  1. Button のオブジェクト btn に Hello 関数群をコールバックとして関連付け
  2. btn に Clicked イベントが発生 (btn に Clicked メッセージを送る)
  3. 関連付けられたコールバック群が実行される

delegate と event の使い処

たまに "delegate を使うべきか、 event を使うべきか" という話を聞きます。

私はそれぞれ次のようなものだと考えています。
  • delegate: 関数ポインターの拡張
  • event: イベント-コールバック というイベント駆動のための機能
個人的には、 単に関数(メソッド)を格納して呼び出したい場合など基本は delegate を使い、 GUI にかぎらずイベント駆動的な処理がしたいときに event を使えばいいのではないかと思います。


 

Clojure でのファイルの入出力

今回は Clojure でファイルの読み書きを行う方法についての記事です。

ファイルの読み込み

Clojure でファイルの読み込みを行う場合、 reader を使います。
この reader などの IO 系の関数は Ver. 1.3 まで contrib.duck-streams などに分かれていましたが、 今はほとんど clojure.java.io に統合されています。
(use 'clojure.java.io)
(reader "test.txt")   ; #<BufferedReader>
ファイルなので使い終わったら閉じる必要があります。 リソースの解放を忘れずにしないといけない場合には、 Ruby のブロックや C# の using のように、ブロックを抜けると必ず解放してくれる仕組みが欲しいところです。
Clojure にも with-open というマクロが用意されています。
(with-open [fin (reader "test.txt")]
  ;; 読み取り処理
  )
このブロックの中で reader が返すオブジェクト(fin)を使って読み取り処理を行います。 これは Java の BufferedReader オブジェクトなので、 .read や .readLine といった関数が使えます。
それをそのまま使ってもいいのですが、もっと Clojure っぽい書き方もできます。 line-seq を使うとシーケンスとして扱えるようになります。

readsamp.clj :
(use 'clojure.java.io)

(with-open [fin (reader "test.txt")]
  (doseq [str (line-seq fin)]
    (println str))
  )
~/clojure/fileio $ cat test.txt 
foo
bar
~/clojure/fileio $ clojure.bat readsamp.clj 
foo
bar
サンプルコードの実行方法などに関しては以前の記事をご覧下さい。

ファイルの書き込み

書き込みの場合は writer を使います。 使い方は読み込みとほとんど同じですが、 オプションで :append を使うと追加書き込みができます。

(use 'clojure.java.io)

(with-open [fout (writer  "hello.txt" :append true)]
  (.write fout (str "hello" " world"))
  )
writer の戻り値は BufferdWriter のオブジェクトであり、 書き込みの場合はそのメソッドをそのまま使っています。

日本語の文字コード

日本で使う以上、日本語のファイルを読み書きできなければなりません。 日本語を扱うためには reader, writer のオプション :encoding で文字コードを指定します。
(writer "java_io.txt" :encoding "UTF-8")
ここで指定する文字コードは Java でサポートされているコードです。 よく使われている文字コードのエンコード名は次表のようになります。
エンコード名 文字コード
ISO-2022-JP JIS コード
EUC-JP 日本語 EUC
SJIS シフト JIS
MS932 Windows-31J
(シフト JIS とほとんど同じだけど、Windows の文字コードは正確にはこちら)
UTF-8 8 bit Unicode
JISAutoDetect 文字コードの自動判定 (reader のみ)

出力の時はいいのですが、読み込み時には文字コードがわかっていないことの方が普通です。 JISAutoDetect を指定していると自動で文字コードを判別してくれます。

jcode.clj :
(use 'clojure.java.io)

(with-open [fin (reader "japanese.txt" :encoding "JISAutoDetect")]
  (doseq [str (line-seq fin)]
    (println str))
  )
~/clojure/fileio $ cat japanese.txt 
こんにちは、日本
~/clojure/fileio $ clojure.bat jcode.clj 
こんにちは、日本

標準入力、標準出力

標準出力等を使う場合には次の変数を使います。
対象 変数 特殊変数
標準入力 System/in *in*
標準出力 System/out *out*
標準エラー出力 System/err *err*

*in*, *out* などの変数は read-line や println で使われる出力先です。

interact.clj :
(print "Input name : ")
(flush)
(let [name (read-line)]
  (println "Hello" name))
*in*, *out* はデフォルトでは System/in、System/out になっているのですが、これはスクリプトや REPL など状況でクラスは違ってきます。

標準入出力と Java のファイル IO クラス

標準入出力や *in*, *out* で使われているクラスを見てみます。

ioclass.clj :
(println "*in* " (class *in*))
(println "*out*" (class *out*))
(println "*err*" (class *err*))
(println "System/in " (class System/in))
(println "System/out" (class System/out))
(println "System/err" (class System/err))
~/clojure/fileio $ clojure.bat ioclass.clj 
*in*  clojure.lang.LineNumberingPushbackReader
*out* java.io.OutputStreamWriter
*err* java.io.PrintWriter
System/in  java.io.BufferedInputStream
System/out java.io.PrintStream
System/err java.io.PrintStream
C, C++ など多くの言語では、標準入出力とファイルの IO クラスは同じように扱えるのが普通です。
これに対して Java の IO は複雑で使いづらいと悪名を馳せています。

Clojure のファイル IO も呼び出しを軽くラップしているだけで、 実質は Java のクラスです。 このため、文字コードとあわせて突き詰めていくと非常に複雑になります。
ここではファイル IO と標準入出力を同じように扱うための方法を少しだけ書いてみたいと思います。

入力

読み込みで挙げた line-seq 関数は System/in のクラスである BufferedInputStream に対しては使えません。
BufferedInputStream などから Reader を作る必要があります。
(doseq [str (line-seq (clojure.java.io/reader *in*))]
  (println str))

出力

標準出力では println などメソッドに持つ PrintStream クラスです。 BufferdWriter で print 系のメソッドを使うには PrintWriter のオブジェクトにする必要があります。
(use 'clojure.java.io)
(import (java.io PrintWriter))

(with-open [fout (PrintWriter. (writer  "hello.txt"))]
  (.println fout (str "hello" " world"))
  )
~/clojure/fileio $ clojure.bat printsamp.clj 
~/clojure/fileio $ cat hello.txt 
hello world

ファイルの一括読み込み、書き出し

ファイルを一行づつ読み書きするのではなく、一度に読み取ったり、出力したりすることもできます。
  • 読み込み :
    • (slurp f contents & options)
  • 書き出し :
    • (spit f & options)
spitslurp.clj :
(spit "version.txt" "Ver. 1.2.3")
(println (slurp "version.txt"))
~/clojure/fileio $ clojure.bat spitslurp.clj 
Ver. 1.2.3
指定するオプションは reader, writer で使用したものと同じものが使用できます。
(spit "event.log" "foo\n" :append true)
また、 slurp の入力元はローカルファイルだけでなく、ネット上のファイルも可能ですし、 Reader, InputStream のような入力用のオブジェクトも利用できます。
(slurp "http://yohshiy.blog.fc2.com/blog-category-29.html")
(slurp *in*)

サンプルコード

最後にもう少し長いサンプルとして簡単な cat プログラムを作ってみました。 cat は引数として受け取ったファイルを標準出力に出力するプログラムです。
対象 リンク
ブラウズ simpcat
圧縮ファイル simpcat.zip

以前、標準入出力にも対応するべきという記事を書きました。 ここでもなるべく頑張って対応するようにしています。
  • 引数を全て省略すると標準入力
  • -o(--ouput) オプションでファイルに出力
~/clojure/fileio/simpcat $ lein run -- -h
Usage: simpcat [Options] [FILE ...]

FILE: Input file path.  (Default: standard input)

Options:
  -h, --help             Show help.
  -v, --version          Show program version.
  -o, --output OUT_FILE  Output file path (Default: standard output)
オプション引数の解析には前回紹介した tools.cli を使用しています。
;; オプション仕様定義
(def option-spec
  [["-h" "--help" "Show help."]
   ["-v" "--version" "Show program version."]
   ["-o" "--output OUT_FILE" "Output file path (Default: standard output)"]
   ])

(defn -main [& args]
  (let [{:keys [options arguments errors summary]} (parse-opts args option-spec)]
    :
--output オプションがあれば、出力先はオプション引数で指定されたファイルになります。
    (if (:output options)
      (with-open [fout (PrintWriter. (writer (:output options)))]
        (cat-files arguments fout))
      (cat-files arguments System/out))
入力先には、引数が空であれば標準入力を使い、そうでなければ入力ファイルとして順に使います。
(defn cat-files [fpaths fout]
  (if-not (empty? fpaths)
    (doseq [fpath fpaths]
      (with-open [fin (reader fpath)]
        (print-file fin fout)
        ))
    (print-file (reader System/in) fout)
    ))
入出力用のクラスを調整して渡しているので、ファイルの読み込み、 書き出し処理では分岐することなく、同じように処理できます。
(defn print-file [fin fout]
  (doseq [str (line-seq fin)]
    (.println fout str)))  
 
このページをシェア
アクセスカウンター
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

03月 | 2014年04月 | 05月
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 - - -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。