【一人 Ruby Advent Calendar 2017】文字列から任意のクラスオブジェクトを取得する【2日目】

一人 Ruby Advent Calendar 2017 2日目の記事になります。
今回は "Integer" みたいな文字列からその名前のクラスオブジェクトを参照する方法を書いてみます。

定義したクラスは誰が保持するのか

Ruby でクラスを定義すると『そのスコープの定数』としてクラスオブジェクトが保持されます。
例えば、下記のように Hoge クラスのサブクラスとして定義した場合は Hoge クラスの定数として定義されます。

class Hoge
    PI = 3.14

    class Foo
    end
end

p Hoge.constants
# => [:PI, :Foo]
p Hoge.const_get "Foo"
# => Hoge::Foo

なのでサブクラスの場合は Hoge.const_get "クラス名" のような形でクラスオブジェクトを取得する事ができます。

トップレベルで定義されているクラスオブジェクトを取得する

トップレベルで定義されているクラスは Object クラスが保持しているので、Object クラスの定数から取得することができます。

p Object.constants
# => [:Binding, :Math, :StringIO, :Integer, :Float, :String, :Array, :Hash, :NilClass, :STDOUT, :STDIN, :NIL, :STDERR, :ARGF, :UncaughtThrowError, :SimpleDelegator, :FileTest, :File, :Delegator, :GC, :Fiber, :FiberError, :Data, :TrueClass, :TRUE, :FalseClass, :FALSE, :Encoding, :ZeroDivisionError, :FloatDomainError, :Numeric, :Rational, :Hoge, :ObjectSpace, :Gem, :DidYouMean, :ENV, :Complex, :Struct, :RegexpError, :Comparable, :Enumerator, :Enumerable, :Regexp, :RUBY_VERSION, :RUBY_RELEASE_DATE, :StopIteration, :RUBY_PLATFORM, :Fixnum, :RubyVM, :Thread, :RUBY_REVISION, :RUBY_DESCRIPTION, :RUBY_PATCHLEVEL, :RUBY_ENGINE, :RUBY_ENGINE_VERSION, :RUBY_COPYRIGHT, :TracePoint, :MatchData, :TOPLEVEL_BINDING, :CROSS_COMPILING, :Bignum, :ARGV, :ThreadGroup, :Dir, :ThreadError, :Mutex, :Queue, :ClosedQueueError, :SizedQueue, :ConditionVariable, :Marshal, :Time, :Range, :IOError, :EOFError, :Process, :Monitor, :IO, :RbConfig, :MonitorMixin, :Random, :Symbol, :Exception, :Signal, :SystemExit, :BasicObject, :Object, :Module, :Class, :Kernel, :Interrupt, :StandardError, :SignalException, :Proc, :IndexError, :TypeError, :ArgumentError, :ScriptError, :KeyError, :RangeError, :NotImplementedError, :SyntaxError, :LoadError, :SystemStackError, :NoMethodError, :Method, :RuntimeError, :SecurityError, :NoMemoryError, :EncodingError, :NameError, :SystemCallError, :UnboundMethod, :Errno, :Warning, :LocalJumpError, :RUBYGEMS_ACTIVATION_MONITOR]

p Object.const_get "Integer"
# => Integer


class Hoge
    class Foo
    end
end


# サブクラスの場合は A::B という風に取得することが出来る
p Object.const_get "Hoge::Foo"
# => Hoge::Foo

ネストしてるサブクラスも A::B で取得することができるので便利ですね。

【一人 vimrc Advent Calendar 2017】set nocompatible とは【2日目】

一人 vimrc Advent Calendar 2017 2日目の記事になります。
今回は vimrc の設定でよくみかける(気がする) set nocompatible について簡単に解説します。

set nocompatible とは

set nocompatible とは 'compatible' オプションを無効にするため設定です。
では 'compatible' オプションとは何なのかというと『Vim をなるべく Vi互換にする』ためのオプションになります。
つまり 'compatible' オプションが有効な場合は、『Vim が Vi互換となっている』為、『Vim の便利な機能』が使えません。
なので VimVim らしく使うためには 'compatible' オプションを無効にするために set nocompatible をする必要があります。

set nocompatible は vimrc に記述する必要がある?

さて、この 'compatible' オプションはは既定では有効になっています。
ただし、以下の場合に限り 'compatible' オプションは無効になります。

  • Vim の起動時に 'vimrc' を読み込む
  • Vim の起動時に 'gvimrc' を読み込む

つまり
vimrc を読み込んだ時にはすでに 'compatible' オプショは無効になっている』
ので
vimrcset nocompatible しても意味はない』
と、言うことになります。
まあなので vimrcset nocompatible する必要はありませんね。

ちなみに Vim のオプションは『no + オプション名』とすることでそのオプションが無効になります。

【初心者C++er Advent Calendar 2017】#include <iostream> は何をやっているのか【1日目】

初心者C++er Advent Calendar 2017 1日目の記事になります。
別の Advent Calendar の記事を書いていたら遅れてしまって申し訳ない。
まだ、Advent Calendar の参加者に飽きがあるので気になっている人はどんどん参加してもええんやで…。

さて、初心者ネタって言うことで C++ (や C言語)を学び始めると『おまじない』とよく言われる #include についてちょっと解説してみようかと思います。

コンパイルの流れ

#include を理解するにあたってコンパイルがどのように処理されているのかを知る必要があります。
コンパイル時の流れをざっくりまとめると以下のような感じになります。

#include はここでいう『プリプロセス』という処理に該当します。

プリプロセス処理

プリプロセス処理とはその名の通り『コンパイルの前に行われる』処理になります。
#include 以外にも #define#if#error など # から始まる命令がプリプロセス処理に該当します。
また、このような命令の事をプリプロセッサと呼びます。

#include は何をやっているのか

#includeプリプロセッサというコンパイル前に処理される命令というのはわかりました。
では、#include は何をするのでしょうか。
#include は『指定されたファイルをその場所に展開する(読み込む)』というような命令になります。
例えば、以下のようなヘッダーファイル(test.h)があった時に

