W3cubDocs

/Crystal 0.31

Method arguments

This is the formal specification of method and call arguments.

Components of a method definition

A method definition consists of:

  • required and optional positional arguments
  • an optional splat argument, whose name can be empty
  • required and optional named arguments
  • an optional double splat argument

For example:

def foo(
  # These are positional arguments:
  x, y, z = 1,
  # This is the splat argument:
  *args,
  # These are the named arguments:
  a, b, c = 2,
  # This is the double splat argument:
  **options
  )
end

Each one of them is optional, so a method can do without the double splat, without the splat, without keyword arguments and without positional arguments.

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 arguments

When invoking a method, the algorithm to match call arguments to method arguments is:

  • First positional arguments are matched with positional method arguments. The number of these must be at least the number of positional arguments without a default value. If there's a splat method argument 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 method argument.
  • Then named arguments are matched, by name, with any argument in the method (it can be before or after the splat method argument). If an argument was already filled by a positional argument then it's an error.
  • Extra named arguments are placed in the double splat method argument, as a NamedTuple, if it exists, otherwise it's an error.

When a splat method argument has no name, it means no more positional arguments can be passed, and next arguments 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 method argument has a name, arguments 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 arguments: 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

Arguments 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 arguments):

# A method with two required named arguments: x and y
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 arguments (without a default value) after the splat method argument must be passed by name, two methods with different required named arguments 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 arguments 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 argument. The external name is the one used when passing an argument as a named argument, and the internal name is the one used 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 arguments:

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 argument 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/docs/syntax_and_semantics/default_values_named_arguments_splats_tuples_and_overloading.html