Coming from Ruby, one of the best recent features has been keyword arguments in ruby2.0, and especially the required keyword arguments in ruby2.1. Being able to know at the call site what each arg is helps comprehension, and in the method knowing that everything has been passed in, and nothing extra helps maintainability.
I'd love to see this as well. Ruby has conventions for working around this (pass in a Hash, delete keys corresponding to expected arguments, ensure Hash is empty at the end), but that's really cumbersome and verbose.
Take a look at this: http://crystal-lang.org/docs/syntax_and_semantics/default_and_named_arguments.html
ie. they're already supported but translate to arguments with defaults in the method definition, ending up with a compile code with no overhead: it just calls the method with the arguments in the correct order.
def x(a, b = nil, c = 3)
end
x(1, c: 4, b: 2)
It sounds like the only part that isn't implemented is the "required" part?
In Ruby 2.1+ you can say something like:
def x(foo: )
end
x() # ArgumentError
x(foo: 1) # OK.
I've used required named arguments in Ruby a few times already, it would be cool to have the same in Crystal.
Yes, this isn't possible, because kwargs only match arguments with a default value:
def x(foo)
end
x(foo: 1) # => wrong number of arguments for 'x' (0 for 1)
We might want to be able to do what @ysbaddaden commented last. In particular some classes have long initializers, for example OAuth::Consumer, so in those cases it makes the code more readable and understandable.
The readability is the win when it comes to keyword args. With too many arguments into a function, having them named makes it easier to understand what is being passed into the function.
A side-effect is also DSL readability.
For example, let's say I have a function copy that can copy a file or directory to new location.
# this is a readable if the variables are named correctly
file, target_dir = "README", "/tmp"
copy(file, target_dir)
# this is readable without having to allocate variabled
copy(file: "README", target_dir: "/tmp")
@jtarchie I can't help but feel like that's a weird examples, since 99% of copy functions take the target followed by the source...
A better example, IMO, is with GUI libraries (yes, this example's in Python):
x = MyLibrary.SomeLabel(
name='xyz',
x='123',
label='blah',
offset=21321321,
...
)
which currently would have to be written like this:
x = MyLibrary.SomeLabel()
x.name = 'xyz'
x.label = ...
@kirbyfan64 In your example, what is the signature of MyLibrary.SomeLabel, and what happens if you don't provide name/x/label/offset? Do these have default values?
@asterite They normally do. I just made that off the top of my head based on my experience with using PyGObject and PySide.
I just found another interesting example of the usefulness of keyword arguments. Try to figure out what this does:
Gtk.render_layout(obj.get_style_context(), cairo, 2,
-obj.window_to_buffer_coords(
Gtk.TextWindowType.RIGHT, 2, 0)[1],
layout)
This is more readable:
Gtk.render_layout(context=obj.get_style_context(), cr=cairo, x=2,
y=-obj.window_to_buffer_coords(
win=Gtk.TextWindowType.RIGHT, window_x=2, window_y=0)[1],
layout=layout)
Good example of what @asterite was saying about functions with a complex signature: matching arguments without defaults looks like a great addition to the language!
Would love to see named arguments without defaults.
+1 here)
+1 being able to pass named args whether default valued or not in functions param-declaration.
Does anybody still feel strong about required keyword arguments? The general tone in #2537 was against them, so I'd like to close this.
I only just came across #2537 just now. I'm still hoping for required keyword arguments, but let me read that other issue think it over.
Yeah, I think the real goal of this issue was to be able to name arguments at call sites for compression there. One way is this way by having optional keyword arguments and required arguments. However #2537 has the same result of call-site readability through a different means, and that's ok.
Losing the ability to force callees to name is a bit of a loss, but not enough to have both ways, so I'm in favor of closing this.
Let's close then.
We could have a way to force named args to be used, or to disallow named args. The idea would be to allow an external name for an argument:
def foo(external internal)
# here use internal
end
# At the call site use the external name
foo(external: 1)
foo(1) # Error, can't use positional argument here
To disallow the usage of a named argument you'd use an underscore for the external name:
def foo(_ internal)
# here use internal
end
# At the call site we must use a positional argument
foo(internal: 1) # Error
foo(1) # OK
Of course you can use the same name for the internal and external name.
This would be almost exactly as in Swift. The difference is that in Swift the external names are part of the method signature, so you can have multiple overloads with the same number of arguments, and even the same types, but with different argument names:
# These are two different overloads in Swift
foo(with: 1)
foo(without: 1)
I personally wouldn't mind adding the external argument feature to make mandatory, or disallow, named arguments for a particular argument. Changing how overloading works, and specially bringing a whole new way to define methods: I'm not sure about that (should we rethink the whole standard library to make use of this feature?).
However, all of this will inevitably make the language more complex, so I personally would leave arguments as they are now, but of course this is open to discussion :-)