// test.h
struct X{
    int value;
    int value2;
};

以下のように #include "test.h" してみます。

// main.cpp
#include "test.h"

int
main(){
    X x{};
    return 0;
}

では、実際にどのようにプリプロセスが行われるのか見てみましょう。
clang や gcc では -E オプションで『プリプロセスの結果を出力する』ことができるのでこれを利用して試してみましょう。

# -P は『'#line'指示子を生成しないようにする』オプション
$ clang++ -E -P main.cpp


struct X{
    int value;
    int value2;
};

int
main(){
    X x{};
    return 0;
}

このように #include "test.h"test.h を読み込んでいることがわかると思います。

ちなみに今回は #include を確認するために -E オプションを使いましたが、#define を使ったマクロなどを確認する場合にも -E を利用することができます。

#include <hoge.h>#include "hoge.h" の違い

さて #include を行う場合に #include <iostream> みたいに

  • <filename> を使った書き方

#include "test.h" みたいに

  • "filename" を使った書き方

の 2種類の書き方があります。

では、この違いはなんなのでしょうか。
この2つは『ファイルを読み込む場所の優先順位』が異なります。

  • <filename> の場合

    • 標準(ライブラリ)のインクルードディレクトリを優先して読み込む
  • "filename" の場合

    • #include しているファイルのディレクトリを優先して読み込む

こんな感じです。
なので <iostream> とすれば標準ライブラリの iostream ファイルを読み込み、"iostream" とすれば(同じディレクトリに iostream ファイルがあれば)同ディレクトリの iostream ファイルを読み込みます。

まあ平たくいえば

  • ライブラリのファイルは <filename> で読み込む
  • ローカルのファイルは "filename" で(相対パスで)読み込む

って感じで使い分ける事が一般的だと思います。
ただ、この辺り、処理系依存なのが言語仕様なのかがよくわからなかったので詳しく書いてくれる人を募集します。
Advent Calendar にはまだ飽きがありますぞ!

インクルードガードの必要性

さて、#include が何をやっているのかわかってきたと思います。
では次にヘッダーファイルを作る時によく耳にする『インクルードガード』について説明してみたいと思います。
例えば、次のように同じファイルを #include しているコードがあるとします。

#include "test.h"
#include "test.h"

int
main(){
    X x{};
    return 0;
}

上記のコードだとちょっと極端ですが、複数のヘッダーファイルから同じヘッダーファイルを読み込んでいることはよくあると思います。
#include では同じファイルが何回も読み込まれるのでこの場合、

struct X{
    int value;
    int value2;
};

struct X{
    int value;
    int value2;
};

int
main(){
    X x{};
    return 0;
}

のように #include "test.h" が2回読み込まれます。
しかし、上記のコードのように同名のクラスを複数定義することは C++ では不正なのでコンパイルエラーとなってしまいます。

こういう時に利用するのが『インクルードガード』という手法になります。
インクルードガードを追加した test.h は以下のような感じになります。

// test.h
#ifndef TEST_H
#define TEST_H

struct X{
    int value;
    int value2;
};

#endif /* TEST */

このように

  • 1回目の読み込み時に #define TEST_H でマクロで定義する
  • 2回目以降の読み込みでは #ifndef TEST_H を用いて処理を読み飛ばす

とすることで2回目以降の定義を防止する事ができます。

余談ですが同等の機能を持つ #pragma once というプリプロセッサが使える処理系もあります。
ちなみに TEST_H みたいなマクロ名をよく __TEST_H__ と書いているコードがありますが、__ を含めた名前は規約違反なので使わないでください。

ヘッダーファイル以外を読み込む

C言語から C++ に入ると気になると思うんですが #include <iostream>#include <stdio.h> のように拡張子がありません。
これは C++ では『拡張子を省略して書ける』わけではなくて単に iostream ファイルに『拡張子がないだけ』です。
逆にいえば『テキストファイルであればなんでも』読み込むことが出来ると言うことになります。
なので、次のように『ただのテキストファイルにデータを記述してく』ことで『コンパイル時にそのデータを読み込むこと』ができます。

data.txt

1, 2, 3,
4, 5, 6,
7, 8, 9,

main.cpp

int
main(){
    auto data = {
       #include "data.txt"
    };

    return 0;
}

結果

$ clang++ -E -P main.cpp
int
main(){
 auto data = {


1, 2, 3,
4, 5, 6,
7, 8, 9,
 };

 return 0;
}

出力結果は整形されていないのでちょっと不格好ですが、こんな感じにソースコードに直接データを埋め込むことができます。
これによりソースコード外で設定などを記述する事が可能です。

まとめ

  • #include <iostream> は標準ライブラリの iostream というファイルを読み込んでいる
  • ヘッダーファイルを作る場合はインクルードガードを付ける
  • #include はテキストファイルであればなんでも読み込める


と、言うことで簡単に #include についてまとめてみました。
#include はおまじない』と言われていますがやっていることは単純にファイルを読み込んでその場に展開しているだけなのでそんなに難しくはないと思います。

何か質問等があればコメントや Twitter までおねがいします。
斧を投げたい人はその旨を書いた 初心者C++er Advent Calendar 2017 への登録をおねがいします。

【一人 vimrc Advent Calendar 2017】vimrc をつくろう【1日目】

この記事は 一人 vimrc Advent Calendar 2017 1日目の記事になります。

一人 vimrc Advent Calendar 2017 とは

vimrc を整理しながら vimrc や Vim の Tips をだらだら書いていこうかなーというような Advent Calendar になります。
特にネタも決めてないのでその日 vimrc を見ながらてきとーに書いていこうかと思います。
ちなみに基本的には vimrc に関することを書いていこうと思っているので Vim の使い方みたいなのは書かない予定です。
なので記事の内容はある程度使えることが前提になると思います。

vimrc をつくろう

まずは Vim を使うにあたって vimrc をつくらないと始まらないだろう、ということで vimrc をつくりましょう。
Vim では起動時に vimrc ファイルが読み込まれ、この vimrc ファイルに設定を記述していくことで自分好みに拡張していきます。

