팬 레 터 프로 그래 밍 (15) - 팬 레 터 상태 - 임 의 수 생 성기

11407 단어
OOP 프로그래머 에 게 팬 레 터 상태 변화 (functional state transition) 는 낯 선 과제 다.팬 레 터 상태 변 화 는 팬 레 터 상태 데이터 구조 (functional state) 를 통 해 이 루어 진다.State 는 팬 레 터 프로 그래 밍 에 나타 나 는 형식 (type) 입 니 다.다른 데이터 형식 과 마찬가지 로 State 역시 자신의 팬 레 터 조작 함수 와 조합 함수 (combinators) 가 필요 합 니 다. 우 리 는 다음 장 에서 State 데이터 유형 에 관 한 디자인 방안 을 토론 할 것 입 니 다.
     스테이 트 유형 을 본 격 적 으로 소개 하기 전에 우 리 는 수의 산기 (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)}}}
  }

보 세 요. 코드 가 점점 간결 해 지 는 것 아 닙 니까? 그리고 마치 수학 세계 에 들 어간 것 같 습 니 다. 지금 은 프로 그래 밍 이 마치 고등학교 에서 수학 문 제 를 푸 는 것 처럼 느껴 졌 습 니 다. 함수 설명 을 받 으 면 기 존의 함수 로 해결 할 방법 을 생각 하기 시 작 했 습 니 다. 그리고 유형 을 맞 추고 예전 의 예 를 찾 아 보 세 요......................................순서

좋은 웹페이지 즐겨찾기