类型限制¶
类型限制应用于方法参数,以限制该方法接受的类型。
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
来自实例变量的限制¶
在某些情况下,可以根据方法参数的使用情况来限制方法参数的类型。例如,考虑以下示例
class Foo
@x : Int64
def initialize(x)
@x = x
end
end
在这种情况下,我们知道初始化函数的参数 x
必须是一个 Int64
,并且没有必要将其保持为无限制的。
当编译器找到从方法参数到实例变量的赋值时,它就会插入这样的限制。在上面的示例中,调用 Foo.new "hi"
会失败(注意类型限制)
Error: no overload matches 'Foo.new' with type String
Overloads are:
- Foo.new(x : ::Int64)
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"
散点中的类型限制¶
可以在散点中指定类型限制
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
匹配任何类型的一个或多个元素的简单方法是使用 _
作为限制
def foo(*args : _)
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
散点类型限制¶
如果散点参数的限制也包含一个散点,则该限制必须命名一个 Tuple
类型,并且与该参数相对应的参数必须与散点限制中的元素匹配
def foo(*x : *{Int32, String})
end
foo(1, "") # OK
foo("", 1) # Error
foo(1) # Error
直接在散点限制中指定元组类型的情况非常罕见,因为上面的情况可以通过简单地不使用散点来表示(即 def foo(x : Int32, y : String)
)。但是,如果限制是一个自由变量,那么它会被推断为一个包含所有对应参数类型的 Tuple
def foo(*x : *T) forall T
T
end
foo(1, 2) # => Tuple(Int32, Int32)
foo(1, "") # => Tuple(Int32, String)
foo(1) # => Tuple(Int32)
foo() # => Tuple()
在最后一行,T
被推断为空元组,这对于具有非散点限制的散点参数是不可能的。
双散点参数类似地支持双散点类型限制
def foo(**x : **T) forall T
T
end
foo(x: 1, y: 2) # => NamedTuple(x: Int32, y: Int32)
foo(x: 1, y: "") # => NamedTuple(x: Int32, y: String)
foo(x: 1) # => NamedTuple(x: Int32)
foo() # => NamedTuple()
此外,也可以在泛型类型中使用单散点限制,一次提取多个类型参数
def foo(x : Proc(*T, Int32)) forall T
T
end
foo(->(x : Int32, y : Int32) { x + y }) # => Tuple(Int32, Int32)
foo(->(x : Bool) { x ? 1 : 0 }) # => Tuple(Bool)
foo(->{ 1 }) # => Tuple()