Spec-Zone.ru › Crystal

Ограничения типов

Ограничения типов применяются к параметрам методов, чтобы ограничить типы, принимаемые этим методом.

def add(x : Number, y : Number)
  x + y
end

# Ok
add 1, 2

# Error: no overload matches 'add' with types Bool, Bool
add true, false

Обратите внимание: если бы мы определили add без ограничений типов, мы также получили бы ошибку времени компиляции:

def add(x, y)
  x + y
end

add true, false

Приведённый выше код вызывает следующую ошибку компиляции:

Error in foo.cr:6: instantiating 'add(Bool, Bool)'

add true, false
^~~

in foo.cr:2: undefined method '+' for Bool

  x + y
    ^

Это происходит потому, что при вызове add метод инстанцируется с типами аргументов: каждый вызов метода с другой комбинацией типов приводит к созданию отдельного экземпляра метода.

Единственное различие в том, что первое сообщение об ошибке немного понятнее, но оба определения безопасны, поскольку в любом случае вы получите ошибку времени компиляции. Поэтому, как правило, предпочтительно не указывать ограничения типов и использовать их почти исключительно для определения разных перегрузок методов. Это позволяет писать более универсальный и многократно используемый код. Например, если мы определим класс с методом +, но он не является Number, мы сможем использовать метод add без ограничений типов, но не сможем использовать метод add с ограничениями.

# A class that has a + method but isn't a Number
class Six
  def +(other)
    6 + other
  end
end

# add method without type restrictions
def add(x, y)
  x + y
end

# OK
add Six.new, 10

# add method with type restrictions
def restricted_add(x : Number, y : Number)
  x + y
end

# Error: no overload matches 'restricted_add' with types Six, Int32
restricted_add Six.new, 10

Обозначения, используемые в ограничениях типов, описаны в грамматике типов.

Обратите внимание, что ограничения типов не применяются к переменным внутри самих методов.

def handle_path(path : String)
  path = Path.new(path) # *path* is now of the type Path
  # Do something with *path*
end

Ограничение self

Особое ограничение типа — это self:

class Person
  def ==(other : self)
    other.name == name
  end

  def ==(other)
    false
  end
end

john = Person.new "John"
another_john = Person.new "John"
peter = Person.new "Peter"

john == another_john # => true
john == peter        # => false (names differ)
john == 1            # => false (because 1 is not a Person)

В предыдущем примере self — это то же самое, что написать Person. Но в общем случае self означает то же, что и указание типа, которому в итоге будет принадлежать этот метод; при использовании модулей это становится полезнее.

К слову, поскольку Person наследует Reference, второе определение == не требуется, так как оно уже определено в Reference.

Обратите внимание, что self всегда означает соответствие типу экземпляра, даже в методах класса:

class Person
  getter name : String

  def initialize(@name)
  end

  def self.compare(p1 : self, p2 : self)
    p1.name == p2.name
  end
end

john = Person.new "John"
peter = Person.new "Peter"

Person.compare(john, peter) # OK

Можно использовать self.class, чтобы ограничить тип значением Person. В следующем разделе рассматривается суффикс .class в ограничениях типов.

Классы в качестве ограничений

Использование, например, Int32 в качестве ограничения типа означает, что метод принимает только экземпляры Int32:

def foo(x : Int32)
end

foo 1       # OK
foo "hello" # Error

Если нужно, чтобы метод принимал только тип Int32 (а не его экземпляры), используйте .class:

def foo(x : Int32.class)
end

foo Int32  # OK
foo String # Error

Это полезно для создания перегрузок в зависимости от типов, а не от экземпляров:

def foo(x : Int32.class)
  puts "Got Int32"
end

def foo(x : String.class)
  puts "Got String"
end

foo Int32  # prints "Got Int32"
foo String # prints "Got String"

Ограничения типов в splat-аргументах

В splat-аргументах можно указывать ограничения типов:

def foo(*args : Int32)
end

def foo(*args : String)
end

foo 1, 2, 3       # OK, invokes first overload
foo "a", "b", "c" # OK, invokes second overload
foo 1, 2, "hello" # Error
foo()             # Error

При указании типа все элементы кортежа должны соответствовать этому типу. Кроме того, пустой кортеж не соответствует ни одному из приведённых выше вариантов. Чтобы поддержать случай с пустым кортежем, добавьте ещё одну перегрузку:

def foo
  # This is the empty-tuple case
end

Простой способ сопоставить один или несколько элементов любого типа — использовать Object в качестве ограничения:

def foo(*args : Object)
end

foo()       # Error
foo(1)      # OK
foo(1, "x") # OK

Свободные переменные

С помощью forall можно задать ограничение типа, используя тип аргумента или часть типа аргумента:

def foo(x : T) forall T
  T
end

foo(1)       # => Int32
foo("hello") # => String

То есть T становится типом, который фактически использовался для инстанцирования метода.

Свободную переменную можно использовать для извлечения аргумента типа из обобщённого типа в ограничении типа:

def foo(x : Array(T)) forall T
  T
end

foo([1, 2])   # => Int32
foo([1, "a"]) # => (Int32 | String)

Чтобы создать метод, принимающий имя типа, а не экземпляр типа, добавьте .class к свободной переменной в ограничении типа:

def foo(x : T.class) forall T
  Array(T)
end

foo(Int32)  # => Array(Int32)
foo(String) # => Array(String)

Можно также указать несколько свободных переменных для сопоставления типов нескольких аргументов:

def push(element : T, array : Array(T)) forall T
  array << element
end

push(4, [1, 2, 3])      # OK
push("oops", [1, 2, 3]) # Error

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/reference/1.2/syntax_and_semantics/type_restrictions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API