Scala 컬렉션은 맵 작업에서 올바른 컬렉션 유형을 어떻게 반환 할 수 있습니까?
참고 :이 문제는 자주 발생하는 것으로 보이며 검색을 통해 쉽게 찾을 수있는 위치에 배치하고 싶습니다.
예를 들면 :
"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]
scaladoc을 살펴보면이 모든 map
작업은에서 상속 된 작업을 사용 TraversableLike
하므로 항상 가장 구체적인 유효한 컬렉션을 반환 할 수있는 이유는 무엇입니까? 암시 적 변환을 통해 String
제공하는 Even map
.
Scala 컬렉션은 영리한 것입니다 ...
컬렉션 라이브러리의 내부는 Scala 땅에서 더 발전된 주제 중 하나입니다. 여기에는 더 높은 종류의 유형, 추론, 분산, 암시 적 및 CanBuildFrom
메커니즘이 포함됩니다.이 모든 것이 사용자 대면 관점에서 믿을 수 없을 정도로 일반적이고 사용하기 쉽고 강력합니다. API 디자이너의 관점에서이를 이해하는 것은 초보자가 수행 할 수있는 가벼운 작업이 아닙니다.
반면에이 깊이에서 컬렉션 작업을 실제로해야하는 경우는 매우 드뭅니다.
그럼 시작하겠습니다 ...
Scala 2.8의 출시와 함께 컬렉션 라이브러리는 중복을 제거하기 위해 완전히 다시 작성되었으며, 많은 메서드가 한 곳으로 이동되어 지속적인 유지 관리와 새로운 컬렉션 메서드 추가가 훨씬 쉬워졌지만 계층 구조도 더 어려워졌습니다. 이해하다.
가지고 List
예를 들어,에서이 상속 (차례)
LinearSeqOptimised
GenericTraversableTemplate
LinearSeq
Seq
SeqLike
Iterable
IterableLike
Traversable
TraversableLike
TraversableOnce
그것은 아주 소수입니다! 그렇다면 왜 이렇게 깊은 계층이 있습니까? XxxLike
특성을 잠시 무시하면 해당 계층 구조의 각 계층이 약간의 기능을 추가하거나 상속 된 기능의보다 최적화 된 버전을 제공합니다 (예를 들어, 인덱스별로 요소를 가져 오려면 및 작업 Traversable
의 조합이 필요하며 , 인덱스 시퀀스에서는 매우 비효율적입니다. ). 가능한 경우 모든 기능은 가능한 한 계층 구조 위로 밀어 올려 사용할 수있는 하위 클래스 수를 최대화하고 중복을 제거합니다.drop
head
map
하나의 예일뿐입니다. 이 방법은 TraversableLike
( XxxLike
특성은 라이브러리 디자이너에게만 존재 하지만 일반적으로 Traversable
대부분의 의도와 목적 을 위한 방법으로 간주됩니다. 곧 그 부분에 대해 설명하겠습니다), 널리 상속되고 있습니다. 일부 하위 클래스에서 최적화 된 버전을 정의 할 수 있지만 여전히 동일한 서명을 따라야합니다. map
(질문에서도 언급했듯이) 의 다음 용도를 고려하십시오 .
"abcde" map {_.toUpperCase} //returns a String
"abcde" map {_.toInt} // returns an IndexedSeq[Int]
BitSet(1,2,3,4) map {2*} // returns a BitSet
BitSet(1,2,3,4) map {_.toString} // returns a Set[String]
각각의 경우 출력은 가능한 한 입력과 동일한 유형입니다. 그것이 불가능하면 하나가 발견 될 때까지, 입력 유형의 슈퍼 클래스는 체크 하지 유효한 반환 형식을 제공합니다. 이 작업을 제대로하려면 많은 작업이 필요했습니다. 특히 String
컬렉션이 아니라고 생각할 때 암시 적으로 하나로 변환 할 수 있습니다.
그럼 어떻게 되나요?
퍼즐의 절반은이다 XxxLike
(나는 특성 않았다 누구의 주요 기능입니다 취할 수 ... 내가 그들에게 얻을 것이라고 말한다) Repr
가 진정한 서브 클래스가 실제로 존재 알 수 있도록 형 PARAM을 (짧은 "표현"에 대한) 운영. 따라서 예 TraversableLike
는과 동일 Traversable
하지만 Repr
매개 변수 유형을 통해 추상화 됩니다. 이 매개 변수는 퍼즐의 후반부에서 사용됩니다. CanBuildFrom
컬렉션 변환 작업에서 사용할 소스 컬렉션 유형, 대상 요소 유형 및 대상 컬렉션 유형을 캡처 하는 유형 클래스입니다.
예를 들어 설명하는 것이 더 쉽습니다!
BitSet은 CanBuildFrom
다음과 같은 암시 적 인스턴스를 정의합니다 .
implicit def canBuildFrom: CanBuildFrom[BitSet, Int, BitSet] = bitsetCanBuildFrom
컴파일 할 때 BitSet(1,2,3,4) map {2*}
컴파일러는 다음의 암시 적 조회를 시도합니다.CanBuildFrom[BitSet, Int, T]
이것은 영리한 부분입니다 ... 처음 두 가지 유형 매개 변수와 일치하는 암시 적 범위는 하나뿐입니다. 첫 번째 매개 변수는 특성에 Repr
의해 캡처 된이고 두 번째 매개 변수는 XxxLike
현재 컬렉션 특성에 의해 캡처 된 요소 유형입니다 (예 :) Traversable
. map
동작은 다음의이 타입은 타입으로 매개 변수화되는 T
받는 제 타입 파라미터에 기초하여 추정되는 CanBuildFrom
내재적 위치한 예. BitSet
이 경우.
따라서 처음 두 유형 매개 변수 CanBuildFrom
는 암시 적 조회에 사용되는 입력이고 세 번째 매개 변수는 추론에 사용되는 출력입니다.
CanBuildFrom
BitSet
따라서 in 은 두 가지 유형 BitSet
과 일치 Int
하므로 조회가 성공하고 추론 된 반환 유형도 BitSet
.
When compiling BitSet(1,2,3,4) map {_.toString}
, the compiler will attempt an implicit lookup of CanBuildFrom[BitSet, String, T]
. This will fail for the implicit in BitSet, so the compiler will next try its superclass - Set
- This contains the implicit:
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A]
Which matches, because Coll is a type alias that's initialised to be BitSet
when BitSet
derives from Set
. The A
will match anything, as canBuildFrom
is parameterised with the type A
, in this case it's inferred to be String
... Thus yielding a return type of Set[String]
.
So to correctly implement a collection type, you not only need to provide a correct implicit of type CanBuildFrom
, but you also need to ensure that the concrete type of that of that collection is supplied as the Repr
param to the correct parent traits (for example, this would be MapLike
in the case of subclassing Map
).
String
is a little more complicated as it provides map
by an implicit conversion. The implicit conversion is to StringOps
, which subclasses StringLike[String]
, which ultimately derives TraversableLike[Char,String]
- String
being the Repr
type param.
There's also a CanBuildFrom[String,Char,String]
in scope so that the compiler knows that when mapping the elements of a String
to Char
s, then the return type should also be a string. From this point onwards, the same mechanism is used.
The Architecture of Scala Collections online pages have a detailed explanation geared towards the practical aspects of creating new collections based on the 2.8 collection design.
Quote:
"What needs to be done if you want to integrate a new collection class, so that it can profit from all predefined operations at the right types? On the next few pages you'll be walked through two examples that do this."
It uses as example a collection for encoding RNA sequences and one for Patricia trie. Look for the Dealing with map and friends section for the explanation of what to do to return the appropriate collection type.
'developer tip' 카테고리의 다른 글
조건부 연산자는 암시 적으로 캐스팅 할 수 없습니까? (0) | 2020.12.05 |
---|---|
Google Org Chart API와 비교할 때 더 나은 자바 스크립트 조직도가 있습니까? (0) | 2020.12.05 |
함수 내부에서 파이썬 함수의 Docstring을 인쇄하는 방법은 무엇입니까? (0) | 2020.12.05 |
캔버스 요소에서 이미지를 가져 와서 img src 태그에서 사용할 수 있습니까? (0) | 2020.12.05 |
Python : csv.DictReader에서 #으로 표시된 주석 줄을 건너 뜁니다. (0) | 2020.12.05 |