SML/njで乱数
SMLの標準(Basis)では, どうやら乱数を提供する関数は用意されていない様子です.
SML/njにはRandom/RAND structureが用意されていますがプリミティブ過ぎて使いづらいです.
(どうしても関数内部に状態を持たせたくない様子…?)
ですがMoscowMLという処理系には,
最初から疑似乱数を生成する関数が分かりやすい形で提供されています.
今回はSML/njで提供されているモジュールを使ってMoscowMLのRandom structureと同等な物を定義してみました.
(* newgen()を同時刻に複数回呼ぶと同じ値になるバグを修正しました…orz *)
signature
とりあえずインターフェースから.
signature MOSCOWRANDOM = sig type generator val newgenseed : real -> generator val newgen : unit -> generator val random : generator -> real val randomlist : int * generator -> real list val range : int * int -> generator -> int val rangelist : int * int -> int * generator -> int list end
見れば明らかですね.
newgenで生成した値を整数または実数に変換します.
structure
実装
structure MoscowRandom :> MOSCOWRANDOM = struct structure R = Rand structure T = Time type generator = R.rand fun newgenseed r = let val now_num = Word31.fromLargeInt $ T.toSeconds $ T.fromReal r in R.mkRandom now_num () end (* 追記: 内部に時刻で初期化した種を持つ *) val now_num = Word31.fromLargeInt $ T.toMilliseconds $ T.now () val ctx = ref (R.mkRandom now_num ()) (* 変更: 適用される度にrandomによってctxを更新する *) fun newgen () = (ctx:=R.random (!ctx); !ctx) fun random r = R.norm r fun randomlist (n, r) = let val ctx = ref (newgen ()) in List.tabulate (n, fn _=>(ctx:=R.random (!ctx); random (!ctx))) end fun range (from,to) r = R.range (from,to) r fun rangelist (from,to) (n, r) = let val ctx = ref (newgen ()) in List.tabulate (n, fn _=>(ctx:=R.random (!ctx); range (from, to) (!ctx))) end end
randomlistとrangelistでは, 単純にnewgen()を何度も評価すればよさげですが,
時間が掛かりそうなのでRand.randomを使っています.
どうでもいいですがRand以外は標準のstructureだけを使っているハズです.
実行
例としてrangelistを呼び出してみます.
- structure MR = MoscowML; structure MR : MOSCOWRANDOM - MR.rangelist (0,10) (10, MR.newgen()); val it = [9,10,2,10,2,0,2,5,4,0] : int list - MR.rangelist (0,10) (10, MR.newgen()); val it = [6,10,6,7,0,0,6,3,1,8] : int list - MR.rangelist (0,10) (10, MR.newgen()); val it = [3,10,10,4,9,1,0,0,10,5] : int list
たしかに呼び出すたびに毎回異なる値が出力されます.
まとめ
SML/nj用のお手軽乱数が出来ました! やったね!