From: "austin (Austin Ziegler)" Date: 2022-08-28T05:15:28+00:00 Subject: [ruby-core:109745] [Ruby master Feature#18959] Handle gracefully nil kwargs eg. **nil 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: