Выбрать главу

При вызове метода круглые скобки вокруг аргументов не являются обязательными и часто опускаются для удобства чтения. В этом примере puts — это метод, аналогичный jump_year? и его аргумент является результатом последнего. ставит leap_year? 1900 — это то же самое, что и puts(leap_year?(1900)).

Имена методов подобны переменным и соответствуют соглашению об использовании только строчных букв, цифр и подчеркиваний. Кроме того, имена методов могут заканчиваться символами вопроса или восклицательного знака. Они не имеют специального значения в языке, но обычно применяются в соответствии со следующим соглашением:

• Метод, заканчивающийся на ? может указывать на то, что метод проверяет какое-то условие и возвращает значение Bool. Он также часто используется для методов, которые возвращают объединение некоторого типа и Nil для обозначения состояния сбоя.

• Метод, заканчивающийся на ! указывает на то, что выполняемая им операция в некотором роде «опасна», и программисту следует быть осторожным при ее использовании. Иногда может существовать «более безопасный» вариант метода с тем же именем, но без ! символ.

Методы могут основываться на других методах. Посмотрите это, например:

def day_count(year)

    leap_year?(year) ? 366 : 365

end

Методы могут быть перегружены по количеству аргументов. Посмотрите это, например:

def day_count(year, month)

    case month

    when 1, 3, 5, 7, 8, 10, 12

      31

    when 2

      leap_year?(year) ? 29 : 28

    else

      30

    end

end

В этом случае метод будет выбран в зависимости от того, как вы расставите аргументы для его вызова:

puts day_count(2020) # => 366

puts day_count(2021) # => 365

puts day_count(2020, 2) # => 29

Внутри методов ключевое слово 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 имеет специальную семантику для указания типов: можно ограничить типы, которые может принимать параметр. При вызове метода компилятор проверяет, соответствует ли тип аргумента ограничению типа параметра. Если да, то для этого типа будет создана специализированная версия метода. Вот некоторые примеры: