いわゆるGo2 proposalsのひとつ、contractsが以下で提案されています。
https://go-review.googlesource.com/c/go/+/187317
さる12月15日、このproposalに大きな変更が加わったのでこれをまとめてみました。自分用に翻訳したものなので、おぼつかないところはご容赦ください。
READMEの翻訳
ステータス
このCLが最後に更新されてから、大きな進展がありました。
いくつかの例外(下記参照)を除き、contractsの提案のほとんどの側面は、少なくともいくつかのテスト(但し、バグはあるだろう)で実装されています。それぞれのファイルに記載されている少数の既知の問題を除き、go/typesはtestdataとexamplesディレクトリにあるすべての*.go2テストファイルをタイプチェックできるようになりました。
代替表記
contractsに対する代替表記を試すために、この実装ではコントラクトの代わりに(パラメータ化される可能性がある)インタフェースを使用できます。具体的には、次の表記が許可され、同等です:
contract C(P1, P2) {
P1 m1(x P1)
P2 m2(x P1) P2
P2 int, float64
}
// used in a generic function
func f (type P1, P2 C) (x P1, y P2) P2
// それぞれの型パラメータP1とP2の範囲(制約)を
// 記述する一対のパラメータ化された、同等のインターフェース
// I1とI2を使うことができます:
type I1(type P1) interface {
m1(x P1)
}
type I2(type P1, P2) interface {
m2(x P1) P2
type int, float64
}
上記の関数fは、次のように記述できます:
func f(type P1 I1(P1), P2 I2(P1, P2)) (x P1, y P2) P2
明らかに、インターフェース表記は一般的に長くなりますが、根本的な新しいメカニズムを必要としません(型リストを列挙する能力を別にすれば)。多くの場合(ほとんど?)、インターフェースをパラメータ化する必要はまったくありません。その場合、タイピングのオーバーヘッドはそれほど大きくありません。インタフェースを制約として使用することで、同時に既存のインタフェース(例: io.Reader)を直接使用できるようになります。
内部的には、型チェッカーは「コンポーネントインタフェース」にコントラクトを「分解」します。各型パラメータはコントラクトの各型パラメータでパラメータ化されることがあります。コントラクトの代わりにインタフェースを直接使用できるようにすると、実装が簡単になります。言い換えると、コントラクト表記は、インターフェースとして表現される型パラメーター範囲に対する糖衣構文と見なすことができます。
既知の問題
- gofmtがパラメータ化されたコードでは部分的にしか動作しない。
- ジェネリックを実装しエクスポートしたパッケージのインポートは実装されていない。(このプロトタイプには実装されないだろう)
- 実際の実装では、はるかに優れたエラーメッセージが実装されていることだろう。
- コントラクトの埋め込みはまだ実装されていない。
- インターフェースの埋め込みはインターフェースの型リストを無視する。
- ジェネリクスの式に対するさまざまな型固有の操作がまだ機能しない(比較的簡単に実装できる)。
例など
今回追加された examples/testdata の中からいくつか興味深いものをご紹介します。
インターフェースを用いたNode/Edgeの例
// Same Graph using interface bounds instead of a contract.
type AltGraph (type Node NodeFace(Edge), Edge EdgeFace(Node)) struct { }
func AltNew (type Node NodeFace(Edge), Edge EdgeFace(Node)) (nodes []Node) *AltGraph(Node, Edge)
func (g *AltGraph(N, E)) ShortestPath(from, to N) []E
type NodeFace(type Edge) interface {
Edges() []Edge
}
type EdgeFace(type Node) interface {
Nodes() (from, to Node)
}
メソッドに自身の型を受け取る Adder interface
type Adder(type T) interface {
Add(T) T
}
func adderSum(type T Adder(T))(data []T) T {
var s T
for _, x := range data {
s = s.Add(x)
}
return s
}
以下、面白そうなものがあれば追記します。
感想
これまでの変更より曖昧さが排除されて、より具体的なイメージがつくようになりました。依然複雑なところは複雑にはなるので賛否両論ありそうですが、contractsの枠に縛られずinterfaceの拡張という方向に落とし込んだのは流石だなと感じました。個人的にはとても楽しみです。
これからより一層議論を重ねて仕様も熟成されてくると思うので、引き続きチェックしていきたいと思います!