Sự khác biệt giữa =>, () => và Đơn vị =>


153

Tôi đang cố gắng biểu diễn một hàm không có đối số và không trả về giá trị (Tôi đang mô phỏng hàm setTimeout trong JavaScript, nếu bạn phải biết.)

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

không biên dịch, nói rằng các tham số "` val 'có thể không được gọi bằng tên "

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

biên dịch, nhưng phải được gọi một cách kỳ lạ, thay vì

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

Tôi phải làm điều này

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

Những gì cũng hoạt động là

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

nhưng được viện dẫn theo một cách thậm chí ít nhạy cảm hơn

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

(Biến của loại Đơn vị sẽ là gì?) Tất nhiên điều tôi muốn là một hàm tạo có thể được gọi theo cách tôi sẽ gọi nó nếu đó là một hàm thông thường:

 Scheduled(40, println("x") )

Cho bé bú bình!


3
Một cách khác để sử dụng các lớp trường hợp với parms theo tên, là đặt chúng trong danh sách tham số phụ, vd case class Scheduled(time: Int)(callback: => Unit). Điều này hoạt động vì danh sách tham số phụ không được hiển thị công khai và cũng không được bao gồm trong các phương thức được tạo equals/ hashCode.
nilskp

Một vài khía cạnh thú vị hơn liên quan đến sự khác biệt giữa các tham số tên và hàm 0-arity được tìm thấy trong câu hỏi này và câu trả lời. Đó thực sự là những gì tôi đang tìm kiếm khi tôi tìm thấy câu hỏi này.
lex82

Câu trả lời:


234

Gọi theo tên: => Loại

Các => Typeký hiệu viết tắt của cuộc gọi-by-tên, đó là một trong những rất nhiều cách thông số có thể được thông qua. Nếu bạn không quen thuộc với họ, tôi khuyên bạn nên dành chút thời gian để đọc bài viết trên wikipedia đó, mặc dù ngày nay nó chủ yếu là gọi theo giá trị và gọi theo tham chiếu.

Điều đó có nghĩa là những gì được thông qua được thay thế cho tên giá trị bên trong hàm. Ví dụ: lấy chức năng này:

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

Nếu tôi gọi nó như thế này

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

Sau đó, mã sẽ thực thi như thế này

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

Mặc dù điều đó làm tăng quan điểm về những gì sẽ xảy ra nếu có xung đột tên định danh. Trong cách gọi tên truyền thống, một cơ chế được gọi là thay thế tránh bắt giữ diễn ra để tránh xung đột tên. Tuy nhiên, trong Scala, điều này được thực hiện theo một cách khác với cùng kết quả - tên định danh bên trong tham số không thể tham chiếu hoặc định danh bóng trong hàm được gọi.

Có một số điểm khác liên quan đến tên gọi mà tôi sẽ nói đến sau khi giải thích hai cái còn lại.

Hàm 0-arity: () => Loại

Cú pháp () => Typelà viết tắt của loại a Function0. Đó là, một hàm không có tham số và trả về một cái gì đó. Điều này tương đương với, gọi, gọi phương thứcsize() - nó không có tham số và trả về một số.

Tuy nhiên, điều thú vị là cú pháp này rất giống với cú pháp cho một hàm ẩn danh theo nghĩa đen , là nguyên nhân gây ra một số nhầm lẫn. Ví dụ,

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

là một hàm ẩn danh theo nghĩa đen của arity 0, có kiểu

() => Unit

Vì vậy, chúng tôi có thể viết:

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

Tuy nhiên, điều quan trọng là không nhầm lẫn loại với giá trị, tuy nhiên.

Đơn vị => Loại

Đây thực sự chỉ là một Function1, có tham số đầu tiên là loại Unit. Những cách khác để viết nó sẽ là (Unit) => Typehoặc Function1[Unit, Type]. Vấn đề là ... điều này khó có thể trở thành điều người ta muốn. Các Unitmục đích chính của loại được chỉ ra một giá trị không được quan tâm, vì vậy không có ý nghĩa để nhận được giá trị đó.

Xem xét, ví dụ,

def f(x: Unit) = ...

Người ta có thể làm gì với x? Nó chỉ có thể có một giá trị duy nhất, vì vậy người ta không cần phải nhận nó. Một cách sử dụng có thể là các hàm chuỗi trở lại Unit:

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

Bởi vì andThenchỉ được xác định Function1và các chức năng mà chúng tôi đang kết nối sẽ quay trở lại Unit, chúng tôi phải xác định chúng là loại Function1[Unit, Unit]để có thể xâu chuỗi chúng.

Nguồn gây nhầm lẫn