では、その vimrc ファイルはどこに作ればいいのでしょうか…?
この vimrc ファイルを置く場所は OS ごとに異なっており :help vimrc を参照すると以下のように書かれています。

個人の初期化ファイルの配置場所:
        Unix            $HOME/.vimrc、$HOME/.vim/vimrc
        OS/2            $HOME/.vimrc、$HOME/vimfiles/vimrc、
                        $VIM/.vimrc (または _vimrc)
        MS-Windows      $HOME/_vimrc、$HOME/vimfiles/vimrc、
                        $VIM/_vimrc
        Amiga           s:.vimrc、home:.vimrc、home:vimfiles:vimrc、
                        $VIM/.vimrc

基本的には $HOME.vimrc または _vimrc というファイルを作れば問題なさそうですね。
また、:version を実行すると以下のように『どの vimrc を読み込むのか』という情報も出力されるのでそれを参照してみるのもよいと思います。

更に詳しい Vim の起動時の処理については :help startup を読んでみると流れがわかると思います。

読み込む vimrc ファイルのパスを設定する

読み込む vimrc ファイルのパスは $MYVIMRC という名前の環境変数で指定することもできます。
例えば、『github で vimrc ファイルを監理したいので全然別のところに保存したい』みたいな場合は $MYVIMRC にそのパスを設定する事でその vimrc ファイルを読み込むことができます。

vimrc を編集する

先ほど $MYVIMRC で vimrc のパスを指定できると書きましたが、この『$MYVIMRC が設定されていない場合』に Vim を起動すると『最初に見つかった vimrc のパス』が $MYVIMRC に設定されます。
なので

:edit $MYVIMRC

とすることで読み込んでいる vimrc ファイルを編集する事ができます。

vimrc を再読込する

さて、vimrc を編集したら設定を反映したいですよね。
その場合は :source % とすることで現在開いている vimrc ファイルを再読込します。
また、:source $MYVIMRC とすることで vimrc を開いていない状態でも $MYVIMRC のファイル(つまり現在の vimrc)を読み込むことができます。

まとめ

  • vimrc ファイルは Vim の起動時に読み込まれる
  • vimrc ファイルに設定を記述することで Vim を拡張していく
  • :edit $MYVIMRC で vimrc ファイルを編集することが出来る
  • :source $MYVIMRC で vimrc ファイルを再読み込みすることが出来る

ちなみに gvim の場合は gvimrc ファイルが別に読み込まれるので、gvim 固有の設定を行いたい場合はこちらに記述するとよいと思います。
と、言うような感じでだらだらと書いていきたいと思います。
なんか思ったよりも書いた気がするんだけどこれで25日間持つのだろうか…。

【一人 Ruby Advent Calendar 2017】def や class の戻り値【1日目】

一人 Ruby Advent Calendar 2017 1日目の記事になります。

一人 Ruby Advent Calendar 2017 とは

完全に勢いで立てました。
まあなんか Ruby 界隈が盛り上がればいいかなーと
まあ Ruby に関する小ネタみたいなのを簡単な書いてこうかなーと思います。
書き溜めとか特にしないでだらだら書いていこうかと思います。
なんか書いてほしいネタとかあれば Twitter とかで言ってもらえば書きます、多分。

classdef の戻り値

さて、まずは簡単なネタから。
Ruby では構文がだいたい式なんですが、classdef なんかも式になります。
式ということは当然 classdef などにも戻り値が存在します。

classmodule の戻り値

classmodule なんかは『一番最後の式』を返します。

p class X
    42
end
# => 42

p class X
    ancestors
end
# => [X, Object, Kernel, BasicObject]

p module M
    self
end
# => M

def の戻り値

def は『定義したメソッドの名前』を Symbol で返します。

p def hoge
end
# => :hoge

p def foo
end
# => :foo

p def +@
end
# => :+@

def の戻り値を利用して privatemodule_function など定義する

def の戻り値を直接 private メソッドや module_function メソッドに渡すことができます。

class X
    # private メソッドに対して def の戻り値を渡す
    private def hoge
        :hoge
    end

    def foo
        hoge
    end
end

p X.new.foo
# => :hoge


module M
    module_function def hoge
        :hoge
    end
end

p M.hoge
# => :hoge

任意のメソッドのみ private にしたい時などはこういう書き方のほうがすっきりしそうですね。
と、言うような簡単なネタを書いていきたいと思います。

などと思っていたらこんな Advent Calendar がいつの間にか立っておりすごい。

【Ruby Advent Calendar 2017】Ruby で型チェックを実装してみよう【1日目】

Ruby Advent Calendar 2017 1日目の記事になります。
Ruby 3 では静的型づけが入るとか言われていますが、今回は Ruby で動的に『型チェック』を行うコードを実装してみようと思います。

前書きという名の注意点

  • 型チェックといいながら一般的な意味での型の話はしない
  • あくまでも Ruby で『型チェックのようなもの』を実装するという話
  • 型過激派の人は生暖かい目で見てください
  • 今回は動的に型チェックを行うのでパフォーマンスに関しては考慮しない

今回実装する機能

  • メソッドの引数に対して型チェック(精査)を行う
  • 型によってメソッドの多重定義を行う

ゴール

class Person
    attr_accessor :name, :age

    def initialize
    end

    # name は String
    # age  は Integer
    # で受け取る initialize メソッドを定義
    define_typed_method(:initialize, name: String, age: Integer){
        # Hash のキーで変数を参照する
        self.name = name
        self.age  = age
    }

    define_typed_method(:set, name: String, age: Integer){
        self.name = name
        self.age  = age
    }

    def print
        puts "name:#{name} age:#{age}"
    end

    # フォーマットを渡して出力したり
    define_typed_method(:print, fmt: String){
        printf(fmt, name, age)
    }
end

homu = Person.new("homu", 14)

homu.print
# => name:homu age:14

homu.print("%s : %d\n")
# => homu : 14


mami = Person.new
mami.set("mami", 15)
mami.print
# => name:mami age:15

とりあえず、最終的には上記のようなコードが動作するようにしたいと思います。

