The Crystal Programming Language Forum

Double splats not allowed after named arguments

While designing an api I notice that double splats can’t be used after named arguments. I am not sure if there is a reason to not allow them.

The api I was defining can be reduced to

def named(*, name, **opts)
  generic(name: name, **opts) # Error: expected named argument, not **
end

def generic(**opts)
  opts.to_h
end

puts named(name: "Stuart", fruit: "banana")

It was more natural to me to write generic(name: name, **opts) but that does not work: Error: expected named argument, not **

Using generic(**opts, name: name) works just fine. As well as manually creating the arguments via named tuple merge args = {name: name}.merge(opts);generic(**args).

Is there a fundamental reason to disallow double splats after named arguments?

I think I missed that case when I implemented it. It should be relatively easy to implement this. Let me know if you’d like me to give it a try.

I noticed this last week or so, too. I wasn’t super bothered, but I was surprised. It’s nice to know it wasn’t an intentional decision and that it can be implemented relatively easily! :slightly_smiling_face:

It turns out this is already possible. However, named arguments must come after the double splat:

def named(*, name, **opts)
  generic(**opts, name: name) # No error!
end

def generic(**opts)
  opts.to_h
end

puts named(name: "Stuart", fruit: "banana")

I think the reason is that **opts is represented in the compiler as an argument more that’s double splatting, and named argument must come after regular arguments, never before.

In fact, you can specify a double splat multiple times:

def named(*, name, **opts)
  more_opts = {foo: 1}
  generic(**opts, **more_opts, name: name) # No error!
end

def generic(**opts)
  opts.to_h
end

puts named(name: "Stuart", fruit: "banana")

I think that’s what Brian was saying :slightly_smiling_face:

Oooh… I didn’t read that part :blush:

Well, that’s my answer :slight_smile:

It seems in Ruby you can put them in any other… it’s just a bit more annoying to implement that way, but it’s totally possible.

1 Like

This seems reasonable to be the cause of the (unintended) limitation.

It’s not a stopper issue. And building the named tuple manually allows overriding value which **opts, name: name will (and should) not.

I think is more natural/reasonable to write m(name: name, **opts). But there is no rush to have that.

Although it could be implemented, another option is to change the error message for this case.