ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:95104] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
@ 2019-09-26 13:01 ` zverok.offline
  2019-09-26 15:52 ` [ruby-core:95114] " sawadatsuyoshi
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 7+ messages in thread
From: zverok.offline @ 2019-09-26 13:01 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been reported by zverok (Victor Shepelev).

----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95114] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
  2019-09-26 13:01 ` [ruby-core:95104] [Ruby master Feature#16183] Hash#with_default zverok.offline
@ 2019-09-26 15:52 ` sawadatsuyoshi
  2019-09-26 16:36 ` [ruby-core:95115] " shevegen
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 7+ messages in thread
From: sawadatsuyoshi @ 2019-09-26 15:52 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by sawa (Tsuyoshi Sawada).


Duplicate of #11761

----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81748

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95115] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
  2019-09-26 13:01 ` [ruby-core:95104] [Ruby master Feature#16183] Hash#with_default zverok.offline
  2019-09-26 15:52 ` [ruby-core:95114] " sawadatsuyoshi
@ 2019-09-26 16:36 ` shevegen
  2019-09-26 19:23 ` [ruby-core:95117] " zverok.offline
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 7+ messages in thread
From: shevegen @ 2019-09-26 16:36 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by shevegen (Robert A. Heiler).


sawa has it all covered. ;)

The explanation by matz is interesting.

"Use tap. Methods with side-effect should be handled with care. Making it chainable
has little benefit."

Personally I think other names, be these default_set, `Hash#default_proc_set or
with_default, do not completely correlate towards #tap, in my opinion; but I think
the problem is of special methods that may each have special meaning in different
parts of ruby. So from this point of view, unifying via .tap is simpler.

On the other hand, "tap" itself, at the least to me, conveys a slightly different
meaning than #with_default does, so I am not sure the two use cases overlap 100%.

Not sure if this would warrant the addition of a new syntax + idiom, and I am not
really pro/con either, so that could be discussed.

The complexity of method chains should be considered too, though. This may be
an individual's style, but these huge chains may impose a cognitive load to 
some ruby users possibly.

----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81749

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95117] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
                   ` (2 preceding siblings ...)
  2019-09-26 16:36 ` [ruby-core:95115] " shevegen
@ 2019-09-26 19:23 ` zverok.offline
  2019-09-26 21:49 ` [ruby-core:95120] " eregontp
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 7+ messages in thread
From: zverok.offline @ 2019-09-26 19:23 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by zverok (Victor Shepelev).


> Duplicate of #11761

I don't think it is a duplicate, though I haven't clarified an important behavior indeed:

```ruby
h = {a: 1, b: 2}
h2 = h.with_default(3)
h.default # => nil
h2.default # => 3
```
So, I propose side-effect-less method, that **is** acceptable for chaining, I believe.

----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81751

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95120] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
                   ` (3 preceding siblings ...)
  2019-09-26 19:23 ` [ruby-core:95117] " zverok.offline
@ 2019-09-26 21:49 ` eregontp
  2019-09-27  6:02 ` [ruby-core:95125] " zverok.offline
  2019-09-27  8:35 ` [ruby-core:95127] " duerst
  6 siblings, 0 replies; 7+ messages in thread
From: eregontp @ 2019-09-26 21:49 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by Eregon (Benoit Daloze).


zverok (Victor Shepelev) wrote:
> So, I propose side-effect-less method, that **is** acceptable for chaining, I believe.

That means a copy of the Hash is necessary on each call to #with_default.
I.e., it would be the same as:
```ruby
h = {a: 1, b: 2}
h2 = h.dup.tap { _1.default = 3 }
```

I think it's better to keep a potentially expensive copy of the Hash explicit with the `.dup` call + `default=`.
But I agree there is some beauty to change the default value in a safe, non-mutable way with a single method call.


----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81754

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95125] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
                   ` (4 preceding siblings ...)
  2019-09-26 21:49 ` [ruby-core:95120] " eregontp
@ 2019-09-27  6:02 ` zverok.offline
  2019-09-27  8:35 ` [ruby-core:95127] " duerst
  6 siblings, 0 replies; 7+ messages in thread
From: zverok.offline @ 2019-09-27  6:02 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by zverok (Victor Shepelev).


> That means a copy of the Hash is necessary on each call to `#with_default`.

