From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, FORGED_GMAIL_RCVD,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_PASS shortcircuit=no autolearn=no autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id B30E61F4B5 for ; Wed, 20 Nov 2019 05:04:23 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 4B4581208DA; Wed, 20 Nov 2019 14:04:13 +0900 (JST) Received: from o1678948x4.outbound-mail.sendgrid.net (o1678948x4.outbound-mail.sendgrid.net [167.89.48.4]) by neon.ruby-lang.org (Postfix) with ESMTPS id 184EE1208B1 for ; Wed, 20 Nov 2019 14:04:10 +0900 (JST) Received: by filter0071p3iad2.sendgrid.net with SMTP id filter0071p3iad2-1749-5DD4C94D-18 2019-11-20 05:04:13.469815531 +0000 UTC m=+18632.164588788 Received: from herokuapp.com (unknown [3.89.145.218]) by ismtpd0069p1iad1.sendgrid.net (SG) with ESMTP id VXeYh8_XRa-daXibrPuDVg for ; Wed, 20 Nov 2019 05:04:13.389 +0000 (UTC) Date: Wed, 20 Nov 2019 05:04:13 +0000 (UTC) From: zverok.offline@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 71527 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 14423 X-Redmine-Issue-Author: zverok X-Redmine-Sender: zverok X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?3be0g8093pjUjT94eiCA64csFDBI=2FmHQTWm54P5gda6jsmFPHn21ynVHF5Js39?= =?us-ascii?Q?EcP3IiRbl1A6fZEw2fWh=2FekDBJHyiTsf=2FLol2S5?= =?us-ascii?Q?Z0NBmoMafd8MxQ7PAxu1Wp00jdIse0VaNapSqvp?= =?us-ascii?Q?Td0ZHU4GuNzEnOTYYawo6HwKPpMAqCB3G98t3tc?= =?us-ascii?Q?IC8DsSBosV+ISVMKAnJwujDIYKhH63NNJgQ=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 95893 Subject: [ruby-core:95893] [Ruby master Feature#14423] Enumerator from single object X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #14423 has been updated by zverok (Victor Shepelev). This one can be closed (`Enumerator#produce` was implemented in 2.7) ---------------------------------------- Feature #14423: Enumerator from single object https://bugs.ruby-lang.org/issues/14423#change-82732 * Author: zverok (Victor Shepelev) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- **UPD: Current proposal** Introduce method `Object#enumerate` for producing infinite enumerator by applying block to result of previous call. Reference implementation: ```ruby class Object def enumerate(&block) Enumerator.new { |y| val = self y << val loop do val = block.call(val) y << val end } end end ``` Possible usages: ```ruby # Most idiomatic "infinite sequence" possible: p 1.enumerate(&:succ).take(5) # => [1, 2, 3, 4, 5] # Easy Fibonacci p [0, 1].enumerate { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] # Enumerable pagination page.enumerate { |page| Faraday.get(page.next) if page.next }.take_while { |p| !p.nil? } ``` Reference to similar things: * Clojure [iterate](https://clojuredocs.org/clojure.core/iterate) "Returns a lazy sequence of `x`, `(f x)`, `(f (f x))` etc." No converging, just infinite sequence... And maybe that is even more basic and idiomatic. The name is nice, too. * WolframLang [FixedPoint](http://reference.wolfram.com/language/ref/FixedPoint.html) * Ramda [converge](http://ramdajs.com/docs/#converge) * Elixir [Stream#unfold](https://hexdocs.pm/elixir/Stream.html#unfold/2) (ends iteration when `nil` is returned) * Scala [Iterator#iterate](https://www.scala-lang.org/api/current/scala/collection/Iterator$.html#iterate%5BT%5D%28start%3AT%29%28f%3AT%3D%3ET%29%3AIterator%5BT%5D) (just infinite sequence) --- **Initial proposal** Sometimes (or rather often), there is a programming pattern of "start from one object, do something, look at the result, do the same, look at the result (and so on)". Examples: * fetch page by URL, if pagination present, fetch next page; * take 10 jobs from the queue, process them, exit when queue is empty; Typically, those are represented by `while` or `loop` + `break` which somehow feels "not functional enough", and even "not Ruby enough", so much less expressive than `map` and other `Enumerable`/`Enumerator`-based cycles. In some functional languages or libraries, there is function named `FixedPoint` or `converge`, whose meaning is "take an initial value, repeat the block provided on the result on prev computation, till it will not 'stable'". I believe this notion can be useful for Rubyists too. Reference implementation (name is disputable!): ```ruby class Object def converge(&block) Enumerator.new { |y| prev = self y << self loop do cur = block.call(prev) raise StopIteration if cur == prev y << cur prev = cur end } end end ``` Examples of usage: ```ruby # Functional kata: find the closest number to sqrt(2): 1.0.converge { |x| (x + 2 / x) / 2 }.to_a.last # => 1.414213562373095 Math.sqrt(2) # => 1.4142135623730951 # Next page situation: get(url).converge { |page| page.next } # => returns [page, page.next, page.next.next, ...til the result is nil, or same page repeated] # Job queue situation: queue.top(10).converge { |jobs| jobs.each(&:perform) queue.top(10) } # => takes top 10 jobs, till queue is empty (`[]` is returned two successful times) # Useful for non-converging situations, too: 2.converge { |x| x ** 2 }.take(4) # => [2, 4, 16, 256] # Idiomatic Fibonacci: [0, 1].converge { |f0, f1| [f1, f0 + f1] }.take(10).map(&:first) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] ``` Reference to similar things: * Clojure [iterate](https://clojuredocs.org/clojure.core/iterate) "Returns a lazy sequence of `x`, `(f x)`, `(f (f x))` etc." No converging, just infinite sequence... And maybe that is even more basic and idiomatic. The name is nice, too. * WolframLang [FixedPoint](http://reference.wolfram.com/language/ref/FixedPoint.html) * Ramda [converge](http://ramdajs.com/docs/#converge) * Elixir [Stream#unfold](https://hexdocs.pm/elixir/Stream.html#unfold/2) (ends iteration when `nil` is returned) * Scala [Iterator#iterate](https://www.scala-lang.org/api/current/scala/collection/Iterator$.html#iterate%5BT%5D%28start%3AT%29%28f%3AT%3D%3ET%29%3AIterator%5BT%5D) (just infinite sequence) Possible call-seq: * If converges: `Object#converge(&block)`, `Enumerator.converge(object, &block)`; * If just an infinite sequence: `Object#iterate(&block)`, `Object#deduce(&block)` (as opposed to `reduce`), `Enumerator.iterate(object, &block)`, `Enumerator#enumerate(object, &block)`. WDYT?.. PS: Can imagine somebody already proposed that, yet can't find nothing similar in the tracker for all keywords I've tried. -- https://bugs.ruby-lang.org/