diff --git a/ko/overviews/collections/arrays.md b/ko/overviews/collections/arrays.md new file mode 100644 index 0000000000..24456c151e --- /dev/null +++ b/ko/overviews/collections/arrays.md @@ -0,0 +1,119 @@ +--- +layout: overview-large +title: 배열 + +disqus: true + +partof: collections +num: 10 +language: ko +--- + +[배열(Array)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html)은 스칼라에 있는 특별한 종류의 컬렉션이다. 한편, 스칼라 배열은 자바의 배열과 일대일 대응한다. 즉, 스칼라 배열 `Array[Int]`는 자바의 `int[]`로 표현되며, `Array[Double]`은 자바의 `double[]`로, `Array[String]`은 `Java String[]`로 표현될 수 있다. 그러나 이와 동시에, 스칼라 배열은 자바 배열 유사체보다 훨씬 많은 것을 제공한다. 첫 번째로, 스칼라 배열은 **제너릭(Generic)** 할 수 있다. 이는 `Array[T]`를 사용할 수 있다는 의미이며, `T`는 타입 매개 변수나 추상 타입을 의미한다. 두 번째로, 스칼라 배열은 스칼라 시퀀스와 호환된다. `Seq[T]`가 필요할 때, `Array[T]`를 전달할 수 있다는 것을 의미한다. 마지막으로, 스칼라 배열은 모든 시퀀스 연산을 지원한다. 직접 예시를 통해서 알아보자: + + scala> val a1 = Array(1, 2, 3) + a1: Array[Int] = Array(1, 2, 3) + scala> val a2 = a1 map (_ * 3) + a2: Array[Int] = Array(3, 6, 9) + scala> val a3 = a2 filter (_ % 2 != 0) + a3: Array[Int] = Array(3, 9) + scala> a3.reverse + res1: Array[Int] = Array(9, 3) + +주어진 스칼라 배열은 자바의 배열과 똑같이 표현된다. 스칼라는 이러한 추가적인 기능을 어떻게 지원할까? 사실, 위의 질문의 답은 스칼라 2.8 버전과 그 이전 버전에 따라 다르다. 예전에는, 스칼라 컴파일러가 "마법같이" 박싱(boxing)과 언박싱(unboxing) 과정에서, 필요할 때 배열을 `Seq` 객체로 변환하고 되돌렸다. 자세한 내용은 꽤 복잡하며, 특히 제너릭 타입 `Array[T]`에서 새로운 배열을 생성할 때는 더욱 어렵다. 헷갈리는 복합 경계 조건(corner case)들이 있고, 배열 연산의 성능을 예측하기 어려웠다. + +스칼라 2.8의 설계는 훨씬 간단하다. 컴파일러가 하는 모든 마법은 거의 사라졌다. 대신에 스칼라 2.8의 배열 구현은 암시적 변환의 체계적인 사용을 가능케 한다. 스칼라 2.8에서는, 배열은 시퀀스**처럼** 행동하지 않는다. 실제로 기본 배열의 데이터 타입이 `Seq`의 자식 타입(subtype)이 아니기 때문에 그럴 수 없다. 대신에, 배열과 `Seq`의 자식 클래스인 `scala.collection.mutable.WrappedArray`의 인스턴스 사이에는 암시적으로 "감싸주는(wrapping)" 변환이 존재한다. 직접 알아보자: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> val a4: Array[Int] = s.toArray + a4: Array[Int] = Array(1, 2, 3) + scala> a1 eq a4 + res2: Boolean = true + +위의 상호작용은 배열에서 `WrappedArray`로의 암시적인 변환이 있기 때문에, 배열이 시퀀스와 호환되는 것을 보여준다. `WrappedArray`에서 `배열`로 변환하기 위해서는, `Traversable`에 정의된 `toArray` 메소드를 사용할 수 있다. 마지막 REPL 줄에서 `toArray`를 통한 감싸고 풀어주는 변환이, 시작했던 배열과 동일한 것을 만들어주는 것을 볼 수 있다. + +배열에 적용되는 또 다른 암시적 변환이 존재한다. 이 변환은 모든 시퀀스 메소드를 배열에 "더하지만" 배열 자체를 시퀀스로 바꾸지 않는다. "더한다"는 것은 배열이 모든 시퀀스 메소드를 지원하는 `ArrayOps` 타입의 객체로 감싸진다는 것을 의미한다. 일반적으로, 이 `ArrayOps` 객체는 오래 가지 않는다; 보통 시퀀스 메소드가 불려진 후에는 접근할 수 없고, 메모리 공간은 재활용된다. 현대의 가상 머신은 종종 이러한 객체의 생성을 생략하기도 한다. + +이러한 배열에서의 두 가지 암시적 변환의 차이점은 다음 REPL 다이얼로그에서 확인할 수 있다: + + scala> val seq: Seq[Int] = a1 + seq: Seq[Int] = WrappedArray(1, 2, 3) + scala> seq.reverse + res2: Seq[Int] = WrappedArray(3, 2, 1) + scala> val ops: collection.mutable.ArrayOps[Int] = a1 + ops: scala.collection.mutable.ArrayOps[Int] = [I(1, 2, 3) + scala> ops.reverse + res3: Array[Int] = Array(3, 2, 1) + +`WrappedArray`인 `seq`를 반전시키는 것이 다시 `WrappedArray`를 돌려줌을 볼 수 있다. 감싸진 배열은 `Seqs`이고, 어떤 `Seq`를 반전시키는 것은 다시 `Seq`를 얻게 될 것이기 때문에 논리적인 일이다. 한편, `ArrayOps` 클래스의 값 ops를 반전시키면 `Seq`가 아니라 `배열`을 돌려줄 것이다. + +위의 `ArrayOps` 예제는 `WrappedArray`와의 차이를 보여주기 위해 상당히 인위적이다. 일반적으로, `ArrayOps`에 값을 정의하는 일은 일어나지 않는다. 단순히 배열에서 `Seq` 메소드를 호출하면 된다: + + scala> a1.reverse + res4: Array[Int] = Array(3, 2, 1) + +`ArrayOps` 객체는 암시적 변환에 의해 자동으로 삽입된다. 따라서 윗줄은 아래와 동일하다. + + scala> intArrayOps(a1).reverse + res5: Array[Int] = Array(3, 2, 1) + +`intArrayOps`가 앞서 삽입되었던 암시적 변환이다. 이것은 어떻게 컴파일러가 `WrappedArray`로 변환해주는 위에 있는 다른 암시적 변환을 제치고, `intArrayOps`를 선택하는지에 대한 문제를 던져준다. 결국 두 변환 모두 입력이 정해진 reverse 메소드를 지원하는 타입으로 배열을 변환하는 것이다. 질문에 대한 답은 두 암시적 변환에 우선 순위가 있다는 것이다. `ArrayOps` 변환은 `WrappedArray` 변환에 우선한다. `ArrayOps`는 `Predef` 객체에 선언되어있는 반면에, `WrappedArray`는 `Predef`에서 상속받은 `scala.LowPriorityImplicits` 클래스에 선언되어있다. 자식 클래스와 자식 객체(subobject)의 암시적 변환은 부모 클래스의 암시적 변환에 우선한다. 따라서, 두 가지 변환이 모두 가능할 경우에는, `Predef`에 있는 것이 선택된다. 문자열에도 유사한 방법이 사용된다. + +이제 어떻게 배열이 시퀀스와 호환되며, 어떻게 모든 시퀀스 연산이 지원되는지 알게 되었을 것이다. 제너릭은 어떻게 이루어질까? 자바에서는 타입 매개변수 `T`에 대해 `T[]`를 사용할 수 없다. 그러면 어떻게 스칼라의 `Array[T]`가 구현될까? 사실 `Array[T]` 같은 제너릭 배열은 런타임시에 `byte[]`, `short[]`, `char[]`, `int[]`, `long[]`, `float[]`, `double[]`, `boolean[]` 같은 자바의 8개 원시 타입 중 하나이거나, 객체의 배열일 수 있다. 이러한 모든 타입들의 런타임시 형태는 `AnyRef` (혹은, 해당하는 `java.lang.Object`)이기 때문에, 스칼라 컴파일러는 `Array[T]`를 `AnyRef`로 맵핑한다. 런타임시에 `Array[T]` 타입의 원소가 접근되거나 갱신되면 실제 배열 타입을 검사하고, 자바 배열에 대해 올바른 배열 연산을 수행한다. 이러한 타입 검사는 배열 연산을 조금 느리게 만든다. 제너릭 타입 배열에 접근하는 것은 원시 배열이나 객체(object) 배열에 접근하는 것보다 3 ~ 4배 정도 느리다고 예상할 수 있다. 만약 최대 성능을 원한다면, 제너릭 배열보다는 구체적인 타입 배열을 쓰는 것이 좋다. 제너릭 배열을 구현하는 것만으로는 충분치 않으며, 생성하는 방법 또한 필요하다. 이것은 더욱 어려운 문제이고, 당신의 도움이 조금 필요하다. 문제를 설명하기 위해, 아래의 배열을 생성하기 위한 제너릭 메소드를 기술하는 접근을 살펴보자. + + // this is wrong! + def evenElems[T](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +`evenElems` 메소드는 인수로 넘어온 벡터 `xs`의 짝수 위치에 있는 모든 원소를 포함한 새로운 배열을 반환한다. `evenElems` 본문의 첫째 줄은 인수와 동일한 타입을 가지는 배열을 생성한다. 그러므로 실제 타입 매개 변수 `T`에 따라 `Array[Int]`, `Array[Boolean]`, 혹은 자바의 다른 원시 타입 배열이나 참조 타입의 배열일 수 있다. 하지만 이 타입들은 런타임시에 모두 다르게 구현이 되는데, 스칼라는 어떻게 런타임에서 올바른 타입을 찾아낼 수 있을까? 사실, 이것은 주어진 정보로는 해결할 수 없다. 이는 타입 매개 변수 `T`에 대응하는 실제 타입이 런타임시에 지워지기 때문이다. 이것이 위의 코드를 컴파일 하려고 하면 아래와 같은 에러 메시지가 출력되는 이유이다. + + error: cannot find class manifest for element type T + val arr = new Array[T]((arr.length + 1) / 2) + ^ + +여기에 필요한 것은, 실제 `evenElems`의 타입이 무엇인지에 대한 런타임 힌트를 컴파일러에게 제공해서 도와주는 것이다. 이 런타임 힌트는 `scala.reflect.ClassManifest` 클래스 매니페스트의 형태를 가진다. 클래스 매니페스트란 타입의 최상위 클래스가 무엇인지를 설명하는 타입 설명 객체이다. 클래스 매니페스트대신에, 타입의 모든 것을 설명하는 `scala.reflect.Manifest`라는 전체 매니페스트도 있다. 그러나 배열 생성시에는, 클래스 매니페스트만이 필요하다. + +스칼라 컴파일러에게 클래스 매니페스트를 만들도록 지시를 내린다면 알아서 클래스 매니페스트를 생성한다. "지시한다"는 의미는 클래스 매니페스트를 아래와 같이 암시적 매개 변수로 요청한다는 뜻이다: + + def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ... + +문맥 범위(context bound)를 이용하면 더 짧게 클래스 매니페스트를 요청할 수 있다. 이것은 타입 뒤에 쌍점(:)과 함께 따라오는 클래스의 이름 `ClassManifest`를 기술하는 것을 의미한다.: + + // this works + def evenElems[T: ClassManifest](xs: Vector[T]): Array[T] = { + val arr = new Array[T]((xs.length + 1) / 2) + for (i <- 0 until xs.length by 2) + arr(i / 2) = xs(i) + arr + } + +변경된 두 가지 버전의 `evenElems`는 정확히 동일하다. 두 경우 모두, `Array[T]`가 생성될 때, 컴파일러는 타입 매개변수 T에 대해서 클래스 매니페스트를 검색하고, `ClassManifest[T]`의 암시적 값을 찾는다. 해당 값이 발견되면, 매니페스트는 올바른 종류의 배열을 생성한다. 그러지 않으면, 위와 같은 에러 메시지를 보게 될 것이다. + +아래의 `evenElems` 메소드를 사용하는 REPL 상호작용을 살펴보자. + + scala> evenElems(Vector(1, 2, 3, 4, 5)) + res6: Array[Int] = Array(1, 3, 5) + scala> evenElems(Vector("this", "is", "a", "test", "run")) + res7: Array[java.lang.String] = Array(this, a, run) + +양 쪽 모두, 스칼라 컴파일러가 자동으로 원소의 타입(`Int`와 `String`)에 대한 클래스 매니페스트를 생성하였고, `evenElems` 메소드의 암시적 매개 변수로 전달하였다. 컴파일러는 모든 구체적인 타입에 대해서 매니페스트를 생성해주지만, 인수 자체가 클래스 매니페스트 없는 타입 매개 변수일 경우에는 불가능하다. 예를 들어, 아래의 실패 사례를 살펴보자: + + scala> def wrap[U](xs: Array[U]) = evenElems(xs) + :6: error: could not find implicit value for + evidence parameter of type ClassManifest[U] + def wrap[U](xs: Array[U]) = evenElems(xs) + ^ +여기에서 일어난 일은 `evenElems`는 타입 매개 변수 `U`에 대해서 클래스 매니페스트를 요청했지만, 아무 것도 발견되지 않았다. 물론 이러한 경우에 대한 해결책은 `U`에 대한 또 다른 암시적 클래스 매니페스트를 요청하는 것이다. 따라서 아래와 같이 동작한다: + + scala> def wrap[U: ClassManifest](xs: Array[U]) = evenElems(xs) + wrap: [U](xs: Array[U])(implicit evidence$1: ClassManifest[U])Array[U] + +이 예시를 통해 `U`의 정의에 대한 문맥 범위가 `ClassManifest[U]` 타입의 `evidence$1`라는 이름의 암시적 매개 변수로 약칭한 것을 볼 수 있다. + +요약하면, 제너릭 배열의 생성은 클래스 매니페스트를 요구한다. 그러므로 타입 매개 변수 `T`의 배열을 생성할 때, `T`에 대한 암시적 클래스 매니페스트를 제공해주어야 한다. 이를 위한 가장 쉬운 방법은, `[T: ClassManifest]` 처럼 문맥 범위 안에 타입 매개 변수와 함께 `ClassManifest`를 선언해주는 것이다. + diff --git a/ko/overviews/collections/concrete-immutable-collection-classes.md b/ko/overviews/collections/concrete-immutable-collection-classes.md new file mode 100644 index 0000000000..21865e9aab --- /dev/null +++ b/ko/overviews/collections/concrete-immutable-collection-classes.md @@ -0,0 +1,210 @@ +--- +layout: overview-large +title: 변경 불가능한 컬렉션에 속하는 구체적인 클래스들 + +disqus: true + +partof: collections +num: 8 +language: ko +--- + +스칼라에는 필요에 따라 선택 가능한 구체적인 선택 불가능 컬렉션 클래스가 많이 있다. 각각의 차이는 어떤 트레잇을 구현하고 있는가(맵, 집합, 열), 무한한가 유한한가, 각각의 연산의 수행 시간(복잡도) 등에 있다. 여기서는 스칼라에서 가장 많이 사용되는 변경 불가능한 컬렉션 타입들을 설명할 것이다. + +## 리스트(List) + +[List(리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/List.html)는 유한한 변경 불가능한 열이다. 리스트의 첫 원소를 가져오거나, 첫 원소를 제외한 나머지를 가져오는 연산에는 상수 시간이 걸린다. 또한 새 원소를 리스트 맨 앞에 붙이는(보통 콘스cons라 부름) 연산도 상수시간이 소요된다. 다른 연산들은 보통 선형 시간(즉, 리스트의 길이 N에 비례)이 걸린다. + +리스트는 계속해서 스칼라 프로그래밍시 사용하는 주요 데이터 구조였다. 따라서 여기서 다시 자세히 설명할 필요는 없을 것이다. 2.8에서 가장 큰 변화는 `List` 클래스, 그리고 그 하위 클래스인 `::`와 하위 객체 `Nil`이 이제는 논리적인 위치에 맞게 `scala.collection.immutable` 패키지에 들어가 있다는 점이다. 여전히 `scala` 패키지에도 타입 별칭 `List`, `Nil`, `::`가 존재한다. 따라서 사용자 관점에서 볼때는 예전에 사용하던 방식대로 계속 사용 가능하다. + +또 다른 변화는 이제 리스트도 컬렉션 프레임워크에 더 밀접하게 통합되어 있따는 점이다. 따라서 이전보다 덜 특별하게 취급된다. 예를 들어 원래는 `List` 짝 객체에 존재하던 여러 연산이 이제는 사용하지 않도록 표시되었다. 이러한 연산들은 모든 컬렉션이 상속하는 [일정한 생성 메소드들]({{ site.baseurl }}/overviews/collections/creating-collections-from-scratch.html)로 대치되었다. + +## 스트림 + +[Stream(스트림)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stream.html)은 리스트와 같지만 각 원소가 지연계산된다는 점에서 차이가 있다. 따라서 무한한 스트림도 가능하다. 요청을 받을 때만 원소를 계산한다. 그 점을 제외하면 나머지 동작 특성은 리스트와 같다. +리스트가 `::` 연산으로 구성되는 반면, 스트림은 비슷한 `#::`로 구성된다. 다음은 정수 1, 2, 3을 포함하는 스트림을 만드는 과정을 보여준다. + + scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty + str: scala.collection.immutable.Stream[Int] = Stream(1, ?) + +이 스트림의 머리는 1이고, 꼬리는 2와 3이다. 꼬리는 출력이 되지 않는다. 왜냐하면 아직 그 값을 계산하지 않았기 때문이다! 스트림은 지연계산을 위한 것이기 때문에, 스트림의 `toString` 메소드는 추가 계산을 수행하지 않게 조심하도록 구현되어 있다. + +다음은 좀 더 복잡한 예제이다. 이 예에서는 주어진 두 정수로 시작하는 피보나치 수열의 스트림을 계산한다. 피보나치 수열은 각 원소가 바로 이전 두 원소의 합인 수열이다. + + scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) + fibFrom: (a: Int,b: Int)Stream[Int] + +이 함수는 믿을 수 없이 간단하다. 열의 첫번째 원소는 분명 `a`이고, 나머지 열은 `b` 다음에 `a + b`가 오는 피보나치 수열이다. 교묘한 부분은 이 열을 계산할 때 무한한 재귀호출을 하지 않는다는 점이다. `::`를 `#::` 대신 사용했다면 함수 호출이 자기 자신을 호출하게 되어서 무한 재귀 호출이 발생했을 것이다. 하지만 `#::`를 사용하기 때문에 우항은 요청되기 전까지 계산되지 않는다. + +다음은 1을 두개 사용해 생성되는 피보나치 수열의 앞 부분 항을 몇 개 보여준다. + + scala> val fibs = fibFrom(1, 1).take(7) + fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?) + scala> fibs.toList + res9: List[Int] = List(1, 1, 2, 3, 5, 8, 13) + +## 벡터 + +리스트는 그것을 다루는 알고리즘이 목록의 첫 원소만을 처리하도록 주의깊게 고안되어 있는 경우에는 아주 효율적이다. 리스트의 첫 원소를 억세스하고, 추가하고, 삭제하는 작업은 상수 시간만 걸리지만, 그외 다른 곳에 위치한 원소를 억세스하거나 변경하는 작업은 리스트의 길이에 정비례하는 시간이 걸린다. + +[Vector(벡터)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Vector.html)는 임의 접근시 성능이 떨어지는 리스트의 문제점을 해결한 컬렉션 유형이다(스칼라 2.8에 도입됨). 벡터에 속한 어떤 원소이건 "실질적으로" 상수 시간에 억세스할 수 있다. 상수 자체는 리스트의 첫 원소를 가져오는 것이나 배열의 원소를 읽는 시간보다는 더 큰 편이다. 하지만, 여전히 상수시간은 상수시간이다. 따라서 벡터를 사용하는 알고리즘에서는 열의 첫 원소만을 처리하도록 조심할 필요가 없다. 원한다면 어떤 위치에 있는 원소이던지 억세스하거나 변경할 수 있다. 따라서 훨씬 사용하기에 편리하다. + +벡터도 다른 열 객체들과 마찬가지로 생성하고 변경할 수 있다. + + scala> val vec = scala.collection.immutable.Vector.empty + vec: scala.collection.immutable.Vector[Nothing] = Vector() + scala> val vec2 = vec :+ 1 :+ 2 + vec2: scala.collection.immutable.Vector[Int] = Vector(1, 2) + scala> val vec3 = 100 +: vec2 + vec3: scala.collection.immutable.Vector[Int] = Vector(100, 1, 2) + scala> vec3(0) + res1: Int = 100 + +벡터는 분기계수(branching factor)가 큰 트리로 표현된다(트리나 그래프에서 분기계수란 각 노드가 가질 수 있는 자식의 갯수를 의미한다). 모든 트리 노드는 최대 32개의 벡터 원소를 가지거나, 32개의 다른 트리 노드를 포함할 수 있다. 32개 이하의 원소를 가지는 벡터는 노드 하나로 표현할 수 있다. `32 * 32 = 1024`개 까지의 원소는 단 한번만 간접참조를 거치면 된다. 트리 루트에서 두 단계를 거치면 최대 215 원소를 지원할 수 있고, 3 단계는 220, 네 단계는 225개의 원소, 다섯 단계를 거치는 경우에는 230개 까지 가능하다. 따라서 적당한 범위내의 크기를 가지는 벡터라면 최대 5번의 원시 배열 선택을 거치면 모두 처리할 수 있다. 앞에서 "실질적"이라는 말로 원소 억세스에 대해 설명했던 이유가 바로 이것이다. + +벡터는 변경 불가능하다. 따라서 벡터의 원소를 변경할 수 없다. 하지만 `updated` 메소드를 사용하면 기존 벡터와 요소 하나만 차이가 나는 새 벡터를 만들 수 있다. + + scala> val vec = Vector(1, 2, 3) + vec: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + scala> vec updated (2, 4) + res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4) + scala> vec + res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3) + +마지막 줄에서 보듯, `updated`를 호출해도 원래의 벡터 `vec`은 변화가 없다. 원소 억세스시와 마찬가지로 함수형 벡터 변경도 또한 "실질적으로는 상수 시간"에 수행된다. 벡터의 중간에 있는 원소를 변경 하려면 그 원소를 포함하고 있는 노드와 루트에서 해당 노드에 도달하는 경로에 있는 모든 선조 노드들도 복사하는 방식으로 이루어진다. 따라서 함수형으로 변경하는 경우에 최대 5개의 노드를 복사/생성해야 함을 의미한다. 이때 각 노드는 32개의 원소를 포함하거나, 최대 32개까지 다른 하위 트리에 대한 참조를 저장하게 된다. 물론 변경 가능한 배열에서 직접 변경하는 것보다는 확실히 더 비용이 비싸다. 하지만 전체 벡터를 복사하는 것과 비교하면 훨씬 적은 비용이 든다. + +벡터는 빠른 임의 선택과 빠른 임의의 함수적 변경 사이의 균형을 잘 잡고 있다. 따라서 첨자가 있는 열(immutable.IndexedSeq) 트레잇의 기본 구현은 벡터로 되어 있다. + + scala> collection.immutable.IndexedSeq(1, 2, 3) + res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3) + +## 변경 불가능한 스택 + +후입선출(LIFO, 나중에 넣은게 처음 나오는) 열이 필요하다면 [Stack(스택)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Stack.html)이 있다. 스택 위에 원소를 넣을 때는 `push`를 쓰고, 스택의 맨 위 원소를 꺼내올 때는 `pop`을 쓴다. 또한 스택의 맨 위에 있는 원소를 꺼내지 않고 확인만 하고 싶다면 `top`을 사용하면 된다. 이 세 연산은 모두 상수 시간이 소요된다. + +다음 예는 스택에 대해 간단한 연산을 수행하는 것을 보여준다. + + + scala> val stack = scala.collection.immutable.Stack.empty + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> val hasOne = stack.push(1) + hasOne: scala.collection.immutable.Stack[Int] = Stack(1) + scala> stack + stack: scala.collection.immutable.Stack[Nothing] = Stack() + scala> hasOne.top + res20: Int = 1 + scala> hasOne.pop + res19: scala.collection.immutable.Stack[Int] = Stack() + +변경 불가능한 스택은 스칼라에서 거의 사용되지 않는다. 왜냐하면 리스트가 스택의 모든 역할을 다 할 수 있기 때문이다. 변경 불가능한 스택에 `push`하는 것은 리스트에 `::` 하는 것과 같고, `pop`은 리스트에 `tail`을 하는 것과 같다. + +## 변경 불가능한 큐 + +[Queue(큐)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Queue.html)는 스택과 비슷하지만 후입선출이 아니고 선입선출(FIFO,처음 넣은 것이 처음 나오는)이라는 점이 다르다. + +다음은 변경 불가능한 빈 큐를 생성하는 것을 보여준다. + + scala> val empty = scala.collection.immutable.Queue[Int]() + empty: scala.collection.immutable.Queue[Int] = Queue() + +`enqueue`를 사용해 변경 불가능한 큐에 원소를 추가할 수 있다. + + scala> val has1 = empty.enqueue(1) + has1: scala.collection.immutable.Queue[Int] = Queue(1) + +여러 원소를 큐에 추가하려면 `enqueue` 호출시 컬렉션을 인자로 넘기면 된다. + + scala> val has123 = has1.enqueue(List(2, 3)) + has123: scala.collection.immutable.Queue[Int] + = Queue(1, 2, 3) + +큐의 맨 앞에서 원소를 제거하기 위해서는 `dequeue`를 사용하라. + + scala> val (element, has23) = has123.dequeue + element: Int = 1 + has23: scala.collection.immutable.Queue[Int] = Queue(2, 3) + +`dequeue`가 반환하는 것이 제거된 맨 앞의 원소와 그 원소를 제외한 나머지 큐의 튜플이라는 사실에 주의하라. + +## 범위(Range) + +[Range(범위)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/Range.html)는 동일한 간격으로 떨어진 정수들의 순서가 있는 열이다. 예를 들어 "1, 2, 3"이나 "5, 8, 11, 14" 등이 범위이다. 스칼라에서 범위를 만들 때에는 미리 정의된 `to`와 `by` 메소드를 사용한다. + + scala> 1 to 3 + res2: scala.collection.immutable.Range.Inclusive + with scala.collection.immutable.Range.ByOne = Range(1, 2, 3) + scala> 5 to 14 by 3 + res3: scala.collection.immutable.Range = Range(5, 8, 11, 14) + +상한값을 제외한 나머지만 포함한 범위를 만들고 싶다면 `to`대신에 `until` 메소드를 사용하면 된다. + + scala> 1 until 3 + res2: scala.collection.immutable.Range.Inclusive + with scala.collection.immutable.Range.ByOne = Range(1, 2) + +범위는 하한값, 상한값, 그리고 증가값의 세 값으로 표현 가능하기 때문에 상수 공간 복잡도를 가진다. 이런 표현방식을 사용하기 때문에 범위에 대한 연산은 매우 빠르다. + +## 해시 트라이(hash trie) + +해시 트라이는 변경 불가능한 집합과 맵을 효율적으로 구현하기 위해 쓰이는 표준적인 방법이다. 이들은 [immutable.HashMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/HashMap.html) 클래스가 지원한다. 해시 트라이의 구조는 벡터와 비슷하다. 해시 트라이는 각 노드가 32개의 원소나 하위 트리를 가지는 트리로 표현된다. 하지만 키의 선택이 해시 값에 기반해 이루어진다는 점이 다르다. 예를 들어 맵에서 어떤 키를 찾기 위해서는 해당 키의 해시값을 먼저 계산해야 한다. 그리고 나서 해시코드의 최하위 5비트를 사용해 첫번째 하위 트리를 찾고, 나머지 5비트를 사용해 그 다음 하위 트리를 찾는 식으로 탐색을 수행한다. 탐색 과정은 노드에 저장된 모든 해시 코드가 서로 다른 시점에 종료된다. +해시 트라이는 충분히 빠른 검색과 충분히 효율적인 함수적 추가(`+`) 및 삭제(`-`) 사이에 잘 균형잡힌 구현을 제공한다. 그런 이유로 스칼라에서는 변경 불가능한 맵과 집합에 해시 트라이를 기본 구현으로 사용한다. 실제로는 원소가 5개 미만인 변경 불가능한 맵과 집합에 대해서는 조금 더 최적화를 한다. 원소가 1개에서 4개 사이인 집합과 맵은 해당 원소들(맵의 경우 키/값 쌍들)만을 필드로 포함하는 단일 객체로 표현된다. 빈 변경 불가능한 집합이나 맵은 각각 유일한 객체로 표현된다. 변경 불가능한 빈 집합이나 맵은 항상 비어있는 채이기 때문에 객체를 중복해 만들 필요가 없다. + + +## 적-흑 트리(Red-Black Tree) + +적흑트리는 균형트리의 일종으로, 일부 노드는 "빨간색", 나머지는 "검은색"으로 지정될 수 있다. 다른 모든 균형 트리와 마찬가지로 트리에 대한 연산은 트리 크기의 로그(log, 대수)에 비례한 시간 복잡도를 가진다. +스칼라는 내부적으로 적흑트리를 사용하는 변경 불가능한 집합과 맵을 제공한다. [TreeSet](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeSet.html)과 [TreeMap](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/TreeMap.html)이라는 이름으로 이를 사용할 수 있다. + + scala> scala.collection.immutable.TreeSet.empty[Int] + res11: scala.collection.immutable.TreeSet[Int] = TreeSet() + scala> res11 + 1 + 3 + 3 + res12: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 3) + +적흑트리는 스칼라의 `SortedSet` 집합의 기본 구현이다. 이유는 적흑트리를 사용하면 모든 원소를 정렬된 순서로 반환하는 반복자를 효율적으로 제공할 수 있기 때문이다. + +## 변경 불가능한 비트 집합 + +[BitSet(비트집합)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/BitSet.html)은 작은 정수의 컬렉션을 큰 정수의 비트들로 표현한 것이다. 예를 들어 3, 2, 0을 포함하는 비트집합은 2진수로 1101, 즉 10진수 13으로 표현할 수 있다. +내부적으로 비트집합은 64비트 `Long` 배열을 사용한다. 배열의 최초의 `Long`은 0부터 63, 두번째는 64부터 127, 이런 식으로 사용한다. 따라서 집합에서 가장 큰 수가 몇백 정도인 경우라면 비트 집합은 메모리를 아주 작게 차지한다. +비트 집합에 대한 연산은 아주 빠르다. 원소가 속해있는지 판단하는 것은 상수시간이 걸린다. 집합에 원소를 추가하는 것은 내부에 있는 배열의 `Long` 갯수에 비례하지만, 보통 그 갯수가 그리 크지는 않다. 다음은 비트 집합을 사용하는 예를 간단히 보여준다. + + scala> val bits = scala.collection.immutable.BitSet.empty + bits: scala.collection.immutable.BitSet = BitSet() + scala> val moreBits = bits + 3 + 4 + 4 + moreBits: scala.collection.immutable.BitSet = BitSet(3, 4) + scala> moreBits(3) + res26: Boolean = true + scala> moreBits(0) + res27: Boolean = false + +## 리스트 맵 + +[ListMap(리스트맵)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/immutable/ListMap.html)은 맵을 키-값 쌍의 연결된 리스트로 표현한다. 일반적으로 리스트맵에 대한 연산을 하려면 전체 리스트를 순회해야 한다. 따라서 리스트 맵에 대한 연산은 맵 크기에 대해 선형 시간이 필요하다. 스칼라에서 리스트맵을 사용하는 경우는 흔치 않다. 왜냐하면 표준적인 변경 불가능한 맵이 거의 대부분 더 빠르기 때문이다. 성능상 리스트맵이 더 유리할 수 있는 경우가 있다면, 어떤 이유로든 맵의 첫 원소가 다른 원소들보다 훨씬 더 자주 선택되어야 하는 경우일 것이다. + + scala> val map = scala.collection.immutable.ListMap(1->"one", 2->"two") + map: scala.collection.immutable.ListMap[Int,java.lang.String] = + Map(1 -> one, 2 -> two) + scala> map(2) + res30: String = "two" + + + + + + + + + + + + + + + + + + + + + + diff --git a/ko/overviews/collections/concrete-mutable-collection-classes.md b/ko/overviews/collections/concrete-mutable-collection-classes.md new file mode 100644 index 0000000000..7c5ef77882 --- /dev/null +++ b/ko/overviews/collections/concrete-mutable-collection-classes.md @@ -0,0 +1,182 @@ +--- +layout: overview-large +title: 변경 가능한 컬렉션에 속하는 구체적인 클래스들 + +disqus: true + +partof: collections +num: 9 +language: ko +--- + +스칼라 표준 라이브러리가 제공하는 가장 일반적으로 사용되는 변경 불가능한 컬렉션 클래스에 대해 살펴보았다. 이제 변경가능한 컬렉션 클래스를 살펴보자. + +## 배열 버퍼 + +[ArrayBuffer(배열버퍼)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayBuffer.html)는 배열과 크기를 저장한다. 배열 버퍼에 대한 대부분의 연산은 배열에 대한 연산과 같은 복잡도이다. 왜냐하면 이러한 연산들은 단지 하부의 배열을 억세스하거나 변경하기 때문이다. 추가로 배열 버퍼에서는 데이터를 효율적으로 맨 마지막에 추가할 수 있다. 배열 버퍼에 데이터를 추가하는 작업의 상환 시간 복잡도(amortized time complexity)도 상수 시간이다. 따라서 배열 버퍼는 새 데이터가 맨 마지막에만 추가되는 큰 컬렉션을 효율적으로 구축할 때 유용하다. + + scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int] + buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() + scala> buf += 1 + res32: buf.type = ArrayBuffer(1) + scala> buf += 10 + res33: buf.type = ArrayBuffer(1, 10) + scala> buf.toArray + res34: Array[Int] = Array(1, 10) + +## 리스트 버퍼 + +[ListBuffer(리스트버퍼)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ListBuffer.html)는 배열 대신 내부적으로 연결된 리스트를 사용한다는 점만 다르고 배열 버퍼와 비슷하다. 만약 컬렉션을 구축한 다음 리스트로 변환할 생각이라면, 배열 버퍼 대신 리스트 버퍼를 사용하도록 하라. + + scala> val buf = scala.collection.mutable.ListBuffer.empty[Int] + buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() + scala> buf += 1 + res35: buf.type = ListBuffer(1) + scala> buf += 10 + res36: buf.type = ListBuffer(1, 10) + scala> buf.toList + res37: List[Int] = List(1, 10) + +## 스트링빌더 + +배열 버퍼가 배열을 만들 때 유용하고, 리스트 버퍼는 리스트를 만들 때 유용한 것과 같이, [StringBuilder(스트링빌더)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/StringBuilder.html)는 스트링을 만들 때 유용하다. 스트링빌더는 자주 사용되기 때문에 기본 네임스페이스에 임포트되어 있다. 단지 아래와 같이 `new StringBuilder`라고 하면 만들 수 있다. + + scala> val buf = new StringBuilder + buf: StringBuilder = + scala> buf += 'a' + res38: buf.type = a + scala> buf ++= "bcdef" + res39: buf.type = abcdef + scala> buf.toString + res41: String = abcdef + +## 연결 리스트 + +연결 리스트는 변경 가능한 열로 각 노드는 다음번 노드를 가리키는 포인터로 서로 연결되어 있다. 이들은 [LinkedList(연결리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/LinkedList.html) 클래스로 지원된다. 대부분의 언어에서 `null`이 빈 연결 리스트로 사용된다. 스칼라 컬렉션에서는 그렇게 할 수 없는데 왜냐하면 빈 열도 열이 제공하는 모든 메소드를 지원해야 하기 때문이다. 특히 `LinkedList.empty.isEmpty`가 `true`를 반환해야지 `NullPointerException`을 발생시키면 안된다. 따라서 빈 연결 리스트는 특별한 방식으로 처리된다. 빈 연결리스트의 `next` 필드는 자기 자신을 가리킨다. 변경 불가능한 리스트와 마찬가지로 연결리스트도 순차적으로 순회할 때 가장 성능이 좋다. 추가로 연결리스트를 사용하면 원소나 연결 리스트를 다른 연결 리스트에 추가하는 것이 쉽다. + +## 이중 연결 리스트 + +이중연결리스트는 (단일)연결리스트와 비슷하지만, `next` 필드 외에도 이전 노드를 가리키는 변경 기능한 필드 `prev`가 추가되었다는 점이 다르다. 이렇게 링크를 추가하면 원소 삭제가 상수시간으로 매우 빨라진다. 스칼라에서는 [DoubleLinkedList(이중연결리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/DoubleLinkedList.html) 클래스로 이를 지원한다. + +## 변경 가능한 리스트 + +[MutableList(변경 가능한 리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html)는 리스트의 맨 마지막을 표시하는 노드(비어있는 노드임)를 가리키는 필드와 단일연결리스트로 구성된다. 따라서 리스트 뒤에 다른 리스트를 붙이는 작업이 상수 시간에 수행될 수 있다. 왜냐하면 리스트의 끝을 찾기 위해 전체 리스트를 순회할 필요가 없기 때문이다. [MutableList(변경가능리스트)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/MutableList.html)는 현재 스칼라에서 [mutable.LinearSeq(선형 열)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/LinearSeq.html)의 표준 구현이다. + +## 큐 + +스칼라는 변경 불가능한 큐와 더불어 변경 가능한 큐도 제공한다. `mQueue` 사용법은 변경 불가능한 큐와 비슷하다. 다만 `enqueue` 대신 `+=`나 `++=`를 원소/목록 추가를 위해 사용한다. 또한 변경 가능한 큐에서 `dequeue` 메소드는 단지 머리 원소를 큐에서 제거하고 그 원소를 반환한다. 다음은 실행 예이다. + + scala> val queue = new scala.collection.mutable.Queue[String] + queue: scala.collection.mutable.Queue[String] = Queue() + scala> queue += "a" + res10: queue.type = Queue(a) + scala> queue ++= List("b", "c") + res11: queue.type = Queue(a, b, c) + scala> queue + res12: scala.collection.mutable.Queue[String] = Queue(a, b, c) + scala> queue.dequeue + res13: String = a + scala> queue + res14: scala.collection.mutable.Queue[String] = Queue(b, c) + +## 배열 열 + +배열 열은 크기가 고정된 변경 가능한 열이다. 내부적으로 원소를 `Array[Object]`에 저장한다. 이는 스칼라에서 [ArraySeq(배열열)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArraySeq.html)클래스로 구현되어 있다. + +`ArraySeq` 사용하는 전형적인 상황은 배열과 같은 성능 특성이 필요하지만 각 원소의 타입을 모르거나, 런타임에 제공할 `ClassManifest(클래스 메니페스트)`가 없어서 일반적인 열 인스턴스를 만들어야만 할 때를 들 수 있다. 이에 대해서는 [배열]({{ site.baseurl }}/overviews/collections/arrays.html)에 대해 설명할 때 더 자세히 다룰 것이다. + +## 스택 + +변경 불가능한 스택에 대해 보았다. 마찬가지로 변경 가능한 버전도 있다. 지원하는 클래스는 [mutable.Stack](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/Stack.html)이다. 변경 가능 스택은 변경 불가능한 스택과 똑같이 동작하지만, 기존 스택의 내부 상태를 변경한다는 점에서만 차이가 있다. + + scala> val stack = new scala.collection.mutable.Stack[Int] + stack: scala.collection.mutable.Stack[Int] = Stack() + scala> stack.push(1) + res0: stack.type = Stack(1) + scala> stack + res1: scala.collection.mutable.Stack[Int] = Stack(1) + scala> stack.push(2) + res0: stack.type = Stack(1, 2) + scala> stack + res3: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.top + res8: Int = 2 + scala> stack + res9: scala.collection.mutable.Stack[Int] = Stack(1, 2) + scala> stack.pop + res10: Int = 2 + scala> stack + res11: scala.collection.mutable.Stack[Int] = Stack(1) + +## 배열 스택 + +[ArrayStack(배열스택)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/ArrayStack.html)은 변경 가능한 스택의 또 다른 구현이다. 내부적으로 배열을 사용하고, 필요할 때마다 배열 크기를 재설정한다. 배열스택은 빠른 첨자 검색을 제공하며, 일반적인 변경 가능 스택에 비해 대부분의 연산에서 조금 더 효율적이다. + +## 해시 테이블 + +해시테이블은 내부 배열에 원소를 저장한다. 각 원소의 배열상의 위치는 원소의 해시 코드에 의해 결정된다. 해시테이블에 원소를 추가하는 것은 이미 그 위치에 저장된 원소가 없다면(즉, 해시 코드가 같은 원소가 없다면) 상수 시간만 필요하다. 따라서 원소들의 해시 코드가 고르게 분포되어 있는 한 해시 테이블 연산은 매우 빠르다. 따라서 스칼라에서 변경 가능한 맵의 기본 구현은 해시 테이블을 기반으로 한다. 필요하면 직접 [mutable.HashSet(해시집합)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashSet.html)과 [mutable.HashMap(해시맵)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/HashMap.html)이라는 이름을 사용해 해시 테이블을 사용할 수 있다. + +해시 집합과 맵은 다른 집합이나 맵과 동일하다. 다음은 몇가지 예이다. + + scala> val map = scala.collection.mutable.HashMap.empty[Int,String] + map: scala.collection.mutable.HashMap[Int,String] = Map() + scala> map += (1 -> "make a web site") + res42: map.type = Map(1 -> make a web site) + scala> map += (3 -> "profit!") + res43: map.type = Map(1 -> make a web site, 3 -> profit!) + scala> map(1) + res44: String = make a web site + scala> map contains 2 + res46: Boolean = false + +해시 테이블에 대한 이터레이션은 항상 어떤 정해진 순서를 따라 실행된다는 보장이 없다. 이터레이션시 단지 내부 배열에 저장된 원소들을 저장된 차례대로 처리할 뿐이다. 정해진 이터레이션 순서가 필요하다면 일반적인 해시맵이나 집합 대신 _연결(linked)_ 해시맵이나 해시집합을 사용하라. 연결 해시맵이나 집합은 일반 해시맵이나 집합과 동일하지만 추가된 순서에 따른 원소들의 연결 리스트를 추가로 가지고 있다. 이런 컬렉션에 대해 이터레이션을 하면 항상 원소가 추가된 순서에 따라 동일한 순서로 원소를 처리할 수 있다. + +## 약한 해시 맵 + +약한 해시 맵은 특별한 해시 맵으로 이 맵에 저장된 키에 대한 참조는 쓰레기 수집기가 살아있는 객체를 체크할 때 검토되지 않는다. 따라서 이 맵에 저장된 키와 값에 대한 약한 해시맵 바깥의 참조가 모두 사라지게 되면 해당 키와 값은 사라지게 된다. 약한 해시 맵은 같은 값에 대해 재호출시 반복 계산을 하면 비용이 너무 비싸지는 함수값 등에 대한 캐시등을 할 때 유용하다. 키와 함수 결과값이 일반 맵에 저장되어 있다면 맵의 크기는 제약 없이 커지고, 키 객체들은 결코 재활용되지 못할 것이다. 약한 해시 맵을 사용하면 이런 문제를 피할 수 있다. 어떤 키 객체가 도착불가능하게 되면 그 엔트리는 약한 해시 맵에서 제외된다. 스칼라에서 약한 해시맵은 [WeakHashMap(약해시맵)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/WeakHashMap.html)으로 제공되며, 이 클래스는 자바 클래스인 `java.util.WeakHashMap`을 둘러싼 클래스이다. + +## 병렬 맵 + +병렬맵은 여러 쓰레드가 동시에 억세스할 수 있다. 일반적인 [Map(맵)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Map.html) 연산과 더불어 다음과 같은 원자적 연산을 제공한다. + +### ConcurrentMap(병렬맵)의 연산들 + +| 사용법 | 하는일 | +| ------ | ------ | +| `m putIfAbsent(k, v)` |키 `k`가 `m`에 정의되어 있지 않은 경우 키/값 연관관계 `k -> m`를 추가한다. | +| `m remove (k, v)` |키 `k`가 값 `v`로 연관되어 있는 경우 이를 제거한다. | +| `m replace (k, old, new)` |키 `k`에 연관되어 있는 값을 `new`로 변경한다. 다만 기존값이 `old`일 경우에만 변경을 수행한다. | +| `m replace (k, v)` |키 `k`에 연관되어 있는 값을 `new`로 변경한다. 다만 맵에 키가 이미 있는 경우에만 변경을 수행한다.| + +`ConcurrentMap`은 스칼라의 컬렉션 라이브러리의 트레잇이다. 하지만 현재 이를 지원하는 유일한 구현은 자바의 `java.util.concurrent.ConcurrentMap`뿐이다. 이 경우 자바 객체에서 스칼라 객체로의 변환은 [표준 자바/스칼라 컬렉션 변환]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.html)에 의해 자동으로 수행된다. + +## 변경가능한 비트집합 + +타입 [mutable.BitSet(비트집합)](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/mutable/BitSet.html)은 객체 상태 변경이 가능하다는 점을 제외하고는 변경 불가능한 비트집합과 거의 비슷하다. 변경 가능한 비트 집합을 변경하는것은 변경 불가능한 비트집합의 원소를 변경하는 것보다 약간 더 효율적이다. 이유는 바뀔 필요가 없는 `Long` 값들을 복사하지 않기 때문이다. + + scala> val bits = scala.collection.mutable.BitSet.empty + bits: scala.collection.mutable.BitSet = BitSet() + scala> bits += 1 + res49: bits.type = BitSet(1) + scala> bits += 3 + res50: bits.type = BitSet(1, 3) + scala> bits + res51: scala.collection.mutable.BitSet = BitSet(1, 3) + + + + + + + + + + + + + + + + + + diff --git a/ko/overviews/collections/conversions-between-java-and-scala-collections.md b/ko/overviews/collections/conversions-between-java-and-scala-collections.md new file mode 100644 index 0000000000..e089b9e34c --- /dev/null +++ b/ko/overviews/collections/conversions-between-java-and-scala-collections.md @@ -0,0 +1,58 @@ +--- +layout: overview-large +title: 자바와 스칼라 컬렉션간 변환 + +disqus: true + +partof: collections +num: 17 +language: ko +--- + +스칼라와 마찬가지로 자바 또한 풍부한 컬렉션 라이브러리를 제공한다. 둘 사이에는 반복자(iterator), 반복가능(iterable), 집합(set), 맵(map)과 열(sequence) 등 유사한 점이 많지만 중요한 차이점 또한 존재하는데, 특히 스칼라 라이브러리의 경우 변경 불가능한 컬렉션을 훨씬 더 강조하거나 더욱 많은 컬렉션간 변환 연산을 지원하는 점이 그러하다. + +때때로 한 컬렉션 프레임워크를 다른 것으로 대체해야 할 경우가 생긴다. 예를 들어, 이미 존재하는 자바 컬렉션을 마치 스칼라 컬렉션처럼 접근하거나, 자바 컬렉션을 인자로 받는 자바 메소드에 스칼라 컬렉션을 넘기고 싶을 수 있다. 이러한 작업은 모든 주요 컬렉션간의 암시적 변환을 제공하는 [JavaConversions](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) 객체를 통해 손쉽게 가능하다. 구체적으로 다음과 같은 양방향 변환등을 찾을 수 있다. + + + Iterator <=> java.util.Iterator + Iterator <=> java.util.Enumeration + Iterable <=> java.lang.Iterable + Iterable <=> java.util.Collection + mutable.Buffer <=> java.util.List + mutable.Set <=> java.util.Set + mutable.Map <=> java.util.Map + mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap + +이러한 변환을 활성화하려면 단순히 [JavaConversions](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/JavaConversions$.html) 객체에서 임포트하면 된다: + + scala> import collection.JavaConversions._ + import collection.JavaConversions._ + +이제 자동적으로 스칼라 컬렉션과 이에 대응하는 자바 컬렉션간의 변환이 가능하다. + + scala> import collection.mutable._ + import collection.mutable._ + scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> val buf: Seq[Int] = jul + buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3) + scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2) + m: java.util.Map[String,Int] = {hello=2, abc=1} + +내부적으로, 이러한 변환은 원본 컬렉션 객체로 모든 연산을 전달하는 "래퍼(wrapper)" 객체를 통해 이루어진다. 따라서 자바와 스칼라간 변환에 있어 컬렉션은 절대 복사되지 않는다. 흥미로운 것은 왕복 변환이 일어날 때, 예를 들어 자바에서 스칼라로, 다시 자바로 변환되는 경우, 처음과 정확히 동일한 컬렉션 객체가 된다는 점이다. + +스칼라에서 자바로 변환은 가능하지만 반대로는 불가능한 공통 스칼라 컬렉션도 있다: + + Seq => java.util.List + mutable.Seq => java.utl.List + Set => java.util.Set + Map => java.util.Map + +자바는 변경 가능한 컬렉션과 변경 불가능한 컬렉션을 자료형으로 구분하지 않기 때문에, `scala.immutable.List`는 `java.util.List`로 변환되지만 모든 변경 연산은 "UnsupportedOperationException"을 발생시킨다. 예제를 보자: + + scala> jul = List(1, 2, 3) + jul: java.util.List[Int] = [1, 2, 3] + scala> jul.add(7) + java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:131) + diff --git a/ko/overviews/collections/creating-collections-from-scratch.md b/ko/overviews/collections/creating-collections-from-scratch.md new file mode 100644 index 0000000000..f961eac1b0 --- /dev/null +++ b/ko/overviews/collections/creating-collections-from-scratch.md @@ -0,0 +1,63 @@ +--- +layout: overview-large +title: 컬렉션 만들기 + +disqus: true + +partof: collections +num: 16 +language: ko +--- + +`List(1, 2, 3)` 구문을 사용해 3개의 정수로 이루어진 리스트를 만들 수 있고, `Map('A' -> 1, 'C' -> 2)`를 사용해 두 연관관계가 포함된 맵을 만들 수 있다. 이는 스칼라 컬렉션에서 일반적인 기능이다. 어떤 컬렉션이든 이름뒤에 괄호를 붙이고, 원소 목록을 넣을 수 있다. 그러면 해당 원소들을 포함한 새로운 컬렉션을 만들 수 있다. 다음은 그 예이다. + + Traversable() // 빈 순회가능 객체 + List() // 빈 리스트 + List(1.0, 2.0) // 1.0, 2.0이 원소인 리스트 + Vector(1.0, 2.0) // 1.0, 2.0이 원소인 벡터 + Iterator(1, 2, 3) // 3개의 정수를 반환하는 반복자 + Set(dog, cat, bird) // 세 동물의 집합 + HashSet(dog, cat, bird) // 세 동물의 해시집합 + Map(a -> 7, 'b' -> 0) // 문자에서 정수로 연관시켜주는 맵 + +위 각 식은 내부적으로 어떤 객체의 `apply` 메소드를 호출한다. 예를 들어 세번째 줄은 다음과 같이 펼칠 수 있다. + + List.apply(1.0, 2.0) + +따라서 이는 `List`의 짝 객체의 `apply`를 호출하는 것이다. 그 메소드는 임의의 갯수의 인자를 받아서 리스트를 만든다. 스칼라의 모든 컬렉션 클래스에는 이러한 `apply` 메소드가 정의된 짝 객체가 존재한다. 해당 컬렉션 클래스가 `List`, `Stream`, `Vector`등과 같이 구체적인 구현을 나타내고 있는지 `Seq`, `Set`, `Traversable`등 추상적인 것인지와 관계 없이 짝 객체는 존재한다. + +후자의 경우 `apply`를 호출하면 추상 기반 클래스의 기본 구현으로 되어 있는 타입의 객체가 만들어진다. 예를 들면 다음과 같다. + + scala> List(1, 2, 3) + res17: List[Int] = List(1, 2, 3) + scala> Traversable(1, 2, 3) + res18: Traversable[Int] = List(1, 2, 3) + scala> mutable.Traversable(1, 2, 3) + res19: scala.collection.mutable.Traversable[Int] = ArrayBuffer(1, 2, 3) + +모든 컬렉션 짝 객체는 `apply` 외에도 비어있는 컬렉션을 반환하는 필드 `empty`가 정의되어 있다. 따라서 `List()` 대신 `List.empty`를, `Map()` 대신 `Map.empty`를 쓸 수 있다. + +`Seq`를 상속한 클래스들은 또한 짝 객체에 다른 팩토리 연산도 제공한다. 이들은 다음 표에 정리되어 있다. 간단히 설명하자면, + +* `concat`은 임의의 갯수의 순회가능 객체들을 서로 연결한다. +* `fill`과 `tabulate`는 주어진 식이나 채워넣기 함수에 의해 초기화된 다차원 열(1차원 포함)을 만들어낸다. +* `range`는 주어진 범위와 증분값에 따라 정수 열을 만들어낸다. +* `iterate`는 어떤 함수를 시작 원소에 반복적용해서 나오는 결과값의 열을 만들어낸다. + +### 열의 팩토리 메소드 + +| 사용법 | 하는일 | +| ------ | ------ | +| `S.empty` | 빈 열을 반환한다. | +| `S(x, y, z)` | 원소 `x, y, z`로 이루어진 열을 반환한다. | +| `S.concat(xs, ys, zs)` | `xs, ys, zs` 세 열의 원소들로 이루어진 열을 반환한다. | +| `S.fill(n){e}` | 각 원소가 식 `e`에 의해 계산된 길이 `n`짜리 열을 반환한다. | +| `S.fill(m, n){e}` | 각 원소가 식 `e`에 의해 계산된 `m×n` 크기의 2차원 열을 반환한다(더 고차원에 대해서도 정의되어 있다). | +| `S.tabulate(n){f}` | 첨자 `i`번째에 있는 원소가 `f(i)`에 의해 계산된 길이 `n`짜리 열을 반환한다. | +| `S.tabulate(m, n){f}` | 첨자 `(i, j)`번째에 있는 원소가 `f(i, j)`에 의해 계산된 `mxn`크기의 2차원 열을 반환한다(더 고차원에 대해서도 정의되어 있다). | +| `S.range(start, end)` | 정수 `start` ... `end-1`의 열을 반환한다. | +| `S.range(start, end, step)`| `start`부터 시작해서 `step`을 증분으로 증가하고 `end`보다 작은 값으로 된 정수 열을 반환한다(end는 제외된다). | +| `S.iterate(x, n)(f)` | `x`, `f(x)`, `f(f(x))`, ... 으로 이루어진 길이 n인 열을 반환한다. | + + + diff --git a/ko/overviews/collections/equality.md b/ko/overviews/collections/equality.md new file mode 100644 index 0000000000..a54640d0dd --- /dev/null +++ b/ko/overviews/collections/equality.md @@ -0,0 +1,31 @@ +--- +layout: overview-large +title: 동일성 + +disqus: true + +partof: collections +num: 13 +language: ko +--- + +스칼라 컬렉션 라이브러리는 동일성 판단과 해싱에 있어 일괄적인 방법을 사용한다. 먼저, 컬렉션을 집합(set), 맵(map)과 열(sequence)로 나누어 종류가 다를 경우 언제나 다른 것으로 본다. 예를 들어 `Set(1, 2, 3)`과 `List(1, 2, 3)`은 같은 요소를 갖지만 서로 다르다. 종류가 같은 컬렉션끼리는 요소가 일치하는 경우에만 같은 것으로 본다. (열의 경우 순서 또한 동일해야 한다). 예를 들어 `List(1, 2, 3) == Vector(1, 2, 3)`과 `HashSet(1, 2) == TreeSet(2, 1)`은 참이다. + +동일성 판단에 있어 해당 컬렉션의 변경 가능 여부는 중요하지 않다. 변경 가능한 컬렉션에서는 단순히 동일성 판단 시점의 요소만을 고려한다. 이는 한 컬렉션이 요소의 추가 또는 제거에 따라 서로 다른 시각에 각기 다른 컬렉션과 동일할 수도 있음을 의미한다. 변경 가능한 컬렉션을 해시맵의 키로 사용할 경우 다음과 같은 문제가 생길 수 있다: + + scala> import collection.mutable.{HashMap, ArrayBuffer} + import collection.mutable.{HashMap, ArrayBuffer} + scala> val buf = ArrayBuffer(1, 2, 3) + buf: scala.collection.mutable.ArrayBuffer[Int] = + ArrayBuffer(1, 2, 3) + scala> val map = HashMap(buf -> 3) + map: scala.collection.mutable.HashMap[scala.collection. + mutable.ArrayBuffer[Int],Int] = Map((ArrayBuffer(1, 2, 3),3)) + scala> map(buf) + res13: Int = 3 + scala> buf(0) += 1 + scala> map(buf) + java.util.NoSuchElementException: key not found: + ArrayBuffer(2, 2, 3) + +이 예제에서 마지막 줄의 선택은 이전 줄에서 배열 `xs`의 해시 코드가 달라짐에 따라 십중팔구 실패하게 된다. 즉, 해시 코드 기반 탐색시 `xs`가 저장된 시점과는 다른 위치를 참조하게 되는 것이다. diff --git a/ko/overviews/collections/introduction.md b/ko/overviews/collections/introduction.md new file mode 100644 index 0000000000..e65d97905e --- /dev/null +++ b/ko/overviews/collections/introduction.md @@ -0,0 +1,72 @@ +--- +layout: overview-large +title: 컬렉션 소개 + +disqus: true + +partof: collections +num: 1 +language: ko +--- + +**마틴 오더스키(Martin Odesky)와 렉스 스푼(Lex Spoon) 씀** + +대부분의 사람들에게 있어 스칼라 2.8의 가장 중요한 변화는 새 컬렉션 프레임워크일 것이다. +스칼라에는 예전부터 컬렉션이 포함되어 있었다(실제 새 프레임워크도 이전 컬렉션과 상당부분 호환성이 있다). 하지만 다양한 컬렉션 유형을 포괄하는 일반적이면서 균일한 프레임워크를 제공하는 것은 스칼라 2.8 부터이다. + +처음 봤을 땐 컬렉션에 추가된 내용이 잘 알기 어려울 정도로 작게 느껴질 수도 있다. 하지만, 그 +변화는 여러분의 프로그래밍 스타일에 큰 영향을 끼칠 것이다. 실제로, 컬렉션의 개별 원소 보다는 +전체 컬렉션을 프로그램의 기본 구성 요소로 사용하는 더 높은 레벨에서 작업하는 느낌을 받을 수 +있다. 이런 프로그래밍 스타일에 익숙해지려면 적응이 필요하다. 다행히 새 스칼라 컬렉션이 +제공하는 몇몇 특징 덕분에 더 쉽게 적응할 수 있다. 새 스칼라 컬렉션은 쓰기 쉽고, +간결하며, 안전하고, 빠른 데다가, 범용이다. + +**간편성:** 대부분의 경우 컬렉션과 관련된 문제를 해결하기 위해 필요한 메소드는 수는 +20-50개 정도이며 두세 연산을 조합하여 해결 가능하다. 복잡하게 루프를 돌거나 재귀 호출을 +하기 위해 골머리를 썩힐 필요가 없다. 영속적인 컬렉션과 부작용이 없는 연산을 사용하면 실수로 +기존 컬렉션을 오염시킬 염려가 없다. 반복자와 컬렉션 업데이트간의 간섭도 없다. + +**간결성:** 하나 이상의 루프가 필요했던 작업을 한 단어로 수행할 수 있다. 간결한 문법으로 +연산을 표현할 수 있고, 각 연산을 힘들이지 않고 조합할 수 있다. 따라서 컬렉션 전용으로 고안된 +대수식을 사용하는 것 같은 느낌을 받게될 것이다. + +**안전성:** 경험해 보지 않고 이 부분을 알아채기는 어렵다. 스칼라 컬렉션은 정적 타이핑과 +함수적 특성을 가지기 때문에 프로그래머가 저지를 수 있는 오류의 대부분을 컴파일시 잡아낼 +수 있다. 이유는 (1) 컬렉션 연산을 많이 사용하기 때문에 연산에 대해 충분한 테스트가 되어 있고, +(2) 컬렉션 연산을 사용할 때 입력과 출력을 매개 변수로 넘기는 함수와 결과값으로 명확히 해야 +하며, (3) 이런 명시적인 입/출력이 정적인 타입 검사를 거쳐야 한다는 점 때문이다. 요약하면, +대부분의 잘못된 사용은 타입 오류라로 나타나게 된다. 수백줄 짜리 코드가 첫 시도시 실행되는 +경우도 그리 드물지 않다. + +**속도:** 라이브라리 안의 컬렉션 연산은 최적화와 미세조정이 이루어져 있다. 그 결과 컬렉션을 +사용하는 것은 일반적으로 아주 효율적이다. 손으로 직접 주의깊게 미세조정을 거친 데이터 구조와 +연산을 사용하면 조금 더 나은 결과를 얻을 수도 있을 것이다. 하지만 구현 도중에 최적이 아닌 +선택을 하거나 해서 훨씬 더 나쁜 결과를 가져올 수도 있다. 더 나아가, 최근에는 다중코어 +시스템에서 병렬로 수행되는 컬렉션이 도입되었다. 병렬 컬렉션은 순차적 컬렉션과 동일한 연산을 +지원한다. 따라서 새로운 연산을 배우거나 코드를 재작성할 필요가 없다. 순차적 컬렉션을 병렬 +컬렉션으로 변경하려면 단지 `par` 메소드를 호출하기만 하면 된다. + +**범용:** 어떤 컬렉션 연산이든, 가능한 모든 컬렉션에서 이를 제공하게 되어 있다. 따라서 알고 +있는 연산의 갯수가 적어도 많은 일을 할 수 있다. 예를 들어 문자열은 개념적으로 문자의 +시퀀스이다. 따라서, 스칼라 컬렉션의 문자열은 모든 시퀀스 연산을 지원한다. 배열도 마찬가지이다. + +**예제:** 다음 코드는 스칼라 컬렉션의 여러 장점을 보여준다. + + val (minors, adults) = people partition (_.age < 18) + +이 코드가 하는 일은 직접적이며 분명하다. `people`의 컬렉션을 나이에 따라 `minors`과 +`adults`로 구획한다. `partition` 메소드는 최상위 컬렉션 타입인 `TraversableLike`에 +구현되어 있다. 따라서 배열을 포함한 모든 컬렉션에서 이 코드가 동작할 수 있다. 결과로 +나오는 `minors`과 `adults`는 `people` 컬렉션과 같은 타입이 된다. + +전통적인 컬렉션 처리를 사용하는 경우 루프를 최대 세 개 사용해야 한다는 점과 비교해 보면 이 +코드는 매우 간결하다(배열을 사용하는 경우 중간 결과를 다른곳에 버퍼링하기 위해 루프가 세 개 +필요하다). 일단 컬렉션의 기본 어휘를 배우고 나면, 직접 루프를 도는 것보다 이렇게 코드를 +작성하는 편이 더 쉽고 안전하다는 사실을 알게 될 것이다. 또한, `partition` 연산은 +꽤 빠르며, 다중코어에서 병렬 컬렉션으로 수행한다면 훨씬 더 빨라진다(병렬 컬렉션은 +스칼라 2.9에 포함되어 배포되었다). + +이 문서는 스칼라 컬렉션 클래스의 API를 사용자 관점에서 자세히 논의한다. 이제 모든 기반 +클래스와 그 안에 정의된 메소드에 대해 여행을 떠나 보자. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/iterators.md b/ko/overviews/collections/iterators.md new file mode 100644 index 0000000000..298f914648 --- /dev/null +++ b/ko/overviews/collections/iterators.md @@ -0,0 +1,176 @@ +--- +layout: overview-large +title: Iterators + +disqus: true + +partof: collections +num: 15 +language: ko +--- + +An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. + +The most straightforward way to "step through" all the elements returned by an iterator `it` uses a while-loop: + + while (it.hasNext) + println(it.next()) + +Iterators in Scala also provide analogues of most of the methods that you find in the `Traversable`, `Iterable` and `Seq` classes. For instance, they provide a `foreach` method which executes a given procedure on each element returned by an iterator. Using `foreach`, the loop above could be abbreviated to: + + it foreach println + +As always, for-expressions can be used as an alternate syntax for expressions involving `foreach`, `map`, `withFilter`, and `flatMap`, so yet another way to print all elements returned by an iterator would be: + + for (elem <- it) println(elem) + +There's an important difference between the foreach method on iterators and the same method on traversable collections: When called to an iterator, `foreach` will leave the iterator at its end when it is done. So calling `next` again on the same iterator will fail with a `NoSuchElementException`. By contrast, when called on on a collection, `foreach` leaves the number of elements in the collection unchanged (unless the passed function adds to removes elements, but this is discouraged, because it may lead to surprising results). + +The other operations that Iterator has in common with `Traversable` have the same property. For instance, iterators provide a `map` method, which returns a new iterator: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it.map(_.length) + res1: Iterator[Int] = non-empty iterator + scala> res1 foreach println + 1 + 6 + 2 + 5 + scala> it.next() + java.util.NoSuchElementException: next on empty iterator + +As you can see, after the call to `it.map`, the `it` iterator has advanced to its end. + +Another example is the `dropWhile` method, which can be used to find the first elements of an iterator that has a certain property. For instance, to find the first word in the iterator above that has at least two characters you could write: + + scala> val it = Iterator("a", "number", "of", "words") + it: Iterator[java.lang.String] = non-empty iterator + scala> it dropWhile (_.length < 2) + res4: Iterator[java.lang.String] = non-empty iterator + scala> it.next() + res5: java.lang.String = number + +Note again that `it` has changed by the call to `dropWhile`: it now points to the second word "number" in the list. In fact, `it` and the result `res4` returned by `dropWhile` will return exactly the same sequence of elements. + +There is only one standard operation which allows to re-use the same iterator: The call + + val (it1, it2) = it.duplicate + +gives you _two_ iterators which each return exactly the same elements as the iterator `it`. The two iterators work independently; advancing one does not affect the other. By contrast the original iterator `it` is advanced to its end by `duplicate` and is thus rendered unusable. + +In summary, iterators behave like collections _if one never accesses an iterator again after invoking a method on it_. The Scala collection libraries make this explicit with an abstraction [TraversableOnce](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/TraversableOnce.html), which is a common superclass of [Traversable](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Traversable.html) and [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html). As the name implies, `TraversableOnce` objects can be traversed using `foreach` but the state of that object after the traversal is not specified. If the `TraversableOnce` object is in fact an `Iterator`, it will be at its end after the traversal, but if it is a `Traversable`, it will still exist as before. A common use case of `TraversableOnce` is as an argument type for methods that can take either an iterator or a traversable as argument. An example is the appending method `++` in class `Traversable`. It takes a `TraversableOnce` parameter, so you can append elements coming from either an iterator or a traversable collection. + +All operations on iterators are summarized below. + +### Operations in class Iterator + +| WHAT IT IS | WHAT IT DOES | +| ------ | ------ | +| **Abstract Methods:** | | +| `it.next()` | Returns next element on iterator and advances past it. | +| `it.hasNext` | Returns `true` if `it` can return another element. | +| **Variations:** | | +| `it.buffered` | A buffered iterator returning all elements of `it`. | +| `it grouped size` | An iterator that yields the elements elements returned by `it` in fixed-sized sequence "chunks". | +| `xs sliding size` | An iterator that yields the elements elements returned by `it` in sequences representing a sliding fixed-sized window. | +| **Duplication:** | | +| `it.duplicate` | A pair of iterators that each independently return all elements of `it`. | +| **Additions:** | | +| `it ++ jt` | An iterator returning all elements returned by iterator `it`, followed by all elements returned by iterator `jt`. | +| `it padTo (len, x)` | The iterator that first returns all elements of `it` and then follows that by copies of `x` until length `len` elements are returned overall. | +| **Maps:** | | +| `it map f` | The iterator obtained from applying the function `f` to every element returned from `it`. | +| `it flatMap f` | The iterator obtained from applying the iterator-valued function f to every element in `it` and appending the results. | +| `it collect f` | The iterator obtained from applying the partial function `f` to every element in `it` for which it is defined and collecting the results. | +| **Conversions:** | | +| `it.toArray` | Collects the elements returned by `it` in an array. | +| `it.toList` | Collects the elements returned by `it` in a list. | +| `it.toIterable` | Collects the elements returned by `it` in an iterable. | +| `it.toSeq` | Collects the elements returned by `it` in a sequence. | +| `it.toIndexedSeq` | Collects the elements returned by `it` in an indexed sequence. | +| `it.toStream` | Collects the elements returned by `it` in a stream. | +| `it.toSet` | Collects the elements returned by `it` in a set. | +| `it.toMap` | Collects the key/value pairs returned by `it` in a map. | +| **Coying:** | | +| `it copyToBuffer buf` | Copies all elements returned by `it` to buffer `buf`. | +| `it copyToArray(arr, s, n)`| Copies at most `n` elements returned by `it` to array `arr` starting at index `s`. The last two arguments are optional. | +| **Size Info:** | | +| `it.isEmpty` | Test whether the iterator is empty (opposite of `hasNext`). | +| `it.nonEmpty` | Test whether the collection contains elements (alias of `hasNext`). | +| `it.size` | The number of elements returned by `it`. Note: `it` will be at its end after this operation! | +| `it.length` | Same as `it.size`. | +| `it.hasDefiniteSize` | Returns `true` if `it` is known to return finitely many elements (by default the same as `isEmpty`). | +| **Element Retrieval Index Search:**| | +| `it find p` | An option containing the first element returned by `it` that satisfies `p`, or `None` is no element qualifies. Note: The iterator advances to after the element, or, if none is found, to the end. | +| `it indexOf x` | The index of the first element returned by `it` that equals `x`. Note: The iterator advances past the position of this element. | +| `it indexWhere p` | The index of the first element returned by `it` that satisfies `p`. Note: The iterator advances past the position of this element. | +| **Subiterators:** | | +| `it take n` | An iterator returning of the first `n` elements of `it`. Note: it will advance to the position after the `n`'th element, or to its end, if it contains less than `n` elements. | +| `it drop n` | The iterator that starts with the `(n+1)`'th element of `it`. Note: `it` will advance to the same position. | +| `it slice (m,n)` | The iterator that returns a slice of the elements returned from it, starting with the `m`'th element and ending before the `n`'th element. | +| `it takeWhile p` | An iterator returning elements from `it` as long as condition `p` is true. | +| `it dropWhile p` | An iterator skipping elements from `it` as long as condition `p` is `true`, and returning the remainder. | +| `it filter p` | An iterator returning all elements from `it` that satisfy the condition `p`. | +| `it withFilter p` | Same as `it` filter `p`. Needed so that iterators can be used in for-expressions. | +| `it filterNot p` | An iterator returning all elements from `it` that do not satisfy the condition `p`. | +| **Subdivisions:** | | +| `it partition p` | Splits `it` into a pair of two iterators; one returning all elements from `it` that satisfy the predicate `p`, the other returning all elements from `it` that do not. | +| **Element Conditions:** | | +| `it forall p` | A boolean indicating whether the predicate p holds for all elements returned by `it`. | +| `it exists p` | A boolean indicating whether the predicate p holds for some element in `it`. | +| `it count p` | The number of elements in `it` that satisfy the predicate `p`. | +| **Folds:** | | +| `(z /: it)(op)` | Apply binary operation `op` between successive elements returned by `it`, going left to right and starting with `z`. | +| `(it :\ z)(op)` | Apply binary operation `op` between successive elements returned by `it`, going right to left and starting with `z`. | +| `it.foldLeft(z)(op)` | Same as `(z /: it)(op)`. | +| `it.foldRight(z)(op)` | Same as `(it :\ z)(op)`. | +| `it reduceLeft op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going left to right. | +| `it reduceRight op` | Apply binary operation `op` between successive elements returned by non-empty iterator `it`, going right to left. | +| **Specific Folds:** | | +| `it.sum` | The sum of the numeric element values returned by iterator `it`. | +| `it.product` | The product of the numeric element values returned by iterator `it`. | +| `it.min` | The minimum of the ordered element values returned by iterator `it`. | +| `it.max` | The maximum of the ordered element values returned by iterator `it`. | +| **Zippers:** | | +| `it zip jt` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`. | +| `it zipAll (jt, x, y)` | An iterator of pairs of corresponding elements returned from iterators `it` and `jt`, where the shorter iterator is extended to match the longer one by appending elements `x` or `y`. | +| `it.zipWithIndex` | An iterator of pairs of elements returned from `it` with their indices. | +| **Update:** | | +| `it patch (i, jt, r)` | The iterator resulting from `it` by replacing `r` elements starting with `i` by the patch iterator `jt`. | +| **Comparison:** | | +| `it sameElements jt` | A test whether iterators it and `jt` return the same elements in the same order. Note: At least one of `it` and `jt` will be at its end after this operation. | +| **Strings:** | | +| `it addString (b, start, sep, end)`| Adds a string to `StringBuilder` `b` which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | +| `it mkString (start, sep, end)` | Converts the collection to a string which shows all elements returned by `it` between separators `sep` enclosed in strings `start` and `end`. `start`, `sep`, `end` are all optional. | + +### Buffered iterators + +Sometimes you want an iterator that can "look ahead", so that you can inspect the next element to be returned without advancing past that element. Consider for instance, the task to skip leading empty strings from an iterator that returns a sequence of strings. You might be tempted to write the following + + + def skipEmptyWordsNOT(it: Iterator[String]) = + while (it.next().isEmpty) {} + +But looking at this code more closely, it's clear that this is wrong: The code will indeed skip leading empty strings, but it will also advance `it` past the first non-empty string! + +The solution to this problem is to use a buffered iterator. Class [BufferedIterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/BufferedIterator.html) is a subclass of [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html), which provides one extra method, `head`. Calling `head` on a buffered iterator will return its first element but will not advance the iterator. Using a buffered iterator, skipping empty words can be written as follows. + + def skipEmptyWords(it: BufferedIterator[String]) = + while (it.head.isEmpty) { it.next() } + +Every iterator can be converted to a buffered iterator by calling its `buffered` method. Here's an example: + + scala> val it = Iterator(1, 2, 3, 4) + it: Iterator[Int] = non-empty iterator + scala> val bit = it.buffered + bit: java.lang.Object with scala.collection. + BufferedIterator[Int] = non-empty iterator + scala> bit.head + res10: Int = 1 + scala> bit.next() + res11: Int = 1 + scala> bit.next() + res11: Int = 2 + +Note that calling `head` on the buffered iterator `bit` does not advance it. Therefore, the subsequent call `bit.next()` returns the same value as `bit.head`. diff --git a/ko/overviews/collections/maps.md b/ko/overviews/collections/maps.md new file mode 100644 index 0000000000..e447e5aea2 --- /dev/null +++ b/ko/overviews/collections/maps.md @@ -0,0 +1,166 @@ +--- +layout: overview-large +title: 맵(Map) + +disqus: true + +partof: collections +num: 7 +language: ko +--- + +[Map(맵)](http://www.scala-lang.org/api/current/scala/collection/Map.html)은 키와 값의 쌍으로 구성된 [Iterable(반복가능)](http://www.scala-lang.org/api/current/scala/collection/Iterable.html)한 컬렉션이다(_맵핑_ 또는 _연관_ 이라고도 한다). 스칼라의 [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) 클래스에는 `key -> value`를 `(key, value)` 대신 쓸수 있도록 하는 묵시적 변환이 정의되어 있다. 예를 들어 `Map("x" -> 24, "y" -> 25, "z" -> 26)`와 `Map(("x", 24), ("y", 25), ("z", 26))`는 의미가 완전히 동일하다. 다만 전자가 더 보기 좋을 뿐이다. + +맵에서 제공하는 기본 연산들은 집합과 비슷하다. 이를 분류하면 아래와 같다. 전체는 그 이후 표에 요약해 두었다. + +* **찾기** 연산 `apply`, `get`, `getOrElse`, `contains`, `isDefinedAt` 들은 맵을 키를 받아 값을 반환하는 부분함수로 바꾼다. 맵에서 바탕이 되는 찾기 메소드는 `def get(key): Option[Value]`이다. 연산 "`m get key`"는 맵에 주어진 `key`와 대응하는 연관관계쌍이 들어 있는지를 본다. 만약 그런 쌍이 있다면 그 연관쌍을 `Some`으로 포장해서 내어 놓는다. 맵에 키가 정의되어 있지 않다면 `get`은 `None`을 반환한다. 맵에는 또한 키에 대해 값을 `Option`으로 감싸지 않고 바로 반환하는 `apply` 메소드도 정의되어 있다. 만약 키가 없다면 예외가 발생한다. +* **추가와 변경** 연산 `+`, `++`, `updated`를 사용하면 새로운 연관관계를 맵에 추가하거나, 기존의 관계를 변경할 수 있다. +* **제거** 연산 `-`, `--`는 맵에서 연관 관계를 제거하기 위해 사용한다. +* **부분 컬렉션** 생성 메소드 `keys`, `keySet`, `keysIterator`, `values`, `valuesIterator`는 맵의 모든 키나 모든 값을 별도로 여러 형태로 반환한다. +* **변환** 연산 `filterKeys`와 `mapValues`는 맵을 필터링해 새 맵을 만들거나 기존 맵의 연관관계를 변환할 때 사용한다. + +### 맵 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **찾기:** | | +| `ms get k` |맵 `ms`에 있는 키 `k`에 대응하는 값을 옵션으로 반환한다. 찾을 수 없다면 `None`이 반환된다.| +| `ms(k)` |(`ms apply k`라고 명시적으로 쓸 수 있음) 맵 `ms`에서 키 `k`에 대응하는 값을 반환한다. 값이 없다면 예외를 던진다.| +| `ms getOrElse (k, d)` |맵 `ms`에서 키 `k`에 대응하는 값을 반환한다. 값이 없다면 디폴트 값으로 지정된 `d`를 반환한다.| +| `ms contains k` |맴 `ms`에 키 `k`에 대한 맵핑이 정의되어 있는지 여부를 반환한다.| +| `ms isDefinedAt k` |`contains`와 같다. | +| **추가와 변경:**| | +| `ms + (k -> v)` |`ms`의 모든 연관관계와 더불어 맵핑 `k -> v`, 즉 키 `k`에서 `v`로 가는 연관관계가 추가된 맵을 반환한다.| +| `ms + (k -> v, l -> w)` |`ms`의 모든 연관관계와 더불어 주어진 모든 키-값 연관관계가 추가된 맵을 반환한다.| +| `ms ++ kvs` |`ms`의 모든 연관관계와 더불어 `kvs`에 있는 연관관계가 추가된 맵을 반환한다. (단, `kvs`는 튜플이 원소이거나 맵 타입이어야 정상적으로 동작한다.)| +| `ms updated (k, v)` |`ms + (k -> v)`과 같다.| +| **제거:** | | +| `ms - k` |`ms`의 모든 연관관계 중에서 키 `k`에 대한 연관관계만이 포함되지 않은 맵을 반환한다.| +| `ms - (k, 1, m)` |`ms`의 모든 연관관계 중에서 주어진 여러 키들에 대한 연관관계들이 포함되지 않은 맵을 반환한다.| +| `ms -- ks` |`ms`의 모든 연관관계 중에서 `ks`에 있는 키들에 대한 연관관계들이 포함되지 않은 맵을 반환한다.| +| **부분 컬렉션 :** | | +| `ms.keys` |`ms`의 모든 키를 포함하는 반복가능 객체를 반환한다. | +| `ms.keySet` |`ms`의 모든 키를 포함하는 집합 객체를 반환한다. | +| `ms.keyIterator` |`ms`의 키를 내어놓는 반복자를 반환한다. | +| `ms.values` |`ms`에서 키에 연관된 모든 값을 포함하는 반복가능 객체를 반환한다. | +| `ms.valuesIterator` |`ms`에서 키에 연관된 모든 값을 포함하는 반복자를 반환한다. | +| **변환:** | | +| `ms filterKeys p` |`ms`에서 키가 술어 `p`를 만족하는 연관관계만을 포함하는 새 맵 뷰를 반환한다.| +| `ms mapValues f` |`ms`에서 키에 연관된 모든 값에 대해 함수 `f`를 적용해 얻을 수 있는 새 맵 뷰를 반환한다.| + +변경 가능 맵은 아래 표에 정리된 연산을 추가로 지원한다. + + +### mutable.Map 클래스에 정의된 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추가와 변경:**| | +| `ms(k) = v` |(`ms.update(x, v)`라고 명시적으로 쓸 수도 있음). 키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가한다. 이미 키 `k`에 대한 값이 정의되어 있었다면 이를 덮어쓴다.| +| `ms += (k -> v)` |키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms += (k -> v, l -> w)` |지정된 맵핑들을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms ++= kvs` |`kvs`에 있는 모든 연관관계들을 맵 `ms`에 부작용을 사용해 추가하고 `ms` 자신을 반환한다.| +| `ms put (k, v)` |키 `k`에서 값 `v`로 가는 맵핑을 맵 `ms`에 부작용을 사용해 추가하고, 이전에 `k`와 연관된 값이 있었다면 이를 옵션 객체로 반환한다.| +| `ms getOrElseUpdate (k, d)`|키 `k`가 맵 `ms`에 정의되어 있다면 그 값을 반환하고, 그렇지 않다면 `ms`에 새 연관관계 `k -> d`를 추가한 다음 `d`를 반환한다.| +| **제거:**| | +| `ms -= k` |키 `k`에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms -= (k, l, m)` |지정된 키들에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms --= ks` |`ks`에 있는 키들에 해당하는 맵핑을 맵 `ms`에서 부작용을 사용해 제거한 후, `ms`자신을 반환한다.| +| `ms remove k` |키 `k`에 대한 맵핑을 맵 `ms`에서 부작용을 사용해 제거하고, 이전에 `k`와 연관된 값이 있었다면 이를 옵션 객체로 반환한다.| +| `ms retain p` |`ms`에서 (키,값) 튜플에 대한 술어 `p`를 만족하는 연관 관계들만 남기고 나머지는 다 제거한 다음, `ms` 자신을 반환한다| +| `ms.clear()` |`ms`에서 모든 매핑을 제거한 다음, | +| **변환:** | | +| `ms transform f` |`ms`의 모든 연관 쌍을 `f`를 사용해 변환한다. `ms`자신을 반환한다.| +| **복사:** | | +| `ms.clone` |`ms`과 같은 맵핑들을 포함하는 새로운 변경가능한 맵을 반환한다.| + +맵의 추가와 제거 연산은 집합의 그것과 비슷하다. 집합에서와 마찬가지로 변경 가능한 맵도 부작용을 사용하지 않는 추가 연산 `+`, `-`, `updated`를 지원한다. 하지만 이런 연산들은 변경 가능 맵을 복사하기 때문에 자주 사용되지는 않는다. 대신 변경 가능한 맵 `m`은 보통 "그 자리에서" `m(key) = value`이나 `m += (key -> value)` 연산을 사용해 변경된다. 업데이트 연산에는 이전에 `key`와 연관되어 있던 값을 `Option`으로 돌려주는 `m put (key, value)`도 있다. `put`은 만약 `key`가 맵에 존재하지 않았다면 `None`을 반환한다. + +`getOrElseUpdate`는 캐시처럼 동작하는 맵을 억세스할 때 유용하다. `f`를 호출하면 비용이 많이 드는 계산을 수행해야 하는 경우를 생각해 보자. + + scala> def f(x: String) = { + println("taking my time."); sleep(100) + x.reverse } + f: (x: String)String + +더 나아가 `f`에 부작용이 없다고 한다면, 동일한 인자로 이 함수를 호출할 때마다 항상 같은 결과를 받을 것이다. 이런 경우 예전에 계산했던 값을 인자와 `f`의 결과값을 연관시켜 맴에 저장해 두고, 새로운 인자 값이 들어와 맵에서 예전에 계산해 둔 결과를 찾을 수 없는 경우에만 `f`의 결과를 계산하게 한다면 비용이 줄어든다. 이 경우 맵을 함수 `f`의 계산 결과에 대한 _캐시(cache)_ 라 부를 수도 있다. + + val cache = collection.mutable.Map[String, String]() + cache: scala.collection.mutable.Map[String,String] = Map() + +이제 더 효율이 좋은 `f` 함수의 캐시된 버전을 만들 수 있다. + + scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s)) + cachedF: (s: String)String + scala> cachedF("abc") + taking my time. + res3: String = cba + scala> cachedF("abc") + res4: String = cba + +`getOrElseUpdate`의 두번째 인자는 "이름에 의한 호출(by-name)"이므로, 위의 `f("abc")` 계산은 오직 `getOrElseUpdate`가 두번째 인자 값을 필요로 하는 경우에만 수행된다. 이는 정확하게 말하자면 첫 번째 인자를 `cache` 맵에서 못 찾은 경우이다. 맵의 기본 연산을 활용해 `cachedF`를 직접 구현할 수도 있었겠지만, 그렇게 하려면 조금 길게 코드를 작성해야 한다. + + def cachedF(arg: String) = cache get arg match { + case Some(result) => result + case None => + val result = f(x) + cache(arg) = result + result + } + +### 동기화된 집합과 맵 ### + +쓰레드 안전한 변경 가능한 맵을 만들고 싶다면 `SynchronizedMap` 트레잇을 원하는 맵 구현에 끼워 넣으면 된다. 예를 들어 아래 코드처럼 `SynchronizedMap`을 `HashMap`에 끼워 넣을 수 있다. 아래 예제는 두 트레잇 `Map`과 `SynchronizedMap`, 그리고 클래스 `HashMap`을 패키지 `scala.collection.mutable`에서 임포트한다. 나머지 부분은 싱글턴 `MapMaker`를 만드는 것이다. 이 객체는 메소드 `makeMap`를 구현한다. `makeMap` 메소드는 문자열에서 문자열로 맵핑하는 동기화된 해시맵을 반환한다. + + import scala.collection.mutable.{Map, + SynchronizedMap, HashMap} + object MapMaker { + def makeMap: Map[String, String] = { + new HashMap[String, String] with + SynchronizedMap[String, String] { + override def default(key: String) = + "Why do you want to know?" + } + } + } + +
`SynchronizedMap`트레잇 끼워 넣기
+ +`makeMap`의 첫 문장은 새로운 변경 가능한 `HashMap`을 만들고 `SynchronizedMap` 트레잇을 끼워 넣는다. + + new HashMap[String, String] with + SynchronizedMap[String, String] + +스칼라 컴파일러는 이 코드를 보고 `SynchronizedMap`을 끼워 넣은 `HashMap`의 하위 클래스를 만들고, 그 클래스의 인스턴스를 만든다(그리고 반환한다). 이 합성 클래스는 아래와 같이 `default`라는 메소드를 재정의한다. + + override def default(key: String) = + "Why do you want to know?" + +사용자가 맵에게 어떤 키와 연관된 값을 물어봤는데 그런 연관이 맵에 존재하지 않는다면 기본 동작은 `NoSuchElementException` 예외를 발생시키는 것이다. 하지만 새 맵 클래스를 만들면서 `default` 메소드를 재정의하면 존재하지 않는 키에 대한 질의가 들어올 때 `default` 메소드가 정의하는 값을 반환하게 된다. 따라서 컴파일러가 만든 동기화된 합성 `HashMap` 하위 클래스는 존재하지 않는 키에 대한 질의를 받으면 `"Why do you want to know?"`라는 문자열을 반환한다. + +`makeMap` 메소드가 반환하는 변경 가능한 맵에 `SynchronizedMap` 트레잇을 끼워 넣었기 때문에, 동시에 여러 쓰레드에서 이를 사용할 수 있다. 맵에 대한 억세스는 동기화될 것이다. 다음은 인터프리터상에서 한 쓰레드를 사용해 이 맵을 사용하는 예를 보여준다. + + scala> val capital = MapMaker.makeMap + capital: scala.collection.mutable.Map[String,String] = Map() + scala> capital ++ List("US" -> "Washington", + "Paris" -> "France", "Japan" -> "Tokyo") + res0: scala.collection.mutable.Map[String,String] = + Map(Paris -> France, US -> Washington, Japan -> Tokyo) + scala> capital("Japan") + res1: String = Tokyo + scala> capital("New Zealand") + res2: String = Why do you want to know? + scala> capital += ("New Zealand" -> "Wellington") + scala> capital("New Zealand") + res3: String = Wellington + +동기화된 맵을 만드는 것과 비슷한 방식으로 동기화된 집합도 만들 수 있다. 예를 들어 `SynchronizedSet` 트레잇을 끼워 넣으면 동기화된 `HashSet`을 만들 수 있다. 다음과 같다. + + import scala.collection.mutable + val synchroSet = + new mutable.HashSet[Int] with + mutable.SynchronizedSet[Int] + +마지막으로, 어떤 상황에서 동기화된 컬렉션을 사용하는 것을 고려하게 된다면, 그 상황이 `java.util.concurrent` 컬렉션을 필요로 하는 경우는 아닌지 한번 더 생각해 보도록 하라. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/migrating-from-scala-27.md b/ko/overviews/collections/migrating-from-scala-27.md new file mode 100644 index 0000000000..1064d90738 --- /dev/null +++ b/ko/overviews/collections/migrating-from-scala-27.md @@ -0,0 +1,45 @@ +--- +layout: overview-large +title: 스칼라 2.7로부터 포팅하기 + +disqus: true + +partof: collections +num: 18 +outof: 18 +language: ko +--- + +기본 스칼라 앱을 새 컬렉션에서 사용하기 위해 포팅하는 작업은 거의 자동으로 이루어져야만 한다. 주의해야 할 것은 두세가지 뿐이다. + +일반적으로, 스칼라 2.7 컬렉션의 옛 기능은 그대로 남아있다. 몇몇 특징은 사용하지 않을 것이 권장되며, 향후 배포판에서는 제거될 수 있다. 스칼라 2.8에서 이러한 기능을 사용한다면 _사용금지 경고_를 보게될 것이다. 2.8에서도 그대로 사용되지만, 성능이나 의미는 변경되어 사용 금지를 표시하는 것이 바람직하지 않은 경우도 있다. 이런 경우에는 스칼라 2.8에서 _이전 경고(migration warning)_ 가 발생한다. 코드를 어떻게 바꿔야 할지 참고하기 위해 사용금지와 이전 경고를 모두 다 보고 싶다면 `-deprecation`와 `-Xmigration` 플래그를 `scalac`에 전달하라(`-Xmigration`는 확장 옵션이기 때문에 `X`로 시작한다는 점에 유의하라). `scala` REPL에 같은 플래그를 전달하면 대화식 세션에서도 다음과 같이 경고 메시지를 볼 수 있다. + + >scala -deprecation -Xmigration + Welcome to Scala version 2.8.0.final + Type in expressions to have them evaluated. + Type :help for more information. + scala> val xs = List((1, 2), (3, 4)) + xs: List[(Int, Int)] = List((1,2), (3,4)) + scala> List.unzip(xs) + :7: warning: method unzip in object List is deprecated: use xs.unzip instead of List.unzip(xs) + List.unzip(xs) + ^ + res0: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> xs.unzip + res1: (List[Int], List[Int]) = (List(1, 3),List(2, 4)) + scala> val m = xs.toMap + m: scala.collection.immutable.Map[Int,Int] = Map((1,2), (3,4)) + scala> m.keys + :8: warning: method keys in trait MapLike has changed semantics: + As of 2.8, keys returns Iterable[A] rather than Iterator[A]. + m.keys + ^ + res2: Iterable[Int] = Set(1, 3) + +예전 라이브러리에 있던 기능 전체가 다 변경되어서 금지 경고를 표시할 수 없는 경우로는 다음 두 가지가 있다. + +1. 예전의 `scala.collection.jcl` 패키지는 삭제되었다. 이 패키지는 자바 컬렉션 라이브러리 설계를 스칼라에서 일부 흉내내기 위한 시도였다. 하지만, 그로 인해 많은 대칭성이 깨져 버렸다. 대부분의 경우 자바 컬렉션을 원하는 프로그래머들은 `jcl`을 거치지 않고 바로 `java.util`을 사용했다. 스칼라 2.8에서는 `jcl` 패키지를 대치하는 [자바변환(JavaConversions)]({{ site.baseurl }}/overviews/collections/conversions-between-java-and-scala-collections.md) 객체를 사용한 스칼라와 자바 컬렉션간의 자동 변환이 제공된다. +2. 프로젝션(Projection)은 더 일반화되고 다듬어져서 이제 뷰로 제공된다. 프로젝션을 사용하는 경우가 그리 많지 았았던 것 같으므로, 이 변경에 영향받는 경우도 적을 것이다. + +따라서 여러분이 작성한 코드가 `jcl`이나 프로젝션을 사용한다면 코드를 조금은 다시 작성할 필요가 있다. + diff --git a/ko/overviews/collections/overview.md b/ko/overviews/collections/overview.md new file mode 100644 index 0000000000..ca14ee6248 --- /dev/null +++ b/ko/overviews/collections/overview.md @@ -0,0 +1,121 @@ +--- +layout: overview-large +title: 변경할 수 있는 컬렉션과 변경할 수 없는 컬렉션 + +disqus: true + +partof: collections +num: 2 +language: ko +--- + +시스템적으로 스칼라의 컬렉션을 변경할 수 있는(mutable) 컬렉션과 변경할 수 없는(immutable) +컬렉션으로 나눌 수 있다. _변경할 수 있는_ 컬렉션은 바로 수정하고 확장할 수 있다. 즉, 컬렉션 +요소를 부가작용으로 변경하고 추가하거나 제거할 수 있다. 반면에 _변경할 수 없는_ 컬렉션은 +절대 변경되지 않는다. _변경할 수 없는_ 컬렉션에서도 추가나 삭제, 갱신같은 작업을 할 수 있지만 +이는 기존의 컬렉션은 변경하지 않고 놔둔 채 새로운 컬렉션은 반환한다. + +모든 컬렉션 클래스는 `scala.collection` 패키지와 `scala.collection` 패키지의 하위 +패키지인 `mutable`, `immutable`, `generic`에 있다. 클라이언트 코드에서 필요한 +컬렉션 클래스의 대부분은 `scala.collection`, `scala.collection.immutable`, +`scala.collection.mutable` 패키지에 있다. + +`scala.collection.immutable` 패키지의 컬렉션은 변경되지 않는다는 것을 보장하고 생성된 +이후에 절대로 변경되지 않을 것이다. 그래서 언제 어디서든 반복적으로 같은 컬렉션에 접근하더라도 +항상 같은 요소를 가진 컬렉션에 접근한다는 것을 믿을 수 있다. + +`scala.collection.mutable` 패키지의 컬렉션은 컬렉션을 즉석에서 변경하는 작업을 가지고 +있다. 그러므로 변경할 수 있는 컬렉션을 다룰 때는 코드가 언제 컬렉션을 변경하는 지를 +이해해야 한다. + +`scala.collection` 패키지의 컬렉션은 변경할 수 있을 수도 없을 수도 있다. +예를 들어 [collection.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)는 +[collection.immutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/immutable/IndexedSeq.html)와 +[collection.mutable.IndexedSeq\[T\]](http://www.scala-lang.org/api/current/scala/collection/mutable/IndexedSeq.html) 모두의 수퍼클래스이다. 일반적으로 `scala.collection` 패키지의 루트 컬렉션은 +변경할 수 없는 컬렉션과 같은 인터페이스를 정의하고 `scala.collection.mutable` +패키지의 변경할 수 있는 컬렉션은 이 변경할 수 없는 인터페이스에 부가작용을 하는 +수정작업이 추가되어 있다. + +루트 컬렉션과 변경할 수 없는 컬렉션의 차이점은 루트 컬렉션은 루트 컬렉션의 클라이언트만 +컬렉션을 변경할 수 없지만 변경할 수 없는 컬렉션은 아무도 컬렉션을 변경할 수 없다는 점이다. +루트 컬렉션의 정적 타임이 컬렉션을 변경하는 작업을 제공하지 않더라도 런타임에서의 타입은 +다른 클라이언트가 변경할 수 있는 컬렉션일 수 있다. + +스칼라는 기본적으로 항상 변경할 수 없는 컬렉션을 사용한다. 예를 들어 접두사를 사용하지 않고 +다른 곳에서 `Set`을 임포트하지 않고 `Set`을 사용하면 변경할 수 없는 Set이 되고 +`Iterable`를 작성하면 변경할 수 없는 iterable 컬렉션이 된다. 이는 `scala` 패키지로 +임포트한 기본 바인딩때문이다. 변경가능한 컬렉션을 사용하려면 명시적으로 +`collection.mutable.Set`나 `collection.mutable.Iterable`를 사용해야 한다. + +변경할 수 있는 컬렉션과 변경할 수 없는 컬렉션을 둘 다 사용할 때 유용한 관례는 +`collection.mutable` 패키지를 임포트하는 것이다. + + import scala.collection.mutable + +이제 접두사 없이 사용한 `Set`이 여전히 불변 컬렉션을 참조하는 한편, `mutable.Set`은 가변 +컬렉션을 참조한다. + +컬렉션 계층에서 남은 패키지는 `collection.generic`이다. 이 패키지에는 컬렉션을 구현하는 +코드가 있다. 일반적으로 컬렉션 클래스는 클래스의 일부 작업의 구현을 `generic`의 클래스로 +위임한다. 그럼에도 컬렉션 프레임워크의 사용자들은 예외적인 상황에서만 `generic`의 클래스를 +참조해야 한다. + +편의성과 하위 호환성때문에 일부 중요한 타입은 `scala` 패키지에 별칭을 가지고 있으므로 +임포트하지 않고도 간단한 이름으로 이러한 타입을 사용할 수 있다. 이 대표적인 예가 `List` +타입이고 다음과 같은 방법으로 접근할 수 있다. + + scala.collection.immutable.List // List가 정의되거 있는 곳 + scala.List // via the alias in the scala package + scala.List // scala 패키지의 별칭으로 접근 + List // 항상 scala._가 자동 임포트되므로 + +별칭이 있는 다른 타입으로는 +[Traversable](http://www.scala-lang.org/api/current/scala/collection/Traversable.html), [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html), [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html), [IndexedSeq](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html), [Iterator](http://www.scala-lang.org/api/current/scala/collection/Iterator.html), [Stream](http://www.scala-lang.org/api/current/scala/collection/immutable/Stream.html), [Vector](http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html), [StringBuilder](http://www.scala-lang.org/api/current/scala/collection/mutable/StringBuilder.html), [Range](http://www.scala-lang.org/api/current/scala/collection/immutable/Range.html)가 있다. + +다음 그림에 `scala.collection` 패키지의 모든 컬렉션이 나와 있다. 일반적으로 변경할 수 없는 +구현체 뿐 아니라 변경할 수 있는 구현체도 가지는 고수준의 추상 클래스나 트레이트가 있다. + +[]({{ site.baseurl }}/resources/images/collections.png) + +다음 그림에는 `scala.collection.immutable` 패키지의 모든 컬렉션이 나와 있다. + +[]({{ site.baseurl }}/resources/images/collections.immutable.png) + +다음 그림에는 `scala.collection.mutable` 패키지의 모든 컬렉션이 나와 있다. + +[]({{ site.baseurl }}/resources/images/collections.mutable.png) +(이 세 그림은 decodified.com의 Mathias가 만들었다.) + +## 컬렉션 API 살펴보기 ## + +위의 그림에 가장 중요한 컬렉션 클래스가 나와 있고 모든 클래스에 공통된 아주 약간의 특성이 있는데 +예를 들면 모든 컬렉션을 컬렉션 클래스명뒤에 요소를 작성하는 형태의 일관된 문법으로 생성할 수 있다. + + Traversable(1, 2, 3) + Iterable("x", "y", "z") + Map("x" -> 24, "y" -> 25, "z" -> 26) + Set(Color.red, Color.green, Color.blue) + SortedSet("hello", "world") + Buffer(x, y, z) + IndexedSeq(1.0, 2.0) + LinearSeq(a, b, c) + +같은 원리가 특정 컬렉션 구현체에도 다음과 같이 적용된다. + + List(1, 2, 3) + HashMap("x" -> 24, "y" -> 25, "z" -> 26) + +위에 작성한 것과 같은 방법으로 이러한 모든 컬렉션을 `toString`으로 표현한다. + +모든 컬렉션은 `Traversable`이 제공하는 API를 지원하지만 가능하다면 타입을 전문화한다. 예를 들어 `Traversable`클래스의 `map`메서드를 실행하면 새로운 `Traversable`을 반환하지만 이 결과 타입은 하위 클래스에서 오버라이드된다. 즉, `List`에서 `map`을 호출하면 다시 `List`를 반환하고 `Set`에서 `map`을 호출하면 다시 `Set`을 반환하는 등이다. + + scala> List(1, 2, 3) map (_ + 1) + res0: List[Int] = List(2, 3, 4) + scala> Set(1, 2, 3) map (_ * 2) + res0: Set[Int] = Set(2, 4, 6) + +컬렉션 라이브러리 어디서나 구현된 이 동작을 _통일된 반환 타입 원리(uniform return type principle)_라고 부른다. + +컬렉션 계층의 클래스 대부분은 3가지 종류가 있는데 루트 컬렉션과 변경할 수 있는 컬렉션과 변경할 수 없는 컬렉션이다. Buffer 트레이트만이 변경할 수 있는 컬렉션만 있는 유일한 예외이다. + +이어서 이러한 클래스를 하나씩 살펴 볼 것이다. diff --git a/ko/overviews/collections/performance-characteristics.md b/ko/overviews/collections/performance-characteristics.md new file mode 100644 index 0000000000..9feff61690 --- /dev/null +++ b/ko/overviews/collections/performance-characteristics.md @@ -0,0 +1,85 @@ +--- +layout: overview-large +title: 성능 특성 + +disqus: true + +partof: collections +num: 12 +language: ko +--- + +앞의 설명에서 다른 종류의 콜렉션은 다른 성능 특성(performance characteristics)을 지니는 것을 알 수 있다. 이러한 성능 특성은 여러 콜렉션 가운데 하나를 선택하는 주요 기준이 된다. 아래의 두 개의 표에서 콜렉션들의 공통된 연산들의 성능 특성을 요약하였다. + +시퀀스 타입의 성능 특성: + +| | head | tail | apply | update| prepend | append | insert | +| -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | | | | +| `List` | C | C | L | L | C | L | - | +| `Stream` | C | C | L | L | C | L | - | +| `Vector` | eC | eC | eC | eC | eC | eC | - | +| `Stack` | C | C | L | L | C | C | L | +| `Queue` | aC | aC | L | L | L | C | - | +| `Range` | C | C | C | - | - | - | - | +| `String` | C | L | C | L | L | L | - | +| **mutable** | | | | | | | | +| `ArrayBuffer` | C | L | C | C | L | aC | L | +| `ListBuffer` | C | L | L | L | C | C | L | +|`StringBuilder`| C | L | C | C | L | aC | L | +| `MutableList` | C | L | L | L | C | C | L | +| `Queue` | C | L | L | L | C | C | L | +| `ArraySeq` | C | L | C | C | - | - | - | +| `Stack` | C | L | L | L | C | L | L | +| `ArrayStack` | C | L | C | C | aC | L | L | +| `Array` | C | L | C | C | - | - | - | + +집합과 맵의 성능 특성: + +| | lookup | add | remove | min | +| -------- | ---- | ---- | ---- | ---- | +| **immutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `TreeSet`/`TreeMap`| Log | Log | Log | Log | +| `BitSet` | C | L | L | eC1| +| `ListMap` | L | L | L | L | +| **mutable** | | | | | +| `HashSet`/`HashMap`| eC | eC | eC | L | +| `WeakHashMap` | eC | eC | eC | L | +| `BitSet` | C | aC | C | eC1| +| `TreeSet` | Log | Log | Log | Log | + +각주: 1 비트들이 밀집해서 담겨있다고 전제한다. + +두 표의 각 요소는 아래의 의미를 가진다: + +| | | +| --- | ---- | +| **C** | 연산에 (빠른) 상수 시간이 소요된다 | +| **eC** | 연산에 실질적으로 상수 시간이 소요되지만, 벡터의 최대 길이나 해시 키의 분포등의 전제에 의존한다.| +| **aC** | 평균적으로 상수 시간이 소요된다. 연산의 호출이 떄때로 더 긴 시간이 걸릴 때도 있지만, 여러 번의 연산 소요 시간을 평균내면 한 연산 당 상수 시간이 소요된다.| +| **Log** | 연산 소요 시간이 콜렉션 크기의 로그 연산에 비례한다.| +| **L** | 연산에 선형 시간이 소요되며, 콜렉션 크기에 비례한다.| +| **-** | 연산이 지원되지 않는다.| + +첫 번째 표는 시퀀스 타입(가변과 불변 모두)을 아래의 연산으로 사용하였다: + +| | | +| --- | ---- | +| **head** | 시퀀스의 첫 번째 원소를 선택한다. | +| **tail** | 첫 번째 원소를 제외한 모든 원소를 포함하는 새로운 시퀀스를 생성한다.| +| **apply** | 인덱스 접근. | +| **update** | 불변 시퀀스에서는 함수형 갱신(`updated`), 가변 시퀀스에는 사이드 이펙트 갱신 (`update`).| +| **prepend**| 원소를 시퀀스 제일 앞에 추가한다. 불변 시퀀스에서는 새로운 시퀀스를 생성한다. 가변 시퀀스에서는 존재하는 시퀀스를 수정한다.| +| **append** | 원소를 시퀀스 제일 뒤에 추가한다. 불변 시퀀스에서는 새로운 시퀀스를 생성한다. 가변 시퀀스에서는 존재하는 시퀀스를 수정한다.| +| **insert** | 원소를 시퀀스의 임의 위치에 삽입한다. 가변 시퀀스에서만 지원된다.| + +두 번째 표는 가변, 불변 집합과 맵을 아래의 연산으로 사용하였다: + +| | | +| --- | ---- | +| **lookup** | 원소가 집합에 포함되어있는지를 검사하거나, 키에 관련된 값을 선택한다.| +| **add** | 집합에 새로운 원소를 추가하거나, 맵에 키/값 쌍을 추가한다.| +| **remove** | 집합에서 원소를 제거하거나, 맵에서 키를 삭제한다.| +| **min** | 집합의 가장 작은 원소나 맵의 가장 작은 키를 반환한다.| + diff --git a/ko/overviews/collections/seqs.md b/ko/overviews/collections/seqs.md new file mode 100644 index 0000000000..815832bc2c --- /dev/null +++ b/ko/overviews/collections/seqs.md @@ -0,0 +1,102 @@ +--- +layout: overview-large +title: 열 트레잇 Seq, IndexedSeq, LinearSeq + +disqus: true + +partof: collections +num: 5 +language: ko +--- + +[Seq(열)](http://www.scala-lang.org/api/current/scala/collection/Seq.html) 트레잇은 순서가 있는 열을 표현한다. +열이란 `length`가 있고 원소들이 고정된 첨자로 표현되는 위치를 가지고 있는 반복가능한 객체를 말한다. 첨자는 `0`부터 시작한다. + +표에 열이 제공하는 연산이 정리되어 있다. 각 연산은 다음과 같이 분류할 수 있다. + +* **참조와 길이** 연산으로 `apply`, `isDefinedAt`, `length`, `indices`, `lengthCompare`가 있다. `Seq`에 있어, `apply` 연산은 첨자에 의해 원소를 참조하는 것이다. 따라서 `Seq[T]` 타입의 열은 `Int` 인자(첨자)를 받아서 열의 원소인 타입 `T` 객체를 반환하는 부분함수로 볼 수 있다. 즉, `Seq[T]`는 `PartialFunction[Int, T]`를 구현한다. 열의 원소에는 0부터 열의 `length`-1 까지 첨자가 부여된다. `length` 메소드는 일반적인 컬렉션의 `size`에 대한 별명이다. `lengthCompare` 메소드는 두 열(무한열인 경우도 포함됨)의 길이를 비교해준다. +* **첨자 검색 연산** `indexOf`, `lastIndexOf`, `indexofSlice`, `lastIndexOfSlice`, `indexWhere`, `lastIndexWhere`, `segmentLength`, `prefixLength`는 주어진 값과 같거나 주어진 술어와 일치하는 원소의 첨자를 반환한다. +* **덧붙임** 연산 `+:`, `:+`, `padTo`는 열의 맨 앞이나 뒤에 원소를 추가해 만들어지는 새 열을 반환한다. +* **갱신** 연산 `updated`, `patch`는 원래의 열의 일부 원소를 다른 값으로 대치한 새 열을 반환한다. +* **정렬** 연산 `sorted`, `sortWith`, `sortBy`는 여러 기준에 의해 열을 정렬한다. +* **반전** 연산 `reverse`, `reverseIterator`, `reverseMap`은 열의 원소를 역순으로 처리하거나 내어 놓는다. +* **비교** `startsWith`, `endsWith`, `contains`, `containsSlice`, `corresponds`는 두 열을 연관짓거나 열 안에서 원소를 찾는다. +* **중복집합(Multiset)** 연산인 `intersect`, `diff`, `union`, `distinct`는 두 열의 원소에 대해 집합과 비슷한 연산을 수행하거나, 중복을 제거한다. + +열이 변경 가능하다면 추가로 부작용을 사용하는 `update` 메소드를 제공한다. 이를 사용해 열의 원소를 변경할 수 있다. 스칼라에서 항상 그렇듯이 `seq(idx) = elem`는 단지 `seq.update(idx, elem)`를 짧게 쓴 것 뿐이다. 따라서 `update`를 정의하면 공짜로 대입 문법을 사용할 수 있게 된다. `update`와 `updated`가 다름에 유의하라. `update`는 어떤 열의 원소를 그 자리(새 열을 만들지 않고 열 자체를 갱신)에서 변경하며 변경 가능한 열에서만 사용할 수 있다. `updated`는 모든 열에서 사용 가능하며 항상 원래의 열은 그대로 두고 새로운 열을 반환한다. + +### Seq 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **참조와 길이:** | | +| `xs(i)` |(명시적으로 `xs apply i`라고 쓸수도 있음)`xs`에서 첨자 `i` 번째에 있는 원소를 반환한다.| +| `xs isDefinedAt i` |`i`가 `xs.indices` 안에 있는지 여부를 반환한다.| +| `xs.length` |열의 길이를 반환한다(`size`와 동일).| +| `xs.lengthCompare ys` |`xs`가 `ys`보다 짧으면 `-1`, 길면 `+1`, 같은 길이이면 `0`을 반환한다. 길이가 무한한 경우라도 동작한다(현재는 인자로 정수를 받아 열의 길이와 비교하는 것으로 바뀜)| +| `xs.indices` |`xs`의 첨자 범위를 반환한다. `0`부터 `xs.length - 1`까지 범위를 반환한다.| +| **첨자 검색:** | | +| `xs indexOf x` |`xs`에서 `x`와 같은 첫번째 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs lastIndexOf x` |`xs`에서 `x`와 같은 마지막 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs indexOfSlice ys` |`xs`에서 해당 위치에서부터 시작하는 슬라이스(slice,부분열)이 `ys`와 같은 첫번째 위치를 반환한다.| +| `xs lastIndexOfSlice ys` |`xs`에서 해당 위치에서부터 시작하는 슬라이스(slice,부분열)이 `ys`와 같은 마지막 위치를 반환한다.| +| `xs indexWhere p` |`xs`에서 `p`를 만족하는 첫번째 원소의 첨자를 반환한다(몇 가지 변형이 존재한다).| +| `xs segmentLength (p, i)`|`xs(i)`부터 시작해서 연속적으로 `p`를 만족하는 가장 긴 열의 길이를 구한다.| +| `xs prefixLength p` |`xs`의 맨 앞에서부터 시작해서 연속적으로 `p`를 만족하는 가장 긴 열의 길이를 구한다.| +| **덧붙임:** | | +| `x +: xs` |`xs`의 앞에 `x`를 붙여서 만들어진 새 열을 반환한다.| +| `xs :+ x` |`xs`의 뒤에 `x`를 붙여서 만들어진 새 열을 반환한다.| +| `xs padTo (len, x)` |`xs`의 뒤에 `x`를 전체 길이가 `len`이 될 때까지 붙여서 만들어진 새 열을 반환한다.| +| **갱신:** | | +| `xs patch (i, ys, r)` |`xs`에서 첨자 `i` 번째부터 `r`개 만큼의 원소를 `ys`의 원소로 바꿔치기 해서 얻은 새 열을 반환한다.| +| `xs updated (i, x)` |`xs`에서 첨자 `i`에 있는 원소를 `x`로 바꾼 새 열을 반환한다.| +| `xs(i) = x` |(또는 명시적으로 `xs.update(i, x)`라고 쓴다. `mutable.Seq`에서만 사용 가능하다). `xs`의 `i` 번째 원소를 `x`로 변경한다.| +| **정렬:** | | +| `xs.sorted` |`xs`의 원소를 원소 타입의 표준적인 순서를 사용해 정렬한 결과 열을 반환한다.| +| `xs sortWith lt` |`xs`의 원소를 `lt`를 비교 연산으로 사용해 정렬한 결과 열을 반환한다.| +| `xs sortBy f` |`xs`의 원소를 정렬한 결과 열을 반환한다. 비교시 두 원소에 각각 `f`를 적용한 다음 그 결과값을 서로 비교한다.| +| **반전:** | | +| `xs.reverse` |`xs`의 원소를 역순으로 나열한 열을 반환한다.| +| `xs.reverseIterator` |`xs`의 원소를 역순으로 내어 놓는 이터레이터이다.| +| `xs reverseMap f` |`xs`의 원소를 역순으로 순회하면서 `f`를 적용해 나온 결과 값으로 이루어진 열을 반환한다.| +| **비교:** | | +| `xs startsWith ys` |`xs`가 열 `ys`로 시작하는지 여부를 반환한다(몇 가지 변형이 존재한다).| +| `xs endsWith ys` |`xs`가 열 `ys`로 끝나는지 여부를 반환한다(몇 가지 변형이 존재한다).| +| `xs contains x` |`xs`에 `x`원소가 존재하는지 여부를 반환한다.| +| `xs containsSlice ys` |`xs`에 `ys`과 같은 부분열이 존재하는지 여부를 반환한다.| +| `(xs corresponds ys)(p)` |`xs`와 `ys`에서 서로 대응하는 원소가 술어 `p`를 만족하는지 여부를 반환한다.| +| **중복집합 연산:** | | +| `xs intersect ys` |`xs`와 `ys`의 중복 교집합 연산 결과인 열을 반환한다. `xs`에서의 원소 순서를 유지한다.| +| `xs diff ys` |`xs`와 `ys`의 중복 차집합 연산 결과인 열을 반한한다. `xs`에서의 원소 순서를 유지한다.| +| `xs union ys` |중복 합집합 연산 결과인 열을 반환한다. `xs ++ ys`와 같다.| +| `xs.distinct` |`xs`에서 중복을 제거한(중복된 원소는 하나만 남기고, 하나만 있는 원소는 그대로 둔) 부분열을 반환한다.| + +트레잇 [열(Seq)](http://www.scala-lang.org/api/current/scala/collection/Seq.html)에는 [선형열(LinearSeq)](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html)과 [첨자열(IndexedSeq)](http://www.scala-lang.org/api/current/scala/collection/IndexedSeq.html) 두 하위 트레잇이 있다. 이 두 트레잇에 새로 추가된 메소드는 없지만, 성능상 차이가 나는 특성을 각각 제공한다. 선형 열은 효율적인 `head`와 `tail` 연산이 제공되는 반면, 첨자열은 효율적인 `apply`, `length`, 그리고 (변경 가능한 경우) `update` 연산을 제공한다. 자주 사용되는 선형 열로는 `scala.collection.immutable.List`, `scala.collection.immutable.Stream`이 있다. 자주 사용되는 첨자열로는 `scala.Array`와 `scala.collection.mutable.ArrayBuffer`를 들 수 있다. `Vector(벡터)` 클래스는 첨자열과 선형열 사이에서 흥미로운 절충안을 제공한다. 벡터는 상수 시간에 첨자에 의한 참조가 가능하며 선형 억세스도 상수시간에 가능하다. 이로 인해 첨자 참조와 선형 억세스가 동시에 사용되는 억세스 패턴인 경우 벡터가 좋은 선택일 수 있다. 벡터에 대해서는 [나중에](#vectors) 다룰 것이다. + +### 버퍼 ### + +변경 가능한 열의 하위 범주 중 중요한 것은 `Buffer(버퍼)`이다. 버퍼는 기존 원소를 변경할 수 있을 뿐 아니라, 원소를 추가, 삭제 하거나, 버퍼의 마지막에 효율적으로 원소를 추가할 수 있다. 버퍼가 추가로 지원하는 주요 메소드로는 원소를 끝에 추가하기 위한 `+=`, `++=`, 원소를 앞에 추가하기 위한 `+=:` and `++=:`, 원소를 삽입하기 위한 `insert`와 `insertAll`, 그리고 원소를 제거하기 위한 `remove`, `-=`가 있다. 이 연산을 아래 표에 정리해 두었다. + +자주 사용되는 버퍼 구현을 두 가지 들자면 `ListBuffer`와 `ArrayBuffer`가 있다. 이름이 암시하든 `ListBuffer`는 `List`에 의해 뒷받침되며 원소를 효율적으로 `List`로 변환할 수 있다. 반면 `ArrayBuffer`는 배열에 의해 뒷받침되며 배열로 쉽게 변환 가능하다. + +#### 버퍼 클래스의 연산 #### + +| 사용법 | 하는 일 | +| ------ | ------ | +| **추가:** | | +| `buf += x` |원소 `x`를 버퍼의 끝에 추가하고 `buf` 자신을 반환한다.| +| `buf += (x, y, z)` |여러 원소를 버퍼의 끝에 추가한다.| +| `buf ++= xs` |`xs`의 모든 원소를 버퍼의 끝에 추가한다.| +| `x +=: buf` |`x`를 버퍼의 앞에 추가한다.| +| `xs ++=: buf` |`xs`의 모든 원소를 버퍼의 앞에 추가한다.| +| `buf insert (i, x)` |원소 `x`를 버퍼의 첨자 `i` 번째 원소 앞에 삽입한다.| +| `buf insertAll (i, xs)` |`xs`의 모든 원소를 버퍼의 첨자 `i`번째 원소 앞에 삽입한다.| +| **제거:** | | +| `buf -= x` |원소 `x`를 버퍼에서 제거한다. (중복이 있는 경우 맨 첫 `x`만을 제거한다.)| +| `buf remove i` |첨자 `i` 번째에 이는 원소를 버퍼에서 제거한다.| +| `buf remove (i, n)` |첨자 `i`번째에 있는 원소 부터 `n`개의 원소를 버퍼에서 제거한다.| +| `buf trimStart n` |처음 `n` 개의 원소를 버퍼에서 제거한다.| +| `buf trimEnd n` |마지막 `n` 개의 원소를 버퍼에서 제거한다.| +| `buf.clear()` |모든 원소를 버퍼에서 제거한다.| +| **복사:** | | +| `buf.clone` |`buf` 같은 원소를 포함하고 있는 새로운 버퍼를 만든다.| diff --git a/ko/overviews/collections/sets.md b/ko/overviews/collections/sets.md new file mode 100644 index 0000000000..aee1bf3267 --- /dev/null +++ b/ko/overviews/collections/sets.md @@ -0,0 +1,151 @@ +--- +layout: overview-large +title: 집합(Set) + +disqus: true + +partof: collections +num: 6 +language: ko +--- + +`Set(집합)`은 `Iterable(반복가능)` 중에서 중복 원소가 없는 것이다. 일반적인 집합의 연산은 다음 표에 정리되어 있고, 변경 가능한 집합의 연산은 그 다음에 오는 표에 정리되어 있다. 연산들은 다음과 같은 범주에 들어간다. + +* **검사** 연산으로 `contains`, `apply`, `subsetOf`가 있다. `contains` 메소드는 집합에 원소가 속해 있는지를 검사한다. 집합에 있어 `apply` 메소드는 `contains`과 동일하다. 따라서 `set(elem)`과 `set contains elem`는 같다. 따라서 집합을 원소가 포함되어있는지를 검사하는 검사 함수로 사용 가능하다. + +예를 들면 다음과 같다. + + + val fruit = Set("apple", "orange", "peach", "banana") + fruit: scala.collection.immutable.Set[java.lang.String] = + Set(apple, orange, peach, banana) + scala> fruit("peach") + res0: Boolean = true + scala> fruit("potato") + res1: Boolean = false + + +* **추가** 연산에는 `+` and `++`가 있다. 이들은 집합에 하나 이상의 원소를 추가한 새 집합을 만들어 낸다. +* **제거** 연산인 `-`, `--`는 집합에서 하나 이상의 원소를 제거한 새 집합을 만들어 낸다. +* **집합 연산**으로 합집합, 교집합, 차집합이 있다. 각 연산은 두가지 버전이 존재한다. 하나는 영어문자를 사용한 것이고, 다른 하나는 기호로 된 이름을 사용한 것이다. 영문자 버전은 `intersect`, `union`, `diff`이며, 각각 순서대로 `&`, `|`, `&~`이란 기호 이름과 대응된다. 사실은 `Traversable`에서 상속받은 `++`도 `union` 또는 `|`에 대한 별칭이라 생각할수 있다. 다만 차이가 나는 것은 `++`는 `Traversable`을 매개변수로 받을 수 있지만, `union`과 `|`에는 집합만을 허용한다는 점이다. + +### 집합(Set)의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **검사:** | | +| `xs contains x` |`x`가 `xs`의 원소인지 여부를 반환한다. | +| `xs(x)` |`xs contains x`와 같다. | +| `xs subsetOf ys` |`xs`가 `ys`의 부분집합인지 여부를 반환한다. | +| **추가:** | | +| `xs + x` |`xs`의 모든 원소와 `x`를 원소로 하는 새 집합을 반환한다.| +| `xs + (x, y, z)` |`xs`의 모든 원소와 덧붙인 모든 값들을 원소로 하는 새 집합을 반환한다.| +| `xs ++ ys` |`xs`의 모든 원소와 `ys`의 모든 원소를 원소로 하는 새 집합을 반환한다.| +| **제거:** | | +| `xs - x` |`xs`의 모든 원소 중 `x`를 제거한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs - (x, y, z)` |`xs`의 모든 원소 중 열거한 원소들을 제외한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs -- ys` |`xs`의 모든 원소 중 `ys`의 원소들을 제거한 나머지를 원소로 하는 새 집합을 반환한다.| +| `xs.empty` |`xs`와 동일한 타입의 빈 집합을 반환한다. | +| **이항 연산:** | | +| `xs & ys` |`xs`와 `ys`의 교집합 연산이다. | +| `xs intersect ys` |`xs & ys`와 같다. | +| xs | ys |`xs`와 `ys`의 합집합 연산이다. | +| `xs union ys` |xs | ys와 같다.. | +| `xs &~ ys` |`xs`와 `ys`의 차집합 연산이다. | +| `xs diff ys` |`xs &~ ys`와 같다.. | + +변경 가능한 집합은 원소를 추가, 삭제, 변경하는 연산을 추가로 제공한다. 아래 표에 정리되어 있다. + +### 변경 가능 집합(mutable.Set)의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추가:** | | +| `xs += x` |`xs`에 원소 `x`를 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs += (x, y, z)` |`xs`에 지정된 원소들을 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs ++= ys` |`xs`에 `ys`의 원소들을 부작용을 사용해 추가하고, `xs` 자신을 반환한다.| +| `xs add x` |`xs`에 원소 `x`를 부작용을 사용해 추가하되, `x`가 집합에 이미 포함되어 있었다면 거짓을, 그렇지 않았다면 참을 반환한다.| +| **제거:** | | +| `xs -= x` |`xs`에서 원소 `x`를 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs -= (x, y, z)` |`xs`에서 지정된 원소들을 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs --= ys` |`xs`에서 `ys`의 원소들을 부작용을 사용해 제거하고, `xs` 자신을 반환한다.| +| `xs remove x` |`xs`에서 원소 `x`를 부작용을 사용해 제거하되, `x`가 집합에 이미 포함되어 있었다면 참을, 그렇지 않았다면 거짓을 반환한다.| +| `xs retain p` |`xs`에서 술어 `p`를 만족하는 원소를 남기고 나머지를 제거한다.| +| `xs.clear()` |`xs`의 모든 원소를 제거한다.| +| **변경:** | | +| `xs(x) = b` |(명시적으로 `xs.update(x, b)`라고 쓸 수 있음) `b`가 `true`면 `x`를 `xs`에 추가하고, 그렇지 않으면 `x`를 `xs`에서 제거한다.| +| **복제:** | | +| `xs.clone` |`xs`와 같은 원소를 포함하는 새 변경 가능한 집합을 만든다.| + +변경 불가능한 집합과 마찬가지로 변경가능한 집합도 원소 추가를 위한 `+`, `++`와 원소 제거를 위한 `-`, `--` 연산을 제공한다. 하지만 이 연산들은 집합을 복사하기 때문에 변경 가능한 집합에서는 잘 사용되지 않는다. 더 효율적인 방식으로 `+=`, `-=`가 있다. `s += elem`는 `elem`을 집합 `s`에 부작용을 통해 추가하며, 결과로 집합 자신을 반환한다. 마찬가지로, `s -= elem`은 원소 `elem`을 집합에서 제거하고, 집합 자신을 결과로 반환한다. `+=`와 `-=`와 별개로 반복자나 순회가능 클래스의 원소를 한꺼번에 추가, 삭제하는 `++=`와 `--=` 연산도 있다. + +메소드 이름 `+=`와 `-=`는 변경 가능하거나 불가능한 집합 모두에 아주 비슷한 코드를 사용할 수 있음을 의미한다. 먼저 변경 불가능한 집합 `s`를 사용하는 REPL 실행예를 보자. + + scala> var s = Set(1, 2, 3) + s: scala.collection.immutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + scala> s -= 2 + scala> s + res2: scala.collection.immutable.Set[Int] = Set(1, 3, 4) + +여기서 타입이 `immutable.Set`인 `var`에 `+=`와 `-=` 연산을 적용했다. `s += 4` 식은 `s = s + 4`을 줄인 것이다. 따라서 집합 `s`에 대해 추가 메소드 `+`가 호출 된 다음 이 결과가 다시 변수 `s`에 대입된다. 이제 이와 비슷한 변경 가능한 집합의 동작을 살펴보자. + + + scala> val s = collection.mutable.Set(1, 2, 3) + s: scala.collection.mutable.Set[Int] = Set(1, 2, 3) + scala> s += 4 + res3: s.type = Set(1, 4, 2, 3) + scala> s -= 2 + res4: s.type = Set(1, 4, 3) + +최종 결과는 앞의 예와 아주 비슷하다. `Set(1, 2, 3)`에서 시작해서 `Set(1, 3, 4)`으로 끝난다. 비록 사용된 명령은 앞에서와 같지만 실제 하는 일은 많이 다른 것이다. `s += 4`는 이제 변경 가능한 집합 `s`의 `+=` 메소드를 호출해 집합의 내부 상태를 변경한다. 마찬가지로 `s -= 2` 또한 같은 집합의 `-=` 메소드를 호출한다. + +이 둘 간의 차이는 중요한 원칙을 보여준다. 바로 때로 `val`에 저장된 변경 가능한 컬렉션을 `var`에 저장된 변경 불가능한 것으로 바꾸거나, 그 _역으로_ 바꾸는 것이 가능하다는 사실이다. 외부에서 새 컬렉션이 만들어졌거나 내용이 부작용을 통해 변경되었는지를 관찰할 수 있는 동일 객체에 대한 다른 이름의 참조(alias)가 존재하지 않는 한 이 방식은 잘 동작한다. + +변경 가능한 집합은 또한 `+=`와 `-=`의 변형으로 `add`와 `remove`도 제공한다. 차이는 `add`와 `remove`는 연산에 집합에 작용했는지 여부를 알려주는 불린 값을 돌려준다는 점에 있다. + +현재 변경 가능 집합의 기본 구현은 해시 테이블을 사용해 원소를 저장한다. 변경 불가능한 집합의 기본 구현은 원소의 갯수에 따라 다른 표현방식을 사용한다. 빈 집합은 싱글턴 객체로 표현된다. 원소가 4개 이하인 집합은 모든 원소 객체를 필드로 저장하는 객체로 표현한다. 그보다 큰 변경 불가능 집합은 [해시 트라이(hash trie)](#hash-tries)로 구현되어 있다. + +이런 구현의 차이로 인해 더 작은 크기(4 이하)인 경우 변경 불가능한 집합이 변경 가능한 집합보다 더 작고 효율적이다. 따라서 크기가 작은 집합을 예상한다면 변경 불가능한 집합을 사용하도록 하라. + +집합에는 `SortedSet(정렬된 집합)`과 `BitSet(비트집합)`이 있다. + +### 정렬된 집합 ### + +[SortedSet(정렬된 집합)](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html)은 주어진 순서에 따라 원소를 내어놓는(`iterator`나 `foreach` 사용) 집합이다(순서는 집합을 만들 때 자유롭게 결정할 수 있다). [SortedSet](http://www.scala-lang.org/api/current/scala/collection/SortedSet.html)의 기본 구현은 어떤 노드의 왼쪽 하위 트리에 속한 모든 원소가 오른쪽 하위 트리에 속한 모든 원소보다 작다는 불변조건(invariant)을 만족시키는 순서가 있는 이진 트리이다. 따라서 간단한 중위순회(in order traversal)를 통해 트리의 모든 원소를 증가하는 순서로 반환할 수 있다. 스칼라의 클래스 [immutable.TreeSet(트리집합)](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html)은 이 불변조건을 유지하면서 동시에 _균형잡힌(balanced)_ 특성을 유지하기 위해 _적-흑(red-black)_ 트리를 구현한다. 균형이 잡힌 트리는 루트(root) 노드로부터 리프(leaf) 노드에 이르는 길이가 1 이하로 차이가 나는 경우를 말한다. + +빈 [TreeSet(트리집합)](http://www.scala-lang.org/api/current/scala/collection/immutable/TreeSet.html)을 만들려면 우선 원하는 순서를 지정해야 한다. + + scala> val myOrdering = Ordering.fromLessThan[String](_ > _) + myOrdering: scala.math.Ordering[String] = ... + +그리고 나서 이 순서를 사용해 빈 트리를 다음과 같이 만든다. + + scala> TreeSet.empty(myOrdering) + res1: scala.collection.immutable.TreeSet[String] = TreeSet() + +트리의 순서를 지정하지 않는 대신 원소의 타입을 지정해 빈 트리를 만들 수도 있다. 그렇게 하면 원소의 타입에 따른 기본 순서를 사용하게 된다. + + scala> TreeSet.empty[String] + res2: scala.collection.immutable.TreeSet[String] = TreeSet() + +트리 집합으로부터 (트리를 서로 붙이거나, 걸러내는 등의 방법을 사용해) 새 집합을 만드는 경우, 원래의 집합과 같은 순서를 사용할 것이다. 예를 들면 다음과 같다. + +scala> res2 + ("one", "two", "three", "four") +res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two) + +정렬된 집합은 원소의 범위도 지원한다. 예를 들어 `range` 메소드는 지정된 시작 원소로부터 시작해서 지정된 끝 엘리먼트 직전까지의 모든 원소를 반환한다. `from` 메소드는 집합에서의 순서상 지정된 원소와 같거나 큰 모든 원소를 반환한다. 다음은 그 예이다. + + scala> res3 range ("one", "two") + res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three) + scala> res3 from "three" + res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two) + + +### 비트집합(Bitset) ### + +[BitSet(비트집합)](http://www.scala-lang.org/api/current/scala/collection/BitSet.html)은 음이 아닌 정수 원소들로 이루어진 집합으로, 비트를 한데 묶은(packed) 하나 또는 그 이상의 워드로 되어 있다. [BitSet(비트집합)](http://www.scala-lang.org/api/current/scala/collection/BitSet.html)의 내부 표현은 `Long`의 배열을 사용한다. 첫 `Long`은 0부터 63을 나타내고, 두번째 것은 64부터 127을 나타내는 방식을 사용한다(0부터 127 사이의 수만 표현하는 변경 불가능한 비트셋은 배열을 최적화해서 한두개의 `Long` 필드만을 사용한다). 어떤 원소가 속해 있다면 해당 원소에 대응하는 `Long` 필드내의 비트가 1로 설정되고, 그렇지 않으면 0으로 설정된다. 따라서 비트집합의 크기는 내부에 저장되는 가장 큰 정수의 값에 따라 결정된다. 만약 가장 큰 정수가 `N`이라면 집합의 크기는 `N/64`개의 `Long`워드 또는 `N/8` 바이트이며, 상태정보 저장을 위해 몇 바이트가 추가된다. + +따라서 크기가 작은 정수 원소를 여러개 포함하는 경우 비트집합을 사용하면 다른 집합에 비해 작은 크기로 가능하다. 비트 집합의 또 다른 잇점은 `contains`를 사용한 포함관계 검사나 `+=`, `-=` 등을 사용한 원소 추가/제거가 모두 아주 효율적이라는 점이다. + +번역: 오현석(enshahar@gmail.com) \ No newline at end of file diff --git a/ko/overviews/collections/strings.md b/ko/overviews/collections/strings.md new file mode 100644 index 0000000000..9e3499f8e1 --- /dev/null +++ b/ko/overviews/collections/strings.md @@ -0,0 +1,27 @@ +--- +layout: overview-large +title: 문자열 + +disqus: true + +partof: collections +num: 11 +language: ko +--- + +배열처럼, 문자열이 직접적으로 시퀀스인 것은 아니지만 시퀀스로 변환될 수 있으며 모든 시퀀스 연산을 지원한다. 다음은 문자열에서 호출할 수 있는 연산의 예를 보여준다. + + scala> val str = "hello" + str: java.lang.String = hello + scala> str.reverse + res6: String = olleh + scala> str.map(_.toUpper) + res7: String = HELLO + scala> str drop 3 + res8: String = lo + scala> str slice (1, 4) + res9: String = ell + scala> val s: Seq[Char] = str + s: Seq[Char] = WrappedString(h, e, l, l, o) + +이러한 연산들은 두 가지의 암시적 변환에 의해 지원된다. 먼저 낮은 우선 순위의 변환은 `String`을 `immutable.IndexedSeq`의 자식 클래스인 `WrappedString`로 바꾼다. 이 변환은 위에 있는 마지막 줄처럼, 문자열이 Seq로 변환되는 곳에서 도입된다. 높은 우선 순위의 변환은 문자열을 모든 불변 시퀀스의 메소드를 문자열에 추가한 `StringOps` 객체로 변환시킨다. 이 변환은 예제에 나와있듯이 `reverse`, `map`, `drop`, 그리고 `slice` 메소드가 불릴 때 암시적으로 삽입된다. \ No newline at end of file diff --git a/ko/overviews/collections/trait-iterable.md b/ko/overviews/collections/trait-iterable.md new file mode 100644 index 0000000000..3b89b814de --- /dev/null +++ b/ko/overviews/collections/trait-iterable.md @@ -0,0 +1,67 @@ +--- +layout: overview-large +title: 반복가능(Iterable) 트레잇 + +disqus: true + +partof: collections +num: 4 +language: ko +--- + +컬렉션 계층 구조의 맨 위에서 두번째에 있는 것이 `Iterable`(반복가능)이다. 이 트레잇에 있는 모든 메소드는 추상 메소드 `iterator`를 기반으로 정의되어 있다. 이 추상 메소드는 컬렉션의 원소를 하나씩 내어 놓는다. 트레잇 `Traversable`의 `foreach`는 `Iterable`에서 `iterator`를 기반으로 정의되어 있다. 다음은 실제 구현이다. + + def foreach[U](f: Elem => U): Unit = { + val it = iterator + while (it.hasNext) f(it.next()) + } + +`Iterable`의 하위 클래스 중 상당수는 이 `foreach` 표준 구현을 재정의(override)하고 있다. 왜냐하면 더 효율적인 구현이 가능하기 때문이다. `foreach`가 `Traversable`의 모든 메소드 구현에 사용됨을 기억하라. 따라서, 성능을 진지하게 고려해야 한다. + +`Iterable`에는 반복자를 반환하는 메소드가 두 개 더 있다. 이들은 각각 `grouped`와 `sliding`이다. 그러나 이 반복자들은 원래의 컬렉션의 한 원소만을 반환하는 것이 아니고, 전체 원소의 부분 열을 반환한다. 각 메소드는 이런 부분 열의 최대 크기를 인자로 받는다. `grouped` 메소드는 원소를 일정한 "덩어리(chunked)" 단위로 반환하는 반면, `sliding`은 원소에 대한 "미닫이 창(sliding window)"을 반환한다. 아래 REPL 수행 예를 보면 이 둘 사이의 차이를 명확히 알 수 있을 것이다. + + scala> val xs = List(1, 2, 3, 4, 5) + xs: List[Int] = List(1, 2, 3, 4, 5) + scala> val git = xs grouped 3 + git: Iterator[List[Int]] = non-empty iterator + scala> git.next() + res3: List[Int] = List(1, 2, 3) + scala> git.next() + res4: List[Int] = List(4, 5) + scala> val sit = xs sliding 3 + sit: Iterator[List[Int]] = non-empty iterator + scala> sit.next() + res5: List[Int] = List(1, 2, 3) + scala> sit.next() + res6: List[Int] = List(2, 3, 4) + scala> sit.next() + res7: List[Int] = List(3, 4, 5) + +`Traversable` 트레잇의 메소드 중 이터레이터가 있는 경우에만 효율적으로 구현할 수 있는 메소드 몇 가지를 `Iterable` 트레잇에서 재정의하고 있다. 다음 표에서 이를 요약하였다. + +### Iterable 트레잇의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추상 메소드:** | | +| `xs.iterator` |`iterator`는 `xs`의 모든 원소를 `foreach`가 순회하는 순서대로 하나씩 제공하는 반복자이다.| +| **다른 반복자:** | | +| `xs grouped size` |고정된 크기의 "덩어리"를 컬렉션에서 내어놓는 반복자이다.| +| `xs sliding size` |고정된 크기의 미닫이 창을 내어놓는 반복자이다.| +| **부분 컬렉션:** | | +| `xs takeRight n` |`xs`의 마지막 `n`개의 원소로 이루어진 컬렉션을 반환한다(순서가 없는 컬렉션이라면 임의의 `n`개를 반환한다).| +| `xs dropRight n` |`xs takeRight n`의 결과를 제외한 나머지 컬렉션을 반환한다.| +| **묶기(zip):** | | +| `xs zip ys` |`xs`와 `ys`에서 같은 위치에 있는 원소를 각각 가져와 만든 튜플로 이루어진 컬렉션을 반환한다. 길이가 다르다면 짧은 컬렉션의 원소 갯수 만큼만 반환한다. | +| `xs zipAll (ys, x, y)` |`zip`과 같지만, 길이가 다른 경우 `x`나 `y`를 더 짧은쪽 리스트의 원소가 모자란 경우 대신 사용한다.| +| `xs.zipWithIndex` |`xs`의 원소와 그 위치를 나타내는 첨자를 튜플로 만든 컬렉션을 반환한다.| +| **비교:** | | +| `xs sameElements ys` |`xs`와 `ys`가 같은 순서로 같은 원소를 포함하고 있는지 비교한다.| + +상속 계층에서 Iterable의 하위에는 다음 세 트레잇이 있다. [열(Seq)](http://www.scala-lang.org/docu/files/collections-api/collections_5.html), [집합(Set)](http://www.scala-lang.org/docu/files/collections-api/collections_7.html), [맵(Map)](http://www.scala-lang.org/docu/files/collections-api/collections_10.html)이 그것이다. 이 세 트레잇의 공통점은 모두 다 [부분함수(PartialFunction)](http://www.scala-lang.org/api/current/scala/PartialFunction.html) 트레잇의 `apply`와 `isDefinedAt` 메소드를 정의하고 있다는 것이다. 하지만, 각 트레잇이 [부분함수(PartialFunction)](http://www.scala-lang.org/api/current/scala/PartialFunction.html)를 구현한 방법은 각각 다르다. + +열에서 `apply`는 위치에 따라 첨자를 부여한다. 첨자는 첫번째 원소 `0` 부터 시작한다. 따라서 `Seq(1, 2, 3)(1)`은 `2`를 반환한다. 집합의 경우 `apply`는 포함관계 검사이다. 예를 들어 `Set('a', 'b', 'c')('b')`은 `true`를, `Set()('a')`는 `false`를 반환한다. 마지막으로 맵의 경우 `apply`는 선택(검색)이다. 예를 들어 `Map('a' -> 1, 'b' -> 10, 'c' -> 100)('b')`의 결과는 `10`이다. + +다음 글에서는 위 세 컬렉션을 더 자세히 살펴볼 것이다. + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/trait-traversable.md b/ko/overviews/collections/trait-traversable.md new file mode 100644 index 0000000000..aca7d5b83f --- /dev/null +++ b/ko/overviews/collections/trait-traversable.md @@ -0,0 +1,120 @@ +--- +layout: overview-large +title: 순회가능(Traversable) 트레잇 + +disqus: true + +partof: collections +num: 3 +language: ko +--- + +컬렉션 계층의 최상위에는 트레잇 `Traversable(순회가능)`이 있다. 이 트레잇에 +있는 유일한 추상적인 연산이 바로 `foreach`이다. + + def foreach[U](f: Elem => U) + +`Traversable`을 구현하는 컬렉션 클래스는 단지 이 메소드만 정의하면 된다. +다른 메소드는 자동으로 `Traversable`에서 상속된다. + +`foreach` 메소드는 컬렉션의 모든 원소를 차례로 방문하면서 주어진 연산 f를 각 원소에 +적용한다. 이 연산 f의 타입은 `Elem => U`로 `Elem`은 컬렉션의 원소의 +타입이며, `U`는 임의의 결과 타입이다. `f`는 부작용을 위해 호출된다. 따라서 +f가 내놓는 결과값은 `foreach`가 무시한다. + +`Traversable`에는 여러 구체적 메소드가 정의되어 있다. 이들은 아래 표에 나열되어 있다. +각 메소드들은 다음과 같은 분류에 속한다. + +* **병합** 연산 `++`는 두 방문가능한 객체를 함께 이어붙이거나, 어떤 방문가능 객체에 다른 반복자의 모든 원소를 추가한다. +* **맵** 연산인 `map`, `flatMap`, `collect`는 인자로 넘겨진 함수를 컬렉션 원소에 적용한 결과로 이루어진 새 컬렉션을 만들어낸다. +* **변환** 연산인 `toArray`, `toList`, `toIterable`, `toSeq`, `toIndexedSeq`, `toStream`, `toSet`, `toMap`은 `Traversable` +컬렉션을 더 구체적인 데이터 구조로 변환한다. 런타임에 수신자가 이미 변환 결과 컬렉션 타입이었다면, 각 변환 메소드는 수신자를 그대로 반환한다. 예를 들어 리스트에 `toList`를 적용하면 그 리스트 자신이 반환된다. +* **복사** 연산으로 `copyToBuffer`와 `copyToArray`가 있다. 이름이 말하는데로 각각 컬렉션 원소를 버퍼나 배열에 복사한다. +* **크기 정보** 연산 `isEmpty`, `nonEmpty`, `size`, `hasDefiniteSize`: 순회가능한 컬렉션은 유한할 수도 있고, 무한할 수도 있다. +무한한 순회가능한 컬렉션의 예를 들자면 자연수의 스트림 `Stream.from(0)`이 있다. 메소드 `hasDefiniteSize`는 컬렉션이 무한 컬렉션일 가능성이 있는지를 알려준다. `hasDefiniteSize`가 참을 반환하면 컬렉션이 유한하다는 것이 확실하다. 하지만, 컬렉션이 내부 원소를 아직 완전히 계산해 채우지 않은 경우에는 거짓을 반환하기 때문에, 거짓을 반환한다 해도 유한할 수도 있고 무한할 수도 있다. +* **원소 가져오기** 연산 `head`, `last`, `headOption`, `lastOption`, `find`등은 컬렉션의 첫 원소나 마지막 원소를 선택하거나, 조건을 만족하는 첫 원소를 선택한다. 하지만 모든 컬렉션에서 "첫번째"나 "마지막"의 의미가 잘 정의되어 있는 것은 아니라는 점에 유의하라. 예를 들어 해시 집합은 해시값에 따라 원소를 저장하는데, 이 해시값은 매 실행시마다 변할 수 있다. 이런 경우 해시 집합의 +"첫번째" 원소는 프로그램이 실행될 때마다 바뀔 수 있다. 어떤 컬렉션이 항상 같은 순서로 원소를 표시한다면 이를 _순서있다_ 고 한다. 대부분의 컬렉션은 순서가 있으나, 일부(_e.g._ 해시 집합)는 그렇지 않다 -- 이들은 순서를 포기하는 대신 효율을 택한 것이다. 동일한 테스트를 반복하거나, 디버깅을 할 때 때로 순서가 꼭 필요하다. 이 때문에 모든 스칼라 컬렉션 타입에는 순서가 있는 대체물이 반드시 존재한다. 예를 들어 `HashSet`에 순서가 부여된 것은 `LinkedHashSet`이다. +* **부분 컬렉션**을 가져오는 연산에는 `tail`, `init`, `slice`, `take`, `drop`, `takeWhile`, `dropWhile`, `filter`, `filterNot`, `withFilter` 등이 있다. 이들은 모두 어떤 첨자 범위나 술어 함수(predicate, 참-거짓을 반환하는 함수)에 의해 식별되는 부분 컬렉션을 반환한다. +* **분할** 연산인 `splitAt`, `span`, `partition`, `groupBy` 등은 대상 컬렉션의 원소를 구분해서 여러 부분 컬렉션으로 나눈다. +* **원소 검사** 연산 `exists`, `forall`, `count`는 주어진 술어 함수를 가지고 컬렉션 원소들을 검사한다. +* **폴드** 연산 `foldLeft`, `foldRight`, `/:`, `:\`, `reduceLeft`, `reduceRight`는 인접한 두 원소에 대해 이항 연산자(binary operator)를 반복적용한다. +* **특정 폴드** `sum`, `product`, `min`, `max`는 특정 타입(비교가능하거나 수)의 컬렉션에만 작용한다. +* **문자열** 연산 `mkString`, `addString`, `stringPrefix`는 컬렉션을 문자열로 바꾸는 여러가지 방법을 제공한다. +* **뷰** 연산은 `view` 메소드를 오버로딩한 두 메소드이다. 뷰는 지연 계산될 수 있는 컬렉션이다. 뷰에 대해서는 [나중에](#Views) 다룰 것이다. + +### Traversable 클래스의 연산 ### + +| 사용법 | 하는일 | +| ------ | ------ | +| **추상 메소드:** | | +| `xs foreach f` |함수 `f`를 `xs`의 모든 원소에 적용한다.| +| **병합:** | | +| `xs ++ ys` |`xs`와 `ys`의 모든 원소들로 이루어진 컬렉션. `ys`는 [1회 순회가능(TraversableOnce)](http://www.scala-lang.org/api/current/scala/collection/TraversableOnce.html) 컬렉션이다. 즉, [순회가능(Traversable)](http://www.scala-lang.org/api/current/scala/collection/Traversable.html)이거나 [반복자(Iterator)](http://www.scala-lang.org/api/current/scala/collection/Iterator.html)여야 한다.| +| **맵:** | | +| `xs map f` |함수 `f`를 `xs`의 모든 원소에 적용해 반환된 결과로 이루어진 컬렉션을 반환한다.| +| `xs flatMap f` |결과값이 컬렉션인 함수 `f`를 `xs`의 모든 원소에 적용해 반환된 결과를 차례로 이어붙여서 이루어진 컬렉션을 반환한다.| +| `xs collect f` |부분함수(partial function) `f`를 모든 `xs`에 호출해서 결과값이 정의되어 있는 경우에 대해서만 그 값들을 모아서 이루어진 컬렉션을 반환한다.| +| **변환:** | | +| `xs.toArray` |컬렉션을 배열(Array)로 변환한다.| +| `xs.toList` |컬렉션을 리스트(List)로 변환한다.| +| `xs.toIterable` |컬렉션을 반복가능객체(Iterable)로 변환한다.| +| `xs.toSeq` |컬렉션을 열(Seq)로 변환한다.| +| `xs.toIndexedSeq` |컬렉션을 첨자가 있는 열(IndexedSeq)로 변환한다.| +| `xs.toStream` |컬렉션을 스트림(Stream)으로 변환한다.| +| `xs.toSet` |컬렉션을 집합(Set)으로 변환한다.| +| `xs.toMap` |컬렉션을 키/값 쌍의 맵으로 변환한다. 컬렉션의 원소가 튜플이 아니라면 이 메소드 호출은 컴파일시 타입 오류가 난다.| +| **Copying:** | | +| `xs copyToBuffer buf` |컬렉션의 모든 원소를 버퍼 `buf`에 복사한다.| +| `xs copyToArray(arr, s, n)`|첨자 `s`부터 시작해 최대 `n`개의 원소를 배열 `arr`에 복사한다. 마지막 두 매개변수는 생략할 수 있다.| +| **크기 정보:** | | +| `xs.isEmpty` |컬렉션이 비어있다면 참을 반환한다.| +| `xs.nonEmpty` |컬렉션에 원소가 하나라도 있다면 참을 반환한다.| +| `xs.size` |컬렉션의 원소의 갯수를 반환한다.| +| `xs.hasDefiniteSize` |`xs`가 유한한 크기를 가졌는지 알려져 있다면 참을 반환한다.| +| **원소 가져오기:** | | +| `xs.head` |컬렉션의 첫 원소(순서가 없는 컬렉션이라면 임의의 원소)를 반환한다.| +| `xs.headOption` |`xs`가 비어있다면 None, 그렇지 않다면 첫 원소를 Option에 담아서 반환한다.| +| `xs.last` |컬렉션의 마지막 원소(순서가 없는 컬렉션이라면 임의의 원소)를 반환한다.| +| `xs.lastOption` |`xs`가 비어있다면 None, 그렇지 않다면 마지막 원소를 Option에 담아서 반환한다.| +| `xs find p` |`xs`에서 `p`를 만족하는 첫번째 원소를 Option에 담아 반환한다. 만족하는 원소가 없다면 None을 반환한다.| +| **부분 컬렉션:** | | +| `xs.tail` |`xs.head`를 제외한 나머지 컬렉션이다.| +| `xs.init` |`xs.last`를 제외한 나머지 컬렉션이다.| +| `xs slice (from, to)` |컬렉션 `xs`에서 첨자 범위에 속하는 원소들(`from`부터 시작해서 `to`까지. 단, `from`에 있는 원소는 포함하고, `to`에 있는 원소는 포함하지 않음)로 이루어진 컬렉션을 반환한다.| +| `xs take n` |컬렉션 `xs`에서 앞에서부터 `n`개의 원소로 구성된 컬렉션(만약 순서가 없는 컬렉션이라면 임의의 `n`개의 원소가 선택된다)이다.| +| `xs drop n` |컬렉션에서 `xs take n`하고 난 나머지 컬렉션을 반환한다.| +| `xs takeWhile p` |컬렉션 `xs`의 맨 앞에서부터 술어 `p`를 만족하는 동안 원소를 수집해 만들어진 컬렉션. `p`를 만족하지 않는 첫 원소에서 수집은 끝난다. 따라서, 만약 `xs`의 첫 원소가 `p`를 만족하지 않으면 빈 컬렉션을 반환한다.| +| `xs dropWhile p` |컬렉션 `xs`의 맨 앞에서부터 따져서 술어 `p`를 최초로 만족하는 원소로부터 `xs`의 마지막 원소까지로 이루어진 컬렉션이다.| +| `xs filter p` |`xs`의 원소 중 술어 `p`를 만족하는 원소로 이루어진 컬렉션이다.| +| `xs withFilter p` |컬렉션에 필요시 계산하는 필터를 추가한다. 이 결과 컬렉션에 `map`, `flatMap`, `foreach`, `withFilter` 등이 호출되면 `xs` 중에 술어 `p`를 만족하는 원소에 대해서만 처리가 이루어진다.| +| `xs filterNot p` |`xs`의 원소 중 술어 `p`를 만족하지 않는 원소로 이루어진 컬렉션이다.| +| **분할:** | | +| `xs splitAt n` |`n`위치를 기준으로 `xs`를 둘로 분할한다. `(xs take n, xs drop n)` 쌍과 동일한 컬렉션 쌍을 반환한다.| +| `xs span p` |`xs`를 술어 `p`를 가지고 둘로 분할하되, `(xs takeWhile p, xs.dropWhile p)`과 같은 컬렉션 쌍을 반환한다.| +| `xs partition p` |`xs`를 술어 `p`를 만족하는 원소들과 만족하지 않는 원소의 두 컬렉션으로 분할한 튜플을 반환한다. `(xs filter p, xs.filterNot p)`과 같은 결과를 반환한다.| +| `xs groupBy f` |`xs`를 분류 함수 `f`에 따르는 컬렉션의 맵으로 분할한다.| +| **원소 검사:** | | +| `xs forall p` |술어 `P`가 `xs`의 모든 원소에 대해 성립하는지 여부를 반환한다.| +| `xs exists p` |술어 `P`를 만족하는 원소가 `xs`에 있는지 여부를 반환한다.| +| `xs count p` |`xs`에서 술어 `P`를 만족하는 원소의 갯수를 반환한다.| +| **폴드:** | | +| `(z /: xs)(op)` |이항 연산 `op`를 `xs`의 인접 원소에 대해 `z`부터 시작해 왼쪽부터 오른쪽으로 차례로 적용한다.| +| `(xs :\ z)(op)` |이항 연산 `op`를 `xs`의 인접 원소에 대해 `z`부터 시작해 오른쪽부터 왼쪽으로 차례로 적용한다.| +| `xs.foldLeft(z)(op)` |`(z /: xs)(op)`과 같다.| +| `xs.foldRight(z)(op)` |`(xs :\ z)(op)`과 같다.| +| `xs reduceLeft op` |이항 연산 `op`를 비어있지 않은 `xs`의 인접 원소에 대해 왼쪽부터 오른쪽으로 차례로 적용한다.| +| `xs reduceRight op` |이항 연산 `op`를 비어있지 않은 `xs`의 인접 원소에 대해 오른쪽부터 왼쪽으로 차례로 적용한다.| +| **특정 폴드:** | | +| `xs.sum` |컬렉션 `xs`의 모든 수 원소를 곱한 값이다.| +| `xs.product` |컬렉션 `xs`의 모든 수 원소를 곱한 값이다.| +| `xs.min` |순서가 있는 컬렉션 `xs`에서 가장 작은 원소이다.| +| `xs.max` |순서가 있는 컬렉션 `xs`에서 가장 큰 원소이다.| +| **문자열:** | | +| `xs addString (b, start, sep, end)`|`StringBuilder` `b`에 `xs`의 모든 원소를 `sep`으로 구분해 `start`와 `end` 사이에 나열한 문자열을 추가한다. `start`, `sep`, `end`는 생략 가능하다.| +| `xs mkString (start, sep, end)`|컬렉션 `xs`의 모든 원소를 `sep`로 구분해 문자열 `start`와 `end` 사이에 넣는다. `start`, `sep`, `end`는 생략 가능하다.| +| `xs.stringPrefix` |`xs.toString`이 반환하는 문자열의 맨 앞에 표시될 컬렉션 이름이다.| +| **뷰:** | | +| `xs.view` |`xs`에 대한 뷰를 만든다.| +| `xs view (from, to)` |`xs`에 대해 첨자 범위에 속하는 원소에 대한 뷰를 만든다.| + +번역: 오현석(enshahar@gmail.com) diff --git a/ko/overviews/collections/views.md b/ko/overviews/collections/views.md new file mode 100644 index 0000000000..180e5b33b8 --- /dev/null +++ b/ko/overviews/collections/views.md @@ -0,0 +1,130 @@ +--- +layout: overview-large +title: 뷰 + +disqus: true + +partof: collections +num: 14 +language: ko +--- + +컬렉션에는 새로운 컬렉션을 만들기 위한 메소드가 꽤 들어 있다. 예를 들면 `map`, `filter`, `++` 등이 그렇다. 이러한 메소드를 변환메소드(transformer)라 부르는데, 이유는 최소 하나 이상의 컬렉션을 수신객체로 받아서 결과값으로 다른 컬렉션을 만들어내기 때문이다. + +변환메소드를 구현하는 방법은 크게 두가지가 있다. 하나는 _바로 계산하기(strict)_ 방식이다. 이는 변환 메소드에서 새로 만들어지는 컬렉션에 모든 원소를 만들어서 집어넣어서 반환하는 방식이다. 또 다른 방식은 바로 계산하지 않기(non-strict) 또는 _지연 계산하기(lazy)_ 방식이다. 이는 결과 컬렉션 전체를 구성해 반환하는 대신 그에 대한 대행 객체(proxy)를 반환해서, 실제 결과 컬렉션의 원소를 요청받았을 때에만 이를 만들어내도록 하는 방법이다. + +바로 계산하지 않는 변환메소드의 예로 아래 지연계산 맵 연산을 살펴보자. + + def lazyMap[T, U](coll: Iterable[T], f: T => U) = new Iterable[T] { + def iterator = coll.iterator map f + } + +`lazyMap`이 인자로 받은 `coll`의 모든 원소를 하나하나 읽지 않고 새 `Iterable`을 반환한다는 사실에 유의하라. 주어진 함수 `f`는 요청에 따라 새 컬렉션의 `iterator`에 적용된다. + +`Stream`을 제외한 스칼라의 컬렉션에 있는 변환메소드는 기본적으로 바로 계산하는 방식을 택한다. 스트림은 모든 변환 메소드가 지연계산으로 되어 있다. 하지만, 모든 컬렉션을 지연 계산 방식의 컬렉션으로 바꾸거나 그 _역으로_ 바꾸는 구조적인 방법이 있다. 그 방법은 바로 컬렉션 뷰를 활용하는 것이다. _뷰(view)_ 는 어떤 기반 컬렉션을 대표하되 모든 변환메소드를 지연계산으로 구현하는 특별한 종류의 컬렉션이다. + +컬렉션에서 그 뷰로 변환하려면 해당 컬렉션의 `view` 메소드를 사용하면 된다. +To go from a collection to its view, you can use the view method on the collection. If `xs` is some collection, then `xs.view` is the same collection, but with all transformers implemented lazily. To get back from a view to a strict collection, you can use the `force` method. + +Let's see an example. Say you have a vector of Ints over which you want to map two functions in succession: + + scala> val v = Vector(1 to 10: _*) + v: scala.collection.immutable.Vector[Int] = + Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + scala> v map (_ + 1) map (_ * 2) + res5: scala.collection.immutable.Vector[Int] = + Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +In the last statement, the expression `v map (_ + 1)` constructs a new vector which is then transformed into a third vector by the second call to `map (_ * 2)`. In many situations, constructing the intermediate result from the first call to map is a bit wasteful. In the example above, it would be faster to do a single map with the composition of the two functions `(_ + 1)` and `(_ * 2)`. If you have the two functions available in the same place you can do this by hand. But quite often, successive transformations of a data structure are done in different program modules. Fusing those transformations would then undermine modularity. A more general way to avoid the intermediate results is by turning the vector first into a view, then applying all transformations to the view, and finally forcing the view to a vector: + + scala> (v.view map (_ + 1) map (_ * 2)).force + res12: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Let's do this sequence of operations again, one by one: + + scala> val vv = v.view + vv: scala.collection.SeqView[Int,Vector[Int]] = + SeqView(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +The application `v.view` gives you a `SeqView`, i.e. a lazily evaluated `Seq`. The type `SeqView` has two type parameters. The first, `Int`, shows the type of the view's elements. The second, `Vector[Int]` shows you the type constructor you get back when forcing the `view`. + +Applying the first `map` to the view gives: + + scala> vv map (_ + 1) + res13: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) + +The result of the `map` is a value that prints `SeqViewM(...)`. This is in essence a wrapper that records the fact that a `map` with function `(_ + 1)` needs to be applied on the vector `v`. It does not apply that map until the view is `force`d, however. The "M" after `SeqView` is an indication that the view encapsulates a map operation. Other letters indicate other delayed operations. For instance "S" indicates a delayed `slice` operations, and "R" indicates a `reverse`. Let's now apply the second `map` to the last result. + + scala> res13 map (_ * 2) + res14: scala.collection.SeqView[Int,Seq[_]] = SeqViewMM(...) + +You now get a `SeqView` that contains two map operations, so it prints with a double "M": `SeqViewMM(...)`. Finally, forcing the last result gives: + +scala> res14.force +res15: Seq[Int] = Vector(4, 6, 8, 10, 12, 14, 16, 18, 20, 22) + +Both stored functions get applied as part of the execution of the `force` operation and a new vector is constructed. That way, no intermediate data structure is needed. + +One detail to note is that the static type of the final result is a Seq, not a Vector. Tracing the types back we see that as soon as the first delayed map was applied, the result had static type `SeqViewM[Int, Seq[_]]`. That is, the "knowledge" that the view was applied to the specific sequence type `Vector` got lost. The implementation of a view for some class requires quite a lot of code, so the Scala collection libraries provide views mostly only for general collection types, but not for specific implementations (An exception to this are arrays: Applying delayed operations on arrays will again give results with static type `Array`). + +There are two reasons why you might want to consider using views. The first is performance. You have seen that by switching a collection to a view the construction of intermediate results can be avoided. These savings can be quite important. As another example, consider the problem of finding the first palindrome in a list of words. A palindrome is a word which reads backwards the same as forwards. Here are the necessary definitions: + + def isPalindrome(x: String) = x == x.reverse + def findPalidrome(s: Seq[String]) = s find isPalindrome + +Now, assume you have a very long sequence words and you want to find a palindrome in the first million words of that sequence. Can you re-use the definition of `findPalidrome`? If course, you could write: + + findPalindrome(words take 1000000) + +This nicely separates the two aspects of taking the first million words of a sequence and finding a palindrome in it. But the downside is that it always constructs an intermediary sequence consisting of one million words, even if the first word of that sequence is already a palindrome. So potentially, 999'999 words are copied into the intermediary result without being inspected at all afterwards. Many programmers would give up here and write their own specialized version of finding palindromes in some given prefix of an argument sequence. But with views, you don't have to. Simply write: + + findPalindrome(words.view take 1000000) + +This has the same nice separation of concerns, but instead of a sequence of a million elements it will only construct a single lightweight view object. This way, you do not need to choose between performance and modularity. + +The second use case applies to views over mutable sequences. Many transformer functions on such views provide a window into the original sequence that can then be used to update selectively some elements of that sequence. To see this in an example, let's suppose you have an array `arr`: + + scala> val arr = (0 to 9).toArray + arr: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + +You can create a subwindow into that array by creating a slice of a view of `arr`: + + scala> val subarr = arr.view.slice(3, 6) + subarr: scala.collection.mutable.IndexedSeqView[ + Int,Array[Int]] = IndexedSeqViewS(...) + +This gives a view `subarr` which refers to the elements at positions 3 through 5 of the array `arr`. The view does not copy these elements, it just provides a reference to them. Now, assume you have a method that modifies some elements of a sequence. For instance, the following `negate` method would negate all elements of the sequence of integers it's given: + + scala> def negate(xs: collection.mutable.Seq[Int]) = + for (i <- 0 until xs.length) xs(i) = -xs(i) + negate: (xs: scala.collection.mutable.Seq[Int])Unit + +Assume now you want to negate elements at positions 3 through five of the array `arr`. Can you use `negate` for this? Using a view, this is simple: + + scala> negate(subarr) + scala> arr + res4: Array[Int] = Array(0, 1, 2, -3, -4, -5, 6, 7, 8, 9) + +What happened here is that negate changed all elements of `subarr`, which were a slice of the elements of `arr`. Again, you see that views help in keeping things modular. The code above nicely separated the question of what index range to apply a method to from the question what method to apply. + +After having seen all these nifty uses of views you might wonder why have strict collections at all? One reason is that performance comparisons do not always favor lazy over strict collections. For smaller collection sizes the added overhead of forming and applying closures in views is often greater than the gain from avoiding the intermediary data structures. A probably more important reason is that evaluation in views can be very confusing if the delayed operations have side effects. + +Here's an example which bit a few users of versions of Scala before 2.8. In these versions the Range type was lazy, so it behaved in effect like a view. People were trying to create a number of actors like this: + + + val actors = for (i <- 1 to 10) yield actor { ... } + +They were surprised that none of the actors was executing afterwards, even though the actor method should create and start an actor from the code that's enclosed in the braces following it. To explain why nothing happened, remember that the for expression above is equivalent to an application of map: + + val actors = (1 to 10) map (i => actor { ... }) + +Since previously the range produced by `(1 to 10)` behaved like a view, the result of the map was again a view. That is, no element was computed, and, consequently, no actor was created! Actors would have been created by forcing the range of the whole expression, but it's far from obvious that this is what was required to make the actors do their work. + +To avoid surprises like this, the Scala 2.8 collections library has more regular rules. All collections except streams and views are strict. The only way to go from a strict to a lazy collection is via the `view` method. The only way to go back is via `force`. So the `actors` definition above would behave as expected in Scala 2.8 in that it would create and start 10 actors. To get back the surprising previous behavior, you'd have to add an explicit `view` method call: + + val actors = for (i <- (1 to 10).view) yield actor { ... } + +In summary, views are a powerful tool to reconcile concerns of efficiency with concerns of modularity. But in order not to be entangled in aspects of delayed evaluation, you should restrict views to two scenarios. Either you apply views in purely functional code where collection transformations do not have side effects. Or you apply them over mutable collections where all modifications are done explicitly. What's best avoided is a mixture of views and operations that create new collections while also having side effects. + + + diff --git a/ko/overviews/core/collections.md b/ko/overviews/core/collections.md new file mode 100644 index 0000000000..ecf09c3be1 --- /dev/null +++ b/ko/overviews/core/collections.md @@ -0,0 +1,7 @@ +--- +layout: overview +overview: collections +partof: collections +language: ko +title: Scala 컬렉션 라이브러리 +--- diff --git a/ko/overviews/core/macros.md b/ko/overviews/core/macros.md new file mode 100755 index 0000000000..3be1226710 --- /dev/null +++ b/ko/overviews/core/macros.md @@ -0,0 +1,10 @@ +--- +layout: overview +title: 매크로 +disqus: true +partof: macros +overview: macros +language: ko +label-color: important +label-text: Experimental +--- diff --git a/ko/overviews/index.md b/ko/overviews/index.md new file mode 100755 index 0000000000..a790542aae --- /dev/null +++ b/ko/overviews/index.md @@ -0,0 +1,13 @@ +--- +layout: guides-index +language: ko +title: 가이드 및 개요 +--- + +
+

핵심 필수요소들...

+
+ +{% include localized-overview-index.txt %} + + diff --git a/ko/overviews/macros/bundles.md b/ko/overviews/macros/bundles.md new file mode 100644 index 0000000000..b94c130df0 --- /dev/null +++ b/ko/overviews/macros/bundles.md @@ -0,0 +1,78 @@ +--- +layout: overview-large +title: Macro Bundles + +disqus: true + +partof: macros +num: 6 +outof: 7 +language: ko +--- +MACRO PARADISE + +**Eugene Burmako** + +Macro bundles and macro compilers are pre-release features included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds. + +## Macro bundles + +Currently, in Scala 2.10.0, macro implementations are represented with functions. Once the compiler sees an application of a macro definition, +it calls the macro implementation - as simple as that. However practice shows that just functions are often not enough due to the +following reasons: + +1. Being limited to functions makes modularizing complex macros awkward. It's quite typical to see macro logic concentrate in helper +traits outside macro implementations, turning implementations into trivial wrappers, which just instantiate and call helpers. + +2. Moreover, since macro parameters are path-dependent on the macro context, [special incantations](/overviews/macros/overview.html#writing_bigger_macros) are required to wire implementations and helpers together. + +3. As macros evolved it [became apparent](https://twitter.com/milessabin/status/281379835773857792) that there should exist different +interfaces of communication between the compiler and macros. At the moment compiler can only expand macros, but what if it wanted to +ask a macro to help it with type inference? + +Macro bundles provide a solution to these problems by allowing macro implementations to be declared in traits, which extend +`scala.reflect.macros.Macro`. This base trait predefines the `c: Context` variable, relieving macro implementations from having +to declare it in their signatures, which simplifies modularization. Later on `Macro` could come with preloaded callback methods +such as, for example, `onInfer`. + + trait Macro { + val c: Context + } + +Referencing macro implementations defined in bundles works in the same way as with impls defined in objects. You specify a bundle name +and then select a method from it, providing type arguments if necessary. + + import scala.reflect.macros.Context + import scala.reflect.macros.Macro + + trait Impl extends Macro { + def mono = c.literalUnit + def poly[T: c.WeakTypeTag] = c.literal(c.weakTypeOf[T].toString) + } + + object Macros { + def mono = macro Impl.mono + def poly[T] = macro Impl.poly[T] + } + +## Macro compilers (deprecated) + +It turns out that the flexibility provided by externalizing the strategy of macro compilation hasn't really been useful. +Therefore I'm removing this functionality from macro paradise NEW. + +When I was implementing macro bundles, it became apparent that the mechanism which links macro definitions with macro implementations +is too rigid. This mechanism simply used hardcoded logic in `scala/tools/nsc/typechecker/Macros.scala`, which takes the right-hand side +of a macro def, typechecks it as a reference to a static method and then uses that method as a corresponding macro implementation. + +Now compilation of macro defs is extensible. Instead of using a hardcoded implementation to look up macro impls, +the macro engine performs an implicit search of a `MacroCompiler` in scope and then invokes its `resolveMacroImpl` method, +passing it the `DefDef` of a macro def and expecting a reference to a static method in return. Of course, `resolveMacroImpl` +should itself be a macro, namely [an untyped one](/overviews/macros/untypedmacros.html), for this to work. + + trait MacroCompiler { + def resolveMacroImpl(macroDef: _): _ = macro ??? + } + +Default instance of the type class, `Predef.DefaultMacroCompiler`, implements formerly hardcoded typechecking logic. +Alternative implementations could, for instance, provide lightweight syntax for macro defs, generating macro impls +on-the-fly using `c.introduceTopLevel`. diff --git a/ko/overviews/macros/inference.md b/ko/overviews/macros/inference.md new file mode 100644 index 0000000000..f0dfe406c4 --- /dev/null +++ b/ko/overviews/macros/inference.md @@ -0,0 +1,207 @@ +--- +layout: overview-large +title: Inference-Driving Macros + +disqus: true + +partof: macros +num: 7 +outof: 7 +language: ko +--- +MACRO PARADISE + +**Eugene Burmako** + +Inference-driving macros are pre-release features included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds. + +## A motivating example + +The use case, which gave birth to inference-driving macros, is provided by Miles Sabin and his [shapeless](https://github.com/milessabin/shapeless) library. Miles has defined the `Iso` trait, which represents isomorphisms between types. + + trait Iso[T, U] { + def to(t : T) : U + def from(u : U) : T + } + +Currently instances of `Iso` are defined manually and then published as implicit values. Methods, which want to make use of +defined isomorphisms, declare implicit parameters of type `Iso`, which then get filled in during implicit search. + + def foo[C](c: C)(implicit iso: Iso[C, L]): L = iso.from(c) + + case class Foo(i: Int, s: String, b: Boolean) + implicit val fooIsoTuple = Iso.tuple(Foo.apply _, Foo.unapply _) + + val tp = foo(Foo(23, "foo", true)) + tp : (Int, String, Boolean) + tp == (23, "foo", true) + +As we can see, the isomorphism between a case class and a tuple is trivial (actually, shapeless uses Iso's to convert between case +classes and HLists, but for simplicity let's use tuples). The compiler already generates the necessary methods, +and we just have to make use of them. Unfortunately in Scala 2.10.0 it's impossible to simplify this even further - for every case class +you have manually define an implicit `Iso` instance. + +The real showstopper is the fact that when typechecking applications of methods like `foo`, scalac has to infer the type argument `L`, +which it has no clue about (and that's no wonder, since this is domain-specific knowledge). As a result, even if you define an implicit +macro, which synthesizes `Iso[C, L]`, scalac will helpfully infer `L` as `Nothing` before expanding the macro and then everything crumbles. + +## The proposed solution + +As demonstrated by [https://github.com/scala/scala/pull/2499](https://github.com/scala/scala/pull/2499), the solution to the outlined +problem is extremely simple and elegant. NEW + +In 2.10 we don't allow macro applications to expand until all their type arguments are inferred. However we don't have to do that. +The typechecker can infer as much as it possibly can (e.g. in the running example `C` will be inferred to `Foo` and +`L` will remain uninferred) and then stop. After that we expand the macro and then proceed with type inference using the type of the +expansion to help the typechecker with previously undetermined type arguments. + +An illustration of this technique in action can be found in our [files/run/t5923c](https://github.com/scalamacros/kepler/tree/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923c) tests. +Note how simple everything is. The `materializeIso` implicit macro just takes its first type argument and uses it to produce an expansion. +We don't need to make sense of the second type argument (which isn't inferred yet), we don't need to interact with type inference - +everything happens automatically. + +Please note that there is [a funny caveat](https://github.com/scalamacros/kepler/blob/7b890f71ecd0d28c1a1b81b7abfe8e0c11bfeb71/test/files/run/t5923a/Macros_1.scala) +with Nothings that we plan to address later. + +## Internals of type inference (deprecated) + +From what I learned about this over a few days, type inference in Scala is performed by the following two methods +in `scala/tools/nsc/typechecker/Infer.scala`: [`inferExprInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1123) and +[`inferMethodInstance`](https://github.com/scalamacros/kepler/blob/d7b59f452f5fa35df48a5e0385f579c98ebf3555/src/compiler/scala/tools/nsc/typechecker/Infer.scala#L1173). +So far I have nothing to say here other than showing `-Yinfer-debug` logs of various code snippets, which involve type inference. + + def foo[T1](x: T1) = ??? + foo(2) + + [solve types] solving for T1 in ?T1 + [infer method] solving for T1 in (x: T1)Nothing based on (Int)Nothing (solved: T1=Int) + + def bar[T2] = ??? + bar + + [solve types] solving for T2 in ?T2 + inferExprInstance { + tree C.this.bar[T2] + tree.tpe Nothing + tparams type T2 + pt ? + targs Nothing + tvars =?Nothing + } + + class Baz[T] + implicit val ibaz = new Baz[Int] + def baz[T3](implicit ibaz: Baz[T3]) = ??? + baz + + [solve types] solving for T3 in ?T3 + inferExprInstance { + tree C.this.baz[T3] + tree.tpe (implicit ibaz: C.this.Baz[T3])Nothing + tparams type T3 + pt ? + targs Nothing + tvars =?Nothing + } + inferExprInstance/AdjustedTypeArgs { + okParams + okArgs + leftUndet type T3 + } + [infer implicit] C.this.baz[T3] with pt=C.this.Baz[T3] in class C + [search] C.this.baz[T3] with pt=C.this.Baz[T3] in class C, eligible: + ibaz: => C.this.Baz[Int] + [search] considering T3 (pt contains ?T3) trying C.this.Baz[Int] against pt=C.this.Baz[T3] + [solve types] solving for T3 in ?T3 + [success] found SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int))) for pt C.this.Baz[=?Int] + [infer implicit] inferred SearchResult(C.this.ibaz, TreeTypeSubstituter(List(type T3),List(Int))) + + class Qwe[T] + implicit def idef[T4] = new Qwe[T4] + def qwe[T4](implicit xs: Qwe[T4]) = ??? + qwe + + [solve types] solving for T4 in ?T4 + inferExprInstance { + tree C.this.qwe[T4] + tree.tpe (implicit xs: C.this.Qwe[T4])Nothing + tparams type T4 + pt ? + targs Nothing + tvars =?Nothing + } + inferExprInstance/AdjustedTypeArgs { + okParams + okArgs + leftUndet type T4 + } + [infer implicit] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C + [search] C.this.qwe[T4] with pt=C.this.Qwe[T4] in class C, eligible: + idef: [T4]=> C.this.Qwe[T4] + [solve types] solving for T4 in ?T4 + inferExprInstance { + tree C.this.idef[T4] + tree.tpe C.this.Qwe[T4] + tparams type T4 + pt C.this.Qwe[?] + targs Nothing + tvars =?Nothing + } + [search] considering T4 (pt contains ?T4) trying C.this.Qwe[Nothing] against pt=C.this.Qwe[T4] + [solve types] solving for T4 in ?T4 + [success] found SearchResult(C.this.idef[Nothing], ) for pt C.this.Qwe[=?Nothing] + [infer implicit] inferred SearchResult(C.this.idef[Nothing], ) + [solve types] solving for T4 in ?T4 + [infer method] solving for T4 in (implicit xs: C.this.Qwe[T4])Nothing based on (C.this.Qwe[Nothing])Nothing (solved: T4=Nothing) + +## Previously proposed solution (deprecated) + +It turns out that it's unnecessary to introduce a low-level hack in the type inference mechanism. +As outlined above, there is a much more elegant and powerful solution NEW. + +Using the infrastructure provided by [macro bundles](/overviews/macros/bundles.html) (in principle, we could achieve exactly the same +thing using the traditional way of defining macro implementations, but that's not important here), we introduce the `onInfer` callback, +which macros can define to be called by the compiler from `inferExprInstance` and `inferMethodInstance`. The callback takes a single +parameter of type `c.TypeInferenceContext`, which encapsulates the arguments of `inferXXX` methods and provides methods to infer +unknown type parameters. + + trait Macro { + val c: Context + def onInfer(tc: c.TypeInferenceContext): Unit = tc.inferDefault() + } + + type TypeInferenceContext <: TypeInferenceContextApi + trait TypeInferenceContextApi { + def tree: Tree + def unknowns: List[Symbol] + def expectedType: Type + def actualType: Type + + // TODO: can we get rid of this couple? + def keepNothings: Boolean + def useWeaklyCompatible: Boolean + + def infer(sym: Symbol, tpe: Type): Unit + + // TODO: would be lovely to have a different signature here, namely: + // def inferDefault(sym: Symbol): Type + // so that the macro can partially rely on out-of-the-box inference + // and infer the rest afterwards + def inferDefault(): Unit + } + +With this infrastructure in place, we can write the `materializeIso` macro, which obviates the need for manual declaration of implicits. +The full source code is available in [paradise/macros](https://github.com/scalamacros/kepler/blob/paradise/macros/test/files/run/macro-programmable-type-inference/Impls_Macros_1.scala), here's the relevant excerpt: + + override def onInfer(tic: c.TypeInferenceContext): Unit = { + val C = tic.unknowns(0) + val L = tic.unknowns(1) + import c.universe._ + import definitions._ + val TypeRef(_, _, caseClassTpe :: _ :: Nil) = tic.expectedType // Iso[Test.Foo,?] + tic.infer(C, caseClassTpe) + val fields = caseClassTpe.typeSymbol.typeSignature.declarations.toList.collect{ case x: TermSymbol if x.isVal && x.isCaseAccessor => x } + val core = (TupleClass(fields.length) orElse UnitClass).asType.toType + val tequiv = if (fields.length == 0) core else appliedType(core, fields map (_.typeSignature)) + tic.infer(L, tequiv) + } diff --git a/ko/overviews/macros/overview.md b/ko/overviews/macros/overview.md new file mode 100755 index 0000000000..482ec292e8 --- /dev/null +++ b/ko/overviews/macros/overview.md @@ -0,0 +1,360 @@ +--- +layout: overview-large +title: Def 매크로 + +disqus: true + +partof: macros +num: 1 +outof: 7 +language: ko +--- +EXPERIMENTAL + +**유진 부르마코(Eugene Burmako)** + +## 동기 + +컴파일 시점 메타프로그래밍은 다음과 같은 프로그래밍 테크닉을 가능케 해주는 귀중한 도구이다. + +* 언어 가상화(원래의 프로그래밍 언어의 의미를 재정의/중복정의할 수 있어서 DSL을 한층 더 깊게 내장할수 있다) +* 프로그램의 대상화(reification: 프로그램이 자기 코드를 스스로 검사할 수 있는 방법을 제공한다) +* 자기 최적화(프로그램 대상화를 기반으로 도메인에 특화된 코드를 스스로 적용할 수 있다) +* 알고리즘에 의한 프로그램 구성(원래의 프로그래밍 언어만 가지고는 작성하는 작업이 쉽지 않던 부분을 자동화할 수 있다) + +이 소갯글에서는 스칼라를 위한 매크로 시스템을 알려줄 것이다. 매크로 기능을 사용하면 프로그래머가 매크로 정의를 작성할 수 있다. 이런 매크로 정의는 컴파일 도중에 컴파일러가 투명하게(즉, 영향받지 않고) 로드할 수 있고 실행할 수 있다. 이를 통해 스칼라에서 컴파일 시점 메타프로그래밍이 가능해진다. + +## 간단한 소개 + +다음은 프로토타임과 마찬가지인 매크로 정의이다. + + def m(x: T): R = macro implRef + +처음 보면 매크로 정의는 일반적인 함수 정의와 같아 보인다. 차이는 몸체가 조건부 키워드 `macro`로 시작하고, 그 뒤에 매크로를 구현하는 정적 메소드에 대한 전체 경로를 포함한 이름이 들어간다는 점 뿐이다. + +타입체크를 하는 동안 컴파일러가 매크로 적용 `m(args)`를 발견하면, 이를 그 구현 메소드를 호출해서 확장한다. 이때 구현 메소드에 매크로의 인자로 들어간 식의 추상 구문 트리(abstract syntax tree, 메모리상에서 식을 파싱한 결과를 트리구조로 표현한 것)를 넘겨준다. 매크로 구현을 호출하면 돌려받는 결과는 또 다른 추상 구문 트리이다. 컴파일러는 이 트리를 매크로 호출 부분 대신 끼워 넣고, 타입 체크를 계속 진행하게 된다. + +다음 코드는 `Asserts.assertImpl`를 구현으로 사용하는 매크로 정의를 보여준다(`Asserts.assertImpl`의 구현은 더 아래에 있다). + + def assert(cond: Boolean, msg: Any) = macro Asserts.assertImpl + +`assert(x < 10, "limit exceeded")`와 같은 매크로 호출은 컴파일시 다음과 같은 매크로 구현체 호출을 불러 일으킨다. + + assertImpl(c)(<[ x < 10 ]>, <[ “limit exceeded” ]>) + +여기서 `c`는 매크로 호출 지점에서 컴파일러가 수집해 가지고 있는 정보를 포함하고 있는 컨택스트이다. 또 다른 두 인자는 각각 두 식 `x < 10`, `limit exceeded`을 표현하는 추상 구현 트리이다. + +이 문서에서는 `<[ expr ]>`라는 표기를 사용해 `expr`이라는 식을 표현하는 추상 구문 트리를 표시할 것이다. 이 표기방식 자체는 우리가 제안하는 스칼라 언어 확장(역주: 이 문서는 스칼라에 매크로를 도입하자는 제안서였던 것을 수정한 것이다)에는 대응되는 부분이 없다. 실제로 구문 트리는 `scala.reflect.api.Trees` 트레잇에 있는 타입을 사용해 구현되어야 할 것이고, 위의 두 식은 아마도 다음과 비슷하게 만들어질 수 있을 것이다. + + Literal(Constant("limit exceeded")) + + Apply( + Select(Ident(newTermName("x")), newTermName("$less"), + List(Literal(Constant(10))))) + +다음은 `assert` 매크로를 실제로 구현한 예이다. + + import scala.reflect.macros.Context + import scala.language.experimental.macros + + object Asserts { + def raise(msg: Any) = throw new AssertionError(msg) + def assertImpl(c: Context) + (cond: c.Expr[Boolean], msg: c.Expr[Any]) : c.Expr[Unit] = + if (assertionsEnabled) + <[ if (!cond) raise(msg) ]> + else + <[ () ]> + } + +앞에서 본 바와 같이 매크로 구현은 몇가지 인자를 받는다. 첫번째로 오는 것은 `scala.reflect.macros.Context` 타입의 값이다. 그 다음에 매크로 정의 매개변수에 있는 인자들과 같은 매개변수 목록이 온다. 하지만 원래의 매크로 매개변수가 `T` 타입이었다면, 매크로 구현에서의 매개변수는 `c.Expr[T]`가 되어야 한다. `Expr[T]`는 `Context`에 정의되어 있으며, 타입 `T`의 추상 구문 트리를 감싸고 있다. `assertImpl` 매크로 구현의 결과 타입은 구문 트리를 감싼 `c.Expr[Unit]` 타입이 된다. + +한가지 더 일러둘 것은 매크로가 아직 실험적인 단계이기 때문에, 명시적으로 활성화해야만 사용할 수 있다는 점이다. `import scala.language.experimental.macros`를 사용하는 파일 앞에 붙여주거나, 컴파일시 `-language:experimental.macros`를 (컴파일러에 스위치를 지정해서) 사용해야 한다. + +### 제네릭 매크로 + +매크로 정의와 구현은 제네릭할 수도 있다. 매크로 구현에 타입 매개변수가 있다면, 실제 타입 인자를 명시적으로 매크로 정의 몸체에 추가해야만 한다. 매크로 구현에서 타입 매개변수를 `WeakTypeTag(약한 타입 태그)` 컨텍스트 바운드를 통해 제한할 수도 있다. 이렇게 하면 매크로 호출 위치에서 인스턴스화된 실제 타입 매개변수를 표현하는 타입 태그가 매크로 확장시 전달된다. + +다음 코드 조각은 `QImpl.map` 매크로 구현을 참조하는 매크로 정의 `Queryable.map`을 보여준다. + + class Queryable[T] { + def map[U](p: T => U): Queryable[U] = macro QImpl.map[T, U] + } + + object QImpl { + def map[T: c.WeakTypeTag, U: c.WeakTypeTag] + (c: Context) + (p: c.Expr[T => U]): c.Expr[Queryable[U]] = ... + } + +값 `q`가 `Queryable[String]` 타입이라면 아래와 같이 매크로를 호출하면, + + q.map[Int](s => s.length) + +다음과 같은 리플렉션이 들어간 + + QImpl.map(c)(<[ s => s.length ]>) + (implicitly[WeakTypeTag[String]], implicitly[WeakTypeTag[Int]]) + +## 완전한 예제 + +이번 절에서는 `printf` 매크로를 처음부터 끝까지 만들어 볼 것이다. 이 매크로는 포맷 문자열을 컴파일시 검증해서 적용한다. +설명의 편의를 위해 콘솔 스칼라 컴파일러를 사용할 것이다. 하지만, 메이븐이나 SBT에서도 다음에 설명하는 방식대로 매크로를 사용 할 수 있다. + +매크로 작성은 매크로 정의를 쓰는 것으로부터 시작된다. 매크로 정의는 매크로의 외관(facade) 역할을 한다. 매크로 정의 자체는 멋질것 하나 없는 평범한 함수이다. 하지만, 그 몸체는 구현에 대한 참조일 뿐 아무것도 아니다. 앞에서 이야기한 바와 같이 매크로를 정의하기 위해서는 `scala.language.experimental.macros`를 임포트하거나 컴파일러에 특별한 스위치 `-language:experimental.macros`를 지정해야 한다. + + import scala.language.experimental.macros + def printf(format: String, params: Any*): Unit = macro printf_impl + +매크로 구현은 그 구현을 사용하는 매크로 정의와 대응되어야만 한다(보통은 오직 하나의 정의만 존재하지만, 여러개가 있을 수도 있다). +줄여 말하면, 매크로 정의에 있는 매개변수의 타입 `T`는 구현의 시그니쳐에서는 타입 `c.Expr[T]`가 되어야 한다. 전체 규칙은 꽤 복잡하지만, 문제가 되지는 않는다. 왜냐하면 오류가 있다면 컴파일러가 원하는 타입의 시그니쳐를 오류 메시지에 표시해주기 때문이다. + + import scala.reflect.macros.Context + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = ... + +컴파일러 API는 `scala.reflect.macros.Context`로 노출되어 있다. 가장 중요한 부분인 리플렉션(reflection) API는 `c.universe`로 억세스할 수 있다. 관례적으로 `c.universe._`를 임포트하곤 한다. 왜냐하면 일상적으로 활용되는 대부분의 함수와 타입이 그 안에 정의되어 있기 때문이다. + + import c.universe._ + +매크로는 우선 넘어온 형식 문자열을 분석해야 한다. 매크로 구현 함수는 컴파일시점에 실행되기 때문에 값에 대해 동작하는 대신 구문 트리에 대해 동작한다. +이는 `printf` 매크로가 받는 형식 문자열이 `java.lang.String` 타입의 객체가 아니고, 컴파일시점의 리터럴(literal)이라는 것을 의미한다. +또한, 이로 인해 아래의 코드는 `printf(get_format(), ...)`에 대해서는 동작할 수 없다. 왜냐하면 이런 경우에 `format`이 문자열 리터럴이 아니고, 함수 호출을 표현하는 AST(추상 구문 트리)가 되기 때문이다. + + val Literal(Constant(s_format: String)) = format.tree + +전형적인 매크로는(여기서 다루고 있는 매크로도 예외가 아니다) 스칼라 코드를 표현하는 AST를 만들어내기 위해 사용된다. 스칼라 코드가 생성되는 과정에 대해 더 잘 알고 싶은 독자는 [리플렉션 개요(the overview of reflection)](http://docs.scala-lang.org/overviews/reflection/overview.html)를 참조하라. 아래 코드는 AST를 만듦과 동시에 또한 타입 정보도 조작한다. +`Int`와 `String` 타입을 어떻게 각각 담아두는지를 살펴보라. +위에 링크한 리플렉션에 대한 글을 보면 타입을 조작하는 법에 대해서도 자세히 다루고 있다. +코드 생성시 마지막 단계는 만들어진 코드를 한 `Block`으로 묶는 것이다. AST를 만드는 간편한 방법인 `reify` 함수를 호출한단 사실에 유의하라. + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = newTermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + +아래 코드는 `printf` 매크로의 전체 구현이다. +예제를 따라하고 싶다면 빈 디렉터리를 만들고 `Macros.scala`라는 파일에 아래 코드를 복사해 넣도록 하라. + + import scala.reflect.macros.Context + import scala.collection.mutable.{ListBuffer, Stack} + + object Macros { + def printf(format: String, params: Any*): Unit = macro printf_impl + + def printf_impl(c: Context)(format: c.Expr[String], params: c.Expr[Any]*): c.Expr[Unit] = { + import c.universe._ + val Literal(Constant(s_format: String)) = format.tree + + val evals = ListBuffer[ValDef]() + def precompute(value: Tree, tpe: Type): Ident = { + val freshName = newTermName(c.fresh("eval$")) + evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value) + Ident(freshName) + } + + val paramsStack = Stack[Tree]((params map (_.tree)): _*) + val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map { + case "%d" => precompute(paramsStack.pop, typeOf[Int]) + case "%s" => precompute(paramsStack.pop, typeOf[String]) + case "%%" => Literal(Constant("%")) + case part => Literal(Constant(part)) + } + + val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree) + c.Expr[Unit](Block(stats.toList, Literal(Constant(())))) + } + } + +`printf` 매크로를 사용하기 위해 같은 디렉터리에 `Test.scala`라는 파일을 만들고 다음 코드를 입력하라. +매크로를 사용하는 것이 함수 호출과 마찬가지로 간단하다는 사실을 확인하라. `scala.language.experimental.macros`를 임포트할 필요는 없다. + + object Test extends App { + import Macros._ + printf("hello %s!", "world") + } + +매크로 사용에 있어 중요한 것 중 하나는 컴파일되는 시점의 분리이다. 매크로를 확장하기 위해 컴파일러는 매크로 구현을 실행 가능한 형태로 가지고 있어야 한다. 따라서 매크로 구현은 매크로를 사용하는 쪽을 컴파일하기 전에 컴파일되어야 한다. 그렇지 않으면 다음과 같은 오류가 발생한다. + + ~/Projects/Kepler/sandbox$ scalac -language:experimental.macros Macros.scala Test.scala + Test.scala:3: error: macro implementation not found: printf (the most common reason for that is that + you cannot use macro implementations in the same compilation run that defines them) + pointing to the output of the first phase + printf("hello %s!", "world") + ^ + one error found + + ~/Projects/Kepler/sandbox$ scalac Macros.scala && scalac Test.scala && scala Test + hello world! + +## 팁과 트릭 + +### 명령행 스칼라 컴파일러에서 매크로 활용하기 + +이 시나리오는 앞에서 이미 다룬 것이다. 요약하면, 매크로를 먼저 `scalac`로 컴파일하고, 그 후 매크로를 적용하는 부분을 컴파일하라. 그렇게 하면 문제가 없어야 한다. REPL은 더 사용하기 편하다. 왜냐하면 REPL은 입력되는 줄을 각각 컴파일해 실행하기 때문이다. 따라서 매크로를 정의하고 바로 이를 적용해볼 수 있다. + +### 메이븐이나 SBT에서 매크로 사용하기 + +이 가이드에서 제공한 방식은 가장 단순한 명령행 컴파일을 사용하지만, 메이븐이나 SBT와 같은 빌드도구에서도 매크로를 활용할 수 있다. [https://github.com/scalamacros/sbt-example](https://github.com/scalamacros/sbt-example)이나 [https://github.com/scalamacros/maven-example](https://github.com/scalamacros/maven-example)에서 완전한 예제를 볼 수 있다. +간략하게 말하자면 다음 두가지를 알아두어야 한다. +* 매크로를 사용하기 위해서는 라이브러리 의존성에 scala-reflect.jar가 필요하다. +* 별도 컴파일 제약을 지키려면 매크로를 별도의 프로젝트에 위치시켜서 먼저 컴파일해야 한다. + +### 스칼라 IDE나 인텔리J IDEA에서 사용하기 + +스칼라 IDE나 인텔리J IDEA 모두에서 매크로가 잘 작동한다고 알려져 있다. 다만, 매크로를 별도의 프로젝트로 분리해야 한다. + +### 매크로 디버깅하기 + +매크로를 디버깅하는 것(즉, 매크로 확장 로직을 디버깅하는 것)은 아주 간단하다. 매크로가 컴파일러 안에서 확장되기 때문에, 디버깅을 하려면 컴파일러를 디버거 안에서 실행시키면 된다. 이를 위해 1) 스칼라 홈 디렉터리 아래 lib의 모든(!) 라이브러리를 디버깅시 클래스패스에 추가하고, 2) `scala.tools.nsc.Main`를 시작점으로 설정하고, 3) 컴파일러의 명령행 인자를 `-cp <매크로 클래스들에 대한 경로> Test.scala`로 하라(`Test.scala`는 디버깅하기 위해 만든 매크로 호출이 들어있는 프로그램이다). 이렇게 하고 나서 매크로 구현 부분에 중단점을 설정하고 컴파일러를 디버거에서 실행시키면 된다. + +특별한 도구가 필요할 때는 매크로 확장의 결과(즉, 매크로가 만들어낸 코드)를 디버깅할 필요가 있을 때이다. 이 코드는 직접 작성된 것이 아니기 때문에 그 안에 중단점을 걸 수가 없다. 또한 소스상 한 단계씩 실행(single stepping)도 불가능하다. 언젠가는 스칼라 IDE나 인텔리J IDEA 팀에 의해 디버거에 이런 부분에 대한 지원이 추가되겠지만, 현재 사용 가능한 유일한 대안은 진단 메시지를 추가하는 것 뿐이다. `-Ymacro-debug-lite` (아래 설명함)를 추가하면 매크로가 내어 놓는 코드를 출력해 준다. 생성된 코드의 실행을 추적하기 위해서는 `println`을 만들어지는 코드에 추가하라. + +### 만들어진 코드 살펴보기 + +`-Ymacro-debug-lite` 옵션을 사용해 매크로 확장에 의해 생긴 코드의 의사 스칼라 코드나 원 AST 표현을 볼 수 있다. 두 방식 모두 서로 다른 장점이 있다. 전자는 표면적인 검사에 유용하고, 후자는 미세한 디버깅시 필수적이다. + + ~/Projects/Kepler/sandbox$ scalac -Ymacro-debug-lite Test.scala + typechecking macro expansion Macros.printf("hello %s!", "world") at + source-C:/Projects/Kepler/sandbox\Test.scala,line-3,offset=52 + { + val eval$1: String = "world"; + scala.this.Predef.print("hello "); + scala.this.Predef.print(eval$1); + scala.this.Predef.print("!"); + () + } + Block(List( + ValDef(Modifiers(), newTermName("eval$1"), TypeTree().setType(String), Literal(Constant("world"))), + Apply( + Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("print")), + List(Literal(Constant("hello")))), + Apply( + Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("print")), + List(Ident(newTermName("eval$1")))), + Apply( + Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("print")), + List(Literal(Constant("!"))))), + Literal(Constant(()))) + +### 처리되지 않는 예외를 던지는 매크로 + +매크로가 처리되지 않는 예외를 던지면 어떤 일이 벌어질까? 예를 들어 `printf` 매크로에 잘못된 입력을 주는 경우를 생각해 보자. +다음 출력에서 볼 수 있듯 대단한 일이 벌어지는 것은 아니다. 컴파일러는 잘못된 매크로 사용을 막고, 적절한 스택 추적 메시지를 출력하고, 오류를 보고한다. + + ~/Projects/Kepler/sandbox$ scala + Welcome to Scala version 2.10.0-20120428-232041-e6d5d22d28 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). + Type in expressions to have them evaluated. + Type :help for more information. + + scala> import Macros._ + import Macros._ + + scala> printf("hello %s!") + :11: error: exception during macro expansion: + java.util.NoSuchElementException: head of empty list + at scala.collection.immutable.Nil$.head(List.scala:318) + at scala.collection.immutable.Nil$.head(List.scala:315) + at scala.collection.mutable.Stack.pop(Stack.scala:140) + at Macros$$anonfun$1.apply(Macros.scala:49) + at Macros$$anonfun$1.apply(Macros.scala:47) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:237) + at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:34) + at scala.collection.mutable.ArrayOps.foreach(ArrayOps.scala:39) + at scala.collection.TraversableLike$class.map(TraversableLike.scala:237) + at scala.collection.mutable.ArrayOps.map(ArrayOps.scala:39) + at Macros$.printf_impl(Macros.scala:47) + + printf("hello %s!") + ^ + +### 경고와 오류 보고하기 + +사용자와 상호작용하는 표준적인 방법은 `scala.reflect.macros.FrontEnds`를 사용하는 것이다. +`c.error`는 컴파일 오류를 보고하고 `c.info`는 경고메시지를 출력하며 `c.abort`는 오류를 보고한 후 매크로 실행을 종료한다. + + scala> def impl(c: Context) = + c.abort(c.enclosingPosition, "macro has reported an error") + impl: (c: scala.reflect.macros.Context)Nothing + + scala> def test = macro impl + defined term macro test: Any + + scala> test + :32: error: macro has reported an error + test + ^ + +[SI-6910](https://issues.scala-lang.org/browse/SI-6910)에 있는 바와 같이 현재로써는 오류 보고시 한 지점에서 여러 오류를 보고할 수 없다는 점에 유의하라. 따라서 각 위치에서 발생한 최초의 오류만 보고할 수 있으며 나머지는 사라진다(또한 오류는 같은 지점에서 발생한 경고보다 우선한다. 경고가 더 먼저 발생했다고 해도 그렇다). + + +### 더 큰 매크로 작성하기 + +매크로 구현 코드가 점점 커져서 구현 메소드의 몸체의 모듈화를 보장할 수준 이상이 되어버린다면, 컨택스트 매개변수를 짊어지고 다닐것을 고려해 봐야 한다. 왜냐하면 관심의 대상이 되는 대부분의 요소는 컨텍스트에 경로-의존적이기 때문이다. + +접근 방식 중 하나는 `Context` 타입의 매개변수를 받는 클래스를 만들어서 매크로 구현을 그 클래스 안의 여러 메소드로 나누어 두는 것이다. 이런 방법은 자연스럽고 간단하지만, 제대로 구현하는 것은 어렵다. 다음은 전형적인 컴파일 오류이다. + + scala> class Helper(val c: Context) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper(c) + | c.Expr(helper.generate) + | } + :32: error: type mismatch; + found : helper.c.Tree + (which expands to) helper.c.universe.Tree + required: c.Tree + (which expands to) c.universe.Tree + c.Expr(helper.generate) + ^ + +여기서는 경로-의존적인 타입 불일치가 일어났다. 스칼라 컴파일러는 `impl`의 `c`가 `Helper`의 `c`와 같음을 인지하지 못한다. 심지어 원래의 `c`를 사용해 `helper`가 만들어졌음에도 말이다. + +다행스럽게도 컴파일러에게 살짝 힌트를 주어 어떤 일이 벌어진 것인지 쉽게 알아채도록 만들 수 있다. 한가지 방법은 상세타입(refinement type, 원 타입에 일부 제약을 추가한 하위 타입)을 활용하는 것이다(아래 예는 이 아이디어를 활용하는 가장 단순한 예이다. 예를 들어 명시적 인스턴스화를 없애고 호출을 간단하게 하기 위해 `Context`에서 `Helper`가는 묵시적 변환을 추가할 수도 있을 것이다). + + scala> abstract class Helper { + | val c: Context + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c1: Context): c1.Expr[Unit] = { + | val helper = new { val c: c1.type = c1 } with Helper + | c1.Expr(helper.generate) + | } + impl: (c1: scala.reflect.macros.Context)c1.Expr[Unit] + +또 다른 방법은 컨텍스트의 정체를 명시적인 타입 매개변수로 넘기는 것이다. 아래 예에서는 `Helper`를 만들 때 `c.type`을 지정해서 `Helper.c`가 사실은 원래의 `c`와 같음을 알린다. 스칼라의 타입 추론이 자동으로 이를 파악할 수 없으므로 약간 도와주는 것이다. + + scala> class Helper[C <: Context](val c: C) { + | def generate: c.Tree = ??? + | } + defined class Helper + + scala> def impl(c: Context): c.Expr[Unit] = { + | val helper = new Helper[c.type](c) + | c.Expr(helper.generate) + | } + impl: (c: scala.reflect.macros.Context)c.Expr[Unit] + +## 추가 예제 + +스칼라 매크로는 아직 도입 초기 단계이다. 커뮤니티의 활발한 활동으로 인해 빠르게 프로토타입을 만들 수 있었고, 이제는 참고할 수 있는 코드가 존재한다. 최신 상태에 대해서는 [http://scalamacros.org/news/2012/11/05/status-update.html](http://scalamacros.org/news/2012/11/05/status-update.html)를 참조하라. + +또한 아담 와스키(Adam Warski)가 작성한 짧은 자습서 [http://www.warski.org/blog/2012/12/starting-with-scala-macros-a-short-tutorial](http://www.warski.org/blog/2012/12/starting-with-scala-macros-a-short-tutorial)를 추천한다. 그 자습서 안에는 디버깅용 출력 매크로를 작성하는 방법을 한 단계씩 설명하며, SBT 프로젝트 설정과 `reify`나 `splice`의 사용법, 그리고 AST를 손으로 합치는 방법에 대해서도 설명한다. \ No newline at end of file diff --git a/ko/overviews/macros/paradise.md b/ko/overviews/macros/paradise.md new file mode 100644 index 0000000000..fc8c8f4df8 --- /dev/null +++ b/ko/overviews/macros/paradise.md @@ -0,0 +1,37 @@ +--- +layout: overview-large +title: Macro Paradise + +disqus: true + +partof: macros +num: 2 +outof: 7 +language: ko +--- +MACRO PARADISE + +**Eugene Burmako** + +## Macro paradise for 2.11.x + +Macro paradise is an alias of the experimental `paradise/macros` branch in the official Scala repository, designed to facilitate rapid development of macros without compromising the stability of Scala. To learn more about this branch, check out [my talk](http://scalamacros.org/news/2012/12/18/macro-paradise.html). + +We have set up a nightly build which publishes snapshot artifacts to Sonatype. Consult [https://github.com/scalamacros/sbt-example-paradise](https://github.com/scalamacros/sbt-example-paradise) for an end-to-end example of using our nightlies in SBT, but in a nutshell playing with macro paradise is as easy as adding these three lines to your build (granted you've already [set up SBT](/overviews/macros/overview.html#using_macros_with_maven_or_sbt) to use macros): + + scalaVersion := "2.11.0-SNAPSHOT" + scalaOrganization := "org.scala-lang.macro-paradise" + resolvers += Resolver.sonatypeRepo("snapshots") + +Currently SBT has some problems with updating custom `scala-compiler.jar` to new snapshot versions. The symptoms are as follows. The first time +you compile a project that uses macro paradise, everything works fine. But in a few days when you do `sbt update`, SBT fetches new nightly +builds for `scala-library.jar` and `scala-reflect.jar`, but not for `scala-compiler.jar`. The solution is to use `sbt reboot full`, which +re-downloads SBT itself and the underlying scalac instance. We're investigating this unfortunate issue, but in the meanwhile you can join the discussion of this matter [at the mailing list](https://groups.google.com/forum/?fromgroups=#!topic/simple-build-tool/UalhhX4lKmw/discussion). + +Scaladocs corresponding to paradise nightlies can be found at [our Jenkins server](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html). For example, here's the new API for working with top-level definitions: [scala.reflect.macros.Synthetics](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics). + +## Macro paradise for 2.10.x + +There is also a special build of macro paradise that targets Scala 2.10.x NEW. With macro paradise 2.10, you can already make use of **quasiquotes in production versions of 2.10.x**: compile your macros using the `2.10.2-SNAPSHOT` build of macro paradise, and then these macros will be perfectly usable with a vanilla 2.10.x compiler. It works in such a neat way, because quasiquotes themselves are macros, so after being expanded they leave no traces of dependencies on macro paradise (well, almost). Check out [https://github.com/scalamacros/sbt-example-paradise210](https://github.com/scalamacros/sbt-example-paradise210) for an end-to-end example and a detailed explanation of this cool trick. + +Please note that due to binary compatibility restrictions, macro paradise for 2.10.x doesn't include any features from macro paradise 2.11.x except for quasiquotes. According to our compatibility policy, we cannot update the public API with new methods in any of the minor releases of the 2.10.x series, and essentially everything in the full-fledged paradise requires new APIs. Therefore the only difference between scalac 2.10.2 and macro paradise 2.10.x is the addition of quasiquotes. \ No newline at end of file diff --git a/ko/overviews/macros/quasiquotes.md b/ko/overviews/macros/quasiquotes.md new file mode 100644 index 0000000000..5d8dea890c --- /dev/null +++ b/ko/overviews/macros/quasiquotes.md @@ -0,0 +1,101 @@ +--- +layout: overview-large +title: Quasiquotes + +disqus: true + +partof: macros +num: 4 +outof: 7 +language: ko] +--- +MACRO PARADISE + +**Denys Shabalin, Eugene Burmako** + +Quasiquotes are a pre-release feature included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds. + +As of late, quasiquotes are also available to production users of Scala 2.10.x NEW. Follow the instructions at the macro paradise page for more information. + +## Intuition + +Consider the `Lifter` [type macro](/overviews/macros/typemacros.html), which takes a template of a class or an object and duplicates all the methods in the template with their asynchronous counterparts wrapped in `future`. + + class D extends Lifter { + def x = 2 + // def asyncX = future { 2 } + } + + val d = new D + d.asyncX onComplete { + case Success(x) => println(x) + case Failure(_) => println("failed") + } + +An implementation of such macro might look as the code at the snippet below. This routine - acquire, destructure, wrap in generated code, restructure again - is quite familiar to macro writers. + + case ClassDef(_, _, _, Template(_, _, defs)) => + val defs1 = defs collect { + case DefDef(mods, name, tparams, vparamss, tpt, body) => + val tpt1 = if (tpt.isEmpty) tpt else AppliedTypeTree( + Ident(newTermName("Future")), List(tpt)) + val body1 = Apply( + Ident(newTermName("future")), List(body)) + val name1 = newTermName("async" + name.capitalize) + DefDef(mods, name1, tparams, vparamss, tpt1, body1) + } + Template(Nil, emptyValDef, defs ::: defs1) + +However even seasoned macro writers will admit that this code, even though it's quite simple, is exceedingly verbose, requiring one to understand the details internal representation of code snippets, e.g. the difference between `AppliedTypeTree` and `Apply`. Quasiquotes provide a neat domain-specific language that represents parameterized Scala snippets with Scala: + + val q"class $name extends Liftable { ..$body }" = tree + + val newdefs = body collect { + case q"def $name[..$tparams](...$vparamss): $tpt = $body" => + val tpt1 = if (tpt.isEmpty) tpt else tq"Future[$tpt]" + val name1 = newTermName("async" + name.capitalize) + q"def $name1[..$tparams](...$vparamss): $tpt1 = future { $body }" + } + + q"class $name extends AnyRef { ..${body ++ newdefs} }" + +At the moment quasiquotes suffer from [SI-6842](https://issues.scala-lang.org/browse/SI-6842), which doesn't let one write the code as concisely as mentioned above. A [series of casts](https://gist.github.com/7ab617d054f28d68901b) has to be applied to get things working. + +## Details + +Quasiquotes are implemented as a part of the `scala.reflect.api.Universe` cake, which means that it is enough to do `import c.universe._` to use quasiquotes in macros. Exposed API provides `q` and `tq` [string interpolators](/overviews/core/string-interpolation.html) (corresponding to term and type quasiquotes), which support both construction and deconstruction, i.e. can be used both in normal code and on the left-hand side of a pattern case. + +| Interpolator | Works with | Construction | Deconstruction | +|--------------|------------|----------------------|-------------------------------| +| `q` | Term trees | `q"future{ $body }"` | `case q"future{ $body }"` => | +| `tq` | Type trees | `tq"Future[$t]"` | `case tq"Future[$t]"` => | + +Unlike regular string interpolators, quasiquotes support multiple flavors of splices in order to distinguish between inserting/extracting single trees, lists of trees and lists of lists of trees. Mismatching cardinalities of splicees and splice operators results in a compile-time error. + + scala> val name = TypeName("C") + name: reflect.runtime.universe.TypeName = C + + scala> val q"class $name1" = q"class $name" + name1: reflect.runtime.universe.Name = C + + scala> val args = List(Literal(Constant(2))) + args: List[reflect.runtime.universe.Literal] = List(2) + + scala> val q"foo(..$args1)" = q"foo(..$args)" + args1: List[reflect.runtime.universe.Tree] = List(2) + + scala> val argss = List(List(Literal(Constant(2))), List(Literal(Constant(3)))) + argss: List[List[reflect.runtime.universe.Literal]] = List(List(2), List(3)) + + scala> val q"foo(...$argss1)" = q"foo(...$argss)" + argss1: List[List[reflect.runtime.universe.Tree]] = List(List(2), List(3)) + +## Tips and tricks + +### Liftable + +To simplify splicing of non-trees, quasiquotes provide the `Liftable` type class, which defines how values are transformed into trees when spliced in. We provide instances of `Liftable` for primitives and strings, which wrap those in `Literal(Constant(...))`. You might want to define your own instances for simple case classes and lists (also see [SI-6839](https://issues.scala-lang.org/browse/SI-6839)). + + trait Liftable[T] { + def apply(universe: api.Universe, value: T): universe.Tree + } diff --git a/ko/overviews/macros/typemacros.md b/ko/overviews/macros/typemacros.md new file mode 100644 index 0000000000..758bfbfedc --- /dev/null +++ b/ko/overviews/macros/typemacros.md @@ -0,0 +1,93 @@ +--- +layout: overview-large +title: 타입 매크로 + +disqus: true + +partof: macros +num: 3 +outof: 7 +language: ko +--- +매크로 낙원(MACRO PARADISE) + +**유진 부르마코(Eugene Burmako)** + +타입 매크로는 출시 전(pre-relase) 기능으로서 일명 매크로 낙원이라 불리는 공식 스칼라 저장소의 실험적 브랜치에 포함되어 있다. [매크로 낙원](/overviews/macros/paradise.html) 페이지의 안내에 따라 야간 빌드(nightly builds)를 내려받고 사용해 보자. + +## 간단한 소개 + +Def 매크로가 컴파일러로 하여금 특정 메소드 호출 시 사용자 정의 함수를 실행하도록 하는 것처럼, 타입 매크로를 이용해 특정 타입을 사용할 때 컴파일러를 후킹할 수 있다. 아래 예제는 `H2Db` 매크로의 정의와 사용법을 보여주는데, 이 매크로는 데이터베이스 테이블을 표현하면서 간단한 CRUD기능을 갖는 케이스 클래스를 생성한다. + + type H2Db(url: String) = macro impl + + object Db extends H2Db("coffees") + + val brazilian = Db.Coffees.insert("Brazilian", 99, 0) + Db.Coffees.update(brazilian.copy(price = 10)) + println(Db.Coffees.all) + +`H2Db` 타입 매크로의 전체 소스 코드는 [깃허브(Github)](https://github.com/xeno-by/typemacros-h2db)에서 볼 수 있으며, 이 안내서는 중요한 부분만을 다룬다. 먼저 매크로는 컴파일 시점에 데이터베이스에 접속하여 정적 타입 데이터베이스 래퍼를 만들어낸 다음, (트리 생성은 [리플렉션 개요(the reflection overview)](http://docs.scala-lang.org/overviews/reflection/overview.html)에서 설명한다) NEW `c.introduceTopLevel` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Synthetics))를 사용하여 생성된 래퍼를 컴파일러의 최상단 정의 목록에 추가한 뒤 마지막으로 만들어진 클래스의 상위 생성자를 호출하는 `Apply`노드를 반환한다. `c.Expr[T]` 형태로 확장되는 def 매크로와 달리 타입 매크로는 `c.Tree`로 확장되는데, 이는 `Expr`이 식(term)을 표현하는 것과 달리 타입 매크로는 타입으로 확장되기 때문이다. + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val name = c.freshName(c.enclosingImpl.name).toTypeName + val clazz = ClassDef(..., Template(..., generateCode())) + c.introduceTopLevel(c.enclosingPackage.pid.toString, clazz) + val classRef = Select(c.enclosingPackage.pid, name) + Apply(classRef, List(Literal(Constant(c.eval(url))))) + } + + object Db extends H2Db("coffees") + // 다음과 같음: object Db extends Db$1("coffees") + +합성 클래스(synthetic class)를 생성하여 이를 참조하는 대신, 타입 매크로는 `Template` 트리를 반환하여 원본을 변경할 수 있다. Scalac 내부에서는 클래스와 오브젝트 선언 모두 `Template` 트리에 대한 얇은 래퍼로 표현되므로, 템플릿으로의 확장을 통해 클래스나 오브젝트의 전체 내용을 다시 쓸 수도 있는 것이다. [깃허브](https://github.com/xeno-by/typemacros-lifter)에서 이 기법을 구현한 완전한 예제를 볼 수 있다. + + type H2Db(url: String) = macro impl + + def impl(c: Context)(url: c.Expr[String]): c.Tree = { + val Template(_, _, existingCode) = c.enclosingTemplate + Template(..., existingCode ++ generateCode()) + } + + object Db extends H2Db("coffees") + // 다음과 같음: object Db { + // + // + // } + +## 상세 + +타입 매크로는 def 매크로와 타입 멤버의 복합적 성격을 띤다. 한편으로는 메소드처럼 정의되면서 (값 매개변수, 타입 매개변수를 컨텍스트 지정과 함께 사용할 수 있는 등등), 다른 한편으로는 타입의 네임스페이스에 속하여 타입과 같은 방법으로만 쓸 수 있으며 ([깃허브](https://github.com/scalamacros/kepler/blob/paradise/macros/test/files/run/macro-typemacros-used-in-funny-places-a/Test_2.scala)에 완전한 예제가 있다), 타입과 다른 타입 매크로만을 재정의할 수 있다. + +| 기능 | Def 매크로 | 타입 매크로 | 타입 멤버 | +|----------------------------------------|------------|-------------|-----------| +| 정의와 구현으로 구분됨 | 예 | 예 | 아니오 | +| 값 매개변수를 가질 수 있음 | 예 | 예 | 아니오 | +| 타입 매개변수를 가질 수 있음 | 예 | 예 | 예 | +| 매개변수에 가변성 애노테이션 사용 가능 | 아니오 | 아니오 | 예 | +| 매개변수에 컨텍스트 지정 가능 | 예 | 예 | 아니오 | +| 오버로드 가능 | 예 | 예 | 아니오 | +| 상속 가능 | 예 | 예 | 예 | +| 재정의하거나 재정의될 수 있음 | 예 | 예 | 예 | + +스칼라 프로그램에서 타입 매크로는 타입(type), 응용 타입(applied type), 부모 타입(parent type), 인스턴스 생성(new)과 애노테이션(annotation)의 다섯 가지 역할(role) 중 하나를 수행한다. 매크로의 역할에 따라 가능한 확장 형태가 달라지며, 이는 NEW `c.macroRole` API ([Scaladoc](https://scala-webapps.epfl.ch/jenkins/view/misc/job/macro-paradise-nightly-main/ws/dists/latest/doc/scala-devel-docs/api/index.html#scala.reflect.macros.Enclosures))를 통해 확인할 수 있다. + +| 역할 | 예제 | 클래스 | 비 클래스 | 값 적용 | 템플릿 | +|---------------|-------------------------------------------------|--------|-----------|---------|--------| +| 타입 | `def x: TM(2)(3) = ???` | 가능 | 가능 | 불가능 | 불가능 | +| 응용 타입 | `class C[T: TM(2)(3)]` | 가능 | 가능 | 불가능 | 불가능 | +| 부모 타입 | `class C extends TM(2)(3)`
`new TM(2)(3){}` | 가능 | 불가능 | 가능 | 가능 | +| 인스턴스 생성 | `new TM(2)(3)` | 가능 | 불가능 | 가능 | 불가능 | +| 애노테이션 | `@TM(2)(3) class C` | 가능 | 불가능 | 가능 | 불가능 | + +간단히 말해, 타입 매크로를 확장하면 매크로가 사용된 부분을 해당 매크로가 반환하는 트리로 교체한다. 매크로 확장의 유효성을 판단하려면 머리 속에서 매크로가 사용된 부분을 확장된 형태로 치환한 후 결과가 올바른지 확인해보면 된다. + +예를 들어, `class C extends TM(2)(3)`에 사용된 타입 매크로 `TM(2)(3)`는 `Apply(Ident(newTypeName("B")), List(Literal(Constant(2))))`로 확장될 수 있는데, 이는 `class C extends B(2)`가 유효하기 때문이다. 그러나 `def x: TM(2)(3) = ???`처럼 타입으로 사용된 경우에는 `def x: B(2) = ???`가 되므로 불가능하다. (`B`는 타입 매크로가 아닌 경우에 한한다. 만약 타입 매크로라면 `B`는 재귀적으로 확장될 것이고 그 결과로 프로그램의 유효성을 판단할 것이다.) + +## 팁과 트릭 + +### 클래스와 오브젝트 생성 + +타입 매크로를 작성하다 보면 [스택오버플로우(StackOverflow)](http://stackoverflow.com/questions/13795490/how-to-use-type-calculated-in-scala-macro-in-a-reify-clause)의 예처럼 `reify`의 사용이 불가능한 경우를 만나게 될 것이다. 이럴 때는 직접 트리를 작성하는 대신 매크로 낙원의 또다른 실험적 기능인 [준인용구(quasiquotes)](/overviews/macros/quasiquotes.html) 사용을 고려해 보라. diff --git a/ko/overviews/macros/untypedmacros.md b/ko/overviews/macros/untypedmacros.md new file mode 100644 index 0000000000..7261a578c4 --- /dev/null +++ b/ko/overviews/macros/untypedmacros.md @@ -0,0 +1,74 @@ +--- +layout: overview-large +title: Untyped Macros + +disqus: true + +partof: macros +num: 5 +outof: 7 +language: ko +--- +MACRO PARADISE + +**Eugene Burmako** + +Untyped macros are a pre-release feature included in so-called macro paradise, an experimental branch in the official Scala repository. Follow the instructions at the ["Macro Paradise"](/overviews/macros/paradise.html) page to download and use our nightly builds. + +## Intuition + +Being statically typed is great, but sometimes that is too much of a burden. Take for example, the latest experiment of Alois Cochard with +implementing enums using type macros - the so called [Enum Paradise](https://github.com/aloiscochard/enum-paradise). Here's how Alois has +to write his type macro, which synthesizes an enumeration module from a lightweight spec: + + object Days extends Enum('Monday, 'Tuesday, 'Wednesday...) + +Instead of using clean identifier names, e.g. `Monday` or `Friday`, we have to quote those names, so that the typechecker doesn't complain +about non-existent identifiers. What a bummer - in the `Enum` macro we want to introduce new bindings, not to look up for existing ones, +but the compiler won't let us, because it thinks it needs to typecheck everything that goes into the macro. + +Let's take a look at how the `Enum` macro is implemented by inspecting the signatures of its macro definition and implementation. There we can +see that the macro definition signature says `symbol: Symbol*`, forcing the compiler to typecheck the corresponding argument: + + type Enum(symbol: Symbol*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Expr[Symbol]*): c.Tree = ... + } + +Untyped macros provide a notation and a mechanism to tell the compiler that you know better how to typecheck given arguments of your macro. +To do that, simply replace macro definition parameter types with underscores and macro implementation parameter types with `c.Tree`: + + type Enum(symbol: _*) = macro Macros.enum + object Macros { + def enum(c: Context)(symbol: c.Tree*): c.Tree = ... + } + +## Details + +The cease-typechecking underscore can be used in exactly three places in Scala programs: 1) as a parameter type in a macro, +2) as a vararg parameter type in a macro, 3) as a return type of a macro. Usages outside macros or as parts of complex types won't work. +The former will lead to a compile error, the latter, as in e.g. `List[_]`, will produce existential types as usual. + +Note that untyped macros enable extractor macros: [SI-5903](https://issues.scala-lang.org/browse/SI-5903). In 2.10.x, it is possible +to declare `unapply` or `unapplySeq` as macros, but usability of such macros is extremely limited as described in the discussion +of the linked JIRA issue. Untyped macros make the full power of textual abstraction available in pattern matching. The +[test/files/run/macro-expand-unapply-c](https://github.com/scalamacros/kepler/tree/paradise/macros/test/files/run/macro-expand-unapply-c) +unit test provides details on this matter. + +If a macro has one or more untyped parameters, then when typing its expansions, the typechecker will do nothing to its arguments +and will pass them to the macro untyped. Even if some of the parameters do have type annotations, they will currently be ignored. This +is something we plan on improving: [SI-6971](https://issues.scala-lang.org/browse/SI-6971). Since arguments aren't typechecked, you +also won't having implicits resolved and type arguments inferred (however, you can do both with `c.typeCheck` and `c.inferImplicitValue`). + +Explicitly provided type arguments will be passed to the macro as is. If type arguments aren't provided, they will be inferred as much as +possible without typechecking the value arguments and passed to the macro in that state. Note that type arguments still get typechecked, but +this restriction might also be lifted in the future: [SI-6972](https://issues.scala-lang.org/browse/SI-6972). + +If a def macro has untyped return type, then the first of the two typechecks employed after its expansion will be omitted. A refresher: +the first typecheck of a def macro expansion is performed against the return type of its definitions, the second typecheck is performed +against the expected type of the expandee. More information can be found at Stack Overflow: [Static return type of Scala macros](http://stackoverflow.com/questions/13669974/static-return-type-of-scala-macros). Type macros never underwent the first typecheck, so +nothing changes for them (and you won't be able to specify any return type for a type macro to begin with). + +Finally the untyped macros patch enables using `c.Tree` instead of `c.Expr[T]` everywhere in signatures of macro implementations. +Both for parameters and return types, all four combinations of untyped/typed in macro def and tree/expr in macro impl are supported. +Check our unit tests for more information: [test/files/run/macro-untyped-conformance](https://github.com/scalamacros/kepler/blob/b55bda4860a205c88e9ae27015cf2d6563cc241d/test/files/run/macro-untyped-conformance/Impls_Macros_1.scala). diff --git a/overviews/collections/arrays.md b/overviews/collections/arrays.md index ff8227b3c2..73dd9875cf 100644 --- a/overviews/collections/arrays.md +++ b/overviews/collections/arrays.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 10 -languages: [ja] +languages: [ja, ko] --- [Array](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/Array.html) is a special kind of collection in Scala. On the one hand, Scala arrays correspond one-to-one to Java arrays. That is, a Scala array `Array[Int]` is represented as a Java `int[]`, an `Array[Double]` is represented as a Java `double[]` and a `Array[String]` is represented as a `Java String[]`. But at the same time, Scala arrays offer much more than their Java analogues. First, Scala arrays can be _generic_. That is, you can have an `Array[T]`, where `T` is a type parameter or abstract type. Second, Scala arrays are compatible with Scala sequences - you can pass an `Array[T]` where a `Seq[T]` is required. Finally, Scala arrays also support all sequence operations. Here's an example of this in action: diff --git a/overviews/collections/concrete-immutable-collection-classes.md b/overviews/collections/concrete-immutable-collection-classes.md index 6c3b9d4290..9969ca2af6 100644 --- a/overviews/collections/concrete-immutable-collection-classes.md +++ b/overviews/collections/concrete-immutable-collection-classes.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 8 -languages: [ja] +languages: [ja, ko] --- Scala provides many concrete immutable collection classes for you to choose from. They differ in the traits they implement (maps, sets, sequences), whether they can be infinite, and the speed of various operations. Here are some of the most common immutable collection types used in Scala. diff --git a/overviews/collections/concrete-mutable-collection-classes.md b/overviews/collections/concrete-mutable-collection-classes.md index e60459b334..66d4596c0a 100644 --- a/overviews/collections/concrete-mutable-collection-classes.md +++ b/overviews/collections/concrete-mutable-collection-classes.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 9 -languages: [ja] +languages: [ja, ko] --- You've now seen the most commonly used immutable collection classes that Scala provides in its standard library. Take a look now at the mutable collection classes. diff --git a/overviews/collections/conversions-between-java-and-scala-collections.md b/overviews/collections/conversions-between-java-and-scala-collections.md index 1517d31075..f28d5ef89a 100644 --- a/overviews/collections/conversions-between-java-and-scala-collections.md +++ b/overviews/collections/conversions-between-java-and-scala-collections.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 17 -languages: [ja] +languages: [ja, ko] --- Like Scala, Java also has a rich collections library. There are many similarities between the two. For instance, both libraries know iterators, iterables, sets, maps, and sequences. But there are also important differences. In particular, the Scala libraries put much more emphasis on immutable collections, and provide many more operations that transform a collection into a new one. diff --git a/overviews/collections/creating-collections-from-scratch.md b/overviews/collections/creating-collections-from-scratch.md index 88e29af8b9..a800378167 100644 --- a/overviews/collections/creating-collections-from-scratch.md +++ b/overviews/collections/creating-collections-from-scratch.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 16 -languages: [ja] +languages: [ja, ko] --- You have syntax `List(1, 2, 3)` to create a list of three integers and `Map('A' -> 1, 'C' -> 2)` to create a map with two bindings. This is actually a universal feature of Scala collections. You can take any collection name and follow it by a list of elements in parentheses. The result will be a new collection with the given elements. Here are some more examples: @@ -55,4 +55,4 @@ Descendants of `Seq` classes provide also other factory operations in their comp | `S.tabulate(m, n){f}` | A sequence of sequences of dimension `m×n` where the element at each index `(i, j)` is computed by `f(i, j)`. (exists also in higher dimensions). | | `S.range(start, end)` | The sequence of integers `start` ... `end-1`. | | `S.range(start, end, step)`| The sequence of integers starting with `start` and progressing by `step` increments up to, and excluding, the `end` value. | -| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | \ No newline at end of file +| `S.iterate(x, n)(f)` | The sequence of length `n` with elements `x`, `f(x)`, `f(f(x))`, ... | diff --git a/overviews/collections/equality.md b/overviews/collections/equality.md index 8d36b26fa6..328c5f63ac 100644 --- a/overviews/collections/equality.md +++ b/overviews/collections/equality.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 13 -languages: [ja] +languages: [ja, ko] --- The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, `Set(1, 2, 3)` is unequal to `List(1, 2, 3)` even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements (for sequences: the same elements in the same order). For example, `List(1, 2, 3) == Vector(1, 2, 3)`, and `HashSet(1, 2) == TreeSet(2, 1)`. diff --git a/overviews/collections/introduction.md b/overviews/collections/introduction.md index ae57e0167f..7ac7e9bc72 100644 --- a/overviews/collections/introduction.md +++ b/overviews/collections/introduction.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 1 -languages: [ja] +languages: [ja, ko] --- **Martin Odersky, and Lex Spoon** @@ -93,4 +93,4 @@ as part of Scala 2.9.) This document provides an in depth discussion of the APIs of the Scala collections classes from a user perspective. They take you on -a tour of all the fundamental classes and the methods they define. \ No newline at end of file +a tour of all the fundamental classes and the methods they define. diff --git a/overviews/collections/iterators.md b/overviews/collections/iterators.md index f5b8117e9b..cf0ac3ea4a 100644 --- a/overviews/collections/iterators.md +++ b/overviews/collections/iterators.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 15 -languages: [ja] +languages: [ja, ko] --- An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator `it` are `next` and `hasNext`. A call to `it.next()` will return the next element of the iterator and advance the state of the iterator. Calling `next` again on the same iterator will then yield the element one beyond the one returned previously. If there are no more elements to return, a call to `next` will throw a `NoSuchElementException`. You can find out whether there are more elements to return using [Iterator](http://www.scala-lang.org/api/{{ site.scala-version }}/scala/collection/Iterator.html)'s `hasNext` method. diff --git a/overviews/collections/maps.md b/overviews/collections/maps.md index 24d53a937b..e917859493 100644 --- a/overviews/collections/maps.md +++ b/overviews/collections/maps.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 7 -languages: [ja] +languages: [ja, ko] --- A [Map](http://www.scala-lang.org/api/current/scala/collection/Map.html) is an [Iterable](http://www.scala-lang.org/api/current/scala/collection/Iterable.html) consisting of pairs of keys and values (also named _mappings_ or _associations_). Scala's [Predef](http://www.scala-lang.org/api/current/scala/Predef$.html) class offers an implicit conversion that lets you write `key -> value` as an alternate syntax for the pair `(key, value)`. For instance `Map("x" -> 24, "y" -> 25, "z" -> 26)` means exactly the same as `Map(("x", 24), ("y", 25), ("z", 26))`, but reads better. diff --git a/overviews/collections/migrating-from-scala-27.md b/overviews/collections/migrating-from-scala-27.md index b37fd5e2c5..12c4d6c76b 100644 --- a/overviews/collections/migrating-from-scala-27.md +++ b/overviews/collections/migrating-from-scala-27.md @@ -7,7 +7,7 @@ disqus: true partof: collections num: 18 outof: 18 -languages: [ja] +languages: [ja, ko] --- Porting your existing Scala applications to use the new collections should be almost automatic. There are only a couple of possible issues to take care of. diff --git a/overviews/collections/overview.md b/overviews/collections/overview.md index bb18ec2b17..b2a372a202 100644 --- a/overviews/collections/overview.md +++ b/overviews/collections/overview.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 2 -languages: [ja] +languages: [ja, ko] --- Scala collections systematically distinguish between mutable and @@ -139,4 +139,4 @@ This behavior which is implemented everywhere in the collections libraries is ca Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection. -In the following, we will review these classes one by one. \ No newline at end of file +In the following, we will review these classes one by one. diff --git a/overviews/collections/performance-characteristics.md b/overviews/collections/performance-characteristics.md index 057a5b8155..3e37b00bd1 100644 --- a/overviews/collections/performance-characteristics.md +++ b/overviews/collections/performance-characteristics.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 12 -languages: [ja] +languages: [ja, ko] --- The previous explanations have made it clear that different collection types have different performance characteristics. That's often the primary reason for picking one collection type over another. You can see the performance characteristics of some common operations on collections summarized in the following two tables. diff --git a/overviews/collections/seqs.md b/overviews/collections/seqs.md index 0187b91106..4c32c906eb 100644 --- a/overviews/collections/seqs.md +++ b/overviews/collections/seqs.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 5 -languages: [ja] +languages: [ja, ko] --- The [Seq](http://www.scala-lang.org/api/current/scala/collection/Seq.html) trait represents sequences. A sequence is a kind of iterable that has a `length` and whose elements have fixed index positions, starting from `0`. diff --git a/overviews/collections/sets.md b/overviews/collections/sets.md index 6d65274f32..5db970bfca 100644 --- a/overviews/collections/sets.md +++ b/overviews/collections/sets.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 6 -languages: [ja] +languages: [ja, ko] --- `Set`s are `Iterable`s that contain no duplicate elements. The operations on sets are summarized in the following table for general sets and in the table after that for mutable sets. They fall into the following categories: diff --git a/overviews/collections/strings.md b/overviews/collections/strings.md index 68a57ecc3b..61158306e1 100644 --- a/overviews/collections/strings.md +++ b/overviews/collections/strings.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 11 -languages: [ja] +languages: [ja, ko] --- Like arrays, strings are not directly sequences, but they can be converted to them, and they also support all sequence operations on strings. Here are some examples of operations you can invoke on strings. @@ -24,4 +24,4 @@ Like arrays, strings are not directly sequences, but they can be converted to th scala> val s: Seq[Char] = str s: Seq[Char] = WrappedString(h, e, l, l, o) -These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. \ No newline at end of file +These operations are supported by two implicit conversions. The first, low-priority conversion maps a `String` to a `WrappedString`, which is a subclass of `immutable.IndexedSeq`, This conversion got applied in the last line above where a string got converted into a Seq. the other, high-priority conversion maps a string to a `StringOps` object, which adds all methods on immutable sequences to strings. This conversion was implicitly inserted in the method calls of `reverse`, `map`, `drop`, and `slice` in the example above. diff --git a/overviews/collections/trait-iterable.md b/overviews/collections/trait-iterable.md index 0fb7ab53b2..f89eca4b75 100644 --- a/overviews/collections/trait-iterable.md +++ b/overviews/collections/trait-iterable.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 4 -languages: [ja] +languages: [ja, ko] --- The next trait from the top in the collections hierarchy is `Iterable`. All methods in this trait are defined in terms of an an abstract method, `iterator`, which yields the collection's elements one by one. The `foreach` method from trait `Traversable` is implemented in `Iterable` in terms of `iterator`. Here is the actual implementation: diff --git a/overviews/collections/trait-traversable.md b/overviews/collections/trait-traversable.md index 09ae378b4b..10526e88c1 100644 --- a/overviews/collections/trait-traversable.md +++ b/overviews/collections/trait-traversable.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 3 -languages: [ja] +languages: [ja, ko] --- At the top of the collection hierarchy is trait `Traversable`. Its only abstract operation is `foreach`: @@ -106,4 +106,4 @@ The `foreach` method is meant to traverse all elements of the collection, and ap | `xs.stringPrefix` |The collection name at the beginning of the string returned from `xs.toString`.| | **Views:** | | | `xs.view` |Produces a view over `xs`.| -| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| \ No newline at end of file +| `xs view (from, to)` |Produces a view that represents the elements in some index range of `xs`.| diff --git a/overviews/collections/views.md b/overviews/collections/views.md index 8304d0419d..a43606d6c9 100644 --- a/overviews/collections/views.md +++ b/overviews/collections/views.md @@ -6,7 +6,7 @@ disqus: true partof: collections num: 14 -languages: [ja] +languages: [ja, ko] --- Collections have quite a few methods that construct new collections. Examples are `map`, `filter` or `++`. We call such methods transformers because they take at least one collection as their receiver object and produce another collection in their result. diff --git a/overviews/core/_posts/2012-12-20-macros.md b/overviews/core/_posts/2012-12-20-macros.md index a291455e94..984fc054be 100644 --- a/overviews/core/_posts/2012-12-20-macros.md +++ b/overviews/core/_posts/2012-12-20-macros.md @@ -4,7 +4,7 @@ title: Macros disqus: true partof: macros overview: macros -languages: [ja] +languages: [ja, ko] label-color: important label-text: Experimental --- diff --git a/overviews/index.md b/overviews/index.md index dba3e7dcc8..0e78d19971 100644 --- a/overviews/index.md +++ b/overviews/index.md @@ -1,7 +1,7 @@ --- layout: guides-index title: Guides and Overviews -languages: [es, ja] +languages: [es, ja, ko] ---
diff --git a/overviews/macros/bundles.md b/overviews/macros/bundles.md index 2852894290..b8c2b6d7e7 100644 --- a/overviews/macros/bundles.md +++ b/overviews/macros/bundles.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 6 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE diff --git a/overviews/macros/inference.md b/overviews/macros/inference.md index 7dcd6454d7..4f6813408c 100644 --- a/overviews/macros/inference.md +++ b/overviews/macros/inference.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 7 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE diff --git a/overviews/macros/overview.md b/overviews/macros/overview.md index 10cf3c6ef2..d9c90edde8 100644 --- a/overviews/macros/overview.md +++ b/overviews/macros/overview.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 1 outof: 7 -languages: [ja] +languages: [ja, ko] --- EXPERIMENTAL diff --git a/overviews/macros/paradise.md b/overviews/macros/paradise.md index 0368fedfec..1d92eb8218 100644 --- a/overviews/macros/paradise.md +++ b/overviews/macros/paradise.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 2 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE diff --git a/overviews/macros/quasiquotes.md b/overviews/macros/quasiquotes.md index 1987a115a6..d41f3adc4b 100644 --- a/overviews/macros/quasiquotes.md +++ b/overviews/macros/quasiquotes.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 4 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE diff --git a/overviews/macros/typemacros.md b/overviews/macros/typemacros.md index 92d43a7eb7..93f658f300 100644 --- a/overviews/macros/typemacros.md +++ b/overviews/macros/typemacros.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 3 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE diff --git a/overviews/macros/untypedmacros.md b/overviews/macros/untypedmacros.md index 716b49bcaa..6e8383649e 100644 --- a/overviews/macros/untypedmacros.md +++ b/overviews/macros/untypedmacros.md @@ -7,7 +7,7 @@ disqus: true partof: macros num: 5 outof: 7 -languages: [ja] +languages: [ja, ko] --- MACRO PARADISE