type parameter と linked list
skiplistmapの発表も終わったし、go 1.18 から type parameter のサポート もはじまったということで 自分のpackage も type parameter に対応していこうかなといじりはじめたら、結構罠があったりしたのでそのメモ代わり
そもそも type parameter の仕様ってはっきり記載されてなくて
たぶん type parameter Proposal しかいまだに見るしかないのだろうか? なんかきちんと記載したものがあるなら誰か教えてほしい。
埋め込みの中のfield の unsafe.Offsetof が取れない
まずはいきなりバグにはまったという話
lock free なlinked list である elist_head とかでは
というふうに埋め込んでいる。
定義したリストには 以下のようにmethod をはやす前提だ
ここの例でいうと SampleEntry から ListHead へのoffset を unsafe.Offsetof() をつかってとらないといけない
ここは問題ないのだが
skiplistmap などでは
とかになっている。このときtype parameter をつかって
とかってした場合
を実行すると0 がかえってきてしまう。
どうやら MapHead からのListHead までのoffset を とるらしい。
ということで bug report した。
あっというまに 1.18backport に入り。1.18.3 に入るかと おもったがどうやら間に合わなかったらしくたぶん 1.18.4に入りそう
pointer でmethod をはやしたやつがなやましい。
こんな感じで定義したときに
こういう感じで Base の Int() とかは呼べない。 T が any だから当然。
てことで
これでも呼べない。*Base は CallerInt だけど Base は CallerInt じゃないので
Sample[Hoge] はcontaraint ではじかれて
Sample[*Hoge] だと Sample[T] のメンバーがポインタになってしまう。
こんなことを悩んでいたら
僕らの心の親友であるstackoverflow 先生に How can I instantiate a non-nil pointer of type argument with generic Go? こんな記事があった。
なんていうかどっかでみたtemplate 感があふれてきて、読んでもわからないとか なりそうw
説明すると CallerInt[T] は *T を受けるので PT() してあげると Tのポインタで かつ Int() をもってる状態になるからそれなら呼べるということですね。ポインタでも実体でも method のinterface がというconstraint をつけるにはいまのところこれしか なさそうでこれはなんとかしてほしい感はあるけど難しいかなぁ。
type parameter を使う理由として、interface{} の変換コストがバカにならないので 減らしたいということもある。
しかし このやり方すると PT() のたびに変換はいるし。 制約をいれる時点で interface{} 化してしまうので type any = interface{} だしね
そうすると 単純にinterface{} つかうより constraint で interface{} 変換 PT() と呼ぶところでもう一度実体化と2回走るのでこのあたりはなんとかしてほしい。
method が定義されてるものは method 呼び出しとかはもうちょっと最適化できるだろうと思うがそこはどうなのか
ということで可能な限り constraint を使わないでうまくやるのがパフォーマンスだしつつ恩恵に預かるということで
頭をひねって linked list のtype parameter 版は以下のような感じではじめようかとおもった
Proposal にある
これだと ListElement[T] に next と head があって両方あって空間効率的にすこし無駄なので
こういう感じにすることで List[T] 自体に Prev()/Next() を生やすだけで いままでように 先にmethod を定義するというのは避けられそうな気がしている。