เมื่อใดควรใช้ val หรือ def ในลักษณะ Scala?


90

ฉันกำลังดูสไลด์สกาลาที่มีประสิทธิภาพและกล่าวถึงในสไลด์ 10 ที่จะไม่ใช้valใน a traitสำหรับสมาชิกนามธรรมและใช้defแทน สไลด์ไม่ได้กล่าวถึงรายละเอียดว่าเหตุใดจึงใช้บทคัดย่อvalใน a traitจึงเป็นการต่อต้านรูปแบบ ฉันจะขอบคุณถ้ามีใครสามารถอธิบายแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับการใช้ val vs def ในลักษณะสำหรับวิธีนามธรรมได้

คำตอบ:


130

defสามารถดำเนินการโดยทั้งสองของdefการvalเป็นหรือlazy val objectดังนั้นจึงเป็นรูปแบบนามธรรมที่สุดในการกำหนดสมาชิก เนื่องจากลักษณะมักจะเป็นอินเทอร์เฟซนามธรรมการบอกว่าคุณต้องการvalคือการบอกว่าการใช้งานควรทำอย่างไร หากคุณขอ a valคลาสการนำไปใช้ไม่สามารถใช้def.

valเป็นสิ่งจำเป็นเฉพาะในกรณีที่คุณจำเป็นต้องมีรหัสคงที่เช่นสำหรับชนิดขึ้นอยู่กับเส้นทาง นั่นเป็นสิ่งที่คุณมักไม่ต้องการ


เปรียบเทียบ:

trait Foo { def bar: Int }

object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok

class F2(val bar: Int) extends Foo // ok

object F3 extends Foo {
  lazy val bar = { // ok
    Thread.sleep(5000)  // really heavy number crunching
    42
  }
}

ถ้าคุณมี

trait Foo { val bar: Int }

คุณจะไม่สามารถที่จะกำหนดหรือF1F3


โอเคเพื่อให้คุณสับสนและตอบ @ om-nom-nom การใช้นามธรรมvalอาจทำให้เกิดปัญหาในการเริ่มต้น

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

นี่เป็นปัญหาที่น่าเกลียดซึ่งในความเห็นส่วนตัวของฉันควรจะหายไปในเวอร์ชัน Scala ในอนาคตโดยแก้ไขในคอมไพเลอร์ แต่ใช่แล้วนี่เป็นเหตุผลว่าทำไมเราจึงไม่ควรใช้นามธรรมvals

แก้ไข (ม.ค. 2016): คุณได้รับอนุญาตให้ลบล้างการvalประกาศนามธรรมด้วยlazy valการนำไปใช้งานเพื่อป้องกันการเริ่มต้นล้มเหลว


8
คำเกี่ยวกับลำดับการเริ่มต้นที่ยุ่งยากและค่าว่างที่น่าแปลกใจ?
om-nom-nom

ใช่ ... ฉันจะไม่ไปที่นั่นด้วยซ้ำ จริงอยู่สิ่งเหล่านี้เป็นข้อโต้แย้งต่อวาล แต่ฉันคิดว่าแรงจูงใจพื้นฐานควรเป็นเพียงเพื่อซ่อนการนำไปใช้
0__

2
สิ่งนี้อาจมีการเปลี่ยนแปลงในเวอร์ชัน Scala ล่าสุด (2.11.4 ตามความคิดเห็นนี้) แต่คุณสามารถแทนที่ a valด้วยไฟล์lazy val. คำยืนยันของคุณว่าคุณจะไม่สามารถสร้างได้F3หากbarเป็นสิ่งที่valไม่ถูกต้อง ที่กล่าวว่าสมาชิกที่เป็นนามธรรมในลักษณะควรเป็นdef's
mplis

ฟู / ล้มเหลวเช่นการทำงานตามที่คาดว่าจะมีการเปลี่ยนด้วยval schoko = bar + bar lazy val schoko = bar + barนั่นเป็นวิธีหนึ่งในการควบคุมลำดับการเริ่มต้น นอกจากนี้การใช้lazy valแทนdefในคลาสที่ได้รับจะหลีกเลี่ยงการคำนวณซ้ำ
Adrian

