Feature #4653

[PATCH 1/1] new method Enumerable#rude_map

Added by Shyouhei Urabe about 4 years ago. Updated over 3 years ago.

[ruby-dev:43476]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto

Description

From e6dd7bd9b5769bae5d81416da7a2b4003a43ba06 Mon Sep 17 00:00:00 2001
Message-Id: e6dd7bd9b5769bae5d81416da7a2b4003a43ba06.1304863013.git.shyouhei@ruby-lang.org
From: URABE, Shyouhei shyouhei@ruby-lang.org
Date: Sun, 8 May 2011 22:51:28 +0900
Subject: [PATCH 1/1] new method Enumerable#rude_map

時としてmapが配列を返さない方がいいのにと思うことがあります。

例としてはlzmaで圧縮されており解凍するととても大きくなるテキストファ
イルがあったとして、それを解凍して行番号をふってからHTML escapeして
ではさんでから連結したものをlzmaで圧縮しなおす必要があっ
たとします(注:実話)。ここで「行番号をふって」と「HTML escapeして」を
素直に考えると、lzcatをpopenしたものに対して
io.each_line.map.with_index.map. ... とかいう構造がRubyとしては自然
かと思います。が、mapが配列を作ってしまうというのがメモリ消費量的に
よくありません。できればEnumeratorですっきりと処理したいところです。

というわけでmapなんだけど配列じゃなくてEnumeratorを返すmapの変種があ
るといいとおもうのですがどうでしょうか。1.9にはもうflat_mapがあるの
でなんとか_mapが増える心理的抵抗は少ないかなと思うので新規メソッドに
してみました。いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

Signed-off-by: URABE, Shyouhei shyouhei@ruby-lang.org

diff --git a/enum.c b/enum.c
index 584b838..449406b 100644
--- a/enum.c
+++ b/enum.c
@@ -462,6 +462,41 @@ enum_flat_map(VALUE obj)
return ary;
}

+static VALUE
+rude_map_ii(VALUE i, VALUE y, int c, VALUE v)
+{
+ return rb_funcall(y, rb_intern("<<"), 1, enum_yield(c, v));
+}
+
+static VALUE
+rude_map_i(VALUE y, VALUE i, int c, VALUE *v)
+{
+ return rb_block_call(i, id_each, 0, 0, rude_map_ii, y);
+}
+
+/

+ * call-seq:
+ * enum.rude_map {| obj | block } -> enumerator
+ * enum.rude_map -> enumerator
+ *
+ * Identical to Enumerable#map, except that it returns an enumerator
+ * rather than an array.
+ *
+ * Without a block it is just another Object#to_enum.
+ *
+ /
+
+static VALUE
+enum_rude_map(VALUE obj)
+{
+ VALUE ret;
+ RETURN_ENUMERATOR(obj, 0, 0);
+
+ ret = rb_obj_alloc(rb_cEnumerator);
+ rb_block_call(ret, rb_intern("initialize"), 0, 0, rude_map_i, obj);
+ return ret;
+}
+
/

* call-seq:
* enum.to_a -> array
@@ -2679,6 +2714,7 @@ Init_Enumerable(void)
rb_define_method(rb_mEnumerable, "collect", enum_collect, 0);
rb_define_method(rb_mEnumerable, "map", enum_collect, 0);
rb_define_method(rb_mEnumerable, "flat_map", enum_flat_map, 0);
+ rb_define_method(rb_mEnumerable, "rude_map", enum_rude_map, 0);
rb_define_method(rb_mEnumerable, "collect_concat", enum_flat_map, 0);
rb_define_method(rb_mEnumerable, "inject", enum_inject, -1);

rb_define_method(rb_mEnumerable, "reduce", enum_inject, -1);

1.7.0.4


Related issues

Related to Ruby trunk - Feature #708: Lazy Enumerator#select, Enumerator#map etc. Rejected 11/03/2008
Related to Ruby trunk - Feature #4890: Enumerable#lazy Closed 06/16/2011

History

#1 Updated by Shyouhei Urabe about 4 years ago

  • Status changed from Open to Assigned

#2 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月8日23:01 Shyouhei Urabe shyouhei@ruby-lang.org:

というわけでmapなんだけど配列じゃなくてEnumeratorを返すmapの変種があ
るといいとおもうのですがどうでしょうか。

賛成です。以下のようなコードを書くときいつも気になってました。

big_ary.map {|x| ... }.max

ただ、map だけでなく、select や take にも適用できる convension
を決めるべきだと思います。

