Open9

言語によって引数の不変・反変・共変が違う件

ピン留めされたアイテム
suinsuin

README

言語ごとに引数が不変、反変、共変のどれかを調べてます。

まとめ

言語 不変 反変 共変
PHP
Go
Java
Scala
Kotlin
TypeScript (strictFunctionTypes: true + 関数)
TypeScript (strictFunctionTypes: true + メソッド)
TypeScript (strictFunctionTypes: false + 関数)
TypeScript (strictFunctionTypes: false+ メソッド)
Dart
Swift(関数)
Swift(インターフェイス)

型の健全性としては、リスコフの置換原則的に共変でないほうが良さそう

言語 不変 反変 共変
健全な言語 ✅/❌
suinsuin

PHPは反変

version: 8.1.0

言語 不変 反変 共変
PHP
class Animal {}
class Bird extends Animal {}
class Chiken extends Bird {}

interface BirdWatchable {
	function watch(Bird $bird);
}

class BirdWatcher implements BirdWatchable {
	// 不変
	public function watch(Bird $bird) {}
}
class AnimalWatcher implements BirdWatchable {
	// 反変
	public function watch(Animal $animal) {}
}
class ChikenWatcher implements BirdWatchable {
	// 共変
	public function watch(Chiken $chiken) {}
	// Fatal error: Declaration of ChikenWatcher::watch(Chiken $chiken) must be compatible with BirdWatchable::watch(Bird $bird)
}
suinsuin

Goは不変

version: 1.17.5

言語 不変 反変 共変
Go
package main

type Animal interface {
	Animal()
}
type Bird interface {
	Animal
	Bird()
}
type Chiken interface {
	Bird
	Chiken()
}


type BirdWatchable interface {
	Watch(bird Bird)
}

// implements BirdWatchable
type AnimalWatcher struct {}
func (aw *AnimalWatcher) Watch (animal Animal) {}

// implements BirdWatchable
type BirdWatcher struct {}
func (aw *BirdWatcher) Watch (bird Bird) {}

// implements BirdWatchable
type ChikenWatcher struct {}
func (cw *ChikenWatcher) Watch (chiken Chiken) {}

// 不変
var bw BirdWatchable = (*BirdWatcher)(nil)
// 反変
var aw BirdWatchable = (*AnimalWatcher)(nil)
// cannot use (*AnimalWatcher)(nil) (type *AnimalWatcher) as type BirdWatchable in assignment:
//	*AnimalWatcher does not implement BirdWatchable (wrong type for Watch method)
//		have Watch(Animal)
//		want Watch(Bird)
// 共変
var cw BirdWatchable = (*ChikenWatcher)(nil)
// cannot use (*ChikenWatcher)(nil) (type *ChikenWatcher) as type BirdWatchable in assignment:
//	*ChikenWatcher does not implement BirdWatchable (wrong type for Watch method)
//		have Watch(Chiken)
//		want Watch(Bird)

func main() {}
suinsuin

Javaは不変

version: 1.8.0_282

言語 不変 反変 共変
Java
class Untitled {
	public static void main(String[] args) {
	}
}

class Animal {}
class Bird extends Animal {}
class Chiken extends Bird {}

interface BirdWatchable {
	public void watch(Bird bird);
}

class BirdWatcher implements BirdWatchable {
	// 不変
	public void watch(Bird bird) {}
}
class AnimalWatcher implements BirdWatchable {
	// 反変
	public void watch(Animal animal) {}
	// エラー: AnimalWatcherはabstractでなく、BirdWatchable内のabstractメソッドwatch(Bird)をオーバーライドしません
}
class ChikenWatcher implements BirdWatchable {
	// 共変
	public void watch(Chiken chiken) {}
	// エラー: ChikenWatcherはabstractでなく、BirdWatchable内のabstractメソッドwatch(Bird)をオーバーライドしません
}
suinsuin

Scalaは反変

version: 3.1.0

言語 不変 反変 共変
Scala
class Animal
class Bird extends Animal
class Chiken extends Bird

// 不変
val watchBird: (Bird) => Unit = (bird: Bird) => {}
// 反変
val watchAnimal: (Bird) => Unit = (animal: Animal) => {}
// 共変
val watchChiken: (Bird) => Unit = (chiken: Chiken) => {}
// Found:    Playground.Chiken => Unit
// Required: Playground.Bird => Unit

https://scastie.scala-lang.org/v1PY2LpLQD2D2zvT9EevVw

suinsuin

Kotlinは反変

version: 1.6.10

言語 不変 反変 共変
Kotlin
open class Animal
open class Bird : Animal()
class Chiken : Bird()

