2016-08-20 3 views
4

아래 샘플 코드에서 Iterable [String] test1이 매핑 후 Set을 생성하는 이유는 무엇입니까? 코드를 읽을 때 그 완전히 반 직관적 때문에왜이 Iterable은 매핑 후에 Set을 생성합니까?

val foo = Map("a" -> 1, "b" -> 1) 
val test1: Iterable[String] = foo.keys 
val test2: Iterator[String] = foo.keys.toIterator 

println(test1.map(foo).size) // 1 
println(test2.map(foo).size) // 2 

나는이 당황했다. foo.keys이 단지의 Iterable을 반환하더라도 map를 호출 할 때 반사 코드와 같이, 그것은 Set를 작성

println(test1.map(foo).getClass.getName) // immutable.Set.Set1 
println(test2.map(foo).getClass.getName) // Iterator$$anon$11 

어떻게 표준 라이브러리 그것도의 추론 유형 불구하고, 여기에 immutable.Set를 만들 것을 결정 컬렉션은 Iterable[String]입니까?

답변

2

결과 콜렉션의 작성 방법을 결정하는 암시적인 인수가 있지만이 경우 빌더가 사용하도록 소스 콜렉션을 간단히 쿼리한다는 Kolmar의 의견을 추출합니다.

Iterable.map

:

def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Iterable[A], B, That]): That 

암시 범위 IterableInt 포함한 형 인수 관련된 유형을 포함한다.

Iterable은 원본 컬렉션에서 genericBuilder을 호출하는 "일반"CanBuildFrom을 정의합니다.이것이 결과 유형이 소스에 연결된 방식입니다.

반대로 결과 집합은 CanBuildFrom[From = Nothing, _, _]을 사용하여 원본과 이혼합니다. 이것은 cc.to[Set]이 표현되는 방식으로, 소스 컬렉션 cc에 관계없이 Set이 빌드됩니다. map과 같은 연산의 경우 collection.breakOut 메서드는 결과 유형을 유추 할 수있는 CanBuildFrom을 제공합니다.

당신은 원하는 동작에 대한 임의의 CanBuildFrom를 주입 할 수 있습니다

$ scala 
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_92). 
Type in expressions for evaluation. Or try :help. 

scala> val m = Map("a" -> 1, "b" -> 1) 
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 1) 

scala> val k = m.keys 
k: Iterable[String] = Set(a, b) 

scala> import collection.{generic, mutable}, generic.{CanBuildFrom => CBF}, mutable.ListBuffer 
import collection.{generic, mutable} 
import generic.{CanBuildFrom=>CBF} 
import mutable.ListBuffer 

scala> implicit def `as list`: CBF[Iterable[_], Int, List[Int]] = 
    |  new CBF[Iterable[_], Int, List[Int]] { 
    |  def apply() = new ListBuffer[Int] 
    |  def apply(from: Iterable[_]) = apply() 
    |  } 
as$u0020list: scala.collection.generic.CanBuildFrom[Iterable[_],Int,List[Int]] 

scala> k.map(m) 
res0: List[Int] = List(1, 1) 

이 완성 유형을 표시 할 수 있습니다 추가 가치 2.11.8의 같이

scala> k.map(m) //print<tab> 

$line4.$read.$iw.$iw.k.map[Int, Iterable[Int]]($line3.$read.$iw.$iw.m)(scala.collection.Iterable.canBuildFrom[Int]) // : Iterable[Int] 

breakOut 사용 :

scala> k.map(m)(collection.breakOut) 
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 1) 

scala> k.map(m)(collection.breakOut) //print 

$line4.$read.$iw.$iw.k.map[Int, scala.collection.immutable.IndexedSeq[Int]]($line3.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Any, Int, scala.collection.immutable.IndexedSeq[Int]](scala.Predef.fallbackStringCanBuildFrom[Int])) // : scala.collection.immutable.IndexedSeq[Int] 

그림과 같이 실제로는 CanBuildFrom int를 선택합니다. 비교

scala> "abc".map(_ + 1) 
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(98, 99, 100) 

scala> "abc".map(_ + 1) //print 

scala.Predef.augmentString("abc").map[Int, scala.collection.immutable.IndexedSeq[Int]](((x$1: Char) => x$1.+(1)))(scala.Predef.fallbackStringCanBuildFrom[Int]) // : scala.collection.immutable.IndexedSeq[Int] 

: 같은 작업 종료

scala> k.map(m)(collection.breakOut) : List[Int] //print 