「遅延評価」の英語は、普通は lazy か delay だと思います。
rude というのを聞いたことがありません。ただいずれにしても 5 文字
以上の追加は長すぎるように思います。

遠藤はかつて -er (-or) の接尾辞を提案しました (つまり mapper) 。
http://d.hatena.ne.jp/ku-ma-me/20091111/p2

以下の方は lz の接尾辞を提案されています。
https://github.com/antimon2/enumerable
lz/tree/master/lib/enumerable_lz

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

--
Yusuke Endoh mame@tsg.ne.jp

#3 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月8日23:01 Shyouhei Urabe shyouhei@ruby-lang.org:

というわけでmapなんだけど配列じゃなくてEnumeratorを返すmapの変種があ
るといいとおもうのですがどうでしょうか。

賛成です。以下のようなコードを書くときいつも気になってました。

big_ary.map {|x| ... }.max

ただ、map だけでなく、select や take にも適用できる convension
を決めるべきだと思います。

「遅延評価」の英語は、普通は lazy か delay だと思います。
rude というのを聞いたことがありません。ただいずれにしても 5 文字
以上の追加は長すぎるように思います。

遠藤はかつて -er (-or) の接尾辞を提案しました (つまり mapper) 。
http://d.hatena.ne.jp/ku-ma-me/20091111/p2

以下の方は lz の接尾辞を提案されています。
https://github.com/antimon2/enumerable
lz/tree/master/lib/enumerable_lz

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

--
Yusuke Endoh mame@tsg.ne.jp

#4 Updated by Shyouhei Urabe about 4 years ago

(05/08/2011 11:21 PM), Yusuke ENDOH wrote:

遠藤です。

2011年5月8日23:01 Shyouhei Urabe shyouhei@ruby-lang.org:

というわけでmapなんだけど配列じゃなくてEnumeratorを返すmapの変種があ
るといいとおもうのですがどうでしょうか。

賛成です。以下のようなコードを書くときいつも気になってました。

big_ary.map {|x| ... }.max

たぶん def _map; Enumerator.new {|y| each {|i| y << yield(i) } }; end
はみんな一回は書いたことがあると思うので、需要はほぼ問題ないかと思います

ただ、map だけでなく、select や take にも適用できる convension
を決めるべきだと思います。

まあ、そうなればいいのは間違いないですけども、話を大きくしすぎるのも発散して終
了するいつものパターンになりそうな気もするので。堅実に攻めたいと思います。

「遅延評価」の英語は、普通は lazy か delay だと思います。
rude というのを聞いたことがありません。ただいずれにしても 5 文字
以上の追加は長すぎるように思います。

遅延評価であるとみなせるのは事実としても遅延評価っていう言い方をしなきゃいけな
いことはないですよね。特に私の用途の場合は途中で配列を作らないというだけの話で
あって、結局全部評価するわけですし。

あとまあ、とりあえず名前がしっくり来ないときは長めの名前にしといて後から短いや
つを考えるというのは定石かと。べつにどうしてもrudeじゃなきゃいかんという信念が
あるわけではないです。

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

やっぱmapがいきなり変わるのは堅実ではないので、実装としては今回提案したものを
推していきたい(+名前変更とか)ですね。

#5 Updated by Shyouhei Urabe about 4 years ago

(05/08/2011 11:21 PM), Yusuke ENDOH wrote:

遠藤です。

2011年5月8日23:01 Shyouhei Urabe shyouhei@ruby-lang.org:

というわけでmapなんだけど配列じゃなくてEnumeratorを返すmapの変種があ
るといいとおもうのですがどうでしょうか。

賛成です。以下のようなコードを書くときいつも気になってました。

big_ary.map {|x| ... }.max

たぶん def _map; Enumerator.new {|y| each {|i| y << yield(i) } }; end
はみんな一回は書いたことがあると思うので、需要はほぼ問題ないかと思います

ただ、map だけでなく、select や take にも適用できる convension
を決めるべきだと思います。

まあ、そうなればいいのは間違いないですけども、話を大きくしすぎるのも発散して終
了するいつものパターンになりそうな気もするので。堅実に攻めたいと思います。

「遅延評価」の英語は、普通は lazy か delay だと思います。
rude というのを聞いたことがありません。ただいずれにしても 5 文字
以上の追加は長すぎるように思います。