Ruby における型チェックとは

そもそも Ruby の型とは…型チェックとは…という話になるんですが、本記事では

  • 型: #=== が定義されているオブジェクト
  • 型チェック: 型(#===) を使用して引数の値を精査する事

としたいと思います。
平たくいえば『#=== を使って引数の値をチェックする』って感じですね。
なのでどちらかといえばバリデーションのような機構に近い形になると思います。

では、なぜ #=== を使用するのかというと…。
例えばクラスオブジェクトでは #=== は『引数がレシーバかそのサブクラスのインスタンスである場合』に真を返します。

String === "homu" # => true
String === 42     # => false

Integer === 42    # => true
Integer === 3.14  # => false

Numeric === 42    # => true
Numeric === 3.14  # => true

上記のコードを見るとなんとなく型チェックっぽく見えますよね?
#=== は本来 when で呼び出される事が想定されていますが、今回は

  • #=== が真を返せば OK
  • #=== が偽を返せば NG

という風にしてみたいと思います。
他にも #=== を使用して型チェックを行う利点はあるのですが、それは後で記述します。

型チェックを行うメソッドを定義する

さて、では早速コードを書いていきましょう。
まずは以下のような感じで実装してみます。

  • 型を渡してメソッドを定義する
  • メソッドの呼び出し時に引数に対して型チェックを行う
    • 引数が定義した型に対して OK ならそのままメソッドを呼び出す
    • NG なら super() を呼び出す
# クラスメソッドとして定義するので Module クラスを拡張
class Module
    # 型チェック付きメソッドを定義する
    # 第二引数には #=== で精査する値を渡す
    def define_typed_method name, *sig, &defmethod

        # 型リストと引数を #=== で比較する
        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        define_method(name){ |*args|
            # 型チェックを行い OK ならブロックを呼び出し、NG なら super() を呼ぶ
            valid === args ? defmethod.call(*args) : super(*args)
        }
    end
end

define_method のラッパーとして define_typed_method というメソッドを定義します。
このメソッドに対して『精査するする型』を渡し、内部で『メソッドの呼び出し時に型チェックを行うメソッド』を定義します。
ちなみに super() を呼び出すのは型チェックに失敗した場合にスーパークラスのメソッドにフォワードするためですね。

使い方は以下のような感じです。

class X
    # func(Integer) のみ受け付けるメソッドを定義
    define_typed_method(:func, Integer){ |x|
        "func(Integer: #{x})"
    }

    # plus(Integer, String) のみ受け付けるメソッドを定義
    define_typed_method(:plus, Integer, String){ |a, b|
        a + b.to_i
    }
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.plus 1, "2"
# => 3

# Error: super: no superclass method `func' for #<X:0x0000000001376eb8> (NoMethodError)
p x.func "homu"

とりあえず、まずはこれを基準として改良していきたいと思います。

多重定義する

次は以下のように『型によって複数のメソッドを定義する』ことを考えてみましょう。

class X
    # func(Integer) のみ受け付けるメソッドを定義
    define_typed_method(:func, Integer){ |x|
        "func(Integer: #{x})"
    }

    # func(String) のみ受け付けるメソッドも定義したい
    define_typed_method(:func, String){ |x|
        "func(String: #{x})"
    }
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.func "homu"
# => "func(String: "homu")"

いわゆる多重定義というやつですね。
これを実装するにあたって『Ruby で同名の複数のメソッドを保持する』必要があります。
今回は実装を簡単にしたかったので mixin を利用したいと思います。

class Module
    def define_typed_method name, *sig, &defmethod
        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        # 動的にメソッドを追加していく
        prepend Module.new {
            define_method(name){ |*args|
                # super() を介すことで以前に定義されたメソッドを呼び出す
                valid === args ? defmethod.call(*args) : super(*args)
            }
        }
    end
end


class X
    # func(Integer) のみ受け付けるメソッドを定義
    define_typed_method(:func, Integer){ |x|
        "func(Integer: #{x})"
    }

    # func(String) のみ受け付けるメソッドも定義したい
    define_typed_method(:func, String){ |x|
        "func(String: #{x})"
    }
end

x = X.new
p x.func 42
# => "func(Integer: 42)"
p x.func "homu"
# => "func(String: "homu")"

上記のように prepend を利用して継承リストに対して動的にメソッドを追加していきます。
これにより super() を利用して『以前に定義したメソッド』を呼び出すことが出来るようになります。
また、prepend なので『後から定義したメソッド』が優先して呼び出されます。

class X
    define_typed_method(:func, String){ |x|
        "func(String: #{x})"
    }

    # こちらのほうが優先して呼び出される
    define_typed_method(:func, Object){ |x|
        "func(Object: #{x})"
    }
end

x = X.new
p x.func 42
# => "func(Object: 42)"

# Object === "homu" # => true
# なので Object で定義したメソッドのほうが優先して呼び出される
p x.func "homu"
# => "func(Object: "homu")"

内部で『定義したメソッドのリストを保持する』みたいな方が柔軟性は高いんですが、今回は実装をシンプルにしたかったのでこれで行きます。

定義するメソッド内の selfインスタンスオブジェクトにする

さて、次のように define_typed_method で定義したメソッド内で他のメソッドを呼び出すとエラーになってしまいます。

class X
    def twice x
        x + x
    end

    define_typed_method(:func, String){ |x|
        # Error: undefined method `twice' for X:Class (NoMethodError)
        twice x.to_i
    }
end

x = X.new
p x.func "42"

これは define_typed_method に渡したブロック内の self が X のインスタンスではなくて『define_typed_method を呼び出したコンテキスト(上記の場合では X)』になってしまうからです。

class X
    define_typed_method(:func, String){ |x|
        self
    }
end

x = X.new
p x.func "42"
# => X

これを回避するために #instance_exec を経由してブロックの呼び出しを行います。

class Module
    def define_typed_method name, *sig, &defmethod
        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                # instance_exec 経由で呼び出すことでコンテキストをインスタンスオブジェクトにする
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end
end


class X
    def twice x
        x + x
    end

    define_typed_method(:func, String){ |x|
        # ここのコンテキストは X のインスタンスオブジェクトになる
        twice x.to_i
    }
end

x = X.new
p x.func "42"
# => 84

これにより define_typed_method のブロック内でも他のインスタンスメソッドを呼び出すことができます。

問題点:#instance_exec にはブロック引数を渡すことができない(未解決)

#instance_exec で先ほどの問題は回避することができましたが、#instance_exec を使用した場合では別の問題が発生します。
#instance_exec では以下のようにブロック引数に対して任意の引数を渡す事ができます。

show = proc { |fmt| printf(fmt, self) }

42.instance_exec "%04d\n", &show
# => 0042

上記の場合は show に渡す引数を #instance_exec 経由で渡しています。

では、show に対してブロック引数を渡したい場合はどうでしょう。

# ブロックを受け取って処理を行う
show = proc { |fmt, &block| ... }

# #instance_exec に対してブロック引数も渡したいがすでに &show をブロック引数として渡してる
42.instance_exec("%04d\n", ???, &show)

#instance_exec にはすでに &show をブロック引数として渡しているので『show で受け取るためのブロック引数を #instance_exec で渡すこと』ができません。
うーん、ややこしいですね。
これを回避するには #instance_exec に対して『複数のブロック引数を渡す必要』がありますが、残念ながら Ruby ではそれを行うことができません。

さて、define_typed_method の話に戻ります。
define_typed_method でも同様の問題が発生し、

class Module
    def define_typed_method name, *sig, &defmethod
        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args, &block|
                valid === args ?
                    # 呼び出すメソッドに対してブロック引数も渡したいができない
#                  instance_exec(*args, &block, &defmethod)
                    instance_exec(*args, &defmethod) :
                    # super() は普通に渡せるが…
                    super(*args, &block)
            }
        }
    end