2
ถ้าคุณเปลี่ยนval bar: Intไปdef bar: Int Fail.schokoยังคงเป็นศูนย์
Jasper-M

8

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

คุณควรคำนึงถึงทุกสิ่งเกี่ยวกับการใช้การประกาศวาลนี้ซึ่งจะนำคุณไปสู่ข้อผิดพลาดในที่สุด


อัปเดตด้วยตัวอย่างที่ซับซ้อนมากขึ้น

valแต่มีบางครั้งเมื่อคุณไม่สามารถหลีกเลี่ยงการใช้ ดังที่ @ 0__ ได้กล่าวไว้บางครั้งคุณต้องมีตัวระบุที่มั่นคงและdefไม่ใช่

ฉันจะยกตัวอย่างเพื่อแสดงให้เห็นว่าเขากำลังพูดถึงอะไร:

trait Holder {
  type Inner
  val init : Inner
}
class Access(val holder : Holder) {
  val access : holder.Inner =
    holder.init
}
trait Access2 {
  def holder : Holder
  def access : holder.Inner =
    holder.init
}

รหัสนี้ก่อให้เกิดข้อผิดพลาด:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

หากคุณใช้เวลาสักครู่เพื่อคิดว่าคุณจะเข้าใจว่าคอมไพเลอร์มีเหตุผลที่จะบ่น ในAccess2.accessกรณีที่ไม่สามารถรับประเภทผลตอบแทนด้วยวิธีใด ๆ def holderหมายความว่าสามารถนำไปใช้ในวงกว้างได้ มันสามารถส่งคืนผู้ถือที่แตกต่างกันสำหรับการโทรแต่ละครั้งและผู้ถือนั้นจะรวมInnerประเภทต่างๆไว้ด้วยกัน แต่เครื่องเสมือน Java คาดว่าจะส่งคืนประเภทเดียวกัน


3
ลำดับของการเริ่มต้นไม่สำคัญ แต่เรากลับได้รับ NPE ที่น่าแปลกใจในระหว่างรันไทม์รูปแบบการต่อต้าน vis-a-vis
Jonathan Neufeld

สกาล่ามีไวยากรณ์ที่เปิดเผยซึ่งซ่อนธรรมชาติที่จำเป็นไว้เบื้องหลัง บางครั้งความจำเป็นนั้นก็ใช้งานได้ง่าย
ayvango

-4

การใช้ def เสมอดูเหมือนจะอึดอัดเล็กน้อยเนื่องจากสิ่งนี้ใช้ไม่ได้:

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

คุณจะได้รับข้อผิดพลาดต่อไปนี้:

error: value id_= is not a member of Entity

2
ไม่เกี่ยวข้อง คุณมีข้อผิดพลาดเช่นกันหากคุณใช้ val แทน def (error: reassignment to val) และนั่นเป็นตรรกะที่สมบูรณ์แบบ
volia17

ไม่ใช่ถ้าคุณใช้var. ประเด็นคือถ้าเป็นเขตข้อมูลควรกำหนดให้เป็นเช่นนั้น ฉันแค่คิดว่ามีทุกอย่างเหมือนdefสายตาสั้น
Dimitry

@Dimitry แน่นอนโดยใช้varให้คุณทำลายการห่อหุ้ม แต่การใช้def(หรือ a val) เป็นที่ต้องการมากกว่าตัวแปรส่วนกลาง ฉันคิดว่าสิ่งที่คุณกำลังมองหานั้นเป็นสิ่งcase class ConcreteEntity(override val id: Int) extends Entityที่คุณสามารถสร้างได้จากdef create(e: Entity) = ConcreteEntity(1)สิ่งนี้ปลอดภัยกว่าการทำลายการห่อหุ้มและอนุญาตให้คลาสใด ๆ เปลี่ยนเอนทิตี
Jono
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.