Method arguments
This is the formal specification of method parameters and call arguments.
Components of a method definition
A method definition consists of:
- required and optional positional parameters
- an optional splat parameter, whose name can be empty
- required and optional named parameters
- an optional double splat parameter
For example:
def foo(
# These are positional parameters:
x, y, z = 1,
# This is the splat parameter:
*args,
# These are the named parameters:
a, b, c = 2,
# This is the double splat parameter:
**options
)
end
Each one of them is optional, so a method can do without the double splat, without the splat, without named parameters and without positional parameters.
Components of a method call
A method call also has some parts:
foo(
# These are positional arguments
1, 2,
# These are named arguments
a: 1, b: 2
)
Additionally, a call argument can have a splat (*
) or double splat (**
). A splat expands a Tuple into positional arguments, while a double splat expands a NamedTuple into named arguments. Multiple argument splats and double splats are allowed.
How call arguments are matched to method parameters
When invoking a method, the algorithm to match call arguments to method parameters is:
- First positional call arguments are matched with positional method parameters. The number of these must be at least the number of positional parameters without a default value. If there's a splat parameter with a name (the case without a name is explained below), more positional arguments are allowed and they are captured as a tuple. Positional arguments never match past the splat parameter.
- Then named arguments are matched, by name, with any parameter in the method (it can be before or after the splat parameter). If a parameter was already filled by a positional argument then it's an error.
- Extra named arguments are placed in the double splat method parameter, as a NamedTuple, if it exists, otherwise it's an error.
When a splat parameter has no name, it means no more positional arguments can be passed, and any following parameters must be passed as named arguments. For example:
# Only one positional argument allowed, y must be passed as a named argument
def foo(x, *, y)
end
foo 1 # Error, missing argument: y
foo 1, 2 # Error: wrong number of arguments (given 2, expected 1)
foo 1, y: 10 # OK
But even if a splat parameter has a name, parameters that follow it must be passed as named arguments:
# One or more positional argument allowed, y must be passed as a named argument
def foo(x, *args, y)
end
foo 1 # Error, missing argument: y
foo 1, 2 # Error: missing argument; y
foo 1, 2, 3 # Error: missing argument: y
foo 1, y: 10 # OK
foo 1, 2, 3, y: 4 # OK
There's also the possibility of making a method only receive named arguments (and list them), by placing the star at the beginning:
# A method with two required named parameters: x and y
def foo(*, x, y)
end
foo # Error: missing arguments: x, y
foo x: 1 # Error: missing argument: y
foo x: 1, y: 2 # OK
Parameters past the star can also have default values. It means: they must be passed as named arguments, but they aren't required (so: optional named parameters):
# x is a required named parameter, y is an optional named parameter
def foo(*, x, y = 2)
end
foo # Error: missing argument: x
foo x: 1 # OK, y is 2
foo x: 1, y: 3 # OK, y is 3
Because parameters (without a default value) after the splat parameter must be passed by name, two methods with different required named parameters overload:
def foo(*, x)
puts "Passed with x: #{x}"
end
def foo(*, y)
puts "Passed with y: #{y}"
end
foo x: 1 # => Passed with x: 1
foo y: 2 # => Passed with y: 2
Positional parameters can always be matched by name:
def foo(x, *, y)
end
foo 1, y: 2 # OK
foo y: 2, x: 3 # OK
External names
An external name can be specified for a method parameter. The external name is the one used when passing an argument as a named argument, and the internal name is the one used to refer to the parameter inside the method definition:
def foo(external_name internal_name)
# here we use internal_name
end
foo external_name: 1
This covers two uses cases.
The first use case is using keywords as named parameters:
def plan(begin begin_time, end end_time)
puts "Planning between #{begin_time} and #{end_time}"
end
plan begin: Time.now, end: 2.days.from_now
The second use case is making a method parameter more readable inside a method body:
def increment(value, by)
# OK, but reads odd
value + by
end
def increment(value, by amount)
# Better
value + amount
end
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/syntax_and_semantics/default_values_named_arguments_splats_tuples_and_overloading.html