The Problem
Given the following method definition:
def tag(name, **attr)
end
Calling the method crashes if :name
key appears in the **attr
argument. For instance, the following fails:
# @link https://play.crystal-lang.org/#/r/89f9
tag "meta", name: "some-name", content: "some-content"
# => Error: argument 'name' already specified
Understandably, the above method call is the same as:
tag name: "meta", name: "some-name", content: "some-content"
Hence, the error is that :name
key is passed more than once.
To avoid this, an external name can be used in the method definition, thus:
def tag(ext name, **attr)
end
After which this compiles OK:
tag "meta", name: "some-name", content: "some-content"
But the problem recurs if :ext
key appears in the **attr
argument:
# @link https://play.crystal-lang.org/#/r/89fc
tag "meta", ext: "some-name", content: "some-content"
# => Error: argument 'ext' already specified
The Hack
My current solution is to use an external name which is less likely to be used in the **attr
argument in a method call.
I chose a double underscore, and it works OK. (Single underscore fails with Error: unexpected token: UNDERSCORE
)
def tag(__ name, **attr)
end
The problem with this is that if you introduce another parameter, you are forced to use something like a triple underscore as an external name, since you cannot duplicate the double underscore.
This doesn’t work:
# @link https://play.crystal-lang.org/#/r/89ep
def tag(__ name, __ other, **attr)
end
# => Error: duplicated argument external name: __
This works:
# Note that `other` uses a *triple* underscore
# as an external name
def tag(__ name, ___ other, **attr)
end
The Question
Is there a way to specify in a method definition that a parameter name should never be allowed to be passed as a named argument in a method call?
If the answer is “No”…
The Proposal
It would be great, I think, to have a way to disallow a parameter’s name from being passed as a named argument.
I’m aware single underscores are used in other cases when you really do not need to use a variable name (I’m not sure what this is called: a throwaway variable?). For example:
a, _ = {1, 2}
puts a # => 1
I would propose a single underscore, when used as a method’s external name, should prevent both the parameter’s name and it’s external name (the single underscore) from being passed as a named parameter.
This also means the single underscore should be allowed to be used more than once. Something like this:
def tag(_ name, _ other, **attr)
end
# This should error
tag name: "meta", other: "hello", a: "a", b: "b"
# This should error
tag _: "meta", _: "hello", a: "a", b: "b"
# This should work
tag "meta", "hello", a: "a", b: "b"
I would appreciate help, alternatives and feedback.
Happy holidays, Crystalites!