스칼라 2.8 탈주


225

스칼라 2.8 에는 다음과 같은 객체가 있습니다 scala.collection.package.scala.

def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
    new CanBuildFrom[From, T, To] {
        def apply(from: From) = b.apply() ; def apply() = b.apply()
 }

이 결과는 다음과 같습니다.

> import scala.collection.breakOut
> val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)

map: Map[Int,String] = Map(6 -> London, 5 -> Paris)

무슨 일이야? 왜 내 주장breakOut 에 부름 을 받는가List 있습니까?


13
사소한 응답 존재, 그것은에 인수되지 않습니다 List,하지만에 map.
Daniel C. Sobral

답변:


325

대답은 다음의 정의에 있습니다 map.

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

여기에는 두 개의 매개 변수가 있습니다. 첫 번째는 함수이고 두 번째는 암시 적입니다. 암시 적으로 제공하지 않으면 스칼라는 사용 가능한 가장 구체적인 것을 선택합니다 .

breakOut

그래서, 목적은 breakOut무엇입니까? 질문에 주어진 예를 고려하십시오. 문자열 목록을 가져 와서 각 문자열을 튜플로 변환 (Int, String)한 다음 그중 하나를 생성 Map하십시오. 가장 확실한 방법은 중개자 List[(Int, String)]컬렉션을 생성 한 다음 변환하는 것입니다.

점을 감안 map사용하는이 Builder결과 수집을 생산하기 위해서는 중간을 건너 뛸 수 없을 것 List과에 직접 결과를 수집 Map? 분명히 그렇습니다. 그러나 그렇게하려면에 적절한 값 CanBuildFrom을 전달해야합니다 . map이것이 바로 그 일 breakOut입니다.

다음의 정의를 살펴 보자 breakOut.

def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
  new CanBuildFrom[From, T, To] {
    def apply(from: From) = b.apply() ; def apply() = b.apply()
  }

참고 breakOut파라미터, 그리고의 인스턴스를 반환한다 CanBuildFrom. 공교롭게도, 종류는 From, T그리고 To우리가 알고 있기 때문에 이미 추정 된 map기대하고있다 CanBuildFrom[List[String], (Int, String), Map[Int, String]]. 따라서:

From = List[String]
T = (Int, String)
To = Map[Int, String]

결론적으로 breakOut자체적으로 받은 암시를 살펴 보자 . 유형 CanBuildFrom[Nothing,T,To]입니다. 우리는 이미 이러한 유형을 모두 알고 있으므로 암시 적 유형이 필요하다고 판단 할 수 있습니다.CanBuildFrom[Nothing,(Int,String),Map[Int,String]] . 그러나 그러한 정의가 있습니까?

CanBuildFrom의 정의를 보자 .

trait CanBuildFrom[-From, -Elem, +To] 
extends AnyRef

따라서 CanBuildFrom첫 번째 유형 매개 변수에는 모순이 있습니다. Nothing하위 클래스 이므로 (즉, 모든 하위 클래스이므로) 모든 클래스를 대신 사용할 수 있습니다 Nothing.

그러한 빌더가 존재하므로 Scala는이를 사용하여 원하는 출력을 생성 할 수 있습니다.

건축업자에 관하여

스칼라 컬렉션 라이브러리의 많은 메소드는 원래 컬렉션을 가져 와서 어떻게 든 처리하고 ( map각 요소를 변환 하는 경우 ) 결과를 새 컬렉션에 저장하는 것으로 구성됩니다.

코드 재사용을 최대화하기 위해이 결과 저장은 기본적으로 요소 추가 및 결과 콜렉션 리턴의 두 가지 조작을 지원 하는 빌더 ( scala.collection.mutable.Builder)를 통해 수행됩니다 . 이 결과 콜렉션의 유형은 빌더 유형에 따라 다릅니다. 따라서 List빌더는을 리턴 List하고 Map빌더는을 리턴합니다 Map. map메소드 구현은 결과 유형과 관련이있을 필요가 없습니다. 빌더가이를 처리합니다.

반면에, map이 빌더를 어떻게 든 받아야합니다. Scala 2.8 Collection을 디자인 할 때 직면 한 문제는 가능한 최고의 빌더를 선택하는 방법이었습니다. 예를 Map('a' -> 1).map(_.swap)들어을 쓰면 Map(1 -> 'a')답장 을 받고 싶습니다 . 반면에는 a Map('a' -> 1).map(_._1)를 반환 할 수 없습니다 Map(을 반환합니다 Iterable).

Builder알려진 유형의 표현에서 최상의 결과를 만들어내는 마술은 이 CanBuildFrom암시 적 방법을 통해 수행됩니다 .

CanBuildFrom

무슨 일이 일어나고 있는지 더 잘 설명하기 위해 매핑되는 컬렉션이 Map대신에 예제 가 List있습니다. List나중에 다시 돌아가겠습니다 . 지금은 다음 두 가지 표현을 고려하십시오.

Map(1 -> "one", 2 -> "two") map Function.tupled(_ -> _.length)
Map(1 -> "one", 2 -> "two") map (_._2)

첫 번째는 a를 반환하고 두 번째는 a Map를 반환합니다 Iterable. 피팅 컬렉션을 반환하는 마술은의 작품입니다 CanBuildFrom. map그것을 이해하기 위해 다시 정의를 고려해 봅시다 .

이 메소드 map는에서 상속됩니다 TraversableLike. 그것은 나타내는 파라미터 BThat, 그리고 차종은 형식 매개 변수의 사용 ARepr클래스를 파라미터. 두 정의를 함께 봅시다 :

클래스 TraversableLike는 다음과 같이 정의됩니다.

trait TraversableLike[+A, +Repr] 
extends HasNewBuilder[A, Repr] with AnyRef

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

어디 A에서 Repr왔는지 이해하기 위해 Map자체 정의를 고려해 보겠습니다 .

trait Map[A, +B] 
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]

때문에이 TraversableLike확장하는 모든 특성을 상속 Map, A그리고 Repr그 중 하나에서 상속 할 수있다. 그러나 마지막 것은 선호도를 얻습니다. 따라서 불변의 정의 Map와 그것을 연결하는 모든 특성 에 따라 다음과 같은 결과를 얻 습니다 TraversableLike.

trait Map[A, +B] 
extends Iterable[(A, B)] with Map[A, B] with MapLike[A, B, Map[A, B]]

trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] 
extends MapLike[A, B, This]

trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] 
extends PartialFunction[A, B] with IterableLike[(A, B), This] with Subtractable[A, This]

trait IterableLike[+A, +Repr] 
extends Equals with TraversableLike[A, Repr]

trait TraversableLike[+A, +Repr] 
extends HasNewBuilder[A, Repr] with AnyRef

당신의 type 매개 변수를 전달하면 Map[Int, String]체인 아래로 모두에게 방법을, 우리는 유형에 전달 된 것을 발견 TraversableLike하고, 따라서에서 사용 map이다 :

A = (Int,String)
Repr = Map[Int, String]

예제로 돌아가서 첫 번째 맵은 type 함수를 수신 ((Int, String)) => (Int, Int)하고 두 번째 맵은 type 함수를 수신합니다 ((Int, String)) => String. 이중 괄호를 사용하여 튜플이 수신되고 있음을 강조했습니다 A.

그 정보로 다른 유형을 고려해 봅시다.

map Function.tupled(_ -> _.length):
B = (Int, Int)

map (_._2):
B = String

첫 번째에서 반환 된 유형 mapMap[Int,Int]이고 두 번째는 Iterable[String]입니다. 보고 map의 정의, 이들의 값이라는 것을 쉽게 알 수있다 That. 그러나 그들은 어디에서 왔습니까?

관련된 클래스의 동반 객체를 살펴보면 암시 적 선언을 제공합니다. 대상 Map:

implicit def  canBuildFrom [A, B] : CanBuildFrom[Map, (A, B), Map[A, B]]  

그리고 객체 Iterable의 클래스는 다음과 같이 확장됩니다 Map.

implicit def  canBuildFrom [A] : CanBuildFrom[Iterable, A, Iterable[A]]  

이러한 정의는 매개 변수화 된 팩토리를 제공합니다 CanBuildFrom.

스칼라는 가능한 가장 구체적인 암시 적을 선택합니다. 첫 번째 경우는 첫 번째 CanBuildFrom였습니다. 두 번째 경우 첫 번째가 일치하지 않으므로 두 번째를 선택했습니다 CanBuildFrom.

질문으로 돌아 가기

유형의 유추 방법을 확인 하기 위해 질문 Listmap의 정의에 대한 코드를 다시 살펴 보겠습니다.

val map : Map[Int,String] = List("London", "Paris").map(x => (x.length, x))(breakOut)

sealed abstract class List[+A] 
extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]]

trait LinearSeqLike[+A, +Repr <: LinearSeqLike[A, Repr]] 
extends SeqLike[A, Repr]

trait SeqLike[+A, +Repr] 
extends IterableLike[A, Repr]

trait IterableLike[+A, +Repr] 
extends Equals with TraversableLike[A, Repr]

trait TraversableLike[+A, +Repr] 
extends HasNewBuilder[A, Repr] with AnyRef

def map[B, That](f : (A) => B)(implicit bf : CanBuildFrom[Repr, B, That]) : That 

의 유형 List("London", "Paris")입니다 List[String]종류 때문에, A그리고 Repr에 대한 정의는 TraversableLike다음과 같습니다

A = String
Repr = List[String]

