手册目录

泛型

泛型让类型和函数复用于多种静态类型。

泛型类型

type Box<T> {
  var value: T;

  func get(): T {
    return self.value;
  }
}

let number = Box<int> { value: 42 };
let text = Box<string> { value: "Feng" };

类型构造必须显式给出类型实参;Feng 不从构造参数或上下文推导所属 type 的类型参数。

泛型函数

func identity<T>(value: T): T {
  return value;
}

let first = identity(42);
let second = identity<string>("Feng");

调用时可以显式写出类型实参,也可以在实参、接收者或目标类型足以唯一确定时让编译器推导。

多个类型参数

type Pair<T, U> {
  let first: T;
  let second: U;
}

func make_pair<T, U>(first: T, second: U): Pair<T, U> {
  return Pair<T, U> { first: first, second: second };
}

泛型约束

约束必须引用 spec

spec Named {
  let name: string;
}

func name_of<T: Named>(value: T): string {
  return value.name;
}

对象契约约束允许在泛型实现中直接使用契约成员。可调用契约约束允许直接调用参数。联合契约约束仍需要先通过 match 收窄。

泛型方法

type Box<T> {
  let value: T;

  func pair_with<U>(other: U): Pair<T, U> {
    return Pair<T, U> { first: self.value, second: other };
  }
}

方法自己的类型参数不能与外层类型参数重名。

不变性

泛型实例按不变方式处理。即使 Dog 满足 AnimalBox<Dog> 也不会自动转换为 Box<Animal>。需要这种转换时,应显式遍历并创建新的目标容器或适配对象。

无约束类型参数不提供成员、比较或逻辑运算能力。只有规范明确允许的基础操作,或约束声明提供的能力,才能在泛型实现中使用。

泛型契约与约束组合

四种 spec 都可以声明类型参数,也都可以作为约束。下面同时使用对象、可调用、联合和交叉契约,并在 SizedReader<T> 中声明泛型父契约:

module manual_generic_contracts;
import std.io;
import std.numeric;

/** Reads a value of the chosen type. */
spec Reader<T> { func read(): T; }

/** Extends the explicitly instantiated parent contract. */
spec SizedReader<T>: Reader<T> { func size(): int; }

/** Supplies a label. */
spec Labelled { func label(): string; }

/** Combines two object contracts. */
spec LabelledReader<T>: Reader<T> & Labelled;

/** Produces a value. */
spec Producer<T>(): T;

/** Holds either the chosen type or text. */
spec ValueOrText<T>: T | string;

/** Implements the generic object contracts. */
type Box<T>: SizedReader<T>, Labelled {
  let value: T;

  /** Reads the stored value. */
  func read(): T { return self.value; }

  /** Reports this example's fixed size. */
  func size(): int { return 1; }

  /** Labels the box. */
  func label(): string { return "box"; }
}

/** Calls a value through a callable constraint. */
func produce<F: Producer<int>>(factory: F): int { return factory(); }

/** Uses both parts of an intersection constraint. */
func read_labelled<R: LabelledReader<int>>(reader: R): int {
  println(reader.label());
  return reader.read();
}

/** Narrows a value admitted by a union constraint. */
func number_or_zero<V: ValueOrText<int>>(value: V): int {
  return match value {
    let number: int { number }
    else { 0 }
  };
}

/** Exercises the four forms together. */
func main(args: string[]) {
  let box = Box<int> { value: 7 };
  let child: SizedReader<int> = box;
  let parent: Reader<int> = child;
  let factory: Producer<int> = () -> 5;
  let choice: ValueOrText<int> = "text";
  let read = read_labelled(box);
  println<int>("{0} {1} {2} {3}", read, parent.read(), produce(factory),
    number_or_zero<ValueOrText<int>>(choice));
  println<int>("{0}", number_or_zero<int>(9));
}

输出为 box7 7 5 09。每个泛参至多有一个约束;需要多种对象能力时,先声明交叉契约。SizedReader<T>: Reader<T> 使用的是父契约实例,不能在父列表重新声明泛参约束;已有约束必须足以证明父契约所需的能力。

联合约束允许能进入该联合的类型实参,也允许完整联合自身。上例的 V 分别是完整的 ValueOrText<int>int,不会因为约束而被改成同一种类型;泛型体使用匹配绑定后再操作成员。交叉契约作为类型实参时同样保留完整契约视角。静态 requirement 和受约束方法值见契约与 fit,联合收窄见模式匹配

自约束、目标推导与声明重载

自约束可表达“与自身同类型的值协作”。无约束泛参则适合保存、复制、传递和返回值;这些操作保留具体类型原有的值或引用语义,不要求它提供额外成员。

module manual_generic_inference;
import std.io;
import std.numeric;

/** Compares values of a chosen type through a named operation. */
spec Equal<T> { func same(other: T): bool; }

/** Satisfies a contract instantiated with its own type. */
type Key: Equal<Key> {
  let id: int;

  /** Compares identifiers. */
  func same(other: Key): bool { return self.id == other.id; }
}

/** Requires comparison with the same actual type. */
func same_key<T: Equal<T>>(left: T, right: T): bool {
  return left.same(right);
}

/** Stores one unconstrained type. */
type Cell<T> { let value: T; }

/** A distinct declaration selected by its two type parameters. */
type Cell<T, U> { let first: T; let second: U; }

/** Produces one type. */
spec Mapper<T>(): T;

/** A separate callable declaration with two type parameters. */
spec Mapper<T, U>(value: T): U;

/** Copies and returns a value using its existing semantics. */
func identity<T>(value: T): T { let copy = value; return copy; }

/** Gets its type parameter from the caller's result type. */
func empty<T>(): T[] { return []; }

/** Demonstrates inference and exact generic arity selection. */
func main(args: string[]) {
  let values: int[] = empty();
  let cell = Cell<int> { value: identity(3) };
  let pair = Cell<int, string> { first: 4, second: "four" };
  let source: Mapper<int> = () -> cell.value;
  let convert: Mapper<int, string> = (value: int) -> pair.second;
  println<int>("{0}", source());
  println(convert(pair.first));
  if same_key(Key { id: 7 }, Key { id: 7 }) { println("same"); }
}

输出为 3foursameempty() 的目标 int[] 确定 T = int;类型构造仍必须写成 Cell<int>,不能靠目标类型省略所属类型的泛参。

同一作用域内,同类别的 type 或同形式的 spec 可以按泛参数量区分同名声明;只改参数名或约束、但数量相同,不构成新声明。推导到同一泛参的多个类型必须一致,不能靠寻找共同父契约或插入转换消除冲突。无约束的 T 不能直接使用成员、相等比较或逻辑运算;像 same 这样的能力应通过约束表达,约束本身也不会新增运算符。

函数或方法调用的推导与形成函数值不同:后者须显式闭合来源泛参,见函数值。泛型 fit 的参数对应关系与静态扩展示例见契约与 fit