อะไรคือความแตกต่างระหว่าง“ def” และ“ val” เพื่อกำหนดฟังก์ชั่น


214

อะไรคือความแตกต่างระหว่าง:

def even: Int => Boolean = _ % 2 == 0

และ

val even: Int => Boolean = _ % 2 == 0

even(10)ทั้งสองสามารถเรียกได้ว่าเหมือน


สวัสดีInt => Booleanหมายถึงอะไร ฉันคิดว่าไวยากรณ์ที่กำหนดคือdef foo(bar: Baz): Bin = expr
Ziu

@Ziu นั่นหมายความว่าฟังก์ชั่น 'แม้' ได้รับ Int เป็นอาร์กิวเมนต์และส่งกลับค่าบูลีนเป็นประเภทค่า ดังนั้นคุณสามารถเรียก 'แม้แต่ (3)' ซึ่งประเมินค่าเป็น 'เท็จ' ของบูลีน
Denys Lobur

@DenysLobur ขอบคุณสำหรับการตอบกลับของคุณ! มีการอ้างอิงเกี่ยวกับไวยากรณ์นี้หรือไม่?
Ziu

@Ziu ฉันเป็นพื้นพบว่ามันออกมาจากหลักสูตร Coursera Odersky ของ - coursera.org/learn/progfun1 เมื่อถึงเวลาที่คุณทำเสร็จคุณจะเข้าใจว่า 'Type => Type' หมายถึงอะไร
Denys Lobur

คำตอบ:


325

เมธอดdef evenประเมินการโทรและสร้างฟังก์ชันใหม่ทุกครั้ง (อินสแตนซ์ใหม่ของFunction1)

def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false

val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

ด้วยdefคุณสามารถรับฟังก์ชั่นใหม่ทุกการโทร:

val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - same result

def test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - new result

valประเมินเมื่อกำหนดdef- เมื่อเรียกว่า:

scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing

scala> def even: Int => Boolean = ???
even: Int => Boolean

scala> even
scala.NotImplementedError: an implementation is missing

lazy valทราบว่ามีตัวเลือกที่สาม:

มันประเมินเมื่อเรียกครั้งแรก:

scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>

scala> even
scala.NotImplementedError: an implementation is missing

แต่ส่งคืนผลลัพธ์เดียวกัน (ในกรณีนี้อินสแตนซ์เดียวกันFunctionN) ทุกครั้ง:

lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

lazy val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1068569869
test()
// Int = -1068569869 - same result

ประสิทธิภาพ

val ประเมินเมื่อกำหนด

defประเมินการโทรทุกครั้งดังนั้นประสิทธิภาพอาจแย่กว่าvalการโทรหลายครั้ง คุณจะได้รับประสิทธิภาพเดียวกันด้วยการโทรเพียงครั้งเดียว และเมื่อไม่มีสายคุณจะไม่ได้รับค่าใช้จ่ายdefดังนั้นคุณสามารถกำหนดได้แม้ว่าคุณจะไม่ได้ใช้ในบางสาขา

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

ดังที่ @SargeBorsch ตั้งข้อสังเกตว่าคุณสามารถกำหนดวิธีการและนี่คือตัวเลือกที่เร็วที่สุด:

def even(i: Int): Boolean = i % 2 == 0

แต่ถ้าคุณต้องการฟังก์ชั่น (ไม่ใช่วิธี) สำหรับองค์ประกอบหรือฟังก์ชั่นสำหรับฟังก์ชั่นการสั่งซื้อที่สูงขึ้น (ชอบfilter(even)) valคอมไพเลอร์จะสร้างฟังก์ชั่นจากวิธีการของคุณทุกครั้งที่คุณจะใช้มันเป็นฟังก์ชั่นเพื่อให้ประสิทธิภาพการทำงานที่อาจจะเลวร้ายยิ่งกว่าเล็กน้อย


คุณช่วยเปรียบเทียบพวกเขาเกี่ยวกับประสิทธิภาพได้ไหม? การประเมินฟังก์ชั่นในแต่ละครั้งevenนั้นสำคัญหรือไม่
Amir Karimi

2
defสามารถใช้เพื่อกำหนดวิธีการและนี่คือตัวเลือกที่เร็วที่สุด @ A.Karimi
ชื่อที่ปรากฏ

2
เพื่อความสนุกสนาน: บน even eq even2.12
som-snytt

มีแนวคิดของฟังก์ชั่นแบบอินไลน์เหมือนใน c ++ หรือไม่? ฉันมาจากโลก c ++ ดังนั้นโปรดให้อภัยฉัน
animageofmine