의 유형 (x => (x.length, x))(String) => (Int, String)이므로 유형 B은 다음과 같습니다.

B = (Int, String)

마지막으로 알려지지 않은 유형 That은의 결과 유형이며 map이미 그 유형도 있습니다.

val map : Map[Int,String] =

그래서,

That = Map[Int, String]

breakOut, 유형 또는 하위 유형을 반드시 반환해야합니다 CanBuildFrom[List[String], (Int, String), Map[Int, String]].


61
다니엘, 나는 당신의 대답에서 유형을 꼼짝 못하게 할 수 있지만 일단 끝내면 나는 높은 수준의 이해를 얻지 못한 것처럼 느낍니다. 무엇 입니다 브레이크 아웃은? "breakOut"이라는 이름은 어디에서 유래합니까? 이 경우지도를 꺼내기 위해 왜 필요한가요? 확실히이있다 몇몇 이러한 질문에 간단히 대답 방법은? (세부 사항을 파악하기 위해 긴 유형의
그루 빙

3
@Seth 그것은 유효한 관심사이지만, 내가 과제에 달려 있는지 확실하지 않습니다. 이것의 기원은 here. article.gmane.org/gmane.comp.lang.scala.internals/1812/… 에서 찾을 수 있습니다 . 나는 그것에 대해 생각할 것이지만, 지금은 그것을 향상시킬 많은 방법을 생각할 수 없습니다.
Daniel C. Sobral

2
Map [Int, String]의 전체 결과 유형을 지정하지 않고 대신 'val map = List ( "London", "Paris"). map (x => (x. length, x)) (breakOut [... Map]) '
IttayD

9
@SethTisue이 설명을 읽은 결과, 빌더가 List [String]에서 빌드해야하는 요구 사항을 "해제"하려면 breakOut이 필요합니다. 컴파일러는 제공 할 수없는 CanBuildFrom [List [String], (Int, String), Map [Int, String]]을 원합니다. breakOut 함수는 CanBuildFrom의 첫 번째 유형 매개 변수를 Nothing으로 설정하여 클로버하여이를 수행합니다. 이제 CanBuildFrom [Nothing, (Int, String), Map [Int, String]] 만 제공하면됩니다. Map 클래스에서 제공하기 때문에 쉽습니다.
Mark

2
@Mark breakOut을 발견했을 때 문제가 해결되는 문제는 모나드가 (bind / flatMap을 통해) 자신의 유형으로 매핑을 주장하는 방식이었습니다. 하나의 모나드를 사용하여 다른 모나드 유형으로 매핑 체인을 "분리"할 수 있습니다. 그래도 그것이 아드리안 무 어스 (저자)가 그것에 대해 어떻게 생각하고 있었는지 전혀 모른다!
Ed Staub

86

다니엘의 답을 바탕으로하고 싶습니다. 그것은 매우 철저했지만 주석에서 언급했듯이 브레이크 아웃이 무엇을하는지 설명하지 않습니다.

에서 촬영 재 : 명시 적 빌더에 대한 지원 (2009-10-23)는, 여기에 내가 브레이크 아웃이하는 무엇을 믿는 것입니다 :

컴파일러가 어떤 빌더를 내재적으로 선택할지에 대한 제안을 제공합니다 (본질적으로 컴파일러는 상황에 가장 적합한 팩토리를 선택할 수 있습니다).

예를 들어 다음을 참조하십시오.

scala> import scala.collection.generic._
import scala.collection.generic._

scala> import scala.collection._
import scala.collection._

scala> import scala.collection.mutable._
import scala.collection.mutable._

scala>

scala> def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
     |    new CanBuildFrom[From, T, To] {
     |       def apply(from: From) = b.apply() ; def apply() = b.apply()
     |    }
breakOut: [From, T, To]
     |    (implicit b: scala.collection.generic.CanBuildFrom[Nothing,T,To])
     |    java.lang.Object with
     |    scala.collection.generic.CanBuildFrom[From,T,To]

scala> val l = List(1, 2, 3)
l: List[Int] = List(1, 2, 3)

scala> val imp = l.map(_ + 1)(breakOut)
imp: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4)

scala> val arr: Array[Int] = l.map(_ + 1)(breakOut)
imp: Array[Int] = Array(2, 3, 4)

scala> val stream: Stream[Int] = l.map(_ + 1)(breakOut)
stream: Stream[Int] = Stream(2, ?)

