อะไรคือความแตกต่างระหว่าง =>, () => และหน่วย =>


153

ฉันพยายามที่จะเป็นตัวแทนของฟังก์ชั่นที่ไม่มีข้อโต้แย้งและคืนค่า (ฉันกำลังจำลองฟังก์ชั่น setTimeout ใน JavaScript ถ้าคุณต้องรู้)

case class Scheduled(time : Int, callback :  => Unit)

ไม่ได้คอมไพล์โดยพูดว่าพารามิเตอร์ "` val 'อาจไม่ใช่ชื่อที่เรียก "

case class Scheduled(time : Int, callback :  () => Unit)  

คอมไพล์ แต่ต้องถูกเรียกใช้อย่างผิดปกติแทน

Scheduled(40, { println("x") } )

ฉันต้องทำสิ่งนี้

Scheduled(40, { () => println("x") } )      

สิ่งที่ใช้งานได้ก็คือ

class Scheduled(time : Int, callback :  Unit => Unit)

แต่ถูกเรียกใช้ในวิธีที่ไม่สมเหตุสมผล

 Scheduled(40, { x : Unit => println("x") } )

(ตัวแปรประเภทหน่วยจะเป็นอย่างไร) สิ่งที่ฉันต้องการแน่นอนคือตัวสร้างที่สามารถเรียกวิธีที่ฉันจะเรียกใช้ถ้ามันเป็นฟังก์ชั่นธรรมดา:

 Scheduled(40, println("x") )

ให้ขวดนมแก่เขา!


3
วิธีการใช้การเรียนกรณีที่มี Parms case class Scheduled(time: Int)(callback: => Unit)โดยชื่ออีกอย่างหนึ่งคือการใส่ไว้ในรายการพารามิเตอร์รองเช่น ใช้งานได้เนื่องจากรายการพารามิเตอร์รองไม่ได้เปิดเผยต่อสาธารณะและไม่รวมอยู่ในการสร้างequals/ hashCodeวิธีการ
nilskp

ประเด็นที่น่าสนใจอีกสองสามประการเกี่ยวกับความแตกต่างระหว่างพารามิเตอร์ตามชื่อและฟังก์ชัน 0-arity พบได้ในคำถามนี้และคำตอบ จริงๆแล้วมันคือสิ่งที่ฉันกำลังมองหาเมื่อฉันพบคำถามนี้
lex82

คำตอบ:


234

Call-by-Name: => Type

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

หมายความว่าอะไรที่ส่งผ่านถูกแทนที่สำหรับชื่อค่าภายในฟังก์ชัน ตัวอย่างเช่นใช้ฟังก์ชันนี้:

def f(x: => Int) = x * x

ถ้าฉันเรียกมันว่าอย่างนี้

var y = 0
f { y += 1; y }

จากนั้นโค้ดจะทำงานเช่นนี้

{ y += 1; y } * { y += 1; y }

แม้ว่านั่นจะทำให้เกิดสิ่งที่เกิดขึ้นหากมีการระบุชื่อที่ขัดแย้งกัน ในการโทรตามชื่อดั้งเดิมกลไกที่เรียกว่าการทดแทนการหลีกเลี่ยงการดักจับเกิดขึ้นเพื่อหลีกเลี่ยงการปะทะกันของชื่อ อย่างไรก็ตามใน Scala สิ่งนี้จะถูกนำไปใช้ในอีกทางหนึ่งด้วยชื่อผลลัพธ์แบบเดียวกันภายในพารามิเตอร์ที่ไม่สามารถอ้างถึงหรือตัวระบุเงาในฟังก์ชันที่เรียกใช้

มีประเด็นอื่น ๆ ที่เกี่ยวข้องกับชื่อเรียกที่ฉันจะพูดถึงหลังจากอธิบายอีกสองประเด็น

ฟังก์ชั่น 0-arity: () => Type

ไวยากรณ์ยืนสำหรับประเภทของที่() => Type Function0นั่นคือฟังก์ชั่นที่ไม่มีพารามิเตอร์และส่งคืนบางสิ่ง สิ่งนี้เทียบเท่ากับการเรียกใช้เมธอดsize()- มันไม่มีพารามิเตอร์และส่งกลับตัวเลข