end


class X
    define_typed_method(:func, Integer){ |x, &block|
        p x
        # => 42
        p block
        # => nil
    }
end

x = X.new
# ブロック引数を渡したいが…
x.func(42){}

これに関しては Ruby の標準機能だけで解決するのはちょっと難しいので、後ほど gem を使った回避方法を記述します。

型と一緒に変数名も定義したい

ここからちょっと複雑になってきます。
現状の仕様では、

  • define_typed_method にメソッドの引数型を渡す
  • define_typed_method のブロックでメソッドの引数を受け取る

という風になっています。
しかし、以下のように『型と変数名』を一緒に定義したほうがすっきりしますよね。

class X
    attr_accessor :name, :age

    # こんな感じで Hash 引数を使用して変数名と型を一緒に渡したい
    define_typed_method(:set, name: String, age: Integer){
        # 引数を自身に代入
        self.name = name
        self.age  = age
    }
end

これを実装していきたいと思います。
とりあえず、Hash 版の実装を define_typed_method_with_hash として定義します。

class Module
    def define_typed_method name, *sig, &defmethod
        # sig が Hash なら Hash 版を呼び出す
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end

    # とりあえず、別メソッドとして定義
    def define_typed_method_with_hash name, sig, &defmethod
        # この内部で実装していく
    end
end

この define_typed_method_with_hash 対して実装を記述していきます。

変数をメソッドとして定義する

まずは、nameage のような変数名で参照する為に nameage をメソッドとして定義します。

class Module
    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        # Hash の値が型なのでそれを、define_typed_method に渡して型チェックを行うメソッドを定義する
        define_typed_method(name, *sig.values){ |*args|
            # メソッド内部の実装

            # 変数名で参照する為に Hash のキーを名前としたメソッドを定義する
            sig.keys.each_with_index { |name, i|
                # 引数の値を返す(特異)メソッドを定義
                define_singleton_method name, &args[i].method(:itself)
            }

            # 引数を渡さないでメソッドの実装を呼び出す
            instance_exec &defmethod
        }
    end
end

こんな感じで『Hash のキーを名前として引数を返すメソッド』を定義することで nameage などを変数のように参照することができます。

class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        p "#{name} : #{age}"
        # => "homu : 14"

        # name や age などがメソッドとして定義されているので擬似的に変数を参照できる
        self.name = name
        self.age  = age
    }
end

x = X.new
x.set("homu", 14)

インスタンスオブジェクトに直接メソッドを定義しない

これで変数名で参照することはできますが、インスタンスオブジェクトに直接(特異)メソッドを定義するのは大変危険です。

class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        # ...
    }
end

x = X.new

# これの内部で #name メソッド等が書き換えられてしまう
x.set("homu", 14)

# 内部の値を書き換えても
x.name = "mami"

# 先ほど #set で上書きされたメソッドが呼び出される
p x.name
# => "homu"

この問題を解決するために『変数を参照するメソッドを別のオブジェクトで定義』して、それをコンテキストとしてメソッドを呼び出してみたいと思います。

class Module
    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        define_typed_method(name, *sig.values){ |*args|
            # 変数を参照するメソッドを定義するオブジェクトを生成
            Object.new.instance_exec {
                # 生成したオブジェクトの内部で変数を参照するメソッドを定義する
                sig.keys.each_with_index { |name, i|
                    define_singleton_method name, &args[i].method(:itself)
                }

                # 変数を参照するメソッドが呼び出せるように
                # #instance_exec で set メソッドのコンテキストをこのオブジェクトにする
                instance_exec &defmethod
            }
        }
    end
end


class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        # ...
    }
end

x = X.new

# 書き換えるのは別のオブジェクトのメソッドなので
x.set("homu", 14)

# 内部の値を書き換えても
x.name = "mami"

# x のメソッドは上書きされない
p x.name
# => "mami"

このように Object.new を利用して動的にオブジェクトを定義して、そのオブジェクトに対して変数を参照するメソッドを定義します。

X のメソッドを参照出来るようにする

『別のオブジェクトを生成すること』で副作用なく『変数を参照するメソッド』を定義する事ができました。
しかし、この『別のオブジェクトををコンテキストにする』ことによりメソッド内のコンテキストが変わってしまい、X のメソッドにアクセスすることができなくなってしまいました。

