การจับคู่รูปแบบใน Scala ถูกนำไปใช้ในระดับ bytecode อย่างไร?


123

การจับคู่รูปแบบใน Scala ถูกนำไปใช้ในระดับ bytecode อย่างไร?

มันเหมือนกับชุดของif (x instanceof Foo)โครงสร้างหรืออย่างอื่น? ผลกระทบด้านประสิทธิภาพคืออะไร?

ตัวอย่างเช่นเมื่อได้รับรหัสต่อไปนี้ (จากหน้าScala By Example 46-48) โค้ด Java ที่เทียบเท่าสำหรับevalวิธีการจะเป็นอย่างไร

abstract class Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

def eval(e: Expr): Int = e match {
  case Number(x) => x
  case Sum(l, r) => eval(l) + eval(r)
}

ป.ล. ฉันสามารถอ่าน Java bytecode ได้ดังนั้นการแสดง bytecode จะดีพอสำหรับฉัน แต่อาจจะดีกว่าสำหรับผู้อ่านคนอื่น ๆ ที่จะรู้ว่ามันจะเป็นอย่างไรเมื่อเป็นรหัส Java

PPS หนังสือการเขียนโปรแกรมใน Scalaให้คำตอบสำหรับคำถามนี้และคำถามที่คล้ายกันเกี่ยวกับการใช้งาน Scala หรือไม่? ฉันสั่งหนังสือไปแล้วแต่ยังไม่มา


ทำไมคุณไม่รวบรวมตัวอย่างและแยกชิ้นส่วนด้วย Java bytecode disassembler?
Zifre

ฉันอาจจะทำเช่นนั้นเว้นแต่จะมีใครให้คำตอบที่ดีก่อน แต่ตอนนี้ฉันอยากนอนบ้าง ;)
Esko Luontola

27
คำถามนี้มีประโยชน์กับผู้อ่านคนอื่น ๆ !
djondal

1
@djondal: วิธีที่ดีที่สุดในการบอกว่าเป็นเพียงการ
โหวต

คำตอบ:


96

ระดับต่ำสามารถสำรวจได้ด้วยตัวถอดแยกชิ้นส่วน แต่คำตอบสั้น ๆ คือมันเป็นกลุ่มของ if / elses ที่เพรดิเคตขึ้นอยู่กับรูปแบบ

case Sum(l,r) // instance of check followed by fetching the two arguments and assigning to two variables l and r but see below about custom extractors 
case "hello" // equality check
case _ : Foo // instance of check
case x => // assignment to a fresh variable
case _ => // do nothing, this is the tail else on the if/else

มีอะไรอีกมากมายที่คุณสามารถทำได้ด้วยรูปแบบเช่นรูปแบบและชุดค่าผสมเช่น "case Foo (45, x)" แต่โดยทั่วไปแล้วสิ่งเหล่านี้เป็นเพียงส่วนขยายเชิงตรรกะของสิ่งที่ฉันเพิ่งอธิบายไป รูปแบบยังสามารถมียามซึ่งเป็นข้อ จำกัด เพิ่มเติมเกี่ยวกับเพรดิเคต นอกจากนี้ยังมีบางกรณีที่คอมไพลเลอร์สามารถปรับแต่งการจับคู่รูปแบบให้เหมาะสมเช่นเมื่อมีการทับซ้อนกันระหว่างเคสมันอาจรวมเข้าด้วยกันเล็กน้อย รูปแบบขั้นสูงและการปรับให้เหมาะสมเป็นส่วนที่ใช้งานอยู่ในคอมไพเลอร์ดังนั้นอย่าแปลกใจถ้าโค้ดไบต์ดีขึ้นอย่างมากจากกฎพื้นฐานเหล่านี้ในเวอร์ชันปัจจุบันและอนาคตของ Scala

นอกจากนั้นคุณสามารถเขียนตัวแยกที่กำหนดเองของคุณเองนอกเหนือจากหรือแทนค่าเริ่มต้นที่ Scala ใช้สำหรับคลาสเคส หากคุณทำเช่นนั้นค่าใช้จ่ายของการจับคู่รูปแบบจะเป็นต้นทุนของสิ่งที่ตัวแยกทำ ภาพรวมที่ดีพบได้ในhttp://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf


ฉันเชื่อว่านี่คือลิงค์ปัจจุบัน: infoscience.epfl.ch/record/98468/files/…
greenoldman

78

เจมส์ (ด้านบน) พูดได้ดีที่สุด อย่างไรก็ตามหากคุณอยากรู้อยากเห็นคุณควรดูไบต์โค้ดที่ถอดประกอบเป็นแบบฝึกหัดที่ดีเสมอ นอกจากนี้คุณยังสามารถเรียกใช้scalacกับ-printตัวเลือกซึ่งจะพิมพ์โปรแกรมของคุณกับทุก Scala เฉพาะคุณสมบัติลบออก โดยพื้นฐานแล้วมันคือ Java ในเสื้อผ้าของ Scala นี่คือscalac -printผลลัพธ์ที่เกี่ยวข้องสำหรับข้อมูลโค้ดที่คุณให้:

def eval(e: Expr): Int = {
  <synthetic> val temp10: Expr = e;
  if (temp10.$isInstanceOf[Number]())
    temp10.$asInstanceOf[Number]().n()
  else
    if (temp10.$isInstanceOf[Sum]())
      {
        <synthetic> val temp13: Sum = temp10.$asInstanceOf[Sum]();
        Main.this.eval(temp13.e1()).+(Main.this.eval(temp13.e2()))
      }
    else
      throw new MatchError(temp10)
};

34

ตั้งแต่เวอร์ชัน 2.8 เป็นต้นมา Scala มีคำอธิบายประกอบ@switch เป้าหมายคือเพื่อให้แน่ใจว่าการจับคู่รูปแบบจะถูกคอมไพล์เป็นtablewitch หรือ lookupswitchแทนชุดifคำสั่งเงื่อนไข


6
เมื่อใดควรเลือก @switch มากกว่าปกติถ้าเป็นอย่างอื่น
Aravind Yarram

2
การใช้งาน@switchมีประสิทธิภาพมากกว่าการจับคู่รูปแบบปกติ ดังนั้นหากทุกกรณีมีค่าคงที่คุณควรใช้เสมอ@switch(เนื่องจากการใช้งาน bytecode จะเหมือนกับ java switchแทนที่จะเป็น if-else จำนวนมาก)
เลฟ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.