Go 素振り日記

これは日記なので, 読むことでなんらか知見が深まることを期待したり議論を始めようなどと思わないこと.

Splittable PRNG を Go に移植した

たまには Go 力 (ぢから) が鈍らないように素振りをしておきます. 素振りが目的なので特に新規性とかはありません.

github.com

Splittable PRNG っていうのはこのときのやつ:

susisu.hatenablog.com

かいつまんで説明すると, 1 つの PRNG から, 独立と見なせる別の PRNG を作り出せるというもの.

PRNG から生成された値を seed にして新しい PRNG を作れば良いのでは, と思いましたか? 現実はそう単純とは限らなくて, 極端な例では線形合同法xorshift みたいなアルゴリズムでは生成される値が PRNG の内部状態そのものなので, 全く同じ PRNG が複製されてしまったり, あるいは他のアルゴリズムではそこまでではないとしても, PRNG の間になんらか相関が生まれるということがあり得ます. まあそれで十分ならそれでも良いのですが.

都度真の乱数を持ってきて PRNG を初期化しても良いですが, それでは seed を固定して同じ状況を再現させるということができません. もしくは同じ PRNG を直列に使うことでそもそも複製自体を不要にもできたりしますが, 今度は並列化するなど実行順序を柔軟にコントロールすることができなくなります. せっかく Go なのに.

などと言いつつ, これが嬉しい局面は現実に存在するだろうか?

Go の math/rand

といったように一応なんらか使えそうな状況は想定できつつ, なんで今まで移植しなかったかというと, 標準の math/rand のインターフェースがなんかしっくりこなくて, これに合わせてライブラリを設計したいと思えなかったから.

math/rand には Source という RNG を抽象化するインターフェースと, それを便利に使うための Rand という構造体があります. これによっていろんな RNG を同じように使えそうですが...

s := rand.NewSource(42)
r := rand.New(s)
v := r.Float64()

いやちょっと待ってほしい. まず Seed(seed int64) というのはやや仮定が強いのでは?

r := rand.New(s)
r.Seed(666)

当然真の RNG は (使いたいかは別として) お呼びでなさそうですし, 64bit 以上の seed を与えられる PRNG にも制限がかかる形です. そもそも使うことがなければ実装せず無視するのでも良いんですが, それはそれで気分的に微妙...

そしてこの SourceRand という抽象化自体なんか微妙な気がするというか, やっぱりあんまり RNG ごとの拡張を許していない感じがします. 例えば RNG に独自のメソッドがあるとする (具体的には splittable PRNG では Split() みたいなメソッドがある) と, 使い方はこういう感じになるはず:

s := NewMySource()
r := rand.New(s)
// 一般的な乱数生成には r を使う
v := r.Float64()
// RNG ごとの拡張部分には s を使う
s.MyMethod()

いっそのこと素朴な関数にしてしまった方がシンプルな気がするんですが, こういう一般的な状況がそもそも一般的ではないのかな...

s := NewMySource()
// s のことだけ考えていれば良い
v := rand.Float64(s)
s.MyMethod()

で, どうしたかというと, math/rand が期待するものでないなら, 期待したものを提供する別のインターフェースを用意しちゃったわけですね.

github.com

お前がそう思うんならそうなんだろう, お前ん中ではな.