[ruby-core:109745] [Ruby master Feature#18959] Handle gracefully nil kwargs eg. **nil
From:
"austin (Austin Ziegler)" <noreply@...>
Date:
2022-08-28 05:15:28 UTC
List:
ruby-core #109745
Issue #18959 has been updated by austin (Austin Ziegler).
LevLukomskyi (Lev Lukomskyi) wrote in #note-22:
> > I also tend to do `**(options || {})` and I don’t write hard-to-read code like `**({id: id, name: name} if id.present?)`.
> That's interesting, that you are ok for
> ```ruby
> **(options || {})
> ```
> but not ok for
> ```ruby
> **(options if condition)
> ```
> which is kind of the same..
I disagree that it’s the same, but more to the point, I mistyped: I tend **not** to do `**(options || {})`. I disagree that it’s the same because putting an `if` expression *in a parameter position* is less immediately readable than `||` or a ternary.
> > I’d write that as:
> >
> > ```ruby
> > def add_email_to_list(email:, tracking_info: {})
> > EmailSubscriber.find_by(email: email) || EmailSubscriber.new(email: email, **tracking_info)
> > end
> > ```
>
> This does not solve the issue, the method still can be called with `nil` eg.:
> ```ruby
> add_email_to_list(email: 'email@example.com', tracking_info: nil)
> ```
>
> and it'll raise the error, so you still need that boilerplate code `**(tracking_info || {})`
No, you don’t. You need to fail when someone passes you garbage (and `nil` is a garbage value in this case), or you need to ensure better default values (e.g., `{}`) at your call sites. I‘m not sure that `**nil` converting to `{}` is sensible behaviour, given that—especially since `**` uses implicit conversion (`#to_hash`) instead of explicit conversion (`#to_h`).
> > the example you’ve given is completely unconvincing because it is suboptimal, has unnecessary allocations, and is more reminiscent of what I’d see from Perl in 1998 than Ruby of 2022.
> that's true, it has an additional allocation if condition is satisfied, although it's not critical in most cases. Also, it could be probably optimized by interpreter in the future, similar to how `[3, 5].max` is optimized.
I suspect that it can’t be optimized easily, because arbitrarily complex expressions can be specified after `if` or `unless`.
> > 1. If we want to allow `**nil`, the argument should be made to switch `**` from calling `#to_hash` to calling `#to_h`, because that would give you `**nil` _for free_. That would require digging a bit deeper to determine why `**` calls `#to_hash` instead of `#to_h`.
>
> Agree, `to_hash` method is weird, and it just adds confusion to people about why there are both `to_hash` and `to_h`, and which one they should use. I think `to_hash` should be deprecated and later removed.
It’s present of the same reason that `#to_ary` and `#to_a` are present. `#to_hash` implies "this object *is* a hash", whereas `#to_h` says "this object can be converted to a hash".
> BTW the issue with `**nil` can be also fixed by this code:
>
> ```ruby
> class NilClass; alias to_hash to_h end
> ```
That introduces other problems because of implicit hash coercion.
> > 2. `Hash#merge` and `Hash#update` could be modified to ignore `nil` arguments (which would permit your use-case).
>
> Agree, doing nothing on `.merge(nil)` would be good. It will remove redundant allocations like
> ```ruby
> hash.merge(other_hash_param || {})
> ```
>
> Although I guess it's a more radical change..
I think that it’s *generally* a better change for the examples you have provided than `**nil` support.
> One note though, your example:
>
> ```ruby
> { some: 'value' }.merge({ id: id, name: name } if id.present?)
> ```
>
> won't work as you expect, it raises _SyntaxError: unexpected ')', expecting end-of-input_
>
> The valid way would be:
> ```ruby
> { some: 'value' }.update(({ id: id, name: name } if id.present?))
> ```
>
> which looks not so nice..
Fair, but I’ll note that I was adapting your example. I would *never* write code where I use an `if` inline. If we extended the concept for `#merge` to ignore `false` or `nil` values, then we could write it as
```ruby
{some: 'value'}.update(id.present? && {id: id, name: name})
```
I’m still not fond of the pattern, and think that an explicit call to `Hash#delete_if` to remove defaulted values is clearer and more intentional, but whatever.
I *don’t* think that, in general, `**nil` is a good pattern to permit. There’s an easy fix to it by using `#to_h` instead of `#to_hash`, if it turns out to be a good pattern. More than that, I think that we need a *better* use case to demonstrate its need than has been shown to date, because the patterns that have been presented so far are (IMO) unconvincing.
----------------------------------------
Feature #18959: Handle gracefully nil kwargs eg. **nil
https://bugs.ruby-lang.org/issues/18959#change-98982
* Author: LevLukomskyi (Lev Lukomskyi)
* Status: Open
* Priority: Normal
----------------------------------------
The issue:
```ruby
def qwe(a: 1) end
qwe(**nil) #=> fails with `no implicit conversion of nil into Hash (TypeError)` error
{ a:1, **nil } #=> fails with `no implicit conversion of nil into Hash (TypeError)` error
```
Reasoning:
I found myself that I often want to insert a key/value to hash if a certain condition is met, and it's very convenient to do this inside hash syntax, eg.:
```ruby
{
some: 'value',
**({ id: id } if id.present?),
}
```
Such syntax is much more readable than:
```ruby
h = { some: 'value' }
h[:id] = id if id.present?
h
```
Yes, it's possible to write like this:
```ruby
{
some: 'value',
**(id.present? ? { id: id } : {}),
}
```
but it adds unnecessary boilerplate noise.
I enjoy writing something like this in ruby on rails:
```ruby
content_tag :div, class: [*('is-hero' if hero), *('is-search-page' if search_page)].presence
```
If no conditions are met then the array is empty, then converted to nil by `presence`, and `class` attribute is not rendered if it's nil. It's short and so convenient! There should be a similar way for hashes!
I found this issue here: https://bugs.ruby-lang.org/issues/8507 where "consistency" thing is discussed. While consistency is the right thing to do, I think the main point here is to have fun with programming, and being able to write stuff in a concise and readable way.
Please, add this small feature to the language, that'd be so wonderful! 🙏
--
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>