-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compiler: implement double splat #2580
Conversation
What exactly is Why not pass only positional args with * and only named arguments with ** ? |
PEP 3102 -- Keyword-Only Arguments might be an interesting read since you're mentioning this idea. Because otherwise this is probably more similar to Python than Ruby at this point. |
e4e662e
to
236e697
Compare
@BlaXpirit That said, I really (I mean, really ❤️) like that PEP you mention. I think it's perfect, because the logic is super simple (both for a human and a compiler), and it's also a way to force arguments to be passed as named arguments. And it also open the possibility to overload based on these required named arguments: # Warning, silly example follows
def print(array : Array)
end
def print(array : Array, *, without)
print array.delete(element)
end
def print(array : Array, *, and_also)
print array
print and_also
end
print [1, 2, 3] # calls the first overload
print [1, 2, 3], without: 2 # calls the second overload
print [1, 2, 3], and_also: 4, # calls the third overload This for example is possible in Swift. And because arguments after the The only "downside" (not a downside for me, though), it that right now it's possible to have this in Crystal (and, well, Ruby): def foo(x, *args, y)
end
foo 1, 2, 3, 4 # x is 1, args is {2, 3}, y is 4 However, I never found a useful use case for that. And in any case, I think invoking it like this is much clearer: foo 1, 2, 3, y: 4 One clear example of this is the current # One required positional arguments, other optional arguments, and the final argument
# is the one we'll delegate the method to
macro delegate(method, *other_methods, to_object)
end
# delegate foo and bar to baz
delegate foo, bar, baz
# Can't invoke it like this, because right now it's confusing
delegate foo, bar, to_object: baz If apply the PEP we can write it like this: macro delegate(*methods, to)
end
delegate foo, to: bar
delegate foo, bar, to: baz I mean, the definition is shorter and easier to implement, and at the caller side we are forcing a nice, readable syntax. It's a total win. |
If we decide to go that way (following the last comment), I'll probably merge this first because it has the logic for the double splat, and then update the matching logic accordingly. |
Seems pretty clean, the PEP-definition, I can definitely live without post-splat "positional slots". |
@BlaXpirit @ozra I'll try to implement it and then send a PR |
This PR adds double splatting to the language.
A double splat:
If you are familiar with Ruby double splat, it's similar, although a bit different in Crystal because Crystal doesn't have keyword args, and we also use a named tuple instead of a hash.
With some examples all of this will become clear, you can look at the specs here and here.
With this, we can make the delegate macro work with named arguments, because this now works:
Another useful thing is that one can now catch all named arguments with a double splat:
The above is specially useful to avoid repeating a set of options in multiple methods that simply forward this information. Remember that doing
options[:x]
in the code above will give a compile error ifx
is not passed, so it's super type-safe.With the above, we can also force method arguments to always be passed as named arguments. For example:
The only "problem" with the above is that we can pass extra named arguments and we won't get an error. We can solve this problem later with a type restriction on options, to restrict the keys and also the types.
Double splats are also available in macro arguments as well.
In a method/macro, a double splat can be mixed with a splat:
However, this doesn't work if there are positional arguments in the method/macro definition:
The reason is that it becomes very hard to know what would happen, where the args will go, and it might also not be very useful, so for now it's out of the language.
Of course after this the only thing remaining to be able to entirely forward a call's information is forwarding a block. This will probably come in the future, right now it's kind of hard to do.