類型限制¶
類型限制會應用於方法參數,以限制該方法接受的類型。
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"
Splats 中的類型限制¶
您可以在 splats 中指定類型限制
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
Splat 類型限制¶
如果 splat 參數的限制也有 splat,則該限制必須命名為Tuple
類型,並且對應於參數的引數必須符合 splat 限制的元素
def foo(*x : *{Int32, String})
end
foo(1, "") # OK
foo("", 1) # Error
foo(1) # Error
直接在 splat 限制中指定元組類型極為罕見,因為上述情況可以簡單地通過不使用 splat 來表達(即 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
被推斷為空元組,這對於具有非 splat 限制的 splat 參數來說是不可能的。
雙 splat 參數類似地支援雙 splat 類型限制
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()
此外,單 splat 限制也可以在泛型類型內使用,以一次提取多個類型引數
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()