遅延評価であるとみなせるのは事実としても遅延評価っていう言い方をしなきゃいけな
いことはないですよね。特に私の用途の場合は途中で配列を作らないというだけの話で
あって、結局全部評価するわけですし。

あとまあ、とりあえず名前がしっくり来ないときは長めの名前にしといて後から短いや
つを考えるというのは定石かと。べつにどうしてもrudeじゃなきゃいかんという信念が
あるわけではないです。

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

やっぱmapがいきなり変わるのは堅実ではないので、実装としては今回提案したものを
推していきたい(+名前変更とか)ですね。

#6 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月9日0:07 Urabe Shyouhei shyouhei@ruby-lang.org:

遅延評価であるとみなせるのは事実としても遅延評価っていう言い方をしなきゃいけな
いことはないですよね。

絶対にいけないことはないですが、一般的な言葉があるならなるべく合わせる
べきでしょう。

特に私の用途の場合は途中で配列を作らないというだけの話で
あって、結局全部評価するわけですし。

中間データ生成の省略は遅延評価のありがたみの 1 つです。
「結局全部評価する」かどうかは関係ないと思います。

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

2.0 なら selector namespace (というか refinement) が入っているはず、
とか。
互換性が問題なら、Enumerator の #[] とかを呼び出したら勝手に Array に
化ける、とか。

とはいえとりあえず 1.9 の間で凌ぐ術がほしい気持ちもわかるので、うーん、
-er がダメなら接尾辞 L はどうでしょう。mapL とか selectL とか。
2.0 までに投げ捨てたくなる名前、という意味では rude_ もいいのかなあ。

--
Yusuke Endoh mame@tsg.ne.jp

#7 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月9日0:07 Urabe Shyouhei shyouhei@ruby-lang.org:

遅延評価であるとみなせるのは事実としても遅延評価っていう言い方をしなきゃいけな
いことはないですよね。

絶対にいけないことはないですが、一般的な言葉があるならなるべく合わせる
べきでしょう。

特に私の用途の場合は途中で配列を作らないというだけの話で
あって、結局全部評価するわけですし。

中間データ生成の省略は遅延評価のありがたみの 1 つです。
「結局全部評価する」かどうかは関係ないと思います。

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

2.0 なら selector namespace (というか refinement) が入っているはず、
とか。
互換性が問題なら、Enumerator の #[] とかを呼び出したら勝手に Array に
化ける、とか。

とはいえとりあえず 1.9 の間で凌ぐ術がほしい気持ちもわかるので、うーん、
-er がダメなら接尾辞 L はどうでしょう。mapL とか selectL とか。
2.0 までに投げ捨てたくなる名前、という意味では rude_ もいいのかなあ。

--
Yusuke Endoh mame@tsg.ne.jp

#8 Updated by Shyouhei Urabe about 4 years ago

卜部です。

(05/09/2011 12:35 AM), Yusuke ENDOH wrote:

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

2.0 なら selector namespace (というか refinement) が入っているはず、
とか。
互換性が問題なら、Enumerator の #[] とかを呼び出したら勝手に Array に
化ける、とか。

とはいえとりあえず 1.9 の間で凌ぐ術がほしい気持ちもわかるので、うーん、
-er がダメなら接尾辞 L はどうでしょう。mapL とか selectL とか。
2.0 までに投げ捨てたくなる名前、という意味では rude_ もいいのかなあ。

さすがにキャメルケースはないんじゃないですかねえ(何がないって、前例が...)。個
人的にはmapperはそんなにダメじゃないとおもうんですけど、まあまつもとさんがダ
メって言ってる以上はめがないですよね。

でまあやはりflat_mapがある以上なんとかmapがいちばん通りやすいと思うんですけど
どうですかね。lazy
mapとか。

#9 Updated by Shyouhei Urabe about 4 years ago

卜部です。

(05/09/2011 12:35 AM), Yusuke ENDOH wrote:

いきなりmapの戻り値の型が変わるのでも私はいいですけど
ちょっとやりすぎかとも思います。

2.0 でなら、これもいいと思います。

mathnみたいにrequireすると挙動が上書きされて、いやでもそれは状態がグローバルだ
からよくなくてselector namespaceがないと、でもそれはやっぱ話が大きくなりすぎだ
なあ。

2.0 なら selector namespace (というか refinement) が入っているはず、
とか。
互換性が問題なら、Enumerator の #[] とかを呼び出したら勝手に Array に
化ける、とか。

