public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence { this.iterator() }
}
val stream = (1..10).asSequence().asStream()
stream.reduce{acc, s -> acc+s}.get()
stream.fold(0){acc,s -> acc+s} //안된다.
설명 : [1,2,3,4,5]를 filter 메소드를 2번 사용하고, map을 하는 코드이다.
val collection = intArrayOf(1,2,3,4,5).filter { it % 2 !=0 }.filter { it % 1 ==0 }.map { it + 1 }
밑의 자바코드는 위의 코틀린 코드를 decompiling 한 것이다.
map 하기 전의 두번의 filter를 하고 2번의 임시 collection을 만들고 있다. [1,2,3,4,5] 에서 홀수인 것만 filter를 한 결과값 [1, 3, 5]에 다시 1로 나누어떨어지는 수를 filter하여 [1, 3, 5] 란 결과값을 얻었고, 그것을 다시 연산 +1 을 하여 결과값 [2, 4, 6]을 만들어 내고 있다. 이 과정에서 loop 문은 총 11번 실행한 것을 확인할 수 있다.
public final class TestKt {
public static final void main() {
int[] $this$filter$iv = new int[]{1, 2, 3, 4, 5};
int $i$f$map = false;
Collection destination$iv$iv = (Collection)(new ArrayList());
int $i$f$mapTo = false;
int[] var6 = $this$filter$iv;
int var7 = $this$filter$iv.length;
int it;
for(it = 0; it < var7; ++it) {
int element$iv$iv = var6[it];
int var11 = false;
if (element$iv$iv % 2 != 0) {
destination$iv$iv.add(element$iv$iv);
}
}
Iterable $this$map$iv = (Iterable)((List)destination$iv$iv);
$i$f$map = false;
destination$iv$iv = (Collection)(new ArrayList());
$i$f$mapTo = false;
Iterator var15 = $this$map$iv.iterator();
Object item$iv$iv;
boolean var17;
while(var15.hasNext()) {
item$iv$iv = var15.next();
it = ((Number)item$iv$iv).intValue();
var17 = false;
if (it % 1 == 0) {
destination$iv$iv.add(item$iv$iv);
}
}
$this$map$iv = (Iterable)((List)destination$iv$iv);
$i$f$map = false;
destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($this$map$iv, 10)));
$i$f$mapTo = false;
var15 = $this$map$iv.iterator();
while(var15.hasNext()) {
item$iv$iv = var15.next();
it = ((Number)item$iv$iv).intValue();
var17 = false;
Integer var13 = it + 1;
destination$iv$iv.add(var13);
}
List sequence = (List)destination$iv$iv;
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
asSequence를 사용할 경우, 실제로 5번의 루프를 실행하고 종료가 될것이다. 그 이유는 sequence가 filter와 map 을 사용하여 함수로써 갖고 있고, 실행하기 전까지는 함수가 실행이 되지 않기 때문이다. 최종 연산을 할때 연산을 한 후의 값을 갖게 된다. 이 방식은 함수형 프로그래밍에서 사용하는 방식으로(실제로 함수형에선 어떻게 구현되어지는지 아직은 모른다.), 객체지향언어인 코틀린에서는 실제로 구현 코드는 저런 형태로 실행되도록 구현되어져 있다.
val sequence = intArrayOf(1,2,3,4,5).asSequence().filter { it % 2 !=0 }.filter { it % 1 ==0 }.map { it + 1 }
위에서 사용한 filter와 map은 밑에서 SequencesKt.filter와 SequencesKt.map으로 실행 되고 있고, 맨 처음에 SequencesKt.filter() 를 보면 시퀀스 배열 ArraysKt.asSequence(new int[]{1, 2, 3, 4, 5})과 필터에서 사용될 람다( (Function1)null.INSTANCE)를 파라미터로 넣고있다.
밑의 자바코드 또한 위의 코틀린 코드를 decompiling을 한 코드이다.
public final class TestKt {
public static final void main() {
Sequence sequence = SequencesKt.map(SequencesKt.filter(SequencesKt.filter(ArraysKt.asSequence(new int[]{1, 2, 3, 4, 5}), (Function1)null.INSTANCE), (Function1)null.INSTANCE), (Function1)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
인자값으로 sequence = ArraysKt.asSequence(new int[]{1, 2, 3, 4, 5})와 predicate SequencesKt.filter()를 받고 있다. 그리고 Sequence를 상속하여 iterator과 내부 인터페이스를 구현하고 있다. iterator 메소드 안에서 iterator는 인자값으로 받은 sequence로 itertor를 만들고 있다. calcNext는 조건에 맞는지 확인해 주며 next에서 조건에 맞으면 반환하고 있다(return을 사용하여 루프를 끊어주고 있음.). 조건에 맞지 않다면 바로 넘어간다.(return에 걸리지 않음.)
위에서 filter의 코틀린을 자바로 decompiling한 내부구현 코드를 보면 밑의 코드와 같이 구현 되어져 있다.
internal class FilteringSequence<T>(
private val sequence: Sequence<T>,
private val sendWhen: Boolean = true,
private val predicate: (T) -> Boolean
) : Sequence<T> {
override fun iterator(): Iterator<T> = object : Iterator<T> {
val iterator = sequence.iterator()
var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
var nextItem: T? = null
private fun calcNext() {
while (iterator.hasNext()) {
val item = iterator.next()
if (predicate(item) == sendWhen) {
nextItem = item
nextState = 1
return
}
}
nextState = 0
}
override fun next(): T {
if (nextState == -1)
calcNext()
if (nextState == 0)
throw NoSuchElementException()
val result = nextItem
nextItem = null
nextState = -1
@Suppress("UNCHECKED_CAST")
return result as T
}
override fun hasNext(): Boolean {
if (nextState == -1)
calcNext()
return nextState == 1
}
}
}
map은 java코드로 아래와 같은 코드로 구현되어져 있고 이것도 위와 유사하게 Sequence를 상속받아 iterator를 구현하고 있다. 인자값으로는 sequence를 FilteringSequence : Sequence 타입 인자값으로 받는다. 그리고 transformer라는 람다를 받고 있다. 이곳에서의 iterator 메소드 안에서는 우리가 인자값으로 받은 sequence(FilteringSequence타입)를 사용하여 itertor()메소드를 사용하고 있다. next에서는 인자값 sequence로부터 만든 iterator 메소드로 만든 iterator(FilteringSequence타입)의 next()를 구현한다. next()는 위에서 만든 방식으로 실행이 될것이고, 인자로 받은 람다의 인자값으로 next()로 얻은 값을 넣어 수행할 것이다.
internal class TransformingSequence<T, R>
constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {
override fun iterator(): Iterator<R> = object : Iterator<R> {
val iterator = sequence.iterator()
override fun next(): R {
return transformer(iterator.next())
}
override fun hasNext(): Boolean {
return iterator.hasNext()
}
}
internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {
return FlatteningSequence<T, R, E>(sequence, transformer, iterator)
}
}
asSequence를 사용하여 메서드를 channing 하는 방식이 위와같은 방법으로 구현되어져 있다.
내가 보면서 발견한 것은 위의 방법에는 데코레이션 패턴이 적용되어져 있다는 것이다. 매번 자신과 같은 타입을 인자값(위에서는 sequence)으로 넘기고, 각각들이 동일한 인터페이스를 다르게 구현하며, 메소드의 실행이 데코레이션에 들어간 각각의 객체들에게 채이닝 되는 현상을 볼 수 있었다.