อย่างไรก็ตามเป็นที่น่าสนใจว่าวากยสัมพันธ์นี้คล้ายกับไวยากรณ์ของฟังก์ชันนิรนามซึ่งเป็นสาเหตุของความสับสน ตัวอย่างเช่น,

() => println("I'm an anonymous function")

เป็นฟังก์ชันที่ไม่ระบุชื่อตามตัวอักษรของ arity 0 ซึ่งเป็นประเภท

() => Unit

ดังนั้นเราสามารถเขียน:

val f: () => Unit = () => println("I'm an anonymous function")

มันเป็นสิ่งสำคัญที่จะไม่สับสนประเภทที่มีค่าอย่างไรก็ตาม

หน่วย => ประเภท

นี่เป็นเพียงแค่ a Function1พารามิเตอร์ที่มีประเภทUnitแรก วิธีการอื่น ๆ ที่จะเขียนมันจะเป็นหรือ(Unit) => Type Function1[Unit, Type]มันคือ ... นี่ไม่น่าจะเป็นอย่างที่เราต้องการ Unitวัตถุประสงค์หลักของประเภทจะระบุค่าหนึ่งไม่สนใจจึงไม่ได้ทำให้ความรู้สึกที่จะได้รับค่าที่

ยกตัวอย่างเช่น

def f(x: Unit) = ...

เราจะทำxอะไรได้บ้าง? มีเพียงค่าเดียวเท่านั้นดังนั้นจึงไม่จำเป็นต้องได้รับ การใช้งานที่เป็นไปได้อย่างหนึ่งคือการผูกมัดฟังก์ชันที่ส่งคืนUnit:

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

เนื่องจากandThenมีการกำหนดไว้เท่านั้นFunction1และฟังก์ชั่นที่เรากำลังนำเสนอกลับมาUnitเราจึงต้องกำหนดให้เป็นประเภทFunction1[Unit, Unit]เพื่อให้สามารถเชื่อมโยงได้

แหล่งที่มาของความสับสน

แหล่งที่มาของความสับสนแรกกำลังคิดความคล้ายคลึงกันระหว่างชนิดและตัวอักษรที่มีอยู่สำหรับฟังก์ชัน 0-arity ยังมีอยู่สำหรับการโทรตามชื่อ ในคำอื่น ๆ คิดว่าเพราะ

() => { println("Hi!") }

เป็นตัวอักษรสำหรับ() => Unitแล้ว

{ println("Hi!") }

=> Unitจะเป็นตัวอักษรสำหรับ มันไม่ใช่. นั่นคือบล็อกของรหัสไม่ใช่ตัวอักษร

แหล่งที่มาของความสับสนอื่นคือค่าUnitประเภทนั้นถูกเขียนซึ่งดูเหมือนรายการพารามิเตอร์ 0-arity (แต่ไม่ใช่)()


ฉันอาจจะต้องเป็นคนแรกที่ลงคะแนนเสียงหลังจากสองปี ใครบางคนกำลังสงสัยเกี่ยวกับ case => ไวยากรณ์ในวันคริสต์มาสและฉันไม่สามารถแนะนำคำตอบนี้ได้เป็นที่ยอมรับและสมบูรณ์! โลกกำลังจะมาถึงอย่างไร? บางทีชาวมายันก็เพิ่งจะปิดตัวลงในสัปดาห์เดียว พวกเขาคิดในปีอธิกสุรทินอย่างถูกต้องหรือไม่? ออมทรัพย์ตามฤดูกาล
som-snytt

@ som-snytt ดีคำถามไม่ถามcase ... =>ดังนั้นฉันไม่ได้พูดถึง เศร้า แต่จริง :-)
Daniel C. Sobral

1
@Daniel C. Sobral คุณช่วยอธิบายหน่อยได้ไหมว่า "นั่นเป็นบล็อกรหัสไม่ใช่ตัวอักษร" ส่วนหนึ่ง แล้วอะไรคือความแตกต่างที่แน่นอนระหว่างสองสิ่งนี้?
nish1013

