跳至内容

泛型

泛型允许您根据其他类型参数化类型。 泛型提供类型多态性。 考虑一个 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

泛型在实现集合类型时特别有用。 ArrayHashSet 是泛型类型,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 尚未绑定到类型,并且 TMyBox 的类型参数,因此编译器将其绑定到给定参数的类型
  • 编译器生成的 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

具有可变数量参数的泛型

我们可以使用 散列运算符 定义具有可变数量参数的泛型类。

让我们看一个定义名为 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