class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        # self が Object.new になる
        p self
        # => #<Object:0x000000000175dba8>
        # 当然 X のインスタンスオブジェクトではないので X#name を呼ぼうとするとエラーになる
#      self.name = name
    }
end

そこで、method_missing を利用して『参照したメソッドが存在しなければ元のコンテキストのメソッドを参照する』というような処理を追加します。

class Module
    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        define_typed_method(name, *sig.values){ |*args|
            # 変数を参照するメソッドを定義するオブジェクトを生成
            receiver = self
            Object.new.instance_exec {
                sig.keys.each_with_index { |name, i|
                    define_singleton_method name, &args[i].method(:itself)
                }

                # method_missing を利用して参照するメソッドが存在しない場合は
                define_singleton_method(:method_missing){ |name, *args, &block|
                    # 元々のレシーバのメソッドを参照するようにする
                    receiver.__send__ name, *args, &block
                }

                instance_exec &defmethod
            }
        }
    end
end


class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        # self は Object.new のままだが
        p self
        # => #<Object:0x000000000175dba8>

        # name= や age= メソッドは定義されていないが
        # method_missing 経由で X のメソッドが呼び出される
        self.name = name
        self.age  = age
    }
end

x = X.new

x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => 14

このように method_missing を利用することで擬似的に X のメソッドを参照する事ができます。

問題点:インスタンス変数を参照できない(未解決)

method_missing を介すことで X のメソッドを参照する事ができました。
しかし、 method_missing でメソッドを参照することはできても『インスタンス変数』を参照することはできません。

class X
    attr_accessor :name, :age
    define_typed_method(:set, name: String, age: Integer){
        # メソッド経由でインスタンス変数を参照できても
        self.name = name

        # 直接インスタンス変数を参照することはできない
        @age = age
    }
end

x = X.new

x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => nil

この問題を解決するためには『存在しない変数を参照した時』にフックできる variable_missing のようなメソッドが必要なのですが、残念ながらこのようなメソッドは存在しません。
この問題を解決するためには別のアプローチが必要になってきますが、これも標準の機能では解決できないので後で解決方法を記述します。

まとめ

と、言うことで当初やりたかった

  • メソッドの引数に対して型チェック(精査)を行う
  • 型によってメソッドの多重定義を行う

という実装できたので一旦まとめます。

実装コード

class Module
    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args|
                valid === args ? instance_exec(*args, &defmethod) : super(*args)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        define_typed_method(name, *sig.values){ |*args|
            receiver = self
            Object.new.instance_exec {
                sig.keys.each_with_index { |name, i|
                    define_singleton_method name, &args[i].method(:itself)
                }

                define_singleton_method(:method_missing){ |name, *args, &block|
                    receiver.__send__ name, *args, &block
                }

                instance_exec &defmethod
            }
        }
    end
end

使用コード

class Person
    attr_accessor :name, :age

    def initialize
    end

    # name は String
    # age  は Integer
    # で受け取る initialize メソッドを定義
    define_typed_method(:initialize, name: String, age: Integer){
        # Hash のキーで変数を参照する
        self.name = name
        self.age  = age
    }

    define_typed_method(:set, name: String, age: Integer){
        self.name = name
        self.age  = age
    }

    def print
        puts "name:#{name} age:#{age}"
    end

    # フォーマットを渡して出力したり
    define_typed_method(:print, fmt: String){
        printf(fmt, name, age)
    }
end

homu = Person.new("homu", 14)

homu.print
# => name:homu age:14

homu.print("%s : %d\n")
# => homu : 14


mami = Person.new
mami.set("mami", 15)
mami.print
# => name:mami age:15

いい感じですね。

問題点

  • メソッドにブロック引数を渡すことができない
  • メソッド内でインスタンス変数を参照する事ができない

欠点

  • 1つメソッドを定義するごとにモジュールを mixin しているので継承リストがどんどん増えていく
  • Ruby の言語機能だと『mixin したモジュールを削除する事が出来ない』ので定義したメソッドを削除するのが難しい
  • mixin しまくってるので明示的にスーパークラスのメソッドを呼ぶことが出来ない
  • Refinements で #=== を定義しても内部で using することができない(影響を受けない

所感

と、いうことで簡単に『型チェックを行う処理』を実装してみました。
ポイントとしては

  • 型チェックとは何かを考える
    • 今回は型チェックというものを抽象化して #=== にフォーカスを当てた
    • あまり Ruby で型というものを意識しないほうがいい気はする
  • mixin して型チェックを行うメソッドを追加していく
    • 個人的に Ruby らしくて好き
  • メソッド内のコンテキストを意識する
    • どうやってブロックを呼び出すのか
    • ブロック内のコンテキストを何にするのか

という感じでしょうか。
単にメソッド定義をラップするだけならそこまで難しくはありませんが、コンテキストを意識し始めるとちょっと複雑になってきますね。
とはいえ、その割にはコード量自体は思ったよりも少なくなったので個人的には満足。
今回の実装では mixin を利用したり、動的にオブジェクトを生成したり、method_missing したりと結構 Ruby らしいコードにはなったような気はします。