Nguồn gây nhầm lẫn đầu tiên là suy nghĩ về sự giống nhau giữa loại và nghĩa đen tồn tại đối với các hàm 0-arity cũng tồn tại đối với tên gọi. Nói cách khác, nghĩ rằng, bởi vì

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

là một nghĩa đen cho () => Unit, sau đó

{ println("Hi!") }

sẽ là một nghĩa đen cho => Unit. Không phải vậy. Đó là một khối mã , không phải là một nghĩa đen.

Một nguồn gây nhầm lẫn khác là giá trịUnit của loại được viết , trông giống như một danh sách tham số 0-arity (nhưng không phải vậy).()


Tôi có thể phải là người đầu tiên bỏ phiếu sau hai năm. Ai đó đang tự hỏi về trường hợp => cú pháp vào Giáng sinh và tôi không thể đề xuất câu trả lời này là chính tắc và đầy đủ! Những gì được thế giới sắp tới? Có lẽ người Maya chỉ mới nghỉ một tuần. Họ đã tính đúng năm nhuận? Tiết kiệm ánh sáng ban ngày?
som-snytt

@ som-snytt Vâng, câu hỏi không được hỏi về case ... =>, vì vậy tôi đã không đề cập đến nó. Đáng buồn nhưng là sự thật. :-)
Daniel C. Sobral

1
@Daniel C. Sobral bạn có thể vui lòng giải thích "Đó là một khối mã, không phải là nghĩa đen." phần. Vậy sự khác biệt chính xác giữa hai là gì?
nish1013

2
@ nish1013 Một "nghĩa đen" là một giá trị (số nguyên 1, ký tự 'a', chuỗi "abc"hoặc hàm () => println("here"), đối với một số ví dụ). Nó có thể được truyền dưới dạng đối số, được lưu trữ trong các biến, v.v. "Khối mã" là một phân định cú pháp cú pháp - nó không phải là một giá trị, nó không thể được truyền qua hoặc bất cứ thứ gì tương tự.
Daniel C. Sobral

1
@Alex Đó là sự khác biệt tương tự như (Unit) => Typevs () => Type- đầu tiên là a Function1[Unit, Type], trong khi thứ hai là a Function0[Type].
Daniel C. Sobral

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

Công cụ casesửa đổi làm cho ngầm định valra từng đối số cho hàm tạo. Do đó (như ai đó đã lưu ý) nếu bạn xóa, casebạn có thể sử dụng tham số gọi theo tên. Trình biên dịch có thể cho phép nó bằng mọi cách, nhưng nó có thể làm mọi người ngạc nhiên nếu nó được tạo ra val callbackthay vì biến thành lazy val callback.

Khi bạn thay đổi thành callback: () => Unitbây giờ, trường hợp của bạn chỉ có một chức năng chứ không phải là một tham số gọi theo tên. Rõ ràng chức năng có thể được lưu trữ trong val callbacknên không có vấn đề gì.

Cách dễ nhất để có được những gì bạn muốn ( Scheduled(40, println("x") )trong đó tham số gọi bằng tên được sử dụng để vượt qua lambda) có lẽ là bỏ qua casevà tạo một cách rõ ràng cái applymà bạn không thể có được ở vị trí đầu tiên:

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

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

Đang sử dụng:

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

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

3
Tại sao không giữ cho nó một lớp trường hợp và chỉ ghi đè áp dụng mặc định? Ngoài ra, trình biên dịch không thể dịch một tên phụ sang một giá trị lười biếng, vì chúng vốn có ngữ nghĩa khác nhau, lười biếng ít nhất là một lần và theo tên có tại mọi tham chiếu
Viktor Klang

@ViktorKlang Làm thế nào bạn có thể ghi đè phương thức áp dụng mặc định của lớp trường hợp? stackoverflow.com/questions/2660975/ trộm
Sawyer

object ClassName {def áp dụng (Hoài): Vặn = Bắn}
Viktor Klang

Bốn năm sau và tôi nhận ra rằng câu trả lời tôi chọn chỉ trả lời câu hỏi trong tiêu đề, không phải câu trả lời tôi thực sự có (câu hỏi này trả lời).
Malvolio

1

Trong câu hỏi, bạn muốn mô phỏng chức năng SetTimeOut trong JavaScript. Dựa trên các câu trả lời trước, tôi viết mã sau đây:

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
  }
}

Trong REPL, chúng ta có thể nhận được một cái gì đó như thế này:

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

Mô phỏng của chúng tôi không hoạt động giống hệt như SetTimeOut, vì mô phỏng của chúng tôi là chức năng chặn, nhưng SetTimeOut không chặn.


0

Tôi làm theo cách này (chỉ không muốn phá vỡ áp dụng):

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

và gọi nó

Thing.of(..., your_value)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.