[ruby-core:117067] [Ruby master Feature#20326] Add an `undefined` for use as a default argument.

Issue #20326 has been reported by shan (Shannon Skipper). ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326 * Author: shan (Shannon Skipper) * Status: Open ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by jeremyevans0 (Jeremy Evans). The problem with this is you could pass `undefined` just the same as `nil`. You would end up in a similar situation to JavaScript, which has both `null` and `undefined`. The correct way to handle this is by setting a local variable inside the default argument value: ```ruby class Array def count(item = (item_not_set = true)) if item_not_set # item argument was not passed else # item argument was passed end end end ``` Admittedly, this is a bit ugly, but the need to differentiate between passed default value and no supplied argument is fairly rare. Maybe there is a nicer way of supporting the same idea, that can still differentiate between passing the default value and not supplying an argument? ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107141 * Author: shan (Shannon Skipper) * Status: Open ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by nobu (Nobuyoshi Nakada). Any idea/feedback is welcome. ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107145 * Author: shan (Shannon Skipper) * Status: Feedback ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by mame (Yusuke Endoh). Personally, I don't like an API that distinguishes between "nil is passed" and "nothing is passed". I don't think we should introduce any feature to encourage such an API. When we want to conditionally pass the argument to such an API, we need to write `optional_val ? api(optional_val) : api()` or `args = [optional_val].compact; api(*args)` or something ugly. Instead, I hope such an API just accepts and ignores `nil`. It would be good enough in most cases. ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107147 * Author: shan (Shannon Skipper) * Status: Feedback ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by Dan0042 (Daniel DeLorme). mame (Yusuke Endoh) wrote in #note-4:
Personally, I don't like an API that distinguishes between "nil is passed" and "nothing is passed".
I fully agree. ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107150 * Author: shan (Shannon Skipper) * Status: Feedback ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by zverok (Victor Shepelev).
Personally, I don't like an API that distinguishes between "nil is passed" and "nothing is passed".
Unfortunately, such APIs are sometimes unavoidable, especially in implementing various generic algorithms (and not business logic). Take, for example, `Hash#fetch`. `fetch('key')` and `fetch('key', nil)` have different meanings. If such an API is to be implemented in Ruby, there are several options: * use the "value provided" guard like discussed in this ticket (`default = UNDEFINED` or `default = (default_not_set = true))`; * use `(*args)` and then check real arguments provided in the method body (by `.count` or some form of pattern matching). C implementations of the core methods do this constantly (either argument count check, or pattern matching-like `rb_scan_args`), demonstrating the necessity. Maybe having the default way to say "argument was not provided" will help to define cleaner APIs. Another common case is search algorithms, where the initial value for the `found` local variable is set to some sentinel value and checked after the algorithm (while having `found == nil` might be a legitimate case of "value is found, and it is `nil`"). Alternative, is, again, to have an additional boolean `is_found = false` and when the answer is found, set _two_ local vars. For these reasons, I think I saw a lot of codebases (including some written by me and my colleagues) going with some kind of `UNDEFINED`/`NOT_SET` constant, which feels like a kind-of hack, but still clearer than alternatives. That being said, I am not sure I am in favor of having `nil`/`undefined` distinction in Ruby, and never proposed it myself (while considering it several times). ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107151 * Author: shan (Shannon Skipper) * Status: Feedback ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/

Issue #20326 has been updated by shan (Shannon Skipper). zverok (Victor Shepelev) wrote in #note-6:
Unfortunately, such APIs are sometimes unavoidable, especially in implementing various generic algorithms (and not business logic).
I agree it's unfortunately unavoidable too often, so I resign myself to `default = UNDEFINED` or `default = default_not_set = true` like you mention above. zverok (Victor Shepelev) wrote in #note-6:
That being said, I am not sure I am in favor of having nil/undefined distinction in Ruby, and never proposed it myself (while considering it several times).
I'm not sold on `undefined` either, but would love some better way to detect default arguments that aren't set. Crimson on Ruby Discord suggested something similar to `block_given?` might be nice, like an `arg_given?(arg)`. ``` def fetch(item, default = nil) if arg_given?(:default) ``` ---------------------------------------- Feature #20326: Add an `undefined` for use as a default argument. https://bugs.ruby-lang.org/issues/20326#change-107158 * Author: shan (Shannon Skipper) * Status: Feedback ---------------------------------------- Variations around `UNDEFINED = Object.new` are a fairly common pattern to see used as default arguments to distinguish between `nil` and no argument provided. For example, a Ruby implementation of Array#count might look something like: ``` ruby class Array UNDEFINED = Object.new def UNDEFINED.inspect = 'UNDEFINED' UNDEFINED.freeze def count(item = UNDEFINED) if item == UNDEFINED # ... end end end ``` I'd like to propose adding an `undefined` module function method on Kernel to remove the boilerplate for this fairly common use case. An `__undefined__` method or `__UNDEFINED__` keyword would be alternatives to `undefined`. An `undefined?` helper would also be an optional nicety: ``` ruby class Array def count(item = undefined) if item.undefined? # ... end end end ``` A Ruby implementation might look like: ``` ruby module Kernel UNDEFINED = Object.new def UNDEFINED.inspect = -'undefined' UNDEFINED.freeze private_constant :UNDEFINED def undefined? = self == UNDEFINED module_function def undefined = UNDEFINED end ``` -- https://bugs.ruby-lang.org/
participants (6)
-
Dan0042 (Daniel DeLorme)
-
jeremyevans0 (Jeremy Evans)
-
mame (Yusuke Endoh)
-
nobu (Nobuyoshi Nakada)
-
shan (Shannon Skipper)
-
zverok (Victor Shepelev)