あと型チェックを『#=== でチェックする』と抽象化したので思ったよりも拡張性は高いです(理由は後述。
ちなみに似たようなライブラリをつくっているので気になる方はこちらも参照してみください(リファクタリングしたまま放置中なので近々破壊的変更する予定ですが…。


と、言うことで簡単ですが『Ruby で型チェック』を行ってみました。
Rubyメタプログラミングたのしいのでみんなもやってみましょう。
そしてここからが本番は番外編です。

クラスオブジェクト以外で型チェックする

さて、今まで散々説明してきましたが型チェックは #=== で行っています。
つまり『#=== が定義されているオブジェクト』であれば別にクラスオブジェクトである必要はありません。
Ruby では #===when で使用されることを想定しているのでクラスオブジェクト以外にも様々なオブジェクトで #=== が定義さいれています。

と、いうことでクラスオブジェクト以外でもいろいろと試してみましょう。

class X
    # Regexp で受け取る文字列を制限する
    # 文字列が http の URL のみ受け付ける
    define_typed_method(:get, url: /http:.*/){
        url
    }

    # Range で受け取る範囲を制限
    # 1〜12のみ受け付ける
    define_typed_method(:month, month: (1..12)){
        "#{month}"
    }


    # Proc で任意の条件を設定
    # サイズが 5 以下のオブジェクトのみ受け付ける
    define_typed_method(:func, x: -> x { x.size <= 5 }){
        x.size
    }
end


x = X.new

p x.get "http://example.com"
# => "http://example.com"

# Error
# x.get "ftp://example.com"


p x.month 2
# => "2月"

# Error
# p x.month 0

p x.func [1, 2, 3, 4, 5]
# => 5
p x.func "homu"
# => 4

# Error
# p x.func (1..10)
# p x.func "homuhomu"

上記のコードのように RegexpRangeProc ではそれそれ独自に #=== を定義しているので型チェックのような形で引数を精査する事ができます。
このように『#=== で型チェックすること』でクラスオブジェクト以外にも様々なオブジェクトに対して使用することができるので割と柔軟性は高いです。

Array#=== を定義する

さてさて、Array#=== を定義することで次のように利用することもできます。

class Array
    def === other, &block
        size == other.size && zip(other).all? { |a, b| a.=== b, &block }
    end
end


class Person
    attr_accessor :name, :age

    # set [name, age]
    # みたいな配列で渡す
    define_typed_method(:set, data: [String, Integer]){
        self.name = data[0]
        self.age  = data[1]
    }
end


mado = Person.new
mado.set(["mado", 14])

p mado.name
# => "mado"
p mado.age
# => 14

こんな感じで簡単に型チェックするオブジェクトを拡張する事ができます。
今回は実装しませんでしたが、Hash#=== なんかも定義してみると面白そうですね。

#instance_exec にブロック引数を渡す

さて、ここからは先程解決できなかった問題を解決していきます。
まずは、『#instance_exec にはブロック引数を渡すことができない』問題です。

これは proc-unbind を利用して解決します。
proc-unbind を使用することで『Proc オブジェクトを任意のコンテキスト』で呼び出す事ができます。

インストール

$ gem install proc-unbind

使い方

require "proc/unbind"

using Proc::Unbind

expr = proc { |*args, &block|
    p self
    p args
    p block
}

# unbind することで UnboundMethod を返す
expr_unbind = expr.unbind

p expr_unbind.class
# => UnboundMethod


# 任意のオブジェクトで bind する
# bind 後に call を呼び出すことで
# bind したオブジェクトのコンテキストで expr を評価する
expr_unbind.bind(42).call(1, 2, 3, &:to_s)
# => 42
# => [1, 2, 3]
# => #<Proc:0x00000000025a6b10>


expr_unbind.bind("homu").call {}
# => "homu"
# => []
# => #<Proc:0x0000000001012088@/tmp/vnzeaBx/11738:27>

こんな感じで Proc#unbind から UnboundMethod を生成します。
UnboundMethod は任意のオブジェクトをバインドすることでそのオブジェクトをレシーバとして処理を呼び出す事ができます。

さて、これを利用して define_typed_method でブロック引数を渡すことが出来るようにしてみましょう。

require "proc/unbind"


class Module
    using Proc::Unbind

    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args, &block|
                # proc/unbind を利用してレシーバを self にしながらブロックを渡す
                # rebind(self) は unbind.bind(self) と同等
                valid === args ? defmethod.rebind(self).call(*args, &block) : super(*args, &block)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        define_typed_method(name, *sig.values){ |*args, &block|
            receiver = self
            Object.new.instance_exec {
                sig.keys.each_with_index { |name, i|
                    define_singleton_method name, &args[i].method(:itself)
                }

                define_singleton_method(:method_missing){ |name, *args, &block|
                    receiver.__send__ name, *args, &block
                }

                defmethod.rebind(self).call &block
            }
        }
    end
end


class X
    # ブロック引数はブロックの引数として受け取る
    define_typed_method(:func){ |&block|
        block.call 42
    }
end

p X.new.func { |x| x + x }
# => 84

|&block| のようにブロック引数を受け取る必要はありますが、これでブロック引数を渡して受け取る事ができました。
これで問題の1つであった『メソッドにブロック引数を渡すことができない』ということが解決できましたね!!

メソッド内でインスタンス変数を参照出来るようにしてみる(仮)

先に結論から書いておくと一応対応してみましたが、これでも完全な解決にはなりません。
なのでまあこういうアプローチもあるよーぐらいな感じで書いていきます。

さて、そもそもの問題点として『メソッド内から引数を変数名で参照したい』を解決するための手段として

  • 変数を参照するメソッドを定義したオブジェクトを新しく生成する
  • そのオブジェクトを経由して変数を参照する
  • 元のレシーバには method_missing 経由で参照する

という手段を用いました。
しかし、この結果、レシーバのコンテキストが異なってしまい新たな問題が発生してしまいました。

そこでアプローチを変えて以下のような感じにしてみたいと思います。

  • 変数を参照するメソッドを定義した『モジュール』を新しく生成する
  • そのモジュールをレシーバに mixin する
  • ブロックを呼び出すを呼び出す
  • mixin したモジュールを削除する

まあつまり『一時的にモジュールを mixin する』ということで副作用を最小限に抑えようと言うようなアプローチになります。

じゃあ、どうやって『一時的にモジュールを mixin する』のかというと Ruby の標準の機能ではできないので gem-unmixer を使います。
gem-unmixer に関しては以下の記事を参照してください。

今回はこれを利用して先ほどのような機能を実装します。

require "proc/unbind"
require "unmixer"


class Module
    using Proc::Unbind
    using Unmixer

    def define_typed_method name, *sig, &defmethod
        return define_typed_method_with_hash(name, sig.first, &defmethod) if Hash === sig.first

        valid = -> other { sig.size == other.size && sig.zip(other).all? { |a, b| a === b } }
        prepend Module.new {
            define_method(name){ |*args, &block|
                valid === args ? defmethod.rebind(self).call(*args, &block) : super(*args, &block)
            }
        }
    end

    def define_typed_method_with_hash name, sig, &defmethod
        define_typed_method(name, *sig.values){ |*args, &block|
            # キーを変数名として参照するためのモジュールを定義する
            accessor = Module.new {
                sig.keys.each_with_index { |name, i|
                    define_method name, &args[i].method(:itself)
                }
            }

            # defmethod を呼び出す時のみ一時的に mixin する
            extend(accessor){
                # このブロック内では accessor モジュールが有効になっている
                return defmethod.rebind(self).call &block
            }
        }
    end
end


class X
    attr_accessor :name, :age

    define_typed_method(:set, name: String, age: Integer){
        p self.name
        # self は X のインスタンスオブジェクト
        p self
        # => #<X:0x0000000001e98508>

        # なので変数名などを直接参照できる
        @name = name
        @age  = age
    }
end

x = X.new
x.set("homu", 14)
p x.name
# => "homu"
p x.age
# => 14

これで、コンテキストを X のインスタンスオブジェクトにしたままブロック内で変数が参照できるようになりました。
やったね!!

ただし、この実装でも『ブロック内以外(例えば、ブロック内から呼び出した他のメソッド内とか)』でも mixin したモジュールの影響を受けてしまうので解決したとはいえません。
うーん、アプローチとしてはいいと思ったんですがむずかしいですね…。

複数の型でチェックする

最後の最後に本当のおまけです。
例えば以下のように複数の型を許容したい場合があると思います。

# String か Symbol のみを受け付ける
define_typed_method(:func, str: (String | Symbol)){
    str.capitalize
}

これを解決する場合 gem-laurel が利用できます(と、いうかこういう目的のために作った gem になります。

インストール

$ gem install laurel

使い方

require "laurel"

using Laurel::Refine

(x & y).any_method # => x.any_method && y.any_method

こんな感じで (a & b).hoge と呼び出した場合、a.hoge && b.hoge と評価するようなライブラリになります。
つまり (String | Symbol) === a と呼びだされた場合は String === a || Symbol === a となるような感じですね。

require "laurel"

using Laurel::Refine

class X
    # String か Symbol のみを受け付ける
    define_typed_method(:func, str: (String | Symbol)){
        str.capitalize
    }

    # こんな感じで Regexp を組み合わせたり
    # 文字列かつ、数値なら呼ばれる
    define_typed_method(:twice, n: (String & /^-?\d+$/)){
        n.to_i + n.to_i
    }

    # 数値の場合は普通に計算
    define_typed_method(:twice, n: Integer){
        n + n
    }
end

x = X.new
p x.func "homu"
p x.func :mado

# Error
# p x.func 42

p x.twice "-6"
# => 12
p x.twice "5"
# => 10
p x.twice 42
# => 84

# Error
# p x.twice ""
# p x.twice "-"
# p x.twice "-42homu"

これでかなり引数に対する制約が柔軟に定義しやすくなったと思います。

所感

と、調子にのって書きまくったらめっちゃ長くなってしまいました…。
まあこんな感じでお手軽に Ruby でも型チェックできるよーという感じの内容でしたね!!

Ruby で型チェックを行う機構自体は前からいろいろと考えているんですが、型チェック自体は今回のように #=== みたいなメソッドでダックタイピングするのがいいような気はします。
拡張性や柔軟性も高いですし。
最初は Type クラスみたいなのも考えてみたんですが、こっちは『Ruby に置いて型とはなんなのか』みたいなのが定義できなかったので考えるのをやめました…。

ただ、型チェックを考えるにあたって型チェック自体よりも

  • 定義するメソッド(ブロック)の呼び出し方
  • て型チェックを行うメソッドの定義方法
    • define_typed_method みたいなのでラップするのがよいのか?

みたいな『どうやって型チェックを行うメソッドを定義するべきか』みたいなところで悩んだりすることが多かったです。
今回は define_typed_method(:func, name: String, age: Integer) みたいに define_method をラップするような形にしてみましたが。

requires [String, Integer]
def func name, age
end

みたいな感じで定義したほうがすっきりしそうな気もしますし、もしくは

def_.func(name: String, age: Integer){
    
}

みたいな定義方法も考えられますしね。
この辺りは好みもあるし、1人で考えていても答えが見つからないと思うんですが、いかんせん相談できる Rubyist がまわりに少ないので厳しい…。
slack とかに ruby-jp みたいなコミュニティができないかなー

あとは今のところ動的に型チェックを行うことを想定していますが、じゃあ静的に型チェックするには…みたいなのも考える必要が出てきますね。
今のところパフォーマンスに関しては完全に考えていないですし、ここら辺を考えると更にむずかしくなってきますね…。

まあそんな感じで『Ruby で型チェックを行う』というのを考えてみました。
これがベストだとは思いませんが、他の方も型に関して興味を持ってもらえると嬉しいです。
なにか質問や気になる点があればコメントや Twitter なんかで聞いてください。

node-prune を使ってみた

JavaScript で開発を行っていると npm でモジュールを導入する、モジュールを導入すると node_modules が肥大化する、node_modules が肥大化すると dropbox の同期が死ぬ。
ってことで割と node_modules が肥大化するのは切実に困っていたんですが、最近公開された node-prune という golang 製のツールを使えば不要なファイルを除去出来るということで試してみました。

インストール

$ go get github.com/tj/node-prune/cmd/node-prune

使い方

node-prune コマンドの引数に削除する node_modules のパスを渡します(渡さなかった場合はカレントの ./node_modules を参照するぽい?
と、言うことで適当なプロジェクトで試してみた。

$ node-prune ./node_modules/

         files total 7,198
       files removed 2,321
        size removed 5.4 MB
            duration 68ms

と、いうことで約7000ファイル中2000ファイル以上が削除された模様。
思ったよりも削除されたファイルが多くてびっくり。
こんなに不要なファイルがあったのか…。
そんな感じで node_modules が肥大化して困っている人も困っていない人もおすすめです。
しかし、なんで node.js 製じゃなくて golang 製なんだ。