2
@ nish1013 "literal" เป็นค่า (จำนวนเต็ม1อักขระ'a'สตริง"abc"หรือฟังก์ชัน() => println("here")สำหรับตัวอย่าง) มันสามารถส่งผ่านเป็นอาร์กิวเมนต์เก็บไว้ในตัวแปร ฯลฯ "บล็อกของรหัส" คือการคั่นประโยคของประโยค - มันไม่ใช่ค่ามันไม่สามารถส่งผ่านหรืออะไรทำนองนั้น
Daniel C. Sobral

1
@ Alex นั่นคือความแตกต่างเช่นเดียวกับ(Unit) => TypeVS () => Type- ครั้งแรกคือในขณะที่สองเป็นFunction1[Unit, Type] Function0[Type]
แดเนียลซี. Sobral

36
case class Scheduled(time : Int, callback :  => Unit)

โมดิcaseฟายเออร์ทำให้ปริยายvalออกจากแต่ละอาร์กิวเมนต์ไปยังตัวสร้าง ดังนั้น (ตามที่มีคนบันทึกไว้) หากคุณลบcaseคุณสามารถใช้พารามิเตอร์ call-by-name คอมไพเลอร์อาจจะช่วยให้มันอยู่แล้ว แต่มันอาจจะแปลกใจคนถ้ามันสร้างขึ้นแทนการแปรสภาพval callbacklazy val callback

เมื่อคุณเปลี่ยนเป็นcallback: () => Unitกรณีของคุณตอนนี้เพียงแค่ใช้ฟังก์ชั่นแทนพารามิเตอร์ call-by-name เห็นได้ชัดว่าฟังก์ชั่นสามารถเก็บไว้ในval callbackจึงไม่มีปัญหา

วิธีที่ง่ายที่สุดในการได้รับสิ่งที่คุณต้องการ ( Scheduled(40, println("x") )โดยที่พารามิเตอร์การเรียกชื่อโดยใช้ผ่านแลมบ์ดา) น่าจะข้ามไปcaseและสร้างสิ่งapplyที่คุณไม่สามารถทำได้ตั้งแต่แรก:

class Scheduled(val time: Int, val callback: () => Unit) {
    def doit = callback()
}

object Scheduled {
    def apply(time: Int, callback: => Unit) =
        new Scheduled(time, { () => callback })
}

ในการใช้งาน:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x

3
ทำไมไม่ให้เป็นคลาสตัวพิมพ์และแทนที่ค่าเริ่มต้นที่ใช้? นอกจากนี้คอมไพเลอร์ไม่สามารถแปลชื่อตามเป็นสันหลังยาวเนื่องจากพวกเขามีความหมายที่แตกต่างกันโดยทั่วไปขี้เกียจมากที่สุดครั้งเดียวและโดยชื่อมีที่อ้างอิงทุก
Viktor Klang

@ViktorKlang คุณจะสามารถแทนที่วิธีการสมัครเริ่มต้นของชั้นเรียนได้อย่างไร stackoverflow.com/questions/2660975/…
Sawyer

วัตถุ ClassName {def Apply (…): … = …}
Viktor Klang

สี่ปีต่อมาและฉันรู้ว่าคำตอบที่ฉันเลือกตอบเพียงคำถามในชื่อไม่ใช่คำตอบที่ฉันมี
Malvolio

1

ในคำถามคุณต้องการจำลองฟังก์ชัน SetTimeOut ใน JavaScript จากคำตอบก่อนหน้าฉันเขียนรหัสต่อไปนี้:

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

ใน REPL เราสามารถได้รับดังนี้:

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

การจำลองของเราไม่ทำงานเหมือนกับ SetTimeOut เนื่องจากการจำลองของเราคือการปิดกั้นฟังก์ชั่น แต่ SetTimeOut ไม่ใช่การปิดกั้น


0

ฉันทำเช่นนี้ (เพียงไม่ต้องการหยุดใช้):

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

และเรียกมันว่า

Thing.of(..., your_value)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.