Project

General

Profile

Feature #11660

a falsy value (similar to js undefined) that facilitates forwarding of default arguments

Added by bughit (bug hit) about 4 years ago. Updated about 1 month ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:95706]

Description

I'll call it "missing" here. Consider the following scenario:

def foo(default1 = some_expression)
end

def bar(default1 = some_expression)
  foo default1
end

def foobar(default1 = some_expression)
  bar default1
end 

if you had "missing":

def foo(default1 = some_expression)
end

def bar(default1 = missing)
  foo default1
end

def foobar(default1 = missing)
  bar default1
end 

missing passed as arg would be ignored (as if it wasn't passed at all)
and you wouldn't have to repeat the default value expression in every method

I believe that's how undefined works in js6 with respect to default args

History

Updated by bughit (bug hit) about 1 month ago

Compared to javascript default arg values are far less useful in ruby because of the lack of this forwarding capability.

This issue is 4 years old, can ruby core at least consider this?

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

Other than arity, it seems like you would want to use the new argument forwarding syntax:

def foo(default1 = some_expression)
end

def bar(...)
  foo(...)
end

def foobar(...)
  bar(...)
end 

The major issue with adding this support is what the value of this is:

def foobar(default1 = missing)
  default1
end 

Updated by bughit (bug hit) about 1 month ago

Other than arity, it seems like you would want to use the new argument forwarding syntax

I didn't know about new syntax, is that in 2.7? However, I don't think that's general enough. In javascript you can forward individual arguments to an arbitrary depth and the default value is computed once at the end.


function f1(a = defaultValueExpression) {
}

function f2({b = 1, c} = {}) {
  f1(c);
}

function f3(d, e) {
  f2({b: d, c: e});
}

f3(1)

// or

f3(1, undefined)


the missing/undefined second arg to f3 invocation flows all the way down to f1 where it triggers default value computation

You can't do this in ruby, to accomplish the equivalent you'd have to stop using intended default value expressions, forward nils, and then compute the default value in the body of the method.


def f1(a = nil)
  a = default_value_expression if a.nil?
end

def f2(b: 1, c: nil)
  f1(c)
end

def f3(d, e = nil)
  f2(b: d, c: e)
end

f3(1)

So in ruby the default value feature is close to being demoware. In practice, when composing methods, you end up reverting to the above.

The major issue with adding this support is what the value of this is

The value would be missing. Adding something like missing to the language would need to be coupled with a null coalescing operator which would cover both nil and missing (but not false). Something like ?? and ??=

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

bughit (bug hit) wrote:

The value would be missing. Adding something like missing to the language would need to be coupled with a null coalescing operator which would cover both nil and missing (but not false). Something like ?? and ??=

Adding a new keyword and operator seems like a high cost for at best a very minor benefit, in my opinion. I'm definitely against adding this.

Updated by bughit (bug hit) about 1 month ago

It's conceivable that it can be done without a keyword, there are other way to refer to special values.

However what do you have against a null coalescing operator? It's needed regardless of this issue, || and ||= are inadequate because of false.

Updated by alanwu (Alan Wu) about 1 month ago

Since the beginning of time (correct me if I'm wrong), the two falsy values in Ruby has been false and nil, period.
Adding another falsy value is a big change.

the missing/undefined second arg to f3 invocation flows all the way down to f1 where it triggers default value computation
You can't do this in ruby,

You can do it if all the methods in the chain use splat. In 2.7 ... also works:

def f3(foo = 500, bar)
  p [foo, bar]
end

def f2(*args)
  f3(*args)
end

def f1(*args)
  f2(*args)
end

f1(:bar) # [500, :bar]

https://wandbox.org/permlink/3xAq9B2wQk8odafG

Default args have been a feature for a long time, the fact there hasn't been
proposal similar to yours suggests to me that it's not a feature in high
demand.

Updated by decuplet (Nikita Shilnikov) about 1 month ago

For this purpose I built a special value a while ago. It has been working fine all this time. I don't think there's a need in adding such special-cased value to the language. It would only slightly improve the experience over a hand-crafted ten-lines-of-code solution. The whole story of passing arguments in Ruby is already complicated enough.

Updated by bughit (bug hit) about 1 month ago

For this purpose I built a special value a while ago

What purpose? Surely you understand that your special value, having no compiler support, does not trigger default value computations.

You are making my point. Your Undefined is not a real, intended default value, but a signal to compute the real default value in the body of the method after an explicit check for this signal.

This under-baked design of the default value feature is the point of this bug.

Updated by decuplet (Nikita Shilnikov) about 1 month ago

The "penalty" I pay for this is one line of code like period = Undefined.default(period, :day). Given this doesn't occur very often I don't see how "compiler support" would be beneficial.

Updated by Dan0042 (Daniel DeLorme) about 1 month ago

alanwu (Alan Wu) wrote:

Since the beginning of time (correct me if I'm wrong), the two falsy values in Ruby has been false and nil, period.
Adding another falsy value is a big change.

I Agree. Not saying there's no benefit, but this is too big a change for too small a benefit. Usually nil will play the role of the undefined value, so it might be nice if a method definition allowed something like def foo(v ||= 42). But nowadays we have keyword arguments which do exactly what is needed in this case.

def f2(x: 2)
  x
end

def f1(y:1, **kw)
  y + f2(**kw)
end

f1(y: 3) #=> 5

Updated by bughit (bug hit) about 1 month ago

The "penalty" I pay for this is one line of code

The penalty the language pays is it has a demoware, checklist item, default value feature, where in any non-trivial scenario you have to produce the default value twice, first the intermediate default value through the default value expression, then the real default value in the body. You are not doing ruby any favors by defending this "design".

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

bughit (bug hit) wrote:

The penalty the language pays is it has a demoware, checklist item, default value feature, where in any non-trivial scenario you have to produce the default value twice, first the intermediate default value through the default value expression, then the real default value in the body.

There are many non-trivial scenarios where you don't need to do that (or we can have a no-true-Scotsman argument about the definition of non-trivial scenario). Most method calls do not involve calling methods that delegate arguments to another method where both methods have the same optional argument with the same default value. In the cases where that happens, the general practice is just to duplicate the default value in both methods, which is not usually an issue as default values are usually simple values or method calls.

The only time you really want to have the "real default value" in the body is in fairly complex cases, and in those cases, the default argument values are usually set to nil. This is easy to understand and has worked well for Ruby for a long time. Ruby having a single nil, instead of JavaScript's null and undefined, is a net benefit, in my opinion.

You are not doing ruby any favors by defending this "design".

You are not doing Ruby any favors by making a mountain out of a molehill. The popularity of an idea is not always a proxy to its merit, but if nobody else thinks the benefits of a proposal exceed the costs, maybe it isn't worth implementing.

Updated by bughit (bug hit) about 1 month ago

Ruby having a single nil, instead of JavaScript's null and undefined, is a net benefit, in my opinion.

undefined signals the absence of a value and makes possible composable default value design, where missing arguments can be forwarded and still trigger default value computation, which is a clear win for javascript.

The popularity of an idea is not always a proxy to its merit, but if nobody else thinks the benefits of a proposal exceed the costs, maybe it isn't worth implementing.

Ok got it. Popularity does not always equal merit, but often it does and this must be one of those times, because of course it has no merit and it's unpopular. Ergo ipso facto, it has no merit.

Also available in: Atom PDF