// 不変
val f1: (Bird) -> Unit = fun (bird: Bird) {}
// 反変
val f2: (Bird) -> Unit = fun (animal: Animal) {}
// 共変
val f3: (Bird) -> Unit = fun (chiken: Chiken) {}
// Type mismatch: inferred type is (Chiken) -> Unit but (Bird) -> Unit was expected
// Expected parameter of type Bird

fun main() {}

https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS42LjEwIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiIsIm5vbmVNYXJrZXJzIjp0cnVlLCJ0aGVtZSI6ImlkZWEiLCJjb2RlIjoib3BlbiBjbGFzcyBBbmltYWxcbm9wZW4gY2xhc3MgQmlyZCA6IEFuaW1hbCgpXG5jbGFzcyBDaGlrZW4gOiBCaXJkKClcblxudmFsIGYxOiAoQmlyZCkgLT4gVW5pdCA9IGZ1biAoYmlyZDogQmlyZCkge31cbnZhbCBmMjogKEJpcmQpIC0+IFVuaXQgPSBmdW4gKGFuaW1hbDogQW5pbWFsKSB7fVxudmFsIGYzOiAoQmlyZCkgLT4gVW5pdCA9IGZ1biAoY2hpa2VuOiBDaGlrZW4pIHt9XG4vLyBUeXBlIG1pc21hdGNoOiBpbmZlcnJlZCB0eXBlIGlzIChDaGlrZW4pIC0+IFVuaXQgYnV0IChCaXJkKSAtPiBVbml0IHdhcyBleHBlY3RlZFxuLy8gRXhwZWN0ZWQgcGFyYW1ldGVyIG9mIHR5cGUgQmlyZFxuXG5mdW4gbWFpbigpIHt9In0=

suinsuin

TypeScriptはときどき双変

version: 4.5.3

言語 不変 反変 共変
TypeScript (strictFunctionTypes: true + 関数)
TypeScript (strictFunctionTypes: true + メソッド)
TypeScript (strictFunctionTypes: false + 関数)
TypeScript (strictFunctionTypes: false+ メソッド)

コンパイラオプションstrictFunctionTypestrueのとき

// @strictFunctionTypes: true
class Animal {
  #thisTypeIsNominal: any;
}
class Bird extends Animal {
  #thisTypeIsNominal: any;
}
class Chiken extends Bird {
  #thisTypeIsNominal: any;
}

// 不変
const f1: (bird: Bird) => void = (bird: Bird) => {};
// 反変
const f2: (bird: Bird) => void = (animal: Animal) => {};
// 共変
const f3: (bird: Bird) => void = (chiken: Chiken) => {};
// Type '(animal: Chiken) => void' is not assignable to type '(bird: Bird) => void'.

interface BirdWatchable {
  watch(bird: Bird): void;
}
class BirdWatcher implements BirdWatchable {
  // 不変
  watch(bird: Bird): void {}
}
class AnimalWatcher implements BirdWatchable {
  // 反変
  watch(animal: Animal): void {}
}
class ChikenWatcher implements BirdWatchable {
  // 共変
  watch(chiken: Chiken): void {}
}

https://www.typescriptlang.org/play?strictPropertyInitialization=false#code/PTAEAEGcBcCcEsDG0BiBXAds+B7DAVATwAcBTSALlDjVIChEAbAQ0klAEEN4BbZx0AG86oUAGJoAC3iQiZAJKQAcjh7wM-KswyEA3HQC+DFm1AAheLAAmoUgA9opDFfZde-ISPFSZc0opU1DUYtHX0jJlZ2AGFpAGsnWwcnF3NLG2FRCWlZEn9lVXVNUG09Qzo6EFBAWDlASE0GPBhQADMARioACgAjdKoLawBKUABeAD5QADcceBth0G7etMGR8cEDfSrAWeV6xEboFoAmTp7rPvShscnp2fntdxDObj5GC9X1yrBAR0UdvZaAZmOi36VleVxmI3miHiTiosXgCQwoLWGzAflAAHIOndnrDoYiVmCrOjQDJQBgcPsovAAOYaLqMUjUHDUPIYhanJYgglTGbogB0FXUjlgzWYiEZwIA6sxoFDmPTGZlQAB3GVQ9lWM6DKg8qzhYxRTnS2WSUiwEk8YgMnhOaDsKVqyTyhmeURVOpeVUmjVakE665CIwREyuJ78Y1Qs0Wq2kG0YO1Gx3OxVeLb1URe9XY4puZ4Df3gtblSKmOEIiOm828GNxhMOk3J12gKrfT2OjpQ+Ew0Blpz5wmB8pAA

コンパイラオプションstrictFunctionTypesfalseのとき

