泛型¶
泛型允許您基於另一個類型參數化一個類型。泛型提供類型多型。考慮一個 Box 類型
class MyBox(T)
def initialize(@value : T)
end
def value
@value
end
end
int_box = MyBox(Int32).new(1)
int_box.value # => 1 (Int32)
string_box = MyBox(String).new("hello")
string_box.value # => "hello" (String)
another_box = MyBox(String).new(1) # Error, Int32 doesn't match String
泛型對於實作集合類型特別有用。Array
、Hash
、Set
都是泛型類型,Pointer
也是。
允許多個類型參數
class MyDictionary(K, V)
end
任何名稱都可以用作類型參數
class MyDictionary(KeyType, ValueType)
end
泛型類別方法¶
當接收者的類型引數未指定時,泛型類型的類別方法中的類型限制會成為自由變數。這些自由變數隨後會從呼叫的引數中推斷出來。例如,也可以寫成
int_box = MyBox.new(1) # : MyBox(Int32)
string_box = MyBox.new("hello") # : MyBox(String)
在上面的程式碼中,我們不必指定 MyBox
的類型引數,編譯器會按照以下流程推斷它們
- 編譯器會從
MyBox#initialize(@value : T)
產生一個沒有明確定義的自由變數的MyBox.new(value : T)
方法 MyBox.new(value : T)
中的T
尚未繫結到一個類型,且T
是MyBox
的類型參數,因此編譯器會將其繫結到給定引數的類型- 編譯器產生的
MyBox.new(value : T)
會呼叫MyBox(T)#initialize(@value : T)
,其中T
現在已繫結
透過這種方式,泛型類型使用起來就不那麼繁瑣。請注意,為了使其運作,#initialize
方法本身不需要指定任何自由變數。
相同的類型推斷也適用於 .new
以外的類別方法
class MyBox(T)
def self.nilable(x : T)
MyBox(T?).new(x)
end
end
MyBox.nilable(1) # : MyBox(Int32 | Nil)
MyBox.nilable("foo") # : MyBox(String | Nil)
在這些範例中,T
僅被推斷為自由變數,因此接收者本身的 T
仍然是未繫結的。因此,呼叫無法推斷 T
的其他類別方法會發生錯誤
module Foo(T)
def self.foo
T
end
def self.foo(x : T)
foo
end
end
Foo.foo(1) # Error: can't infer the type parameter T for the generic module Foo(T). Please provide it explicitly
Foo(Int32).foo(1) # OK
泛型結構與模組¶
結構與模組也可以是泛型的。當模組是泛型時,您可以這樣引入它
module Moo(T)
def t
T
end
end
class Foo(U)
include Moo(U)
def initialize(@value : U)
end
end
foo = Foo.new(1)
foo.t # Int32
請注意,在上面的範例中,T
會變成 Int32
,因為 Foo.new(1)
會使 U
變成 Int32
,這反過來又會透過引入泛型模組使 T
變成 Int32
。
泛型類型繼承¶
泛型類別和結構可以被繼承。繼承時,您可以指定泛型類型的實例,或委派類型變數
class Parent(T)
end
class Int32Child < Parent(Int32)
end
class GenericChild(T) < Parent(T)
end
具有可變引數數量的泛型¶
我們可以使用 splat 運算子 定義具有可變引數數量的泛型類別。
讓我們看一個範例,我們定義一個名為 Foo
的泛型類別,然後我們將使用不同數量的類型變數來使用它
class Foo(*T)
getter content
def initialize(*@content : *T)
end
end
# 2 type variables:
# (explicitly specifying type variables)
foo = Foo(Int32, String).new(42, "Life, the Universe, and Everything")
p typeof(foo) # => Foo(Int32, String)
p foo.content # => {42, "Life, the Universe, and Everything"}
# 3 type variables:
# (type variables inferred by the compiler)
bar = Foo.new("Hello", ["Crystal", "!"], 140)
p typeof(bar) # => Foo(String, Array(String), Int32)
在以下範例中,我們透過繼承來定義類別,為泛型類型指定實例
class Parent(*T)
end
# We define `StringChild` inheriting from `Parent` class
# using `String` for generic type argument:
class StringChild < Parent(String)
end
# We define `Int32StringChild` inheriting from `Parent` class
# using `Int32` and `String` for generic type arguments:
class Int32StringChild < Parent(Int32, String)
end
如果我們需要使用 0 個引數來實例化一個 class
呢?在這種情況下,我們可以這樣做
class Parent(*T)
end
foo = Parent().new
p typeof(foo) # => Parent()
但是我們不應將 0 個引數與未指定泛型類型變數混淆。以下範例會引發錯誤
class Parent(*T)
end
foo = Parent.new # Error: can't infer the type parameter T for the generic class Parent(*T). Please provide it explicitly
class Foo < Parent # Error: generic type arguments must be specified when inheriting Parent(*T)
end