Ограничение типа аналогично аннотациям типов в большинстве других языков, где вы укажите фактический тип параметра. Но в Crystal нет аннотаций типов. Здесь важно слово «ограничение»: ограничение типа служит для ограничения возможных типы приемлемы. Фактический тип по-прежнему исходит из места вызова. Посмотрите это, например:
def show_type(value : Int | String)
puts "Compile-time type is #{typeof(value)}."
puts "Runtime type is #{value.class}."
puts "Value is #{value}."
end
show_type(10)
# => Compile-time type is Int32.
# => Runtime type is Int32.
# => Value is 10.
x = rand(1..2) == 1 ? "hello" : 5_u8
show_type(x)
# => Compile-time type is (String | UInt8).
# => Runtime type is String.
# => Value is hello.
Интересно видеть, что тело метода всегда специализировано для типов, используемых в вызывайте сайт, не требуя проверок во время выполнения или какого-либо динамизма. Это часть того, что делает Кристалл очень быстрый язык.
Вы также можете применить ограничения типа к возвращаемому типу метода; это будет гарантировать, что метод ведет себя так, как ожидалось, и выдает правильные данные. Посмотрите это, например:
def add(a, b) : Int
a + b
end
add 1, 3 # => 4
add "a", "b" # Error: method top-level add must return Int but it is returning String
Здесь вариант строки не удастся скомпилировать, поскольку a + b
создаст строку, но метод ограничен возвратом Int. Помимо типа, параметры также могут иметь значения по умолчанию.
Значения по умолчанию
Методы могут иметь значения по умолчанию для своих аргументов; это способ пометить их как необязательные. Для этого укажите значение после имени параметра, используя символ равенства. Посмотрите это, например:
def random_score(base, max = 10)
base + rand(0..max)
end
p random_score(5) # => Some random number between 5 and 15.
p random_score(5, 5) # => Some random number between 5 and 10.
Вы можете использовать значение по умолчанию, если метод имеет наиболее распространенное значение, но вы все равно хотите, чтобы при необходимости можно было передавать разные значения. Если параметров много со значениями по умолчанию рекомендуется давать им имена.
Именованные параметры
Когда метод вызывается с множеством аргументов, иногда может быть непонятно, что означает каждый из них. Чтобы улучшить это, параметры могут быть названы в месте вызова. Вот пример:
# These are all the same:
p random_score(5, 5)
p random_score(5, max: 5)
p random_score(base: 5, max: 5)
p random_score(max: 5, base: 5)
Все четыре вызова делают одно и то же, но чем более подробным является вызов, тем легче становится понять, что означает каждый из пяти. Кроме того, вы можете изменить порядок аргументов при использовании именованных параметров.
В некоторых случаях имеет смысл заставить некоторые параметры всегда именоваться. Например, предположим, что у нас есть метод, который возвращает время открытия магазина. Ему необходимо знать, является ли этот день выходным и является ли он частью выходных:
def store_opening_time(is_weekend, is_holiday)
if is_holiday
is weekend ? nil : "8:00"
else
is_weekend ? "12:00" : "9:00"
end
end
В этой реализации нет ничего необычного. Но если вы начнете его использовать, все быстро станет очень запутанным:
p store_opening_time(true, false) # What is 'true' and 'false' here?
You can call the same method while specifying the name of each parameter for clarity:
p store_opening_time(is_weekend: true, is_holiday: false)
Чтобы принудительно дать имена некоторым параметрам, добавьте перед ними символ *
. Все, что слева от *
, будет позиционными параметрами, а все, что справа, всегда будет именованными параметрами. Они также могут иметь значения по умолчанию:
def store_opening_time(*, is_weekend, is_holiday)
# ...
end
p store_opening_time(is_weekend: true, is_holiday: false)