// @strictFunctionTypes: false
class Animal {
  #thisTypeIsNominal: any;
}
class Bird extends Animal {
  #thisTypeIsNominal: any;
}
class Chiken extends Bird {
  #thisTypeIsNominal: any;
}

// 不変
const f1: (bird: Bird) => void = (bird: Bird) => {};
// 反変
const f2: (bird: Bird) => void = (animal: Animal) => {};
// 共変
const f3: (bird: Bird) => void = (chiken: Chiken) => {};

interface BirdWatchable {
  watch(bird: Bird): void;
}
class BirdWatcher implements BirdWatchable {
  // 不変
  watch(bird: Bird): void {}
}
class AnimalWatcher implements BirdWatchable {
  // 反変
  watch(animal: Animal): void {}
}
class ChikenWatcher implements BirdWatchable {
  // 共変
  watch(chiken: Chiken): void {}
}

https://www.typescriptlang.org/play?strictFunctionTypes=false&strictPropertyInitialization=false#code/PTAEAEGcBcCcEsDG0BiBXAds+B7DAVATwAcBTSALlADMBDAG0lIChF7bJJQBBDeAWwagA3s1CgAxNAAW8SETIBJSADkc-eBgZVaGQgG5mAX1btOoAELxYAE1CkAHtFIYbXXgKGjxU2fJKkymoaWvQ6eoYmbBxcAMKyANYu9k4ubpbWdt6SMnIKgarqmtqgugbGzMwgoICwcoCQmqx4MDQAjFQAFABGmVRWtgCUoAC8AHygAG448HZDoF09GQPDY8JGhtWAs8oNiE3QNABMHd22vZmDoxNTM3O6nmE8fIL05ytrVWCAjorbuzQAzEcLPo2F6XabDOaIRIuKjxeBJDAg1aGZiaZywOiIUiLGwAdVo0EhtE69Cx2QA7vjIfMTtj+lRJtNIqYYti8QTpKRYKABMQSfwXNAuEC2YTiaSxKBqvUJRT2dSbKcBvSriITFEzO5HgwRRyuTy+QKhZkdUSSSIJZsGuJZVTbk8qB4nnTQVk1czzLD4TrOdz+LzSPyMILWZTpKbxeJql8ZaH2pC4dDQJ6XM6Ga6KkA

suinsuin

Dartは反変

version: 2.15.0

言語 不変 反変 共変
Dart
void main() {}

class Animal {}

class Bird extends Animal {}

class Chiken extends Bird {}

typedef BirdFunc = void Function(Bird bird);

// 不変
BirdFunc f1 = (Bird bird) => {};
// 反変
BirdFunc f2 = (Animal animal) => {};
// 共変
BirdFunc f3 = (Chiken chiken) => {};
// Error: A value of type 'void Function(Chiken)' can't be assigned to a variable of type 'void Function(Bird)'.

abstract class BirdWatchable {
  void watch(Bird bird);
}

class BirdWatcher implements BirdWatchable {
  // 不変
  void watch(Bird bird) {}
}

class AnimalWatcher implements BirdWatchable {
  // 反変
  void watch(Animal animal) {}
}

class ChikenWatcher implements BirdWatchable {
  // 共変
  void watch(Chiken chiken) {}
  // Error: The parameter 'chiken' of the method 'ChikenWatcher.watch' has type 'Chiken', which does not match the corresponding type, 'Bird', in the overridden method, 'BirdWatchable.watch'.
}
suinsuin

Swiftはプロトコルは不変、関数は反変

version: ???

言語 不変 反変 共変
Swift(関数)
Swift(インターフェイス)
class Animal {}
class Bird: Animal {}
class Chiken: Bird {}

func useBird(a: Bird) -> Void {}
func useAnimal(a: Animal) -> Void {}
func useChiken(a: Chiken) -> Void {}

// 不変
let f1: (Bird) -> Void = useBird
// 反変
let f2: (Bird) -> Void = useAnimal
// 共変
let f3: (Bird) -> Void = useChiken
// Cannot convert value of type '(Chiken) -> Void' to specified type '(Bird) -> Void'

protocol BirdWatchable {
	func watch(a: Bird) -> Void
}

class BirdWatcher: BirdWatchable {
	// 不変
	func watch(a: Bird) -> Void {}
}

class AnimalWatcher: BirdWatchable {
	// 反変
	func watch(a: Animal) -> Void {}
	// Type 'AnimalWatcher' does not conform to protocol 'BirdWatchable'  
}

class ChikenWatcher: BirdWatchable {
	// 共変
	func watch(a: Chiken) -> Void {}
	// Type 'ChikenWatcher' does not conform to protocol 'BirdWatchable'  
}