몇 주 동안이나 기다린 끝에, 친구들과 함께 첫 번째 Dungeons & Dragons (D&D) 게임을 하게 되었어요. 게임의 첫 세션이니만큼, 각 플레이어는 함께 플레이할 캐릭터를 하나씩 만들어야 해요. 캐릭터의 능력치는 6면체 주사위를 굴려서 정해져요. 그런데 그 주사위가 어디 있는 걸까요? 깜짝 놀라 보니, 친구들은 바로 _여러분_이 주사위를 가져오기를 기다리고 있었어요. 어쨌든 D&D를 하자고 한 건 여러분이니까요! 당황한 나머지 주사위를 가져오는 걸 깜빡했다는 걸 깨달아요. 그러면 D&D 게임은 못 하게 되는 셈이죠. 다행히 기본적인 코딩 실력이 있으니, 금방 해결책이 떠올라요. 주사위 굴리기를 시뮬레이션하는 프로그램을 작성하기로요.
Dungeons & Dragons 게임에서는 각 플레이어가 먼저 함께 플레이할 캐릭터를 만들어요. 이 캐릭터에는 여러 가지 중에서도 여섯 가지 능력치가 있어요. 바로 근력, 민첩, 건강, 지능, 지혜, 매력이에요. 이 여섯 능력치의 점수는 무작위로 정해져요. 6면체 주사위 네 개를 굴린 뒤, 가장 큰 세 개의 합을 기록해서 정해요. 이 과정을 각 능력치마다 한 번씩, 총 여섯 번 반복해요.
캐릭터의 초기 생명점은 10 + 캐릭터의 건강 수정치예요. 건강 수정치는 캐릭터의 건강에서 10을 뺀 뒤 2로 나누고, 소수점을 버려서 구해요.
위 규칙을 따르는 무작위 캐릭터 생성기를 작성해요.
예를 들어, 주사위 네 개를 여섯 번 굴린 결과는 다음과 같을 수 있어요.
건강이 3이므로 건강 수정치는 -4이고, 생명점은 6이에요.
대부분의 프로그래밍 언어에는 (의사)난수 생성기가 있지만, 주사위를 굴리도록 설계된 프로그래밍 언어는 거의 없어요. 그런 언어 중 하나가 Troll이에요.
함수형 언어에서 대부분의 함수는 _순수_해요. 즉, 같은 인자 집합에 대해 항상 같은 출력을 반환하고, 그 외에는 아무것도 하지 않아요. 다시 말해, 순수 함수는 결정적이에요.
하지만 정의상 난수는 예측할 수 없고 비결정적이에요. 심지어 의사 난수성, 즉 무작위로 보이는 수열을 반환하는 것조차 순수한 맥락에서는 그리 쉽지 않아요. 의사 난수 생성기(PRNG)는 수열의 다음 수를 반환하기 위해 내부 상태를 계속 추적해야 하기 때문이에요.
Lean은 이 문제에 대해 두 가지 접근법을 제공해요:
PRNG의 내부 상태를 바꿀 수 있게 해주는 모나드를 사용해요.
정확히 이 일을 하는 IO.rand라는 함수가 바로 있어요.
생성기를 인자로 받아, 생성된 의사 난수뿐만 아니라 수열의 다음 수를 위해 "준비된" 갱신된 생성기도 반환하는 순수 함수를 사용해요.
이 연습 문제는 두 번째 접근법을 사용해요. 의사 난수를 생성해야 하는 모든 함수에는 생성기가 전달되고, 그 함수는 값과 갱신된 생성기를 모두 반환해야 해요.
주어진 생성기는 결정적이라는 점에 유의해요. 즉, 항상 같은 값을 만들어요. 수열에서 다음 의사 난수를 생성하려면 갱신된 생성기를 사용해야 해요.
이 문제에서는 카이제곱 검정을 사용해 무작위성을 검사하는데, 유의 수준은 p < 0.0001이에요.
즉, 지침에 따라 능력치를 생성한다면, 올바른 구현이 우연히 테스트에 실패할 확률은 0.01% 미만이에요.
지침에 따르면 능력치는 _편향되지 않은 d6(6면 주사위)를 네 번 굴린 결과 중 가장 큰 세 개의 합_이라는 점에 유의해요.
Exercism에 가입하고 Lean 트랙을 연습 문제 100개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.
주사위 눈을 (겉보기에) 무작위로 만드는 방법을 살펴봐요. 시드, 의사 난수, 그리고 흔히 걸려들기 쉬운 함정에 대해 배워요. 마지막으로, 무작위 값을 반환하는 함수를 테스트할 때 속성 기반 테스트가 얼마나 유용한지 알아봐요.