とはいえとりあえず 1.9 の間で凌ぐ術がほしい気持ちもわかるので、うーん、
-er がダメなら接尾辞 L はどうでしょう。mapL とか selectL とか。
2.0 までに投げ捨てたくなる名前、という意味では rude_ もいいのかなあ。

さすがにキャメルケースはないんじゃないですかねえ(何がないって、前例が...)。個
人的にはmapperはそんなにダメじゃないとおもうんですけど、まあまつもとさんがダ
メって言ってる以上はめがないですよね。

でまあやはりflat_mapがある以上なんとかmapがいちばん通りやすいと思うんですけど
どうですかね。lazy
mapとか。

#10 Updated by Shyouhei Urabe about 4 years ago

(05/09/2011 05:21 PM), Akinori MUSHA wrote:

At Mon, 9 May 2011 16:35:31 +0900,
Urabe Shyouhei wrote:

でまあやはりflat_mapがある以上なんとかmapがいちばん通りやすいと思うんですけど
どうですかね。lazy
mapとか。

ベタ路線なら map_enum がいいと思います。動作を表す語が複数並んで
いたら、返り値は最後の語から判断するものだと思うからです。

rudeやlazyは動作なのかという疑問(形容詞ですからねえ)をさておくとすると、それな
りに説得力があるように思いました。

#11 Updated by Shyouhei Urabe about 4 years ago

(05/09/2011 05:21 PM), Akinori MUSHA wrote:

At Mon, 9 May 2011 16:35:31 +0900,
Urabe Shyouhei wrote:

でまあやはりflat_mapがある以上なんとかmapがいちばん通りやすいと思うんですけど
どうですかね。lazy
mapとか。

ベタ路線なら map_enum がいいと思います。動作を表す語が複数並んで
いたら、返り値は最後の語から判断するものだと思うからです。

rudeやlazyは動作なのかという疑問(形容詞ですからねえ)をさておくとすると、それな
りに説得力があるように思いました。

#12 Updated by Shota Fukumori about 4 years ago

共通なconvensionを用意すると発散すると書かれていたのにやってしまいましたが、

Enumerator#deferを定義してこのような形にすると良いのではと思いました。

個人的に見栄えはこれが一番しっくりくるなあ、と思います。

[1,2,3].map.defer{|x| x*2 }.each_with_index.map.defer do |x,i|
"#{x}: #{i}"
end.each {|x| puts x }

いかがでしょうか。

パッチ: https://gist.github.com/964510

#13 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月10日23:09 Shota Fukumori sorah@tubusu.net:

共通なconvensionを用意すると発散すると書かれていたのにやってしまいましたが、

Enumerator#deferを定義してこのような形にすると良いのではと思いました。

見栄えはいいですが、残念ながら共通の convension にはなりません。

$ ./ruby -e 'p [1, 2, 3].select {|x| x.even? }'
[2]

$ ./ruby -e 'p [1, 2, 3].select.defer {|x| x.even? }.to_a'
[false, true, false]

この問題は、map やら select やらが Array を返すこと前提で、内部的に
rb_ary_push を呼んでしまっているため、中間データ生成を回避しようが
ないということです。なので mapL 、selectL というように各メソッドごとに
対応していかざるを得ません。

Enumerator から Array を得るのは to_a 一発なので、デフォルトで
Array ではなく Enumerator を返してくれれば解決なんですが。
互換性以外は。。

--
Yusuke Endoh mame@tsg.ne.jp

#14 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月10日23:09 Shota Fukumori sorah@tubusu.net:

共通なconvensionを用意すると発散すると書かれていたのにやってしまいましたが、

Enumerator#deferを定義してこのような形にすると良いのではと思いました。

見栄えはいいですが、残念ながら共通の convension にはなりません。

$ ./ruby -e 'p [1, 2, 3].select {|x| x.even? }'
[2]

$ ./ruby -e 'p [1, 2, 3].select.defer {|x| x.even? }.to_a'
[false, true, false]

この問題は、map やら select やらが Array を返すこと前提で、内部的に
rb_ary_push を呼んでしまっているため、中間データ生成を回避しようが
ないということです。なので mapL 、selectL というように各メソッドごとに
対応していかざるを得ません。

Enumerator から Array を得るのは to_a 一発なので、デフォルトで
Array ではなく Enumerator を返してくれれば解決なんですが。
互換性以外は。。

--
Yusuke Endoh mame@tsg.ne.jp

#15 Updated by Shota Fukumori about 4 years ago

2011/5/11 Yusuke ENDOH mame@tsg.ne.jp:

この問題は、map やら select やらが Array を返すこと前提で、内部的に
rb_ary_push を呼んでしまっているため、中間データ生成を回避しようが
ないということです。なので mapL 、selectL というように各メソッドごとに
対応していかざるを得ません。
たしかに。実装みたらそんな感じでした… 残念。
# 別のクラスを作って、defer.map{|x|...}とかto_lazy.map{|x|...}とかかなあ。

こういうインターフェースでスパッと出来る方が見栄えはいいんだけどなあ…

--
Shota Fukumori a.k.a. @sora_h - http://codnote.net/

#16 Updated by Shota Fukumori about 4 years ago

2011/5/11 Yusuke ENDOH mame@tsg.ne.jp:

この問題は、map やら select やらが Array を返すこと前提で、内部的に
rb_ary_push を呼んでしまっているため、中間データ生成を回避しようが
ないということです。なので mapL 、selectL というように各メソッドごとに
対応していかざるを得ません。
たしかに。実装みたらそんな感じでした… 残念。
# 別のクラスを作って、defer.map{|x|...}とかto_lazy.map{|x|...}とかかなあ。

こういうインターフェースでスパッと出来る方が見栄えはいいんだけどなあ…

--
Shota Fukumori a.k.a. @sora_h - http://codnote.net/

#17 Updated by Shyouhei Urabe about 4 years ago

(05/10/2011 11:09 PM), Shota Fukumori wrote:

共通なconvensionを用意すると発散すると書かれていたのにやってしまいましたが、

まあそういうわけで見事に発散しているので。やっぱりとりあえずmapだけに注力した
ほうがいいと思うわけです。とりあえず実装はもう今のでいいんじゃないですかねえ。

名前に関しては今のところlazy_map案とmap_enum案がありますがどうでしょうか。個人
的にはlazy_enumが好きかなと思いますが。

#18 Updated by Shyouhei Urabe about 4 years ago

(05/10/2011 11:09 PM), Shota Fukumori wrote:

共通なconvensionを用意すると発散すると書かれていたのにやってしまいましたが、

まあそういうわけで見事に発散しているので。やっぱりとりあえずmapだけに注力した
ほうがいいと思うわけです。とりあえず実装はもう今のでいいんじゃないですかねえ。

名前に関しては今のところlazy_map案とmap_enum案がありますがどうでしょうか。個人
的にはlazy_enumが好きかなと思いますが。

#19 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月13日3:44 Urabe Shyouhei shyouhei@ruby-lang.org:

名前に関しては今のところlazy_map案とmap_enum案がありますがどうでしょうか。個人
的にはlazy_enumが好きかなと思いますが。

lazy_enum は lazy_map の typo ですよね?
どうしてもそこから選べと言われたら遠藤も lazy_map に一票です。

ちなみに、

http://d.hatena.ne.jp/ku-ma-me/20091111/p2#c1258760977

では matz は enum_map を提案していました。
さらになかださんは -ing という案をあげていました。
個人的には

mapper >= mapping >> mapL >= map_lz >> lazy_map > enum_map > map_enum

です。mapE 、map_e とかでもいいかも。

--
Yusuke Endoh mame@tsg.ne.jp

#20 Updated by Yusuke Endoh about 4 years ago

遠藤です。

2011年5月13日3:44 Urabe Shyouhei shyouhei@ruby-lang.org:

名前に関しては今のところlazy_map案とmap_enum案がありますがどうでしょうか。個人
的にはlazy_enumが好きかなと思いますが。

lazy_enum は lazy_map の typo ですよね?
どうしてもそこから選べと言われたら遠藤も lazy_map に一票です。

ちなみに、

http://d.hatena.ne.jp/ku-ma-me/20091111/p2#c1258760977

では matz は enum_map を提案していました。
さらになかださんは -ing という案をあげていました。
個人的には

mapper >= mapping >> mapL >= map_lz >> lazy_map > enum_map > map_enum

です。mapE 、map_e とかでもいいかも。

--
Yusuke Endoh mame@tsg.ne.jp

#21 Updated by Yui NARUSE almost 4 years ago

  • Target version changed from 1.9.3 to 2.0.0

#22 Updated by Yusuke Endoh over 3 years ago

  • Status changed from Assigned to Rejected

まつもとさんが #4890 のやり方がいいと言ったので (#708) 、
2/8 は Enumerable::Lazy 記念日。

Yusuke Endoh mame@tsg.ne.jp

Also available in: Atom PDF