2
@animageofmine Scala คอมไพเลอร์สามารถลองวิธีการแบบอินไลน์ มี@inlineคุณลักษณะสำหรับสิ่งนี้ แต่มันไม่สามารถใช้ฟังก์ชั่นอินไลน์ได้เพราะการเรียกใช้ฟังก์ชั่นเป็นการเรียกapplyใช้วิธีการเสมือนของวัตถุฟังก์ชั่น JVM อาจ devirtualise และ inline การโทรดังกล่าวในบางสถานการณ์ แต่ไม่ใช่โดยทั่วไป
senia

24

พิจารณาสิ่งนี้:

scala> def even: (Int => Boolean) = {
             println("def"); 
             (x => x % 2 == 0)
       }
even: Int => Boolean

scala> val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>

scala> even(1)
def
res9: Boolean = false

scala> even2(1)
res10: Boolean = false

คุณเห็นความแตกต่างหรือไม่ ในระยะสั้น:

def : สำหรับการเรียกทุกครั้งจะevenเรียกเนื้อความของevenวิธีการอีกครั้ง แต่ด้วยeven2 ie valฟังก์ชั่นจะเริ่มต้นได้เพียงครั้งเดียวในขณะที่การประกาศ (และด้วยเหตุนี้มันจะพิมพ์valที่บรรทัดที่ 4 และไม่เคยอีกครั้ง) และเอาท์พุทเดียวกันจะถูกใช้ในแต่ละครั้งที่มันเข้าถึง ตัวอย่างเช่นลองทำสิ่งนี้:

scala> import scala.util.Random
import scala.util.Random

scala> val x = { Random.nextInt }
x: Int = -1307706866

scala> x
res0: Int = -1307706866

scala> x
res1: Int = -1307706866

เมื่อไหร่ xมีการเริ่มต้นค่าส่งกลับโดยถูกตั้งค่าเป็นค่าสุดท้ายของRandom.nextInt xครั้งต่อไปxจะถูกใช้อีกครั้งมันจะคืนค่าเดิมเสมอ

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

scala> lazy val y = { Random.nextInt }
y: Int = <lazy>

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
ฉันคิดว่าคำอธิบายของคุณอาจบ่งบอกถึงสิ่งที่คุณไม่ได้ตั้งใจ ลองโทรeven2สองครั้งครั้งด้วยและครั้งเดียวกับ1 2คุณจะได้รับคำตอบที่แตกต่างกันในการโทรแต่ละครั้ง ดังนั้นในขณะที่printlnไม่ได้ดำเนินการในการโทรตามมาคุณไม่ได้รับเดียวกันผลeven2จากการโทรที่แตกต่างกันไป สำหรับสาเหตุที่printlnไม่ถูกดำเนินการอีกครั้งนั่นเป็นคำถามที่แตกต่าง
melston

1
ที่จริงแล้วน่าสนใจมาก มันเหมือนกับในกรณีของ val เช่น Even2, val จะถูกประเมินเป็นค่าพารามิเตอร์ ใช่แล้วด้วยการประเมินฟังก์ชั่นของ val มูลค่าของมัน println ไม่ได้เป็นส่วนหนึ่งของค่าที่ประเมิน มันเป็นส่วนหนึ่งของการประเมิน แต่ไม่ใช่ค่าที่ประเมิน เคล็ดลับที่นี่คือค่าที่ประเมินเป็นจริงค่าพารามิเตอร์ที่ขึ้นอยู่กับอินพุตบางส่วน สิ่งที่สมาร์ท
MaatDeamon

1
@ เมลสตันแน่นอน! นั่นคือสิ่งที่ฉันเข้าใจดังนั้นเหตุใด println จึงไม่ทำงานอีกครั้งในขณะที่เอาต์พุตเปลี่ยนไป
aur

1
@aur สิ่งที่คืนค่าโดย even2 เป็นฟังก์ชัน (นิพจน์วงเล็บที่ท้ายคำจำกัดความของ even2) ฟังก์ชั่นนั้นถูกเรียกใช้โดยมีพารามิเตอร์ที่คุณส่งไปยัง Even2 ทุกครั้งที่คุณเรียกใช้
melston

5

ดูนี่:

  var x = 2 // using var as I need to change it to 3 later
  val sq = x*x // evaluates right now
  x = 3 // no effect! sq is already evaluated
  println(sq)

น่าแปลกใจที่สิ่งนี้จะพิมพ์ 4 และไม่ใช่ 9! val (คู่ var) จะถูกประเมินทันทีและกำหนด
ตอนนี้เปลี่ยน val เป็น def .. มันจะพิมพ์ 9! Def คือการเรียกใช้ฟังก์ชัน .. มันจะประเมินทุกครั้งที่เรียกใช้


1

val เช่น "sq" คือโดยคำจำกัดความ Scala ได้รับการแก้ไข มีการประเมินสิทธิ์ ณ เวลาที่ประกาศคุณไม่สามารถเปลี่ยนแปลงได้ในภายหลัง ในตัวอย่างอื่น ๆ โดยที่ Even2 ยัง val แต่มันประกาศด้วยฟังก์ชั่นลายเซ็นเช่น "(Int => บูลีน)" ดังนั้นจึงไม่ได้เป็นประเภท Int มันเป็นฟังก์ชั่นและมันเป็นค่าที่ถูกกำหนดโดยการแสดงออกดังต่อไปนี้

   {
         println("val");
         (x => x % 2 == 0)
   }

ตามคุณสมบัติของ Scala val คุณไม่สามารถกำหนดฟังก์ชันอื่นให้กับ even2 ได้เช่นเดียวกับ sql

เกี่ยวกับสาเหตุที่เรียกฟังก์ชัน eval2 val ไม่พิมพ์ "val" ซ้ำแล้วซ้ำอีก?

รหัส Orig:

val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }

เรารู้ว่าในถ้อยแถลงสุดท้ายของสกาลาเรื่องการแสดงออกเหนือ (ภายใน {.. }) จริง ๆ แล้วกลับไปทางซ้ายมือ ดังนั้นคุณจะสิ้นสุดการตั้งค่า even2 เป็น "x => x% 2 == 0" ฟังก์ชั่นซึ่งตรงกับประเภทที่คุณประกาศสำหรับประเภทคู่ Even2 เช่น (Int => บูลีน) ดังนั้นคอมไพเลอร์จึงมีความสุข ตอนนี้แม้เพียง 2 ชี้ไปที่ "(x => x% 2 == 0)" ฟังก์ชั่น (ไม่มีคำสั่งอื่น ๆ ก่อนเช่น println ("val") ฯลฯ การเรียกใช้ event2 ด้วยพารามิเตอร์ที่แตกต่างกันจะเรียกใช้จริง "(x => x% 2 == 0) "รหัสเนื่องจากจะถูกบันทึกด้วย event2 เท่านั้น

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

เพียงชี้แจงเพิ่มเติมนี้ต่อไปนี้เป็นรหัสรุ่นอื่น

scala> val even2: (Int => Boolean) = {
     |              println("val");
     |              (x => { 
     |               println("inside final fn")
     |               x % 2 == 0
     |             })
     |        }

อะไรจะเกิดขึ้น ? ที่นี่เราเห็น "ภายในขั้นสุดท้าย fn" พิมพ์อีกครั้งและอีกครั้งเมื่อคุณโทร Even2 ()

scala> even2(3)
inside final fn
res9: Boolean = false

scala> even2(2)
inside final fn
res10: Boolean = true

scala> 

1

ดำเนินการคำจำกัดความเช่น def x = eจะไม่ประเมินนิพจน์ e In- stead e จะถูกประเมินเมื่อใดก็ตามที่ x ถูกเรียกใช้

อีกทางเลือกหนึ่ง Scala เสนอคำนิยามค่า val x = eซึ่งจะประเมินด้านขวามือเป็นส่วนหนึ่งของการประเมินของคำนิยาม ถ้า x ถูกใช้ในภายหลังมันจะถูกแทนที่ทันทีด้วยค่าที่คำนวณล่วงหน้าของ e เพื่อไม่ให้นิพจน์ประเมินอีกครั้ง


0

นอกจากนี้ Val เป็นการประเมินมูลค่าด้วย ซึ่งหมายความว่านิพจน์ทางด้านขวาจะถูกประเมินระหว่างการกำหนด ตำแหน่ง Def คือการประเมินชื่อ มันจะไม่ประเมินจนกว่าจะมีการใช้งาน


0

นอกเหนือจากการตอบกลับที่เป็นประโยชน์ข้างต้นแล้วสิ่งที่ฉันค้นพบคือ:

def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int

def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int

def test3(): Int = 4
--test3: test3[]() => Int

ข้างต้นแสดงให้เห็นว่า“ def” เป็นวิธีการ (โดยไม่มีพารามิเตอร์พารามิเตอร์ศูนย์) ที่ส่งกลับฟังก์ชันอื่น "Int => Int" เมื่อเรียกใช้

การอธิบายวิธีการแปลงฟังก์ชั่นเป็นฟังก์ชั่นที่นี่: https://tpolecat.github.io/2014/06/09/methods-functions.html


0

ใน REPL

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

def หมายถึงcall-by-nameประเมินตามความต้องการ

val หมายถึงcall-by-valueประเมินขณะเตรียมใช้งาน


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