(($line6.$read.$iw.$iw.k.map[Int, List[Int]]($line5.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Iterable[String], Int, List[Int]](scala.collection.immutable.List.canBuildFrom[Int]))): scala.`package`.List[scala.Int]) // : List[Int] 

canonical Q&A on breakOut.

+0

타입을 소스에 묶는 것을 오버라이드하는 공통적 인 메커니즘이'scala.collection.breakOut'를 사용하는 것 또한 가치가 있습니다. 그래서'foo.keys.map (foo) (collection.breakOut)'는'Vector (1, 1) : scala.collection.immutable.IndexedSeq [Int]'가 될 것입니다. 이것은 또한 결과로부터 타입 추론을 허용하기 때문에,'val :: List [Int] = foo.keys.map (foo) (collection.breakOut)'는 런타임'List (1, 1)'이 될 것이다. – Kolmar

+0

커뮤니티 답장 옵션이 있지만 키 입력을 알지 못합니다. 지금 서둘러야한다. –

5

foo.keys은 반환 유형이 더 일반 임에도 불구하고 Set을 반환하고 Set의 발신자지도는 또 다른 Set을 생성합니다. 추측 또는 컴파일 시간 유형이 항상 가장 정확한 것은 아닙니다.

당신은 Setkeys 방법은 반환 유형Iterable[A]Set에도 불구하고 반환 볼 수 있습니다

scala> Map(1 -> 2).keys 
res0: Iterable[Int] = Set(1) 
+0

이런 경우라면 당황하지 않을 것입니다. 그러나 사실 : foo.keySet은 Set을 반환합니다. foo.keys는 Iterable을 반환합니다. – Chris

+2

@Chris'Set'은'Iterable'의 서브 클래스이므로'Map.keys'는'keySet'을 호출하고'Set'을'Iterable'로 리턴합니다 : https://github.com/scala/scala/blob /v2.11.8/src/library/scala/collection/MapLike.scala#L192 – Kolmar

+1

'set.map' 결과가 왜 나오는지 아직도 알지 못했습니다. OP와 마찬가지로이 메커니즘이 정적 유형으로 인코딩 된 것으로 생각했습니다. –

0

이 까다로운 암시 마법이다. 간략한 답변 : 암시 적 범위로 전달되는 CanBuildFrom 값이 있습니다. 컴파일러가 가장 일반적인 유형을 검색 할 때 인수 범위에서 함축적 인 내용을 찾습니다.

예에서 컴파일러는 foo.keys에 대한 가장 일반적인 유형이 Set임을 알 수 있습니다. 이것은 합리적으로 들립니다. Set는 값이없는 Map으로 볼 수 있습니다 (java의 HashMap/HashSet에서도 마찬가지입니다). iterable로 변환하면 implicits가 손실되고 Set이 사라집니다 (보조 노트로 CanBuildFrom 해킹은 강력하지 않으며 향후 기존 컬렉션이 확장되는 것을 복잡하게하기 때문에 나중에 사라질 수도 있습니다. this 대답을 읽을 수도 있습니다. 및 의견).

스칼라는 Java의 "de-jure and de facto types"개념을 공유합니다. "de-jure"는 메소드 정의에서 선언 된 것이지만 "사실상"은 상속자 중 하나 일 수 있습니다. 따라서 예를 들어 Map.keys 유형을 Iterable으로 표시하면 실제로는 Set (Map.keySet에서 생성되며 Set de-jure 유형이 있음)입니다.

마지막으로 foo의 모든 값이 동일하고 Set(1,1)Set(1)이되기 때문에 첫 번째 println의 마지막 행은 입니다.

+4

이 답변은 약간 잘못되었습니다. 암시적인 마술이 아닙니다. 일반적인 OOP 다형성 마술처럼. 컴파일러는'foo.keys'가'Set'이라는 것을 알 수 없습니다. 그것은'Iterable' :'Iterable.canBuildFrom'에서'CanBuildFrom'을 사용하지만,'canBuildFrom'은 실제 콜렉션 객체에서'genericBuilder'를 호출합니다. 그리고 런타임에서'Set'이기 때문에' Set.newBuilder '가 사용되고 있습니다. – Kolmar

+1

또한 CBF를 없애려는 계획은 없다고 생각합니다. 링크 된 답변은 해당 소유권 주장을 지원하지 않습니다. 기본적으로 문서에 숨겨져 있어야하며, 이미 오랫동안 구현되어 있습니다. – Kolmar

+0

@Kolmar "no CBF"에 대한 나의 추측은 새로운 콜렉션의 제안에 대한 최근 토론을 기반으로합니다. 그것이 "사라질"수있는 이유입니다. – dveim

관련 문제