【一人 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 の便利な機能』が使えません。
なので Vim を Vim らしく使うためには 'compatible'
オプションを無効にするために set nocompatible
をする必要があります。
set nocompatible
は vimrc に記述する必要がある?
さて、この 'compatible'
オプションはは既定では有効になっています。
ただし、以下の場合に限り 'compatible'
オプションは無効になります。
つまり
『vimrc
を読み込んだ時にはすでに 'compatible'
オプショは無効になっている』
ので
『vimrc
で set nocompatible
しても意味はない』
と、言うことになります。
まあなので vimrc
で set 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つは『ファイルを読み込む場所の優先順位』が異なります。
こんな感じです。
なので <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 とかで言ってもらえば書きます、多分。
class
や def
の戻り値
さて、まずは簡単なネタから。
Ruby では構文がだいたい式なんですが、class
や def
なんかも式になります。
式ということは当然 class
や def
などにも戻り値が存在します。
class
や module
の戻り値
class
や module
なんかは『一番最後の式』を返します。
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
の戻り値を利用して private
や module_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
対して実装を記述していきます。
変数をメソッドとして定義する
まずは、name
や age
のような変数名で参照する為に name
や age
をメソッドとして定義します。
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
のキーを名前として引数を返すメソッド』を定義することで name
や age
などを変数のように参照することができます。
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"
上記のコードのように Regexp
、Range
、Proc
ではそれそれ独自に #===
を定義しているので型チェックのような形で引数を精査する事ができます。
このように『#===
で型チェックすること』でクラスオブジェクト以外にも様々なオブジェクトに対して使用することができるので割と柔軟性は高いです。
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 製なんだ。