2009년 2월 4일 수요일

haskell 에서 랜덤값 사용하기

랜덤하나 부르는것도 힘드네. haskell 의 System.Random 문서를 보고
정리해봤다.

먼저 모든 RNG 는 RandomGen 이란 타입클래스에 속해있는
모냥이군. 음.. RandomGen 의 인스턴스라는 소리를 그 타입클래스에
속해있다.. 라고 표현하는게 맞는 건지 잘 모르겠지만.

class RandomGen g where
  next     :: g -> (Int, g)
  split    :: g -> (g,g)
  genRange :: g -> (Int,Int)

next 는 뭐 간단한 모양이군. 랜덤값 하나를 뽑아주고 이때문에 바뀐 새
상태를 같이 리턴해준다. 뭐 여기까진 이해가 되는데.

split 은 g 를 쪼개주는데.. 문서상에야 설명이 있지만 이해가 잘 안가네.

genRange 는 그냥 그 g 가 뽑아내는 랜덤값의 범위다. 내공이 부족하여
처음에 이 함수 모양만 가지곤 뭐하는지 추측이 안되더군. 단순히 getter
였을줄이야.


위는 타입클래스라.. 실제로 뭔가 돌아가는놈은 위 타입클래스의 인스턴스
일테고 haskell 은 표준으로 StdGen 이란놈을 가지고 있구나.

*Main> :info StdGen
data StdGen = System.Random.StdGen GHC.Int.Int32 GHC.Int.Int32
      -- Defined in System.Random
instance Read StdGen -- Defined in System.Random
instance Show StdGen -- Defined in System.Random
instance RandomGen StdGen -- Defined in System.Random

StdGen 은 밸류 컨트스럭터가 노출이 안되어있고 mkStdGen 이란 함수를
통해서 값을 만들수 있다. 이 함수가 받는 인자는 seed 로 쓰이는
놈이겠지.

자.. 이제 직접 랜덤값을 뽑아낼정도로 함수들을 알게됐으니 일단 불러보자.

*Main> next (mkStdGen 0)
(2147482884,40014 40692)
*Main> genRange (mkStdGen 0)
(0,2147483562)

우왕ㅋ굳ㅋ mkStdGen 0 으로 제네레이터를 만들고 next 로 랜덤값과 새
제네레이터를 뽑아냈다. 여러값을 불러야 하면 새 상태를 적절히 체이닝
해야겠지. 랜덤을 뽑는 행위에 사이드이펙트가 없다는게.. 뭐랄까 응용할
꺼리가 많겠군.


음 그런데 전역상태에 의존해서 랜덤을 생각없이 쓰다가 이렇게 매번 상태를
관리하는 랜덤을 쓰는건 정말 괴롭겠지. 다행히도 haskell 에 IO 모나드에
속한(전역인) StdGen 이 있다. 한마디로 말해 다른 언어에서 랜덤 쓰던것과
동일하게 쓸수 있다. 굳이 새상태를 들고다니는 짓을 안해도 된단말이지.

getStdRandom :: (StdGen -> (a, StdGen)) -> IO a
getStdGen    :: IO StdGen
setStdGen    :: StdGen -> IO ()
newStdGen    :: IO StdGen

getStdGen 와 setStdGen 은 간단한 엑세서 함수다.

무식하게 쓰려면 꺼내서 next 로 새값과 새상태 만들고 새 상태를
setStdGen 으로 박아넣는 식으로 돌아가겠지. 이게 아주 뻔한 작업이 될테니
getStdRandom 함수를 제공해준다. 타입만 봐도 어느정도 돌아가는 모양이
예측이 가능하네.. 하지만 실제 이 소스를 보면 IORef 등 더 공부할게
튀어나온다. 더 깊이는 안보고 넘어가야지..

아 실제로 쓰는건 요리 쓰면 된다.

*Main> getStdRandom next
501819957
*Main> getStdRandom next
557655835
*Main> getStdRandom next
1527018794
*Main> getStdRandom next
1881200278

매번 부를때마다 새로운 값이 튀어나오는게 확인된다.


음 그런데 이렇게 next 를 통해서 값을 가져오면 내가 원하는 모양으로 다시
가공을 해야 하는데(적정 범위안으로 구겨넣는다던지, 이 값을 인덱스로
해서 리스트로부터 랜덤하게 엘리먼트를 뽑는다던지..) 상당히
불편하지... 뭐 불편하다고는 하지만 이미 이렇게 잘 써왔지만.

어쨌거나 haskell 은 위와 같은 가공을 개발자한테 알아서 하라고 그냥 둔게
아니고 적절한 유틸리티 함수들을 제공을 하고 이 함수들을 Random 이란
클래스로 묶어뒀다. 사실 이제까지 내용은 그냥 haskell 은 이러하다.. 란
식이라 머리쓸일이 없었는데 이 Random 클래스는 음.. 좀 어렵군.

class Random a where
  randomR  :: RandomGen g => (a,a) -> g -> (a,g)
  random   :: RandomGen g => g -> (a,g)
  randomRs :: RandomGen g => (a,a) -> g -> [a]
  randoms  :: RandomGen g => g -> [a]
  randomIO :: IO a

먼저 randomR 은 모양만 봐도 next 한 값을 주어진 범위 안으로 구겨넣는
놈이라는걸 알수있다.

*Main> getStdRandom (randomR (0,10))
10
*Main> getStdRandom (randomR (0,10))
9
*Main> getStdRandom (randomR (0,10))
5
*Main> getStdRandom (randomR (0,10))
8

haskell 의 특징인 currying 때문에 위 예제에서 쓰인 randomR (0,10) 의
타입은 정확히 next 와 일치하고 따라서 getStdRandom 의 인자로 사용이
가능하다.. 이런식의 currying 사용은 빨리 익숙해져야 할텐데.

random 은 randomR 의 범위에 리턴받을 타입의 유효범위를
지정해준것. 위에서 봤다시피 그냥 next 의 범위는 genRange 를 불러보면
알겠지만 리턴받을(생성할) 타입의 값과는 무관하다. 따라서 이걸 적절히
바꿔줄 놈이 필요한데 이 함수가 그짓을 해준다.

*Main> getStdRandom random
-864292929

randoms 는 후럴... 희한한 놈이네. 새상태는 리턴해주지 않고 무한히
랜덤값을 찍어내주는 놈이다. 일단 randoms 부르면 함수 안쪽에서 새값을
유지하면서 루프(리커젼)을 돌테니 굳이 새상태를 리턴받지
않는것이겠지.. 음.. 이 무한이란 개념도 내가 지금껏 다뤄오던 언어들에선
접하기 힘든거라 randoms 타입만 봐서는 뭐하는놈인지 정말 애매했다. 만약
내가 이런류의 함수를 만든다면 몇개의 엘리먼트를 뽑을지 인자로 받을텐데
haskell 에서는 그냥 무한리스트를 주고 필요한쪽에서 필요한만큼 가져다
쓰는 방식히 선호되는듯 하다( lazy 하니까 ) 이런 스타일이 어여 손에
익어야 할텐데. (좀더 자세히 말해서 무한리스트를 준다는 표현은
잘못된거지. lazy 하다는 특성때문에 randoms 함수가 불리는 순간에 값이
만들어 지는게 아니니까. 무한리스트를 준다 라는 표현보다는 나중에
필요할때 값을 만들어낼 방법을 미리 정의만 해둔다.. 정도가 맞겠지.)


*Main> take 5 $ (randoms (mkStdGen 0)) :: [Int]
[2092838931,-2143208520,2034827062,-1587933427,-1272503422]

randoms 의 타입을 보면 알겠지만 getStdRandom 하고는 잘 안섞인다(이글을
쓰는 현재 같이 섞어 부르는 방법을 모르겠다. 아마 없을거 같은데)

randomRIO 는 흠. getStdRandom 을 내포한 놈인거 같군. 위 적은 코드중에
getStdRandom 하고 randomR 을 쓴게 있었는데 randomRIO 를 쓰면 한방이구나

*Main> randomRIO (1,10)
6
*Main> randomRIO (1,10)
2
*Main> randomRIO (1,10)
1

randomIO 는 getStdRandom 하고 random 을 섞은놈이구먼.
                                           
*Main> randomIO
-795885308


마지막으로..  Random 이 타입클래스로 빠져있는게 아주 오묘하구나. 내가
랜덤생성을 원하는 타입에 따라 다른 동작을 해준다는 건데 음... 뭔가
느껴지긴 하는데 글로적기는 힘들군. 타입클래스 사용도 좀더 구경하다 보면
감 잡겠지.


              

댓글 없음: