fugafuga.write

日々のログ

すごいH本 part64

ランダム性

ランダム値を扱う。 Haskell は参照透明性を持つので関数が同じ引数で2回呼ばれた場合、 必ず同じ値を返すようになっていなければならない。 その中でランダム値をどのように作るのかを見る。

System.Random モジュールに random という関数があるので見ゆ

*Main Lib> :t random

<interactive>:1:1: error: Variable not in scope: random
*Main Lib> import System.Random

<no location info>: error:
    Could not find module ‘System.Random’
    It is a member of the hidden package ‘random-1.1@random-1.1-9tceXaeYIMZ4JrKq20Egog’.

めっちゃ怒られる

my-project.cabalを編集

executable my-project-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , my-project
                     , random <= 追加
  default-language:    Haskell2010

stack に対して依存パッケージを指定してあげないとダメな模様

再度チャレンジ

*Main Lib> import System.Random
*Main Lib System.Random> :t random
random :: (RandomGen g, Random a) => g -> (a, g)

いけた。

  • RandomGen 型クラスはランダム性の源として扱える型を表現
  • Random 型クラスはランダムな値になることができる型を表現

乱数ジェネレータを受け取り、ランダムな値と新しい乱数ジェネレータを返す

乱数ジェネレータが欲しい。 System.Random に StdGen という型がある。 これは RandomGen のインスタンスになっている。

*Main Lib System.Random> :i RandomGen
class RandomGen g where
  next :: g -> (Int, g)
  genRange :: g -> (Int, Int)
  split :: g -> (g, g)
  {-# MINIMAL next, split #-}
        -- Defined in ‘System.Random’
instance RandomGen StdGen -- Defined in ‘System.Random’

乱数ジェネレータを自分で作るには mkStdGen 関数を使う。

*Main Lib System.Random> :t mkStdGen
mkStdGen :: Int -> StdGen

整数を引数にとってその値を元に乱数ジェネレータを返す。

ランダム値を作ってみる

*Main Lib System.Random> random (mkStdGen 100) :: (Int, StdGen)
(-3633736515773289454,693699796 2103410263)
*Main Lib System.Random> random (mkStdGen 100) :: (Int, StdGen)
(-3633736515773289454,693699796 2103410263)
*Main Lib System.Random> random (mkStdGen 223344) :: (Int, StdGen)
(-262505389206341605,1147014161 2103410263)

タプルの1番目の値がランダム値、2番目が新しい乱数ジェネレータの数値としての表現。 同じ乱数ジェネレータを渡すと同じ値が返ってくる。

型注釈をつけることで、Int 以外のランダム値を得ることができる。

*Main Lib System.Random> random (mkStdGen 100) :: (Bool, StdGen)
(True,4041414 40692)
*Main Lib System.Random> random (mkStdGen 100) :: (Float, StdGen)
(0.6512469,651872571 1655838864)
*Main Lib System.Random> random (mkStdGen 100) :: (Integer, StdGen)
(-3633736515773289454,693699796 2103410263)

コイントス

3回のコイントスをシミュレートする関数を書く

import System.Random

threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in (firstCoin, secondCoin, thirdCoin)

実行

*Main System.Random> threeCoins (mkStdGen 100)
(True,False,False)
*Main System.Random> threeCoins (mkStdGen 200)
(True,False,True)
*Main System.Random> threeCoins (mkStdGen 300)
(True,True,True)

random gen で *Coin の値が 型注釈無しで Bool 値になるのは、 関数の型宣言に書いてあるため、Haskellが型推論できるから。

所感

他の言語でもランダム値の生成については今まであまり触れてこなかったので、 Haskell でどのように乱数が生成されるのかを知れて良かった。

あと、let めっちゃかっこよく使っててすごい。

(参考)

https://qiita.com/katsuyan/items/a132d7bf6817f19af2d6

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る