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用のお手軽乱数が出来ました! やったね!