scala> val seq: Seq[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Seq[Int] = ArrayBuffer(2, 3, 4)

scala> val set: Set[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.Set[Int] = Set(2, 4, 3)

scala> val hashSet: HashSet[Int] = l.map(_ + 1)(breakOut)
seq: scala.collection.mutable.HashSet[Int] = Set(2, 4, 3)

컴파일러가 반환 형식이 암시 적으로 선택되어 예상 형식과 가장 일치하는 것을 볼 수 있습니다. 수신 변수를 선언하는 방법에 따라 다른 결과가 나타납니다.

다음은 빌더를 지정하는 동등한 방법입니다. 이 경우 컴파일러는 빌더 유형에 따라 예상 유형을 유추합니다.

scala> def buildWith[From, T, To](b : Builder[T, To]) =
     |    new CanBuildFrom[From, T, To] {
     |      def apply(from: From) = b ; def apply() = b
     |    }
buildWith: [From, T, To]
     |    (b: scala.collection.mutable.Builder[T,To])
     |    java.lang.Object with
     |    scala.collection.generic.CanBuildFrom[From,T,To]

scala> val a = l.map(_ + 1)(buildWith(Array.newBuilder[Int]))
a: Array[Int] = Array(2, 3, 4)

1
왜 이름이 " breakOut" 인지 궁금합니다 . 나는 ( convert또는 buildADifferentTypeOfCollection더 짧은) 무언가 가 기억하기 쉬울 것이라고 생각하고 있습니다.
KajMagnus

8

Daniel Sobral의 답변은 훌륭하며 Scala Collections Architecture (Scala 의 프로그래밍 25 장) 와 함께 읽어야합니다 .

나는 그것이 왜 불리는 이유에 대해 자세히 설명하고 싶었습니다 breakOut.

왜 그렇게 불려 breakOut요?

우리 는 한 유형에서 다른 유형 으로 분리 하기를 원하기 때문에 :

어떤 유형에서 어떤 유형으로 분리합니까? 상기 살펴 수 있습니다 map에 기능을 Seq예를 들어 :

Seq.map[B, That](f: (A) -> B)(implicit bf: CanBuildFrom[Seq[A], B, That]): That

다음과 같은 시퀀스 요소에 대한 매핑에서 직접 맵을 작성하려는 경우 :

val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))

컴파일러는 다음과 같이 불평합니다.

error: type mismatch;
found   : Seq[(String, Int)]
required: Map[String,Int]

Seq가 다른 Seq을 빌드하는 방법 만 알고 있기 때문입니다 (즉 CanBuildFrom[Seq[_], B, Seq[B]], 사용 가능한 암시 적 빌더 팩토리가 있지만 Seq에서 Map으로 빌더 팩토리 는 없습니다 ).

컴파일하기 위해서는 우리는 어떤 breakOut유형의 요구 사항이 필요하며 map함수가 사용할 맵을 생성하는 빌더를 구성 할 수 있어야 합니다.

Daniel이 설명했듯이 breakOut에는 다음과 같은 서명이 있습니다.

def breakOut[From, T, To](implicit b: CanBuildFrom[Nothing, T, To]): CanBuildFrom[From, T, To] =
    // can't just return b because the argument to apply could be cast to From in b
    new CanBuildFrom[From, T, To] {
      def apply(from: From) = b.apply()
      def apply()           = b.apply()
    }

Nothing는 모든 클래스의 서브 클래스이므로 모든 빌더 팩토리를 대신 사용할 수 있습니다 implicit b: CanBuildFrom[Nothing, T, To]. breakOut 함수를 사용하여 암시 적 매개 변수를 제공 한 경우 :

val x: Map[String, Int] = Seq("A", "BB", "CCC").map(s => (s, s.length))(collection.breakOut)

컴파일러는 breakOut필요한 유형 의을 제공 할 수 있기 때문에 컴파일 CanBuildFrom[Seq[(String, Int)], (String, Int), Map[String, Int]]하는 동안 breakOut이 실제 빌더를 작성하는 데 사용할 CanBuildFrom[Map[_, _], (A, B), Map[A, B]]대신 암시 적 빌더 팩토리 유형 을 대신 CanBuildFrom[Nothing, T, To]찾을 수 있습니다.

참고 CanBuildFrom[Map[_, _], (A, B), Map[A, B]]지도에서 정의, 단순히 시작되고 MapBuilder기본이되는지도를 사용하는을.

이것이 문제를 해결하기를 바랍니다.


4

무엇을 이해하는 간단한 예 breakOut:

scala> import collection.breakOut
import collection.breakOut

scala> val set = Set(1, 2, 3, 4)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)

scala> set.map(_ % 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 0)

scala> val seq:Seq[Int] = set.map(_ % 2)(breakOut)
seq: Seq[Int] = Vector(1, 0, 1, 0) // map created a Seq[Int] instead of the default Set[Int]

예를 주셔서 감사합니다! 또한에 대해 유지 된 val seq:Seq[Int] = set.map(_ % 2).toVector대로 반복되는 값을 제공하지 않습니다 . Setmap
Matthew Pickering

@MatthewPickering이 정확합니다! set.map(_ % 2)생성 Set(1, 0)후 변환 도착하는 첫번째 Vector(1, 0).
fdietze
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.