[#105450] [Ruby master Feature#18228] Add a `timeout` option to `IO.copy_stream` — "byroot (Jean Boussier)" <noreply@...>
SXNzdWUgIzE4MjI4IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGJ5cm9vdCAoSmVhbiBCb3Vzc2llciku
11 messages
2021/09/27
[ruby-core:105297] [Ruby master Feature#18136] take_while_after
From:
"zverok (Victor Shepelev)" <noreply@...>
Date:
2021-09-16 12:15:58 UTC
List:
ruby-core #105297
Issue #18136 has been updated by zverok (Victor Shepelev).
Description updated
@knu
> feels to me like `drop_after`
Hmm, this actually sounds like a good name.
> I want to see use cases without `lazy`.
Of course.
The first case of my initial ticket (take tokens till the token that ends sentence)
```ruby
sentence = tokens.take_while_after { !_1[:ends_sentence] }
# or...
sentence = tokens.drop_after { _1[:ends_sentence] }
```
Here is a code from my other project, navigating some tree (think HTML DOM) and gathering the sequence of parents up to matching one:
```ruby
class Node
def route_to_parent(selector)
Enumerator.produce(parent, &parent).take_while_after { _1 && !_1.match?(selector) }
end
end
# E.g. some_link.route_to_parent('div#section') # => span, p, div#subsection, div#section
```
Dates till next Monday, included (this "included" is the gist of what I am proposing!):
```ruby
Enumerator.produce(Date.today, &:succ).take_while_after{ !_1.monday? }.each { schedule_job(_1) }
# Of course, this can be replaced with take_while { !_1.tuesday? }, but in reality the code was more like
...take_while_after { !is_first_monday_of_next_month?(_1) }
# ...which is hard to convert to take_while
```
One of the examples from #14781 initially had `#take_while`, but it was a bug :)
```ruby
require 'strscan'
scanner = StringScanner.new('7+38/6')
p Enumerator.produce { scanner.scan(%r{\d+|[-+*/]}) }.slice_after { scanner.eos? }.first
# => ["7", "+", "38", "/", "6"]
# I'd actually prefer
p Enumerator.produce { scanner.scan(%r{\d+|[-+*/]}) }.take_while_after { !scanner.eos? }
# ...or, even
p Enumerator.produce { scanner.scan(%r{\d+|[-+*/]}) }.drop_after { scanner.eos? }
```
> The listed use cases are not so clearly convincing. For the Example 2, "can_continue" field often contains a URL to receive the subsequent results (like `"can_continue": "http://api.example.com/foo?since=TIMESTAMP"`). In this case, this proposal is not usable.
I am not sure about the value of this argument. Yes, some APIs work this way. Some work the way I describe. If my proposal can't cover 100% of APIs in the world, it is not usable?.. Or, should I just come with a list of real-life APIs that provide `continue: true` or something along the lines, otherwise nobody believes they exist?..
> Matz said that `take_upto` is a better name than `take_while_after`
Unfortunately, the `upto`, if shorter, clashes with other usages of `upto` (`1.upto(20)`, which implies counting).
**I think `drop_after` (with condition inverted regarding my initial proposal) seems to be quite a good alternative.**
----------------------------------------
Feature #18136: take_while_after
https://bugs.ruby-lang.org/issues/18136#change-93706
* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
----------------------------------------
Sorry, I already tried that once (#16441) but I failed to produce the persuasive example.
So I am back with a couple of them, much simpler and clear than my initial.
**The proposal itself:** Have `take_while_after` which behaves like `take_while` but also includes the last element (first where the condition failed). Reason: there are a lot of cases where "the last good item" in enumeration is the distinctive one (one where enumeration should stop, but the item is still good.
**Example 1:** Take pages from paginated API, the last page will have less items than the rest (and that's how we know it is the last):
```ruby
(0..).lazy
.map { |offset| get_page(offset, limit) }
.take_while_after { |response| response.count == limit } # the last will have, say, 10 items, but should still be included!
.map { process response somehow }
```
**Example 2:** Same as above, but "we should continue pagination" is specified with a separate data key "can_continue":
```ruby
(0..).lazy
.map { |offset| get_page(offset, limit) }
.take_while_after { |response| response['can_continue'] } # the last will have can_continue=false, but still has data
.map { process response somehow }
```
**Exampe 3:** Taking a sentence from a list of tokens like this:
```ruby
tokens = [
{text: 'Ruby', type: :word},
{text: 'is', type: :word},
{text: 'cool', type: :word},
{text: '.', type: :punctuation, ends_sentence: true},
{text: 'Rust', type: :word},
# ...
]
sentence = tokens.take_while_after { !_1[:ends_sentence] }
```
(I can get more if it is necessary!)
Neither of those can be solved by "Using `take_while` with proper condition.", as @matz suggested here: https://bugs.ruby-lang.org/issues/16441#note-9
I typically solve it by `slice_after { condition }.first`, but that's a) uglier and b) greedy when we are working with lazy enumerator (so for API examples, all paginated pages would be fetched at once, and only then processed).
Another consideration in #16441 was an unfortunate naming.
I am leaving it to discussion, though I tend to like `#take_upto` from #16446.
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:[email protected]?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>