สกาล่า 2.8 breakOut


225

ใน Scala 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 

โปรดทราบว่ามันมีสองพารามิเตอร์ ที่แรกก็คือฟังก์ชั่นของคุณและที่สองคือความหมายโดยนัย หากคุณไม่ได้ระบุโดยนัย Scala จะเลือกเฉพาะที่มีอยู่มากที่สุด

เกี่ยวกับ breakOut

ดังนั้นวัตถุประสงค์ของbreakOutอะไร ลองพิจารณาตัวอย่างที่ให้มาสำหรับคำถามคุณนำรายการสตริงเปลี่ยนสตริงแต่ละตัวเป็น tuple (Int, String)จากนั้นสร้างสตริงMapขึ้นมา วิธีที่ชัดเจนที่สุดในการทำเช่นนั้นจะสร้างList[(Int, String)]คอลเลกชันตัวกลางและจากนั้นแปลง

เนื่องจากการmapใช้ a 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เป็นชั้นล่าง (เช่นมันเป็น subclass ของทุกอย่าง) ซึ่งหมายถึงการใด ๆNothingในชั้นเรียนสามารถนำมาใช้ในสถานที่ของ

เนื่องจากผู้สร้างดังกล่าวมีอยู่ Scala สามารถใช้มันเพื่อสร้างผลลัพธ์ที่ต้องการ

เกี่ยวกับผู้สร้าง

วิธีการมากมายจากไลบรารีของคอลเลกชันสกาล่าประกอบด้วยการรวบรวมคอลเลกชันดั้งเดิมประมวลผลอย่างใด (ในกรณีของการmapเปลี่ยนองค์ประกอบแต่ละส่วน) และเก็บผลลัพธ์ในคอลเลกชันใหม่

เพื่อเพิ่มการใช้งานซ้ำของรหัสได้มากที่สุดการจัดเก็บผลลัพธ์นี้กระทำผ่านตัวสร้าง ( scala.collection.mutable.Builder) ซึ่งโดยทั่วไปรองรับการทำงานสองอย่าง: การต่อท้ายองค์ประกอบและการส่งคืนการรวบรวมผลลัพธ์ ประเภทของการรวบรวมผลลัพธ์นี้จะขึ้นอยู่กับประเภทของผู้สร้าง ดังนั้นการListสร้างจะกลับListเป็นMapผู้สร้างจะกลับMapและอื่น ๆ การใช้mapวิธีนี้ไม่จำเป็นต้องเกี่ยวข้องกับประเภทของผลลัพธ์: ตัวสร้างจะดูแลมัน

ในทางตรงกันข้ามนั่นหมายความว่าmapจำเป็นต้องได้รับการสร้างนี้อย่างใด ปัญหาที่ต้องเผชิญเมื่อออกแบบ Scala 2.8 กลุ่มคือวิธีการเลือกผู้สร้างที่ดีที่สุด ตัวอย่างเช่นถ้าฉันจะเขียนMap('a' -> 1).map(_.swap)ฉันต้องการMap(1 -> 'a')กลับไป ในทางกลับกัน a Map('a' -> 1).map(_._1)ไม่สามารถส่งคืน a Map(จะส่งคืน an Iterable)

ความมหัศจรรย์ของการผลิตที่ดีที่สุดBuilderจากประเภทที่รู้จักกันในการแสดงออกจะดำเนินการผ่านทางนี้CanBuildFromโดยปริยาย

เกี่ยวกับ CanBuildFrom

ที่ดีกว่าการอธิบายสิ่งที่เกิดขึ้นผมจะยกตัวอย่างที่คอลเลกชันที่ถูกแมปเป็นผู้มีแทนMap ListฉันจะกลับไปListทีหลัง สำหรับตอนนี้ให้ลองพิจารณานิพจน์ทั้งสองนี้:

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

ผลตอบแทนที่แรกและผลตอบแทนที่สองMap ความมหัศจรรย์ของการกลับมาคอลเลกชันที่เหมาะสมคือการทำงานของIterable CanBuildFromลองพิจารณานิยามของmapอีกครั้งเพื่อทำความเข้าใจ

วิธีการที่จะรับมาจากmap TraversableLikeมันเป็นพารามิเตอร์ในBและThatและใช้ประโยชน์จากพารามิเตอร์ประเภทAและReprซึ่งแปรสภาพชั้นเรียน ลองดูคำจำกัดความทั้งสองร่วมกัน:

คลาส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

หากคุณผ่านพารามิเตอร์ประเภทของMap[Int, String]ห่วงโซ่ทั้งหมดเราจะพบว่าประเภทที่ส่งผ่านไปTraversableLikeและดังนั้นจึงถูกใช้โดยmap:

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

จะกลับไปตัวอย่างแผนที่แรกที่ได้รับการทำงานของประเภทและแผนที่ที่สองได้รับการทำงานของประเภท((Int, String)) => (Int, Int) ((Int, String)) => Stringฉันใช้เครื่องหมายวงเล็บคู่เพื่อเน้นว่าเป็นสิ่งอันดับที่ได้รับเนื่องจากเป็นประเภทAที่เราเห็น

ด้วยข้อมูลนั้นลองพิจารณาประเภทอื่น ๆ

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

