Bug #12521
closedSyntax for retrieving argument without removing it from double-splat catch-all
Description
There is an interesting style of programming that is almost really easy to do in Ruby. It would work elegantly with a simple change. A double-colon keyword argument should be available that will still leave the same argument captured by a double-splat argument if present. With this available, it becomes easy to pass down a "context" through a call chain.
Consider this:
def controller name::, **context … log_then_render something:, name:, **context end def log_then_render **context log context complex_logic_then_render **context end def complex_logic_then_render name::, **context … Bunch of further calls def render name::, something::, **context … end Now assume I decide render needs a foo argument, that I obtain in my controller. The only functions that are aware of or have any need for the argument are controller and render. With functions written in this style, I only need to modify the two functions that need to know about the argument:
def controller name::, **context … log_then_render something:, name:, foo: foo_value, **context end … no changes … def render name::, something::, foo:: **context … now use foo … end This is, I accept, unusual. I've not seen a language that offers this sort of feature (I call them, for various reason I don't have time to go into now, FREST functions). I can basically implement this now with a decorator, but it's a little ugly and slow.
It just occurred to me that an alternative would be a triple-splat final argument (or some such) that gathers all the keywords.
There is a related problem with the way double-splat and regular keyword arguments interact that should be fixed anyway.
Updated by gisborne (Guyren Howe) over 9 years ago
#12522 is a related issue; keyword arguments lvalues have a related issue.
Updated by matz (Yukihiro Matsumoto) over 9 years ago
- Status changed from Open to Closed
I am not sure what we gain comparing to
def controller **context if context.key?(:name) ... end end Is this worth adding new syntax? If you think you can persuade me, please reopen.
Matz.
Updated by joker1007 (Tomohiro Hashidate) about 9 years ago
I want this feature.
Keyword argument is safer and more explanatory than splat style.
But I sometimes want to pass whole arguments to other methods.
Example.
CLI gem that uses AWS api and accepts api key, secret key, region, and endpoint as command options.
class KMS def initialize(key_id, region:, access_key_id: nil, secret_access_key: nil) @client = Aws::KMS::Client.new({ region: region, access_key_id: access_key_id, secret_access_key: secret_access_key, }) @key_id = key_id end def encrypt(value) resp = @client.encrypt(key_id: @key_id, plaintext: YAML.dump(value)) Base64.strict_encode64(resp.ciphertext_blob) end def decrypt(value) resp = @client.decrypt(ciphertext_blob: Base64.strict_decode64(value)) YAML.load(resp.plaintext) end end If I can use __keyargs__ (tentative name), I can omit redundant lines.
class KMS def initialize(key_id, region:, access_key_id: nil, secret_access_key: nil) @client = Aws::KMS::Client.new(__keyargs__) # simple @key_id = key_id end # ... This example has few args, but I sometimes needs so many args when I kick complicated web API.
Updated by nobu (Nobuyoshi Nakada) about 9 years ago
- Description updated (diff)
Updated by Eregon (Benoit Daloze) about 9 years ago
Tomohiro Hashidate wrote:
I want this feature.
Keyword argument is safer and more explanatory than splat style.
But I sometimes want to pass whole arguments to other methods.Example.
CLI gem that uses AWS api and accepts api key, secret key, region, and endpoint as command options.class KMS def initialize(key_id, region:, access_key_id: nil, secret_access_key: nil) @client = Aws::KMS::Client.new({ region: region, access_key_id: access_key_id, secret_access_key: secret_access_key, }) @key_id = key_id end # ... endIf I can use
__keyargs__(tentative name), I can omit redundant lines.class KMS def initialize(key_id, region:, access_key_id: nil, secret_access_key: nil) @client = Aws::KMS::Client.new(__keyargs__) # simple @key_id = key_id end # ...
Why is it better than this?
class KMS def initialize(key_id, **auth) @client = Aws::KMS::Client.new(**auth) # simple @key_id = key_id end # ... It seems to me the Aws::KMS::Client should validate its arguments anyway, and this also reduces duplication.
If a different API must be provided for KMS#initialize, then I think the explicit style is much better as it's less magic and error-prone.
Updated by Eregon (Benoit Daloze) about 9 years ago
Guyren Howe wrote:
def render name::, something::, foo::, **context … now use foo … end
So this would be syntactic sugar for essentially this?
def render **context name = context.fetch :name something = context.fetch :something foo = context.fetch :foo … now use foo … end It's true that's it's verbose.
I'm not particularly fond of double or triple operators with slightly different semantics though.
From a design point of view, it seems a parameter object would work well here,
and would potentially avoid conflicts for two :name with different meaning.