unite-oldfiles つくった
【初心者C++er Advent Calendar 2016 13日目】予約済み識別子に注意しよう
初心者C++er Advent Calendar 2016の13日目が空いてるようなの書きました。
本記事は初心者C++er Advent Calendar 201613日目の記事になります。
初心者C++er Advent Calendar 2016では 3回目の記事になりますが、今回は予約済み識別子の話です。
[予約済み識別子とは]
『予約済み識別子』とは『言語仕様』や『処理系』などで使用するために予約されている識別子のことです。
C++ では以下の3つのいずれかを満たす名前が『予約済み識別子』として予約されています。
- グローバルスコープを持ち、
_
で始まる名前 _
で始まり、その次が大文字の名前__
を含む名前
例えば、__LINE__
みたいな定義済みマクロの名前であったり、処理系依存であれば msvc の _MSC_VER
定義済みマクロがそれにあたります。
要は『__
を含む名前とかはコンパイラやライブラリの実装で使う可能性があるからユーザは使わないでねー』というためのものですね。
ですので、以下のようなコードは未定義の動作になります。
// インクルードガードなどで名前の先頭に _ をつけてるのでダメ #ifndef _INCLUDED_PATH_TO_ #define _INCLUDED_PATH_TO_ #endif // _ から始まってる名前なのでダメ typedef struct _X { // __ が含まれてる名前なのでダメ int value__; } X;
インクルードガードのマクロ名に『_
から始まってる名前』を使ってるコードをよく見かけますが、これも厳密にいえば未定義の動作になります。
[まとめ]
実際のところ予約済み識別子を使ったところでそこまで問題になる可能性は低いと思います。
しかし、わざわざ未定義の動作になる可能性があるコードを書く必要もないので、C++ を書くときは注意するとよいと思います。
言語によっては _value
みたいに『先頭に _
をつける事』を推奨してることもありますが、C++ では基本的に避けるべきです。
ですので、C++ では以下のように『名前の末尾に _
をつけること』が多いです。
// だめ auto _value = 42; // おっけー auto value_ = 42;
こんな感じで基本的には名前の先頭に _
をつけるのを避けるように気をつければ問題ないと思います。
以上で、予約済み識別子の説明は終了です。
予約済み識別子警察には気をつけましょう。
[余談]
Vim を使ってる人は以下のプラグインをいれてみるといいと思います。
[参照]
RSpec のマッチャで === を使う
通常 eq
を使用すると ==
が使われるんですが、これを ===
で比較したかった。
このように任意の演算子でテストしたい場合は次のように書ける。
expect(9).to be > 6 expect(3).to be <= 3 expect(1).to be < 6 expect('a').to be < 'b' expect(String).to be === "hoge"
これは知らなかったので便利。
[参照]
【初心者C++er Advent Calendar 2016 8日目】関数テンプレートと関数オブジェクト
タイトルは釣りです。
初心者C++er Advent Calendar 2016 8日目の記事になります。
8日が空いていたので簡単なものを書きます。
前回の記事を書いたあとに
(省略されたtemplateのが気になった)
— Sd2E 002/100 (@chiwawa_star) 2016年12月6日
と、言われたので今回はその捕捉になります。
[関数テンプレート]
前回『残念ながら関数の引数型で auto を使用して型推論を行うことは出来ません』と書きました。
確かに auto
を使用して型推論を行うことは出来ませんが、template
を使用して型推論を行うことは出来ます。
template<typename A, typename B> auto plus(A a, B b){ return a + b; } plus(1, 2); // int + int = int plus(1, 3.14f); // int + float = float plus(std::string("homu"), std::string("mami")); // std::string + std::string = std::string
こんな感じに auto
ではなくて template
を使用して引数型を推論する形になります。
基本的な挙動はラムダ式と同じになります。
[関数テンプレートの問題点]
これだけじゃ味気ないのでもうちょっと突っ込んで関数テンプレートの場合の問題点を上げます。
さて、次のように std::for_each
とラムダ式を利用して値を出力することが出来ます。
auto output = [](auto x){ std::cout << x << std::endl; }; std::vector<int> xs = {1, 2, 3, 4, 5}; std::for_each(std::begin(xs), std::end(xs), output);
しかし、これを関数テンプレートで行おうとするとエラーになります。
template<typename T> void output(T t){ std::cout << t << std::endl; } std::vector<int> xs = {1, 2, 3, 4, 5}; // error: no matching function for call to 'for_each' std::for_each(std::begin(xs), std::end(xs), output);
これは関数テンプレートを値として使用する場合に『テンプレートの型を明示する』必要があるためです。
ですので、関数などに関数テンプレートを渡す場合は次のようにテンプレートの型を明示する必要があります。
template<typename T> void output(T t){ std::cout << t << std::endl; } std::vector<int> xs = {1, 2, 3, 4, 5}; // output<int> を明示する std::for_each(std::begin(xs), std::end(xs), output<int>);
これで解決します。
[関数オブジェクトを使用する]
さて、関数テンプレートのテンプレート型を明示化することで解決しましたが、毎回型を記述するのは手間です。
そこで、関数ではなくてクラスを利用して回避します。
// operator () を定義したクラスを定義する struct output_{ template<typename T> void operator ()(T t) const{ std::cout << t << std::endl; } }; // そのクラスのオブジェクトを用意する output_ const output; std::vector<int> xs = {1, 2, 3, 4, 5}; // あたかも関数のように振る舞うオブジェクト std::for_each(std::begin(xs), std::end(xs), output);
関数ではなくてメンバ関数として定義することで型を明示化しなくてもよいようになります。
また、このように operator ()
を定義して関数のように振る舞うオブジェクトのことを一般的に関数オブジェクトと呼びます。
[まとめ]
よく『template
は難しい』みたいなことを聞きますが、やってることは単なる型推論なだけなので auto
を理解してるのであれば template
に置き換えるだけなのでそんなに難しくはないと思います。
auto
にしてもそうですが template
を使用することでどんどん型による依存を減らして行くことが可能になります。
【初心者C++er Advent Calendar 2016 3日目】C++ で型推論(auto)しよう
初心者C++er Advent Calendar 2016 3日目の記事です。
3日目が空いてるようなので書いてみました。
今回は初心者向けの Advent Calendar ということで軽めの記事になります。
また、本記事では C++14 を基準としたコードになります。
[C++ で型を書く場面]
さて、C++ は静的型付言語なので、いろんな場面で int
や float
、std::string
などと言った型を書くことが多いです。
// 関数の戻り値型や引数型 int func(std::string, float); // 変数の宣言時に型を指定する int a = func(std::string("homu"), 3.14f);
今回はこれらの型を auto
を使用して省略できる場面を紹介したいと思います。
[変数の宣言時に auto
を使う]
変数の宣言時に auto
を使用することで右辺値から型推論をしてコンパイラが自動的に型を割り当てます。
// 代入式の右辺値である 42 からコンパイラが型を推論して自動的に型を割り当てる // 42 は int 型なので n は int 型になる auto n = 42; // f は float 型である auto f = 3.14f; std::vector<int> v; // it は std::vector<int>::iterator 型になる auto it = std::begin(v);
このように本来は
std::vector<int>::iterator it = std::begin(v);
というように型が長くなってしまう場合、auto
を利用することで
auto it = std::begin(v);
という風に簡略化することが出来ます。
また、当然ですがこの場合は『変数の初期値』を指定する必要があります。
[戻り値型を auto
にする]
関数の戻り値型を auto
にすることで return
文などからコンパイラが自動的に戻り値型を推論します。
// この関数の戻り値型は int 型 + float 型の結果(float 型)になる auto plus(int a, float b){ return a + b; } // 複数の return をする場合は『同一の型』を return する必要がある auto func(int flag){ if( flag ){ return std::string("OK"); } else { // コンパイル時に型を決定する必要があるので違う型を return することは出来ない // return -1 return std::string("NG"); } } // 戻り値を省略した場合は void 型になる auto hello_world(){ std::cout << "hello, world" << std::endl; // こっちでもおk // return; }
このように戻り値型に auto
を使用することが出来ます。
注意点としては必ず『同じ型の値を return する』ということですね。
また、残念ながら関数の引数型で auto
を使用して型推論を行うことは出来ません。
[ラムダ式の引数で auto
を使用する]
先ほど関数の引数では auto
は使えないと書きましたが、実はラムダ式の引数であれば auto
を使用する事が出来ます。
// 引数型を auto にすることでどんな型でも受け取ることができる auto output = [](auto a) { std::cout << a << std::endl; }; output(42); output("homu"); // std::vector<int> も渡すことはできるが、std::cout に対応してないのでエラー // output(std::vector<int>{}); // 戻り値型の auto と組み合わせて使用することもできる // この場合、戻り値型は引数型に依存する auto plus = [](auto a, auto b) -> auto { return a + b; }; plus(1, 2); // int + int = int plus(1, 3.14f); // int + float = float plus(std::string("homu"), std::string("mami")); // std::string + std::string = std::string
ラムダ式の場合、引数型によって戻り値型が変わるというのが面白いですね。
C++ では
[まとめ]
C++ では複雑な型を扱うことが多いのですが、このように auto
を使用することで冗長なコードをすっきり記述できる場面が多いです。
また、今回は使用しませんでしたが template を使用することで更に型を省略することも出来ます。
ただ、C++ では『型について理解することも重要』なので初心者の方はまず最初に『どんな型として扱わるのか』と考えてみましょう。
この関数の戻り値型は何か、式を演算した結果何型を返すのか、暗黙の型変換とは etc...と C++ で型について考えることは尽きません。
その上で型に慣れてきたら auto
を使ってみるとよいと思います。
そんな感じでざっくりとして内容ですが 初心者C++er Advent Calendar 2016 3日目の記事になります。
[参照]
【Ruby Advent Calendar 2016 1日目】 Ruby でブロックを簡潔に定義する
Ruby Advent Calendar 2016の1日目の記事になります。
わたしはあまり Rails と言ったフレームワークや Ruby がよく利用されてる Web 界隈には詳しくはないので、本記事は Ruby という言語に関するネタになります。
[Ruby といえば]
さて、表題のブロックですが、Ruby といえばブロック、ブロックといえば Ruby というぐらい Ruby という言語を語る上では切っては切れない機能の一つになります。
特に #map
や #select
などと言ったリスト操作とブロックの組み合わせは Ruby を書く上での醍醐味と言っても過言ではないと思います。
[ブロックの不満点]
そんな Ruby では多用されるブロックですが、コードを書く上で少し不満点があります。
例えば次のようなコードをみてみましょう。
["foo", "bar", "baz"].map { |it| it.upcase } #=> ["FOO", "BAR", "BAZ"]
コード自体は特に問題はないのですが、ブロックで引数を受け取る関係上 it
を2回も記述する必要があります。
こちらでも書かれていますが、Ruby を書いていると『こういう簡単なブロック』を書くことが多くあり無駄に感じます。
なにより簡潔ではありません。
あと単純にブロック自体書くのがめんどくさいです。
[Symbol#to_proc
を利用する]
ここで Ruby に精通する人はピンと来ると思いますが、上記のブロックのように『レシーバのメソッドを呼び出すだけ』のブロックは次のように Symbol#to_proc
を利用して書き換えることが出来ます。
# 第一引数の #upcase を呼び出すブロックとして処理される ["foo", "bar", "baz"].map &:upcase #=> ["FOO", "BAR", "BAZ"]
おお、これなら簡潔に記述することができるじゃないか!
と、思いますが、残念ながらこのテクニックは次のような場合に利用出来ません。
# 引数メソッドに引数を渡す場合 ["0x12", "0x34", "0x56"].map { |it| it.to_i(16) } # => [18, 52, 86] # 呼び出すメソッドをチェーンする場合 [:foo, :bar, :baz].map { |it| it.to_s.upcase } # => ["FOO", "BAR", "BAZ"]
この場合は Symbol#to_proc
を使用して簡潔に定義することはできませんね。
NOTE: instance method Symbol#to_proc (Ruby 2.3.0)
生成される Proc オブジェクトを呼びだす(Proc#call)と、 Proc#callの第一引数をレシーバとして、 self という名前のメソッドを 残りの引数を渡して呼びだします。
[lambda_driver ではどうか]
ブロックを簡潔に定義するといえば lambda_driver という有名な gem が存在します。
lambda_driver についてはこちらに詳しく書かれています。
lambda_driver は Symbol
と複数の記号を組み合わせることで関数合成などを抽象化して定義することができます。
# [:foo, :bar, :baz].map { |it| it.to_s.upcase } [:foo, :bar, :baz].map(&:to_s >> :upcase ) # => ["FOO", "BAR", "BAZ"]
これは確かに便利だとは思います。
しかし、全く知らない人から見ると式が抽象化され過ぎてて難読化するのがやや気になります。
例えば、次のようなコードは記号が多用されており、パッと見なにをやっているのかわかりづらいです。
# [:foo, :hoge, :bar, :fuga].select{|s| s.to_s.length > 3} [:foo, :hoge, :bar, :fuga].select(&:to_s >> :length >> 3._(:<)) # => [:hoge, :fuga]
以上を踏まえて、Ruby で簡潔にブロックを定義する方法を考えていきたいと思います。
[Ruby 以外の言語に目を向ける]
もっと簡潔に定義できる方法はないか考えてみましょう。
こういう時は Ruby 以外の言語を参考にしてみるのもひとつの手です。
そんなわけで今回は C++ にちょっと目を向けてみます。
Ruby を書いてる人にはちょっと馴染みがないかも知れませんが、C++ に Boost というメジャーなライブラリがあります。
この Boost はいくつかのライブラリが 1つにまとまったものになるのですが、その中に Boost.Lambda というライブラリがあります。
Boost.Lambda とはその名の通り『C++ で簡潔にラムダ式を定義する』ために考えだされたライブラリです。
使い方自体はそんなに難しくなくて、簡単に説明するとプレースホルダというものを引数に置き換えるような形でラムダ式を定義します。
// _1 や _2 などは該当する引数の順番に置き換えられる auto plus3 = _1 + 3; plus3(2); // => 5 auto sum = _1 + _2 + _3; sum(1, 2, 3); // => 6 auto is_even = _1 % 2 == 0; is_even(2); // true is_even(-3); // false
NOTE: 無名関数 - boostjp
C++ をよく知らないという人でもなんとなく使い方のイメージはできると思います。
と、いうことで Ruby でもこんな感じでブロックを定義してみよう!というのが今回の本題です。
[プレースホルダをつくる]
さて、まずはプレースホルダを定義していきたいと思います。
今回定義するプレースホルダは以下のような性質をもたせます。
# n 番目の引数の値を返す _1.(1, 2, 3) # => 1 _2.(1, 2, 3) # => 2 _3.(1, 2, 3) # => 3
これを以下のように実装していきます。
class Lazy def initialize &block @block = block end def call *args, &block @block.call *args, &block end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 # n 番目の引数の値を返す _1.(1, 2, 3) # => 1 _2.(1, 2, 3) # => 2 _3.(1, 2, 3) # => 3
Lazy#call
が呼ばれたら Lazy.new
時に渡しがブロックが呼び出される構造になります。
今回は Proc
クラスを拡張したくなかったので Lazy
というクラスで薄くラップして拡張していきます。
[メソッドの呼び出しを遅延評価する]
次にプレースホルダから遅延してメソッドを呼びだせるように #__send_delay__
というメソッドを定義します。
イメージとしては以下のような感じです。
# 第一引数に対して #upcase を呼び出す to_upcase = _1.__send_delay__(:upcase) # このタイミングで引数に対して #upcase が呼ばれる to_upcase.("homu") # => "HOMU" to_hex = _1.__send_delay__(:to_s, 16) to_hex.(42) # => "2a"
このように『あとで #call
が呼び出された時』に指定したメソッドが評価されるようにします。
class Lazy def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block # Lazy オブジェクトを返す Lazy.new { |*args_, &block_| call(*args_, &block_).__send__(name, *args, &block) } end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 # 第一引数に対して #upcase を呼び出す to_upcase = _1.__send_delay__(:upcase) to_upcase.("homu") # => "HOMU" to_hex = _1.__send_delay__(:to_s, 16) to_hex.(42) # => "2a"
ここで肝なのが『#__send_delay__
は Lazy
オブジェクトを返す』ところです。
これにより『メソッドをチェーンして』遅延評価することが出来ます。
to_upcase_hex = _1.__send_delay__(:to_s, 16).__send_delay__(:upcase) to_upcase_hex.(20161201) # => "133A2B1"
いい感じです。
ただ、毎回 #__send_delay__
を呼び出すのはやや冗長ですね。
[method_missing
を経由して #__send_delay__
を呼び出す]
さて、こんな時はメタプログラミングの出番です。
Ruby には『レシーバに存在しないメソッドを呼び出した時に処理をフックする』ことが出来ます。
この時に定義するメソッドが #method_missing
になります。
これを利用することで #__send_delay__
を経由することなく、自然にメソッドの遅延処理を定義することが出来ます。
NOTE: instance method BasicObject#method_missing (Ruby 2.3.0)
class Lazy def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block ::Lazy.new { |*args_, &block_| call(*args_, &block_).__send__(name, *args, &block) } end # method_missing 経由で #__send_delay__ を呼び出す def method_missing name, *args, &block __send_delay__ name, *args, &block end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 # プレースホルダをレシーバとして直接メソッドを呼び出すことができる to_upcase = _1.upcase to_upcase.("homu") # => "HOMU" to_upcase_hex = _1.to_s(16).upcase to_upcase_hex.(20161201) # => "133A2B1"
こんな感じで『プレースホルダをレシーバとして直接メソッドを呼び出す』ことができるようになりました。
かなり自然にメソッドの呼び出しを書けるようになりましたね。
そして Ruby では『演算子もメソッド』であるため、以下のように演算子も定義することも出来ます。
# _1.+(3) と同等 plus3 = _1 + 3 plus3.(5) # => 8
NOTE: クラス/メソッドの定義 (Ruby 2.3.0)
これは素晴らしい。
ちなみに #method_missing
を利用したかったのが『Proc
クラスを拡張しなかった』理由の一つです。
[Object
で定義されているメソッドは #method_missing
で呼び出されない]
さて、これにより『Lazy
クラスで定義されていないメソッド』は #method_missing
を利用することで簡潔に呼び出す事ができるようになりました。
しかし、 通常 Ruby ではクラスを定義した場合は内部で Object
クラスが自動的に継承されるため、デフォルトでも多数のメソッドが定義されています。
NOTE: class Object (Ruby 2.3.0)
全てのクラスのスーパークラス。 オブジェクトの一般的な振舞いを定義します。
ですので次のように『Object
クラスで定義されてるメソッド』は #method_missing
経由で呼び出すことは出来ません。
# 第一引数の #class を遅延評価して欲しいが Lazy#class が呼び出されてしまう _1.class # => Lazy
[BasicObject
を継承して空のクラスを定義する]
今回は Object
クラスで定義されてる『多数のメソッドも遅延評価したい』ので Lazy
オブジェクトでは定義してほしくありません。
このような場合は BasicObject
を継承することで解決することが出来ます。
NOTE: class BasicObject (Ruby 2.3.0)
Object クラスは様々な便利なメソッドや Kernel から受け継いだ関数的メソッド を多数有しています。 これに対して、 BasicObject クラスはオブジェクトの同一性を識別したりメソッドを呼んだりする 最低限の機能の他は一切の機能を持っていません。
明示的に BasicObject
クラスを継承することで、最低限のメソッドのみが定義されているクラスを定義することが出来ます。
# BasicObject を継承することで最低限のメソッドのみ定義するようにする class Lazy < BasicObject def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block ::Lazy.new { |*args_, &block_| call(*args_, &block_).__send__(name, *args, &block) } end def method_missing name, *args, &block __send_delay__ name, *args, &block end # BasicObject では #== が定義されているので、Lazy の性質に合わせて再定義する def == *args, &block __send_delay__ :==, *args, &block end def ! *args, &block __send_delay__ :!, *args, &block end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 to_class = _1.class to_class.("homu") # => String to_class.([]) # => Array
また、この時に注意するのが #==
など一部のメソッドは BasicObject
でも定義されているので用途に合わせて再定義する必要があります。
NOTE: instance method BasicObject#== (Ruby 2.3.0)
このメソッドは各クラスの性質に合わせて、サブクラスで再定義するべきです。 多くの場合、オブジェクトの内容が等しければ真を返すように (同値性を判定するように) 再定義 することが期待されています。
このあたりのテクニックは #method_missing
を使用する際は覚えておくとよいです。
[引数にプレースホルダを渡す]
さて、次に『遅延評価するメソッドの引数にプレースホルダを渡す』ことを考えてみましょう。
以下のようにメソッドの引数にもプレースホルダを渡したいですよね。
# #to_s の引数は第二引数で受け取るようにする to_s = _1.to_s(_2) to_s.(42, 2) # => "101010"
ですので、Lazy
オブジェクトを『再帰的に評価する』ように修正します。
class Lazy < BasicObject def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block ::Lazy.new { |*args_, &block_| # 式を組み立てるときに渡された引数が Lazy オブジェクトであれば評価する apples = args.map { |it| ::Lazy === it ? it.call(*args_, &block_) : it } call(*args_, &block_).__send__(name, *apples, &block) } end def method_missing name, *args, &block __send_delay__ name, *args, &block end def == *args, &block __send_delay__ :==, *args, &block end def ! *args, &block __send_delay__ :!, *args, &block end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 to_s = _1.to_s(_2) to_s.(42, 2) # => "101010" to_s.(42, 16) # => "2a"
こんな感じです。
ちょっとわかりづらいかもしれませんが、要は『#to_s
の引数が Lazy
だったら先に評価する』ようにしてるだけですね。
これにより次のような演算子を使った式も簡潔に定義できます。
expr = _1 + _2 * _3 expr.(1, 2, 3) # => 7
実にわかりやすいですね。
[プレースホルダの弱点]
この便利そうなプレースホルダですが、1つだけ弱点があります。
それは『プレースホルダをレシーバとして呼び出す』必要があることです。
どういうことかというと例えば、
_1 + "homu"
という風に式を記述することは出来ますが、
"homu" + _1
という風に左辺値にプレースホルダ以外を置いた場合、Lazy
オブジェクトとして定義することは出来ません。
なぜなら #+
は String
クラスで定義されており、Lazy
クラスは干渉出来ないからです。
これを解決したい場合、Ruby では String#+
を拡張するという手段はあるんですが、型のない言語で『特定のクラスに対する処理』というのはあまりやりたくありません。
[Object#to_lazy
を定義する]
そこで今回は Object#to_lazy
というヘルパメソッドを定義して解決したいと思います。
class Lazy < BasicObject def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block ::Lazy.new { |*args_, &block_| apples = args.map { |it| ::Lazy === it ? it.call(*args_, &block_) : it } call(*args_, &block_).__send__(name, *apples, &block) } end def method_missing name, *args, &block __send_delay__ name, *args, &block end def == *args, &block __send_delay__ :==, *args, &block end def ! *args, &block __send_delay__ :!, *args, &block end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 class Object # 自身を Lazy のオブジェクトとして返す def to_lazy Lazy.new { self } end end # 任意のオブジェクトのメソッドを遅延評価できる homu_plus = "homu".to_lazy + _1 p homu_plus.("mami") # => "homumami" ary = [1, 2, 3] add = ary.to_lazy << _1 add.(1) add.(2) add.(3) p ary # => [1, 2, 3, 1, 2, 3]
Object#to_lazy
は単に『自身を返す Lazy
オブジェクト』を返すだけです。
これで任意のメソッドを遅延評価することができるようになりました。
[ブロックに渡せるように #to_proc
を定義する]
最後にブロックに渡せるように #to_proc
を定義します。
Ruby では #to_proc
を定義することでそのオブジェクトをブロックに渡せるようになります。
class Lazy < BasicObject def initialize &block @block = block end def call *args, &block @block.call *args, &block end def __send_delay__ name, *args, &block ::Lazy.new { |*args_, &block_| apples = args.map { |it| ::Lazy === it ? it.call(*args_, &block_) : it } call(*args_, &block_).__send__(name, *apples, &block) } end def method_missing name, *args, &block __send_delay__ name, *args, &block end def == *args, &block __send_delay__ :==, *args, &block end def ! *args, &block __send_delay__ :!, *args, &block end # #to_proc を定義することにより Lazy オブジェクトをブロック引数として渡せる def to_proc ::Proc.new { |*args, &block| call *args, &block } end end def placeholder index Lazy.new { |*args| args[index] } end _1 = placeholder 0 _2 = placeholder 1 _3 = placeholder 2 class Object def to_lazy Lazy.new { self } end end # ブロックに直接 Lazy オブジェクトを渡せる p (1..10).map &_1.to_s(2) # => ["1", "10", "11", "100", "101", "110", "111", "1000", "1001", "1010"] # こんな感じで定義することもできる evens = _1.select(&_1 % 2 == 0) p evens.(1..10) # => [2, 4, 6, 8, 10] # Kernel のメソッドも遅延評価できる (1..10).each &to_lazy.puts(_1)
これで一応完成になります。
[他のコードと比較してみる]
lambda_driver
などと比較してみるとこんな感じです。
# default [:foo, :bar, :baz].map { |s| s.to_s.upcase } # or [:foo, :bar, :baz].map(&:to_s).map(&:upcase) # => ["FOO", "BAR", "BAZ"] # lambda_driver [:foo, :bar, :baz].map(&:to_s >> :upcase) # placeholder [:foo, :bar, :baz].map(&_1.to_s.upcase) # default [:foo, :hoge, :bar, :fuga].select { |s| s.to_s.length > 3 } # => [:hoge, :fuga] # lambda_driver [:foo, :hoge, :bar, :fuga].select(&:to_s >> :length >> 3._(:<)) # placeholder [:foo, :hoge, :bar, :fuga].select(&_1.to_s.length > 3) # default def twice n n + n end puts twice(65).to_s.length # lambda_driver _.twice >> :to_s >> :length >> _.puts < 65 # lambda_driver に合わせて to_lazy を短くする alias _ to_lazy _.puts(_.twice(_1).to_s.length).(65)
プレースホルダを使用することでかなり簡潔にブロックを定義できるようになったと思います。
[唯一の弱点]
さて、ほぼやりたいことはできたんですが、1つだけどうしても出来ないことがあります。
それは &&
演算子と ||
演算子の遅延評価です。
例えば、
cond = _1 >= 5 && _1 <= 10
みたいな式を定義したい場合、これでは上手く動作しません。
これは +
や []
演算子などはメソッドとして扱われますが、&&
や ||
は Ruby 本体の制御構造として扱われる為です。
Ruby では &&
や ||
などの演算子の再定義も禁止されています。
つまり上記の場合は &&
の左辺値(5 <= _1
) が真になるため、cond
には 5 <= _1
が代入されます。
この性質は Ruby の言語仕様上の問題なので現状はどうすることも出来ません。
NOTE: 演算子式 (Ruby 2.3.0)
[まとめ]
そんな感じでいい感じにブロックを簡潔に定義できるようになりました。
なんか1日目からガッツリと書いてしまって重くないか心配です。
最初にも書きましたが Ruby ではどうしてもブロックを多用する言語なので、そのブロックが簡潔に定義できることはかなり便利です。
またこういう『他の言語から影響を受けて実装する』というのはなんか他の言語のいいとこ取りをしている感じがしているのでいいですね、どんどんパクっていきましょう。
特に Ruby は柔軟な言語なのでメタプログラミングでサクッと実現できることが多いので書いていて楽しいです。
Ruby といえば Rails というイメージですが、Ruby 単体でも十分に面白い言語なのでみんな使ってみるといいです。Ruby = Rails と思ってるやつしねばいいのに
もうすぐ Ruby 2.4 もリリースらしいですし、将来的に Ruby に型システムを導入するみたいな話出ていますし、今後は他の言語と比べてどういう風に進化していくのかが楽しみですね。
ちなみに今回実装したコードは iolite
というライブラリで gem 化してます。
使い方などは基本的に同じですが、つくったのが結構前なのでだいぶレガシーなコードで味わい深いです。
興味がある人は example やここなどみてみると面白いです。
そんな感じで2日目に続きます。
Ruby の Hash でキーが存在しなかった時の初期値を設定する
table = [] table[index] ||= [] table[index] << name
みたいに []
で初期化するようなコードは
table = Hash.new { |hash, key| hash[key] = [] } table[index] << name
という風に .new
に渡すブロック内で値を初期化することができる。