Crystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих
Шрифт:
Интервал:
Закладка:
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), и метод имеет несколько перегрузок, компилятор сгенерирует код для проверки фактического типа во время выполнения и выбора правильного реализация метода.
Мультидиспетчеризация также произойдет в иерархии типов, если выражение аргумента имеет абстрактный родительский тип, и для каждого возможного конкретного типа определен метод. В следующей главе вы узнаете больше об определении иерархии типов.
Ограничение типа аналогично аннотациям типов в большинстве других языков, где вы укажите фактический тип параметра. Но в 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.
Вы можете использовать значение по умолчанию, если метод имеет наиболее распространенное значение, но вы все равно хотите, чтобы при необходимости можно было передавать разные значения. Если параметров много со значениями по умолчанию рекомендуется давать им имена.
Именованные параметры
Когда метод вызывается с множеством аргументов, иногда может быть непонятно, что