팬 레 터 프로 그래 밍 (15) - 팬 레 터 상태 - 임 의 수 생 성기
스테이 트 유형 을 본 격 적 으로 소개 하기 전에 우 리 는 수의 산기 (RNG: Random Number Generator) 부터 팬 레 터 RNG 의 원리 분석 에서 스테이 트 설계 방안 으로 이 끌 었 다.
우리 가 아 는 자바 RNG 부터 보 자.
//java code
val rng = new java.util.Random //> rng : java.util.Random = java.util.Random@48533e64
rng.nextInt //> res0: Int = -1412360869
rng.nextInt //> res1: Int = 645622520
rng.nextDouble //> res2: Double = 0.4216477872043267
rng.nextDouble //> res3: Double = 2.306856098814869E-4
이것 은 팬 레 터 스타일 의 RNG 가 아 닐 것 입 니 다. 같은 함수 가 인용 할 때마다 다른 결 과 를 얻 을 수 있 습 니 다.팬 함 함수 (pure function) 의 '등 량 교체' 는 여기에서 성립 되 지 않 습 니 다.또한, 우 리 는 상기 Rg 에서 반드시 하나의 상 태 를 유지 하고 매번 업데이트 할 때마다 부대 적 인 영향 (side effect) 이 생 겼 다 고 상상 하기 어렵 지 않 습 니 다. 이것 은 팬 함 순 함수 (pure function) 의 부대 적 인 영향 을 미 치지 않 는 요구 (referencial transparency) 에 위배 되 었 습 니 다.그럼 어떻게 해 야 되 지?팬 레 터 의 방법 은 명확 한 방식 으로 상 태 를 업데이트 하 는 데 중점 을 둔다. 즉, 내부 상 태 를 유지 하지 말고 새로운 상태 와 결 과 를 직접 되 돌려 주 는 것 이다. 아래 와 같다.
trait RNG {
def nextInt: (Int, RNG)
}
상기 방식 에서 우 리 는 팬 레 터 상태 변화 (state transition) 의 디자인 을 대체적으로 얻 을 수 있다.
우리 에 게 이런 결과 가 있다 고 가정 하 자.
class Foo {
var s: FooState = ...
def bar: Bar
def baz: Int
}
만약 bar 와 baz 가 Foo 의 상 태 를 바 꿀 것 이 라면, 우 리 는 이렇게 bar, baz 함 수 를 설계 해 야 합 니 다.
trait Foo {
def bar: (Bar, Foo)
def baz: (Int, Foo)
}
자, 그럼 팬 레 터 식 의 임 의 생산 기 RNG 를 설계 해 보 겠 습 니 다.
trait RNG {
def nextInt: (Int, RNG)
}
// RNG, RNG
case class seedRNG(seed: Long) extends RNG {
def nextInt: (Int, RNG) = {
val seed2 = (seed*0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
((seed2 >>> 16).asInstanceOf[Int], seedRNG(seed2))
}
}
val rng = seedRNG(System.currentTimeMillis) //> rng : ch6.rng.seedRNG = seedRNG(1427201377395)
val (i,rng2) = rng.nextInt //> i : Int = 1516062234
//| rng2 : ch6.rng.RNG = seedRNG(99356654630658)
val (i2,rng3) = rng2.nextInt //> i2 : Int = 483165502
//| rng3 : ch6.rng.RNG = seedRNG(31664734402533)
val (i3,rng4) = rng2.nextInt //> i3 : Int = 483165502
//| rng4 : ch6.rng.RNG = seedRNG(31664734402533)
함수 nextInt 는 임의의 수 와 새로운 RNG 를 되 돌려 줍 니 다.만약 에 우리 가 같은 RNG 를 사용 해서 생 긴 결과 가 똑 같은 r2 = = r3 라면 팬 레 터 스타일 을 나 타 냈 다.
모든 유형의 팬 함 식 난수 생산 기 는 Int RNG nextInt 에서 유도 할 수 있 습 니 다.
object RNG {
// 0.0 - 1.0 Double
def nextDouble(rng: RNG): (Double, RNG) = {
val (i,rng2) = rng.nextInt
if ( i == Int.MaxValue ) (0.0, rng2)
else ( i.toDouble / Int.MaxValue.toDouble, rng2)
}
def nextPositiveInt(rng: RNG): (Int, RNG) = {
val (i, rng2) = rng.nextInt
if ( i == Int.MaxValue ) (Int.MaxValue, rng2)
else (i.abs, rng2)
}
def nextBoolean(rng: RNG): (Boolean, RNG) = {
rng.nextInt match {
case (i, rng2) => (i % 2 == 0, rng2)
}
}
// tuple (Int, Double)
def nextIntDouble(rng: RNG): ((Int, Double), RNG) = {
val (i,rng2) = nextPositiveInt(rng)
val (d,rng3) = nextDouble(rng2)
((i,d),rng3)
}
// n List
def nextInts(n: Int)(rng: RNG): (List[Int], RNG) = {
def go(n: Int, rng: RNG, acc: List[Int]): (List[Int], RNG) = {
if ( n <= 0 ) (acc, rng)
else {
val (i,rng2) = rng.nextInt
go(n-1,rng2,i :: acc)
}
}
go(n,rng,Nil: List[Int])
}
}
import RNG._
val rng = seedRNG(System.currentTimeMillis) //> rng : ch6.rng.seedRNG = seedRNG(1427204725766)
val (d, rng5) = nextDouble(rng) //> d : Double = 0.6090536781628866
//| rng5 : ch6.rng.RNG = seedRNG(85716684903065)
val (b, rng6) = nextBoolean(rng5) //> b : Boolean = false
//| rng6 : ch6.rng.RNG = seedRNG(123054239736112)
val ((i5,d2), rng7) = nextIntDouble(rng6) //> i5 : Int = 1054924659
//| d2 : Double = 0.8877875771782303
//| rng7 : ch6.rng.RNG = seedRNG(124944993788778)
val (ints, rng8) = nextInts(5)(rng7) //> ints : List[Int] = List(-782449771, -1992066525, -825651621, -440562357, 7
//| 00809062)
//| rng8 : ch6.rng.RNG = seedRNG(230196348539649)
위의 예 에서 이러한 함수 가 일치 하 는 디자인 을 발견 할 수 있 습 니 다. func (RNG): (A, RNG), 즉 RNG = > (A, RNG) 는 lambda function 이 고 순수한 함수 유형 입 니 다.이렇게 보면 임 의 수 생산 기 는 하나의 함수 유형 으로 우 리 는 생산 기 를 함수 의 매개 변수 나 반환 값 으로 사용 할 수 있다.그러면 우 리 는 새로운 유형 을 만들어 보 자.
type Rand[+A] = RNG => (A, RNG)
랜 드 는 바로 마음대로 생산 기 를 세 는 것 이다. 우 리 는 그것 을 전달 할 수 있다.그러면 랜 드 타 입 을 사용 해 보 겠 습 니 다.
type Rand[+A] = RNG => (A, RNG)
def rnInt: Rand[Int] = _.nextInt
def rnPositiveInt: Rand[Int] = nextPositiveInt
}
import RNG._
val rng = seedRNG(System.currentTimeMillis) //> rng : ch6.rng.seedRNG = seedRNG(1427239224137)
rnInt(rng) //> res0: (Int, ch6.rng.RNG) = (-1225681606,seedRNG(201148706995232))
rnPositiveInt(rng) //> res1: (Int, ch6.rng.RNG) = (1225681606,seedRNG(201148706995232))
상기 예 에서 우 리 는 일부 어색 한 것 을 알 수 있 습 니 다. 함수 설명 def rnInt: Rand [Int] 는 인자 가 없 는 것 같 지만 사용 할 때 rnInt (rng) 는 인자 가 필요 합 니 다.그러나 우 리 는 Func (RNG): (A, RNG) 의 lambda 표현 형식 인 RNG = > (A, RNG) 를 생각하면 자 연 스 럽 게 이해 할 수 있다.
우리 가 이미 수의 생산 기 를 랜 드 유형 으로 바 꾸 었 으 니, 우 리 는 수의 생산 기 를 편리 하 게 조합 하고 변형 시 킬 수 있 겠 지?가장 기본 적 인 구성 요소 (combinator) 를 먼저 봅 니 다.
def unit[A](a: A): Rand[A] = {
rng => (a, rng)
}
언뜻 보기 에는 이 유닛 은 아무 소 용이 없 는 것 같 습 니 다. 아무 일 도 하지 않 았 습 니 다.실제로 유닛 은 함수 조합의 가장 기본 적 인 구성 요소 라 고 할 수 있 으 며 매우 유용 하 다.유닛 은 하나의 역할 만 있 습 니 다. a 를 고급 랜 드 에 포함 시 키 는 것 입 니 다. 다시 말 하면 낮은 등급 의 A 를 고급 랜 드 로 올 리 면 다른 랜 드 와 조합 하거나 랜 드 함 수 를 사용 하여 스스로 변형 시 킬 수 있 습 니 다.이 간단 한 예 는 반환 유형 에서 기능 을 얻어 이러한 팬 레 터 프로 그래 밍 스타일 을 실현 하 는 것 을 다시 한 번 제시 했다. Band [A] > > RNG = > (A, RNG) 즉, RNG 하 나 를 주면 나 는 하나 (A, RNG) 를 되 돌 릴 수 있다.
랜 드 형식 에 대한 맵 을 하나 더 추가 합 니 다:
def map[A,B](ra: Rand[A])(f: A => B): Rand[B] = {
rng => {
val (x, rng2) = ra(rng)
(f(x), rng2)
}
}
상기 함수 의 실현 방식 을 통 해 맵 은 임의의 수 생 성기 의 결 과 를 변환 한 후에 도 랜 드 의 고급 형식 형식 을 유지 한 다 는 것 을 알 수 있다.또한 이전 예 와 마찬가지 로 우 리 는 리 턴 타 입 랜 드 를 보 자마자 Rg = > {... (a2, Rg 2)} 이런 실현 스타일 을 생각해 야 한다.
맵 을 사용 하 는 예 를 들 면:
def rnDouble: Rand[Double] = { map(rnPositiveInt){ _ / (Int.MaxValue.toDouble + 1) } }
val rng = seedRNG(System.currentTimeMillis) //> rng : ch6.rng.seedRNG = seedRNG(1427245671833)
rnDouble(rng) //> res0: (Double, ch6.rng.RNG) = (0.6156660546548665,seedRNG(86647294261296))
깔끔 하 다.다음은 두 개의 Rand 를 결합 해 보 겠 습 니 다.
def map2[A,B,C](ra: Rand[A], rb: Rand[B])(f: (A,B) => C): Rand[C] = {
rng => {
val (x,rng2) = ra(rng)
val (y,rng3) = rb(rng2)
(f(x,y), rng3)
}
}
맵 2 를 사용 하 는 예 를 몇 개 쓰 십시오:
def rnPair[A,B](ra: Rand[A], rb: Rand[B]): Rand[(A, B)] = {
map2(ra,rb){(_,_)}
}
def rnIntDoublePair: Rand[(Int, Double)] = {
rnPair(rnInt,rnDouble)
}
def rnDoubleIntPair: Rand[(Double, Int)] = {
rnPair(rnDouble,rnInt)
}
}
import RNG._
val rng = seedRNG(System.currentTimeMillis) //> rng : ch6.rng.seedRNG = seedRNG(1427246457588)
rnIntDoublePair(rng) //> res0: ((Int, Double), ch6.rng.RNG) = ((-1302173232,0.355998701415956),seedR
//| NG(231372613633230))
rnDoubleIntPair(rng) //> res1: ((Double, Int), ch6.rng.RNG) = ((0.6063716635107994,-764501390),seedR
//| NG(231372613633230))
우리 가 맵 2 로 두 개의 랜 드 를 결합 할 수 있다 면 하나의 List 안의 랜 드 를 하나의 랜 드 로 결합 할 수 있 습 니까?
//
def sequence[A](lr: List[Rand[A]]): Rand[List[A]] = {
rng => {
def go(xs: List[Rand[A]], r: RNG, acc: List[A]): (List[A], RNG) = {
lr match {
case Nil => (acc,rng)
case h :: t => {
val (x, rng2) = h(rng)
go(t,rng2,x::acc)
}
}
}
go(lr,rng,List())
}
}
// foldRight
def sequence_1[A](lr: List[Rand[A]]): Rand[List[A]] = {
lr.foldRight(unit(Nil: List[A])) {(h,t) => map2(h,t)(_ :: _)}
}
위 foldlight 구현 방식 에서 유닛 의 역할 을 느 낄 수 있 습 니 다: List[A] 칸 을 올 려 야 랜 드 의 맵 2 유형 과 일치 할 수 있 습 니 다. 맵 2, sequence 를 사용 하여 랜 드 를 조작 할 때 우 리 는 이 RNG 를 신경 쓸 필요 가 없습니다. 이것 은 우리 가 이미 더 높 은 추상 층 으로 발전 했다 는 것 을 증명 합 니 다. 이것 도 팬 레 터 프로 그래 밍 의 진의 입 니 다. 하지만 맵, 맵 2, sequence 만으로 모든 일 을 할 수 있 습 니까? 아 닙 니 다! 그것 을 생각해 보 세 요.수의 생 성기 PositiveInt, rnInt 로 Int. MinValue 가 생 긴 다 면 Int. MinValue 가 아 닌 숫자 가 생 길 때 까지 다시 생 성 합 니 다. map 로 구현 해 보 겠 습 니 다.
def positiveInt: Rand[Int] = {
map(int) { i =>
if (i != Int.MinValue) i.abs else ??
}
}
map 의 조작 함수 유형 은 f: A = > B 입 니 다. 중복 연산 positiveInt 반환 유형 은 Rand [A] 입 니 다. 일치 하지 않 으 면 여기에 걸 립 니 다. 그러나 이 문 제 는 flatMap 으로 해결 할 수 있 습 니 다. flatMap 의 조작 함수 f: A = > Rand [B] 로 유형 이 일치 하기 때 문 입 니 다. 우 리 는 unit 로 i. abs 를 승격 하면 flatMap 으로 문 제 를 해결 할 수 있 습 니 다.
빨리 flatMap 실현:
def flatMap[A,B](ra: Rand[A])(f: A => Rand[B]): Rand[B] = {
rng => {
val (x, rng2) = ra(rng)
f(x)(rng2)
}
}
def positiveIntByFlatMap: Rand[Int] = {
flatMap(rnInt) {
a => {
if ( a != Int.MinValue) unit(a.abs)
else positiveIntByFlatMap
}
}
}
네, flatMap 으로 positiveInt 를 실현 할 수 있 습 니 다. 그러면 flatMap 으로 map, map 2 를 실현 할 수 있 습 니까? 아래 의 구체 적 인 실현 방법 을 보 세 요.
def mapByFlatMap[A,B](ra: Rand[A])(f: A => B): Rand[B] = {
flatMap(ra){ a => unit(f(a)) }
}
def map2ByFlatMap[A,B,C](ra: Rand[A], rb: Rand[B])(f: (A,B) => C): Rand[C] = {
flatMap(ra){ a => map(rb) {b => f(a,b)}}
}
def map3ByFlatMap[A,B,C,D](ra: Rand[A], rb: Rand[B], rc: Rand[C])(f: (A,B,C) => D): Rand[D] = {
flatMap(ra){ a => flatMap(rb) {b => map(rc) {c => f(a,b,c)}}}
}
보 세 요. 코드 가 점점 간결 해 지 는 것 아 닙 니까? 그리고 마치 수학 세계 에 들 어간 것 같 습 니 다. 지금 은 프로 그래 밍 이 마치 고등학교 에서 수학 문 제 를 푸 는 것 처럼 느껴 졌 습 니 다. 함수 설명 을 받 으 면 기 존의 함수 로 해결 할 방법 을 생각 하기 시 작 했 습 니 다. 그리고 유형 을 맞 추고 예전 의 예 를 찾 아 보 세 요......................................순서
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.