Iteratee / Enumerator / Enumeratee 란?
번역 : http://qiita.com/sunny4381/items/a711fa72db26c9263b3f
Play Framework의 Iteratee / Enumerator / Enumeratee 는 공식 문서를 읽어도 잘 모르고, 또한 수학적 설명이 적혀 있기도 해서 불필요하게 이해하기 어렵다. 따라서 몇 가지 예를 통해 이해를 하려합니다.
또한, Understanding Play2 Iteratees for Normal Humans (한글번역)
를 참고하며 Play Framework 2.1.0, Scala 2.10을 대상으로하고 있습니다.
Iteratee / Enumerator / Enumeratee을 간단하게 말하면 :
이름 | 설명 |
---|
Iteratee [E, A] | 루프의 내용. 형태 E에서 형태 A를 생성한다. (소비자역할) |
Enumerator [E] | 컬렉션을 일반화 한 것으로 형태 E를 열거한다. 무한 열거 (Streaming) 할 수도있다. (생산자역할) |
Enumeratee [E, A] | 많이 사용하지 않기 때문에 지금은 생각하지 좋다. |
Iterator와 Iteratee의 차이
Scala는 Iterator [E]라는 친숙한 인터페이스가 있습니다.
Iterator [E]와 Iteratee [E, A]의 차이는 Iterator [E]는 어떤 컬렉션에서 생성되는 반면 Iteratee [E, A]는 순수 함수형 프로그래밍에서 iteration 컨셉의 일반화 입니다. 컬렉션 요소들과 독립하며 불변 ( immutable)에서 논블럭 비동기 처리하에 입력 형식 E 및 출력 형식 A가 정적으로 정의되어 있다는 특징 외에도 결과가 필요할 때까지 아무런 실행되지 않는다는 특징이 있습니다.
먼저 iterator 를 상기시켜 봅시다.
1
2
3
4
5
6
7
8
9
10
| val l = List(1, 234, 455, 987)
var total = 0 // will contain the final total
var it = l.iterator
while( it.hasNext ) {
total += it.next
}
total
=> resXXX : Int = 1677
|
예 1 : Int 형 요소와 Enumerator [Int]과 그 합계를 계산하는 Iteratee [Int, Int]
val initialValue : Int = 0
val sumIteratee : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
val intEnumerator1 : Enumerator [ Int ] = Enumerator ( 1 , 234 , 455 , 987 )
val futureTotal1 : Future [ Int ] = intEnumerator1 . run ( sumIteratee )
val total1 : Int = Await . result ( futureTotal1 , Duration . Inf )
println ( "total =" + total1 . toString )
val intEnumerator2 : Enumerator [ Int ] = Enumerator ( 431 , 57 , 668 )
val futureTotal2 : Future [ Int ] = intEnumerator2 . run ( sumIteratee )
val total2 : Int = Await . result ( futureTotal2 , Duration . Inf )
println ( "total =" + total2 . toString )
다음과 같은 결과가 출력됩니다.
total = 1677
total = 1156
Iteratee [E, A] 는 컬렉션과 독립하고 있기 때문에, intEnumerator1과 intEnumerator2 라는 각각 다른 컬렉션에 대해 수행 할 수 있으며 Iteratee [E, A] 는 비동기적으로 작업을 수행하기 위해 Future [Int ]를 리턴받지요.
Future [Int]에서 결과를 꺼내기 위해서 일부러 Await.result () 메소드를 사용하지 않으면 안되기 때문에 조금 귀찮은 느낌일 것입니다. 저도 그렇게 생각합니다.
예 2 : 무한 Enumerator (Streaming Enumerator)
Enumerator는 무한히 열거 할 수 있습니다.
// 500 밀리 초마다 문자열 "current time % s"를 생성하고 계속 Enumerator
val stringGenerator : Enumerator [ String ] = Enumerator . generateM (
play . api . libs . concurrent . Promise . timeout (
Some ( "current time % s " . format (( new java . util . Date ())))
500
)
)
// 콘솔에 출력하는 Iteratee
val printIteratee : Iteratee [ String , Unit ] = Iteratee . foreach { println _ }
// 5 초 기다리고 그 동안 stringGenerator가 생성한 요소 각각에 대해 printIteratee를 실행하는
val future : Future [ Unit ] = stringGenerator . run ( printIteratee )
Await . result ( future , Duration ( 5 , "seconds" )
다음과 같은 결과가 5초동안 출력됩니다.
current time Tue Feb 26 11:55:58 JST 2013
current time Tue Feb 26 11:55:58 JST 2013
current time Tue Feb 26 11:55:59 JST 2013
current time Tue Feb 26 11:55:59 JST 2013
current time Tue Feb 26 11:56:00 JST 2013
current time Tue Feb 26 11:56:00 JST 2013
current time Tue Feb 26 11:56:01 JST 2013
current time Tue Feb 26 11:56:01 JST 2013
current time Tue Feb 26 11:56:02 JST 2013
current time Tue Feb 26 11:56:02 JST 2013
예 3 : Iteratee [E, A]와 Future [Iteratee [E, A]]
Iteratee [E, A]에서 Future [Iteratee [E, A]]로 변환하고 반대로 Future [Iteratee [E, A]]에서 Iteratee [E, A]로 변환 할 수 있습니다. 양측은 교환 법칙이 성립합니다.
val initialValue : Int = 0
val sumIterator : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
val futureSumIterator : Future [ Iteratee [ Int , Int ]] = sumIterator . unflatten . map ( _ . it )
val sumIteratorAgain : Iteratee [ Int , Int ] = Iteratee . flatten ( futureSumIterator )
예 4 : Iteratee [E, A] .feed와 Input
Iteratee[E, A].feed
() 메소드를 사용하면 Enumerator [E]를 사용하지 않고 요소를 하나씩 Iteratee [E, A]에 전달 할 수 있습니다. Iteratee[E, A].feed
() 메서드는 요소를 일반화 한 Input 클래스의 서브클래스인 El, Empty, EOF 중 하나를 제공합니다. 다음은 Input 3 개의 서브 El, Empty, EOF의 개요를 보여줍니다.
이름 | 설명 |
---|
Input.El [E] | 형태 E 요소를 나타냅니다. |
Input.Empty | 빈 요소를 나타냅니다. |
Input.EOF | 반복을 종료합니다. |
다음의 예는 Enumerator를 사용하지 않고, Iteratee[E, A].feed
() 메소드를 사용하여 요소 열 1, 234, 455, 987을 처음부터 순서대로 하나씩줍니다.
val initialValue : Int = 0
var sumIteratee : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
var futureSumIterator : Future [ Iteratee [ Int , Int ] = null
// 1을 Iteratee에 공급하는
futureSumIterator = sumIteratee . feed ( Input . El ( 1 ))
sumIteratee = Iteratee . flatten ( futureSumIterator )
futureSumIterator = sumIteratee . feed ( Input . El ( 234 ))
sumIteratee = Iteratee . flatten ( futureSumIterator )
futureSumIterator = sumIteratee . feed ( Input . El ( 455 ))
sumIteratee = Iteratee . flatten ( futureSumIterator )
futureSumIterator = sumIteratee . feed ( Input . El ( 987 ))
sumIteratee = Iteratee . flatten ( futureSumIterator )
// 합계를 계산하는
val futureTotal : Future [ Int ] = sumIteratee . run
val total : Int = Await . result ( futureTotal , Duration . Inf )
println ( "0 + 1 + 234 + 455 + 987 =" + total . toString )
다음과 같은 결과가 출력됩니다.
0 + 1 + 234 + 455 + 987 = 1677
만약 당신이 Future 사랑한다면
Future [Iteratee [E, A]]를 Iteratee [E, A] 로 일부러 변환없이 Future [Iteratee [E, A]] 그대로 동일한 작업을 수행 할 수 있습니다.
val initialValue : Int = 0
var sumIteratee : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
var futureSumIteratee : Future [ Iteratee [ Int , Int ] = null
futureSumIteratee = sumIteratee . feed ( Input . El ( 1 ))
futureSumIteratee = futureSumIteratee . flatMap ( _ . feed ( Input . El ( 234 )))
futureSumIteratee = futureSumIteratee . flatMap ( _ . feed ( Input . El ( 455 )))
futureSumIteratee = futureSumIteratee . flatMap ( _ . feed ( Input . El ( 987 )))
val totalFuture : Future [ Int ] = futureSumIteratee . flatMap ( _ . run )
val total = Await . result ( totalFuture , Duration . Inf )
println ( "0 + 1 + 234 + 455 + 987 =" + total . toString )
예 5 : Iteratee [E, A]의 불변성 (Immutability)
Iteratee [E, A]는 인스턴스화 된 후 불변의 내부 상태를 변화시키지 않습니다.
val initialValue : Int = 0
val sumIteratee0 : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
val sumIteratee1 : Iteratee [ Int , Int ] = Iteratee . flatten ( sumIteratee0 . feed ( Input . El ( 1 )))
val sumIteratee2 : Iteratee [ Int , Int ] = Iteratee . flatten ( sumIteratee1 . feed ( Input . El ( 234 )))
val sumIteratee2_1 : Iteratee [ Int , Int ] = Iteratee . flatten ( sumIteratee2 . feed ( Input . El ( 583 )))
val sumIteratee2_2 : Iteratee [ Int , Int ] = Iteratee . flatten ( sumIteratee2 . feed ( Input . El ( 162 )))
println ( "0 =" + Await . result ( sumIteratee0 . run , Duration . Inf ))
println ( "0 + 1 =" + Await . result ( sumIteratee1 . run , Duration . Inf ))
println ( "0 + 1 + 234 = " + Await . result ( sumIteratee2 . run , Duration . Inf ))
println ( "0 + 1 + 234 + 583 = " + Await . result ( sumIteratee2_1 . run , Duration . Inf ))
println ( "0 + 1 + 234 + 162 = " + Await . result ( sumIteratee2_2 . run , Duration . Inf ))
다음과 같은 결과가 출력됩니다.
0 = 0
0 + 1 = 1
0 + 1 + 234 = 235
0 + 1 + 234 + 583 = 818
0 + 1 + 234 + 162 = 397
예 6 : Iteratee [E, A]는 결과가 필요할 때까지 아무런 실행되지 않는다
Iteratee [E, A]는 정말 결과가 필요할 때까지 아무것도 실행되지 않습니다.
val initialValue : Int = 0
val sumIteratee0 : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => {
println ( "e =" + e . toString )
total + e
}
}
// feed 한 것만으로는 계산되지 않는
val sumIteratee1 = Iteratee . flatten ( sumIteratee0 . feed ( Input . El ( 1 )))
val sumIteratee2 = Iteratee . flatten ( sumIteratee1 . feed ( Input . El ( 234 )))
val sumIteratee3 = Iteratee . flatten ( sumIteratee2 . feed ( Input . El ( 455 )))
val sumIteratee4 = Iteratee . flatten ( sumIteratee3 . feed ( Input . El ( 987 )))
// 아직 계산되지 않는
val futureTotal : Future [ Int ] = sumIteratee4 . run
println ( "caclulate result" )
val total = Await . result ( futureTotal , Duration . Inf )
println ( "0 + 1 + 234 + 455 + 987 =" + total . toString )
다음과 같은 결과가 출력됩니다.
caclulate result
e = 1
e = 234
e = 455
e = 987
0 + 1 + 234 + 455 + 987 = 1677
예 7 : Input.Empty과 Input.EOF
Input.Empty을 Iteratee [E, A] 에 부여하면 무시됩니다.
Input.EOF을 Iteratee [E, A]에 공급하면 반복을 중지합니다.
val initialValue : Int = 0
val sumIteratee0 : Iteratee [ Int , Int ] = Iteratee . fold ( initialValue ) { ( total , e ) => total + e }
val sumIteratee1 = Iteratee . flatten ( sumIteratee0 . feed ( Input . El ( 1 )))
val sumIteratee2 = Iteratee . flatten ( sumIteratee1 . feed ( Input . Empty ))
val sumIteratee3 = Iteratee . flatten ( sumIteratee2 . feed ( Input . El ( 234 )))
val sumIteratee4 = Iteratee . flatten ( sumIteratee3 . feed ( Input . EOF ))
val sumIteratee5 = Iteratee . flatten ( sumIteratee4 . feed ( Input . El ( 455 )))
val sumIteratee6 = Iteratee . flatten ( sumIteratee5 . feed ( Input . El ( 987 )))
// Input.Empty를 feed 후 feed 값은 계산되는
println ( "0 + 1 + 234 =" + Await . result ( sumIteratee3 . run , Duration . Inf ))
// Input.EOF를 feed 한 이후에 feed 값은 계산되지 않는
println ( "0 + 1 + 234 + 455 =" + Await . result ( sumIteratee5 . run , Duration . Inf ))
println ( "0 + 1 + 234 + 455 + 987 = " + Await . result ( sumIteratee6 . run , Duration . Inf ))
이 프로그램을 실행하면 다음과 같은 결과가 출력됩니다.
0 + 1 + 234 = 235
0 + 1 + 234 + 455 = 235
0 + 1 + 234 + 455 + 987 = 235
결과에서 알 수 있듯이, Input.EOF을 Iteratee [E, A에 미치는 반복을 멈추기 때문에 Input.EOF을 준 후 Input.El (455)과 Input.El (987)는 주어서 총합은 변화하지 않습니다. 또한 Input.EOF을 공급하면 반복을 중지하는 것을 확인할 수 있습니다.