第9章 ジェネリクス
9.1 ジェネリック型の定義
PHP(PHPDoc + 静的解析ツール)
<?php
// PHP にはネイティブのジェネリクスがない
// PHPDoc で型ヒントを提供し、PHPStan/Psalm で検証
/**
* @template T
*/
class Box {
/** @var T */
private mixed $value;
/** @param T $value */
public function __construct(mixed $value)
{
$this->value = $value;
}
/** @return T */
public function getValue(): mixed
{
return $this->value;
}
}
/** @var Box<string> */
$stringBox = new Box("Hello");
/** @var Box<int> */
$intBox = new Box(42);
TypeScript
// ジェネリッククラス
class Box<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
map<U>(fn: (value: T) => U): Box<U> {
return new Box(fn(this.value));
}
}
const stringBox = new Box<string>("Hello");
const intBox = new Box(42); // Box<number> と推論
// ジェネリック関数
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // string と推論
const num = identity<number>(42); // 明示的に指定
// ジェネリックインターフェース
interface Container<T> {
value: T;
getValue(): T;
}
// ジェネリック型エイリアス
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
VB.NET
' ジェネリッククラス
Class Box(Of T)
Private _value As T
Public Sub New(value As T)
_value = value
End Sub
Public Function GetValue() As T
Return _value
End Function
Public Function Map(Of U)(fn As Func(Of T, U)) As Box(Of U)
Return New Box(Of U)(fn(_value))
End Function
End Class
Dim stringBox As New Box(Of String)("Hello")
Dim intBox As New Box(Of Integer)(42)
' ジェネリックメソッド
Function Identity(Of T)(value As T) As T
Return value
End Function
Dim str = Identity("hello") ' String と推論
Dim num = Identity(Of Integer)(42) ' 明示的に指定
' ジェネリックインターフェース
Interface IContainer(Of T)
ReadOnly Property Value As T
Function GetValue() As T
End Interface
' 複数の型パラメータ
Class Pair(Of TFirst, TSecond)
Public Property First As TFirst
Public Property Second As TSecond
Public Sub New(first As TFirst, second As TSecond)
Me.First = first
Me.Second = second
End Sub
End Class
Dim pair As New Pair(Of String, Integer)("Age", 30)
9.2 型制約
PHP(PHPDoc)
<?php
/**
* @template T of Animal
*/
class Shelter {
/** @var T[] */
private array $animals = [];
/** @param T $animal */
public function add(mixed $animal): void
{
$this->animals[] = $animal;
}
}
// クラス制約
interface Comparable {
public function compareTo(self $other): int;
}
/**
* @template T of Comparable
* @param T[] $items
* @return T|null
*/
function findMax(array $items): mixed
{
// ...
}
TypeScript
// extends による制約
function getLength<T extends { length: number }>(obj: T): number {
return obj.length;
}
getLength("hello"); // OK
getLength([1, 2, 3]); // OK
// getLength(42); // エラー
// インターフェース制約
interface Comparable<T> {
compareTo(other: T): number;
}
function findMax<T extends Comparable<T>>(items: T[]): T | undefined {
return items.reduce((max, item) =>
item.compareTo(max) > 0 ? item : max
);
}
// keyof 制約
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "John", age: 30 };
const name = getProperty(person, "name"); // string
// getProperty(person, "invalid"); // エラー
// 複数の制約
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
VB.NET
' クラス制約
Class Shelter(Of T As Animal)
Private animals As New List(Of T)
Public Sub Add(animal As T)
animals.Add(animal)
End Sub
End Class
' インターフェース制約
Interface IComparable(Of T)
Function CompareTo(other As T) As Integer
End Interface
Function FindMax(Of T As IComparable(Of T))(items As IEnumerable(Of T)) As T
Return items.Aggregate(Function(max, item)
If item.CompareTo(max) > 0 Then Return item Else Return max
End Function)
End Function
' 複数の制約
Class Repository(Of T As {Class, IEntity, New})
' T は参照型で、IEntityを実装し、パラメータなしコンストラクタを持つ
Public Function Create() As T
Return New T()
End Function
End Class
' 利用可能な制約
' Class - 参照型
' Structure - 値型
' New - パラメータなしコンストラクタ
' インターフェース名 - 特定のインターフェースを実装
' クラス名 - 特定のクラスを継承
' 複数制約の例
Class Service(Of T As {Class, IDisposable, New})
Public Function CreateAndUse() As String
Using instance As T = New T()
Return instance.ToString()
End Using
End Function
End Class
9.3 共変性と反変性
概念
| 用語 | 意味 | 例 |
|---|---|---|
| 共変(Covariant) | 派生型に置換可能 |
IEnumerable<Dog> → IEnumerable<Animal>
|
| 反変(Contravariant) | 基底型に置換可能 |
Action<Animal> → Action<Dog>
|
| 不変(Invariant) | 置換不可 |
List<Dog> ≠ List<Animal>
|
PHP(型システムの制限)
<?php
// PHP の配列は共変的に振る舞う(実行時)
class Animal {}
class Dog extends Animal {}
/** @param Animal[] $animals */
function feedAll(array $animals): void {
foreach ($animals as $animal) {
// feed
}
}
/** @var Dog[] */
$dogs = [new Dog(), new Dog()];
feedAll($dogs); // OK(配列は共変)
TypeScript
// TypeScript は構造的型付けなので共変・反変は暗黙的
// 関数の引数は反変、戻り値は共変
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;
// これは安全ではないが、TypeScript では許可される(--strictFunctionTypes で制限)
const animalHandler: AnimalHandler = (a: Animal) => console.log(a);
const dogHandler: DogHandler = animalHandler; // 反変
// 戻り値は共変
type AnimalFactory = () => Animal;
type DogFactory = () => Dog;
const dogFactory: DogFactory = () => new Dog();
const animalFactory: AnimalFactory = dogFactory; // 共変
// ReadonlyArray は共変
const dogs: readonly Dog[] = [new Dog()];
const animals: readonly Animal[] = dogs; // OK
// 通常の配列は不変
// const mutableAnimals: Animal[] = dogs; // エラー
VB.NET
' Out = 共変(出力位置のみ)
Interface IProducer(Of Out T)
Function Produce() As T
End Interface
' In = 反変(入力位置のみ)
Interface IConsumer(Of In T)
Sub Consume(item As T)
End Interface
Class Animal
End Class
Class Dog
Inherits Animal
End Class
' 共変の例
Class DogProducer
Implements IProducer(Of Dog)
Public Function Produce() As Dog Implements IProducer(Of Dog).Produce
Return New Dog()
End Function
End Class
Dim dogProducer As IProducer(Of Dog) = New DogProducer()
Dim animalProducer As IProducer(Of Animal) = dogProducer ' OK(共変)
' 反変の例
Class AnimalConsumer
Implements IConsumer(Of Animal)
Public Sub Consume(item As Animal) Implements IConsumer(Of Animal).Consume
Console.WriteLine("Consuming animal")
End Sub
End Class
Dim animalConsumer As IConsumer(Of Animal) = New AnimalConsumer()
Dim dogConsumer As IConsumer(Of Dog) = animalConsumer ' OK(反変)
' .NET の標準型における共変・反変
' IEnumerable(Of Out T) - 共変
' IComparer(Of In T) - 反変
' Func(Of Out TResult) - 共変
' Action(Of In T) - 反変
Dim dogs As IEnumerable(Of Dog) = New List(Of Dog)()
Dim animals As IEnumerable(Of Animal) = dogs ' OK
Dim animalComparer As IComparer(Of Animal) = Nothing
Dim dogComparer As IComparer(Of Dog) = animalComparer ' OK
9.4 ジェネリックの実用例
コレクション操作
// TypeScript
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
return arr.map(transform);
}
function reduce<T, U>(
arr: T[],
reducer: (acc: U, item: T) => U,
initial: U
): U {
return arr.reduce(reducer, initial);
}
' VB.NET
Function Filter(Of T)(arr As IEnumerable(Of T),
predicate As Func(Of T, Boolean)) As IEnumerable(Of T)
Return arr.Where(predicate)
End Function
Function Map(Of T, U)(arr As IEnumerable(Of T),
transform As Func(Of T, U)) As IEnumerable(Of U)
Return arr.Select(transform)
End Function
Function Reduce(Of T, U)(arr As IEnumerable(Of T),
reducer As Func(Of U, T, U),
initial As U) As U
Return arr.Aggregate(initial, reducer)
End Function
リポジトリパターン
// TypeScript
interface Entity {
id: string;
}
interface Repository<T extends Entity> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
class InMemoryRepository<T extends Entity> implements Repository<T> {
private items = new Map<string, T>();
async findById(id: string): Promise<T | null> {
return this.items.get(id) ?? null;
}
async findAll(): Promise<T[]> {
return Array.from(this.items.values());
}
async save(entity: T): Promise<T> {
this.items.set(entity.id, entity);
return entity;
}
async delete(id: string): Promise<void> {
this.items.delete(id);
}
}
' VB.NET
Interface IEntity
Property Id As String
End Interface
Interface IRepository(Of T As IEntity)
Function FindByIdAsync(id As String) As Task(Of T)
Function FindAllAsync() As Task(Of IEnumerable(Of T))
Function SaveAsync(entity As T) As Task(Of T)
Function DeleteAsync(id As String) As Task
End Interface
Class InMemoryRepository(Of T As {Class, IEntity})
Implements IRepository(Of T)
Private ReadOnly items As New Dictionary(Of String, T)
Public Async Function FindByIdAsync(id As String) As Task(Of T) _
Implements IRepository(Of T).FindByIdAsync
Dim result As T = Nothing
items.TryGetValue(id, result)
Return result
End Function
Public Async Function FindAllAsync() As Task(Of IEnumerable(Of T)) _
Implements IRepository(Of T).FindAllAsync
Return items.Values.ToList()
End Function
Public Async Function SaveAsync(entity As T) As Task(Of T) _
Implements IRepository(Of T).SaveAsync
items(entity.Id) = entity
Return entity
End Function
Public Async Function DeleteAsync(id As String) As Task _
Implements IRepository(Of T).DeleteAsync
items.Remove(id)
End Function
End Class