Внутри методов ключевое слово return можно использовать для досрочного завершения выполнения метода, при необходимости доставляя значение вызывающему методу. Последнее выражение в теле метода ведет себя как неявный возврат. Чаще всего он используется внутри условий для исключительных путей. Посмотрите это, например:
def day_count(year, month)
if month == 2
return leap_year?(year) ? 29 : 28
end
month.in?(1, 3, 5, 7, 8, 10, 12) ? 31 : 30
end
Поскольку типы могут быть опущены при объявлении метода, типы параметров определяются при вызове метода. Посмотрите это, например:
def add(a, b) # 'a' and 'b' could be anything.
a + b
end
p add(1, 2) # Here they are Int32, prints 3.
p add("Crys", "tal") # Here they are String, prints "Crystal".
# Let's try to cause issues: 'a' is Int32 and 'b' is String.
p add(3, "hi")
# => Error: no overload matches 'Int32#+' with type String
Каждый раз, когда метод вызывается с другим типом, генерируется его специализированная версия. В этом примере один и тот же метод можно использовать для сложения чисел и объединения строк. Его нельзя путать с динамической типизацией: в каждом варианте метода параметр a
имеет известный тип.
В третьем вызове он пытается вызвать add
с Int32
и String
. Опять же, для этих типов создается новая специализированная версия add
, но теперь она не будет работать, поскольку a + b
не имеет смысла при смешивании чисел и текста.
Отсутствие указания типов допускает использование шаблона ввода «утка». Говорят, что a + b
, то они будут разрешены, потому что это все, о чем заботится реализация, даже если они относятся к типу, никогда ранее не встречавшемуся. Этот шаблон может быть полезен для предоставления более общих алгоритмов и поддержки неожиданных вариантов использования.
Добавление ограничений типа
Отсутствие типов — не всегда лучший вариант. Вот несколько преимуществ указания типов:
• Сигнатуру метода с типами легче понять, особенно в документации.
• Для разных типов можно добавлять перегрузки с разными реализациями.
• Если вы допустили ошибку и вызвали какой-либо метод с неправильным типом, сообщение об ошибке будет более четким при вводе параметров.
Crystal имеет специальную семантику для указания типов: можно ограничить типы, которые может принимать параметр. При вызове метода компилятор проверяет, соответствует ли тип аргумента ограничению типа параметра. Если да, то для этого типа будет создана специализированная версия метода. Вот некоторые примеры:
def show(value : String)
puts "The string is '#{value}'"
end
def show(value : Int)
puts "The integer is #{value}"
end
show(12) # => The integer is 12
show("hey") # => The string is 'hey'
show(3.14159) # Error: no overload matches 'show' with type Float64
x = rand(1..2) == 1 ? "hey" : 12
show(x) # => Either "The integer is 12" or "The string is 'hey'"
Параметр можно ограничить типом, написав его после двоеточия. Обратите внимание, что пробел до и после двоеточия обязателен. Типы будут проверяться всякий раз, когда метод вызывается для обеспечения корректности. Если предпринята попытка вызвать метод с недопустимый тип, он будет обнаружен во время компиляции и выдаст правильное сообщение об ошибке.
В этом примере вы также видите тип Int
. Это объединение всех целочисленных типов и особенно полезен при ограничениях. Вы также можете использовать другие союзы.
Последняя строка показывает концепцию множественной диспетчеризации в Crystal: если аргумент вызова тип объединения (в данном случае Int32 | String
), и метод имеет несколько перегрузок, компилятор сгенерирует код для проверки фактического типа во время выполнения и выбора правильного реализация метода.
Мультидиспетчеризация также произойдет в иерархии типов, если выражение аргумента имеет абстрактный родительский тип, и для каждого возможного конкретного типа определен метод. В следующей главе вы узнаете больше об определении иерархии типов.