Yes, the same way it is for, say, `merge`, and we still use it in a lot of cases even when source hash would be dropped -- for the sake of chainability:

```ruby
FONTS = {body: 'Tahoma'}.merge(OS_FONTS.fetch(current_os)).merge(Settings.custom_fonts).freeze
```
↑ one may argue that it is tragically ineffective, but for the cases like this we just ignore it.

So I believe it is reasonable that something like:
```ruby
def render(settings, values)
  render_widget(settings[:type], settings[:name], values.with_default('<empty>'))
end
```
I believe from this code it is obvious that `values` parameter is not altered (and so call site wouldn't be surprised), and passed further is its copy with some reasonable default value that makes sense for UI.

For a lot of features, we have both chainable (copying) and non-chainable (destructive, inplace) version, why not for one of the Hash's usefulest features?..

----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81760

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [ruby-core:95127] [Ruby master Feature#16183] Hash#with_default
       [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
                   ` (5 preceding siblings ...)
  2019-09-27  6:02 ` [ruby-core:95125] " zverok.offline
@ 2019-09-27  8:35 ` duerst
  6 siblings, 0 replies; 7+ messages in thread
From: duerst @ 2019-09-27  8:35 UTC (permalink / raw)
  To: ruby-core

Issue #16183 has been updated by duerst (Martin Dürst).


zverok (Victor Shepelev) wrote:
> Eregon (Benoit Daloze) wrote:
> > That means a copy of the Hash is necessary on each call to `#with_default`.
> 
> Yes, the same way it is for, say, `merge`, and we still use it in a lot of cases even when source hash would be dropped -- for the sake of chainability:

Well, yes, but at least in my view, `merge` is a two-sided (symmetric) operation. There are two hashes, and you merge them. It would be strange if one of them is changed, but not the other. The fact that Ruby, because it's object-oriented, uses one of the hashes as a receiver is in first approximation just a syntax issue. Of course there are cases where `merge` is used in a asymmetric way (your examples are all of this nature), but that's not the original nature of `merge`.

`with_default`, on the other hand, similar to the current methods that set a default, is *by nature* asymmetric. Also, it's really rare (if such examples exist at all) that a new hash is needed because there's both a version with a default and a version without a default. So adding the default in place and not making a copy seems to be the natural thing to do.

I think that when you go through Ruby's builtin classes and standard library, there are many case that can easily explained in similar terms.


----------------------------------------
Feature #16183: Hash#with_default
https://bugs.ruby-lang.org/issues/16183#change-81762

* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Reasons: there is no way, currently, to *declaratively* define Hash with default value (for example, to store it in constant, or use in an expression). Which leads to code more or less like this:


```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.tap { |h| h.default = 'Courier' }.freeze

# Grouping indexes:
ary.each_with_object(Hash.new { |h, k| h[k] = [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

With proposed method:
```ruby
FONTS = {
  title: 'Arial',
  body: 'Times New Roman',
  blockquote: 'Tahoma'
}.with_default('Courier').freeze

ary.each_with_object({}.with_default { [] }).with_index { |(el, h), idx| h[el.downcase] << idx }
```

About the block synopsys: I am not 100% sure, but I believe that _most_ of the time when `default_proc` provided, it looks like `{ |h, k| h[k] = some_calculation }`. So, I believe for this "declarative simplification" of defaults, it is acceptable to assume it as the only behavior (pass only key to block, and always store block's result); more flexible form would still be accessible with `Hash.new`.



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2019-09-27  8:35 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <redmine.issue-16183.20190926130122@ruby-lang.org>
2019-09-26 13:01 ` [ruby-core:95104] [Ruby master Feature#16183] Hash#with_default zverok.offline
2019-09-26 15:52 ` [ruby-core:95114] " sawadatsuyoshi
2019-09-26 16:36 ` [ruby-core:95115] " shevegen
2019-09-26 19:23 ` [ruby-core:95117] " zverok.offline
2019-09-26 21:49 ` [ruby-core:95120] " eregontp
2019-09-27  6:02 ` [ruby-core:95125] " zverok.offline
2019-09-27  8:35 ` [ruby-core:95127] " duerst

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).