[Dart] Iterable이란?

Jinwook Kim·2024년 8월 12일

Dart

목록 보기
4/7
post-thumbnail

다트에서 리스트(List)나 세트(Set), 맵(Map) 등 컬렉션(Collection) 타입의 데이터를 다루게 되면 마주치게 되는 데이터 타입이 바로 이터러블(Iterable)이다. 예를 들어, 리스트의 where() 메서드를 사용하게 되면 리턴되는 타입이 바로 이터러블인데, 우리는 흔히 이 이터러블을 toList()를 통해 리스트로 바꾸고는 한다. 평소에 자세히 생각하지 않고 지나쳤던 이 이터러블이란 데이터에 대해 알아보고자 한다.

이터러블의 어원은 Iterate이다. 사전적 정의는 반복하다이다. 여기에서도 알 수 있듯, 리스트나 세트, 맵이 이터러블인 것은, 즉 이들 자료형이 반복하면서 속의 아이템을 얻어낼 수 있기 때문이다. 쉽게 말해서, 리스트에서 forwhile로 순차적으로 반복하면서 다음 아이템을 얻을 수 있는 것을 생각하면 되겠다.

이터러블은 반복자라고도 불리는 이터레이터(Iterator)라는 객체를 통해 요소들에 차례대로 접근할 수 있다. 이터레이터의 current로 현재 요소를 얻을 수 있고, moveNext()로 다음 요소에 접근할 수 있다. moveNext()를 사용하면 current가 다음 요소값으로 바뀜과 동시에 불리언 값을 반환하는데, 다음 요소가 존재하면 true를 반환하고, 다음 요소가 없으면, 즉 끝인 경우엔 false를 반환한다. for이나 forEach() 등의 반복문은 내부적으로 moveNext()false일 때까지 요소를 순회하는 원리를 이용한다.

const List<int> numbers = [1, 2, 3];

var iterator = numbers.iterator;

iterator.moveNext(); // true
iterator.current; // 1
iterator.moveNext(); // true
iterator.current; // 2
iterator.moveNext(); // true
iterator.current; // 3
iterator.moveNext(); // false

moveNext()가 불리언 값을 반환한다는 원리를 이용하면, 직접 모든 요소를 순회하는 코드를 구현할 수도 있다.

const List<String> colors = ["blue", "yellow", "red"];

var colorsIterator = colors.iterator;

while (colorsIterator.moveNext()) {
	print(colorsIterator.current);
}

이터러블은 추상 클래스(Abstract Class)이기에, 생성자로 인스턴스를 만들 수는 없지만, 리스트나 세트를 통해 우회적으로 생성이 가능하다.

const Iterable<int> iterable = [1, 2, 3];

리스트와 비슷해 보이지만, 이터러블은 리스트와는 달리 인덱스로 접근이 불가하다.

iterable[0]; // ERROR!

대신 요소에 접근할 때에는 elementAt()을 이용해야 한다. 내부적으로 moveNext()를 반복하는 메커니즘을 이용하기 때문에 O(n)의 시간 복잡도를 가진다.

iterable.elementAt(0); // 1

이터러블은 엄밀히 말해 리스트와는 다르지만, 리스트에서 제공하는 수많은 기능들을 공통적으로 갖고 있다. 사실 리스트나 세트, 맵 또한 이들 기능을 이터러블으로부터 상속을 받은 것이다.

iterable.length; // 3
iterable.isEmpty; // false
iterable.isNotEmpty; // true
iterable.first; // 1
iterable.last; // 3
iterable.contains(3); // true

참고로, 이터러블과 같은 개념은 다른 언어에서도 공통적으로 존재한다. 자바, 파이썬, 자바스크립트에서 이름도 똑같은 Iterable의 개념이 존재한다. 그런데, 왜 리스트를 직접 건드리지 않고 이터러블을 통하는 것일까?

몇 가지 이유를 생각해볼 수 있는데, 먼저, 코드 추상화에 용이하기 때문이다. 다양한 컬렉션 타입의 데이터(리스트, 맵, 세트)를 위한 메서드나 속성을 각각 따로 만드는 것은 매우 비효율적인 일이다. 그래서 이들이 이터러블을 상속하게 만들고, 공통적인 기능들을 이터러블에 정의해놓는 것이다.

또, 다트는 이터러블을 통해 지연 평가(Lazy Evaluation)의 개념을 구현하고 있다. Lazy라는 용어에서도 알 수 있듯, 어떤 연산을 뒤로 미루는 것이다. 예를 들어 다트의 map()이나 where()은 실제 값이 요청되기 전까지는 연산이 수행되지 않는다. 이를 통해 메모리를 효율적으로 사용하고, 성능을 높일 수 있다.

이터러블의 존재는 코드의 안정성에도 유리하다. 예를 들어, 변경이 잦은 리스트가 있다고 하자. 길이가 시도 때도 없이 변하기에, 컴퓨터는 리스트에 안정적으로 접근이 어려워진다. 하지만 이터러블의 개념이 도입되면, moveNext()를 통해 손쉽게 마지막 요소가 어디인지 알 수 있기에, 변화무쌍한 데이터 타입에도 안정적일 수 있다.

이로써 왜 이터러블이란 개념이 다트뿐만 아니라 거의 모든 프로그래밍 언어에서도 존재하는지를 알게 되었다. 이터러블은 코드의 안정성과 확장성에 용이한 개념이기 때문이다.

profile
200 Everything Okay.

0개의 댓글