JavaScript で実装した XorShift を Haskell に移植しました。既存の実装は知っていますが、ライブラリが追加できない環境で手軽に使いたかったので移植しました。
オンラインで実行 (Repl.it)
移植元
Xorshift
Xorshift は実装が簡単な割に良好な擬似乱数を生成します。
シード値を渡すと擬似乱数を返します。これは次のシード値として使います。
import Data.Bits (xor, shiftL, shiftR)
import Data.Word (Word32)
getNext :: Word32 -> Word32
getNext seed = s3 where
s1 = seed `xor` (shiftL seed 13)
s2 = s1 `xor` (shiftR s1 17)
s3 = s2 `xor` (shiftL s2 5)
シード値から一意に決まるため参照透過です。
シード値
ナノ秒単位で現在時刻を取り出してシード値を作ります。シード値 0 からは 0 しか得られないため、32bit 化して 0 になった場合はやり直します。
import Data.Time.Clock.POSIX (getPOSIXTime)
getSeed :: IO Word32
getSeed = do
t <- getPOSIXTime
let ret = floor $ t * 1e9 :: Word32
if ret == 0 then getSeed else return ret
現在時刻は毎回異なり参照透過ではないため IO モナドです。
浮動小数点数化
得られた擬似乱数を 0 以上 1 未満の浮動小数点数に加工します。
word32MaxNext :: Num a => a
word32MaxNext = 2 ^ 32
getFloat :: Fractional a => Word32 -> a
getFloat w32 = (fromIntegral w32) / word32MaxNext
※ fromIntegral
で型変換してから割り算します。
ラップ
IO モナドの中で簡単に使えるように、シード値の初期化から一連の流れをラップします。
import Data.IORef (newIORef, readIORef, writeIORef)
rand :: Fractional a => Word32 -> IO (IO a)
rand seed = do
s <- newIORef seed
return $ do
oldSeed <- readIORef s
let newSeed = getNext oldSeed
writeIORef s newSeed
return $ getFloat newSeed
> rnd <- rand =<< getSeed
> rnd
=> 0.15449975966475904
> rnd
=> 0.31265981029719114
> rnd
=> 0.979753604857251
オンライン実行環境では [run ▶] で実行した後に、REPL で上記の実行例が試せます。
テスト
10 回のループで値を確認します。
import Control.Monad (sequence_)
main :: IO ()
main = do
rnd <- rand =<< getSeed
sequence_ $ replicate 10 $ print =<< rnd
0.7494283774867654
0.19796228897757828
0.4463836853392422
0.19902197853662074
0.39511674898676574
0.46158441342413425
0.4156568821053952
0.8667732949834317
0.5666300777811557
0.7868200021330267
※ シード値は現在時刻に依存するため、実行結果は毎回異なります。
経緯
Repl.it はオンライン開発環境としては割と使い勝手が良さそうでしたが、Haskell ではライブラリの追加がサポートされていないようです。
※ Python のサポートは手厚く import
するだけで自動的に追加されます。
乱数が必要な用途で使おうとして困りました。仮に JavaScript で実装を始めましたが、標準の乱数ではシード値を設定することができなかったため、自前で Xorshift を用意する羽目になりました。自前で用意するなら Haskell だって一緒だと思い、移植しました。
参考
IO モナドの基本的な使い方は以下を参照してください。