map (_._2):
B = String

เราจะเห็นว่าประเภทที่ส่งกลับโดยครั้งแรกmapเป็นและที่สองคือMap[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ในกรณีที่สองเป็นครั้งแรกที่ไม่ตรงกับมันเลือกที่สอง

กลับไปที่คำถาม

ลองดูรหัสสำหรับคำถามที่ว่าList'และmap' s นิยาม (อีกครั้ง) เพื่อดูว่าประเภทที่มีการอ้างถึง:

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]

ซึ่งหมายความว่าจะต้องจำเป็นต้องกลับมาเป็นประเภทหรือชนิดย่อยของbreakOutCanBuildFrom[List[String], (Int, String), Map[Int, String]]


61
ดาเนียลฉันสามารถคลานตามประเภทของคำตอบของคุณได้ แต่เมื่อฉันไปถึงจุดสิ้นสุดฉันรู้สึกว่าฉันไม่ได้รับความเข้าใจระดับสูง อะไรคือการฝ่าวงล้อม? ชื่อ "breakOut" มาจากไหน (สิ่งที่ฉันแตกออก) เหตุใดจึงจำเป็นในกรณีนี้เพื่อให้สามารถใช้งานแผนที่ได้ แท้จริงก็คือมีบางวิธีที่จะตอบสั้น ๆ คำถามเหล่านี้หรือไม่ (แม้ว่าจะต้องมีการ
เซาะร่อง

3
@Seth นั่นเป็นข้อกังวลที่ถูกต้อง แต่ฉันไม่แน่ใจว่าฉันทำอะไรอยู่ ต้นกำเนิดของนี้สามารถพบได้ในที่นี่: article.gmane.org/gmane.comp.lang.scala.internals/1812/... ฉันจะคิดเกี่ยวกับมัน แต่ตอนนี้ฉันไม่สามารถคิดวิธีปรับปรุงมาก
Daniel C. Sobral

2
มีวิธีใดที่จะหลีกเลี่ยงการระบุประเภทผลลัพธ์ทั้งหมดของแผนที่ [Int, String] และสามารถเขียนดังนี้: 'val map = List ("London", "Paris") map (x => (x ความยาว, x)) (breakOut [... Map]) '
มัน

9
@SethTisue จากการอ่านคำอธิบายนี้ของฉันดูเหมือนว่า breakOut จำเป็นต้อง "แยก" ตามข้อกำหนดที่ผู้สร้างของคุณต้องการสร้างจาก List [String] คอมไพเลอร์ต้องการ CanBuildFrom [List [String], (Int, String), Map [Int, String]] ซึ่งคุณไม่สามารถให้ได้ ฟังก์ชั่น breakOut ทำสิ่งนี้โดยการปิดกั้นพารามิเตอร์ประเภทแรกใน CanBuildFrom โดยการตั้งค่าเป็น Nothing ตอนนี้คุณเพียงแค่ให้ CanBuildFrom [ไม่มีอะไร (Int, String), Map [Int, String]] นี่เป็นเรื่องง่ายเพราะจัดทำโดยคลาสแผนที่
ทำเครื่องหมาย

2
@ Mark เมื่อฉันพบ breakOut ปัญหาที่ฉันเห็นมันคือวิธีที่ monads ยืนยันในการทำแผนที่ (ผ่าน bind / flatMap) ตามประเภทของตัวเอง อนุญาตให้หนึ่ง "แบ่งออก" ของเชนการแม็พโดยใช้หนึ่ง monad เป็นชนิด monad อื่น ฉันไม่รู้ว่านั่นเป็นสิ่งที่ Adriaan Moors (ผู้เขียน) คิดเกี่ยวกับมันหรือไม่!
Ed Staub

86

ฉันต้องการสร้างคำตอบของแดเนียล มันเป็นอย่างละเอียดมาก แต่ตามที่ระบุไว้ในความคิดเห็นมันไม่ได้อธิบายสิ่งที่เกิดการฝ่าวงล้อม

นำมาจากRe: การสนับสนุนสำหรับ Builders อย่างชัดเจน (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 นั้นยอดเยี่ยมมากและควรอ่านร่วมกับArchitecture of Scala Collections (บทที่ 25 ของการเขียนโปรแกรมใน Scala)

ฉันแค่ต้องการอธิบายอย่างละเอียดถึงสาเหตุที่เรียกว่า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 เพื่อให้พารามิเตอร์ implicit:

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]]ในขณะที่คอมไพเลอร์สามารถหาโรงงานสร้างประเภทโดยปริยายCanBuildFrom[Map[_, _], (A, B), Map[A, B]]แทนCanBuildFrom[Nothing, T, To]สำหรับ breakOut เพื่อใช้ในการสร้างตัวสร้างที่แท้จริง

โปรดทราบว่า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จะไม่ให้ค่าซ้ำในขณะที่ได้รับการเก็บรักษาไว้สำหรับSet map
Matthew Pickering

@ MatthewPickering ถูกต้อง! set.map(_ % 2)สร้างครั้งแรกที่แล้วได้รับการแปลงเป็นSet(1, 0) Vector(1, 0)
fdietze
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.