Sự khác biệt giữa các khu vực def def và khác là gì để xác định chức năng


214

Sự khác biệt giữa:

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

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

Cả hai có thể được gọi là như thế even(10).


Xin chào, có Int => Booleannghĩa là gì? Tôi nghĩ cú pháp xác định làdef foo(bar: Baz): Bin = expr
Ziu

@Ziu có nghĩa là hàm 'chẵn' nhận một Int làm đối số và trả về Boolean dưới dạng loại giá trị. Vì vậy, bạn có thể gọi 'chẵn (3)' để đánh giá Boolean là 'sai'
Denys lobur

@DenysLobur cảm ơn bạn đã trả lời! Bất kỳ tài liệu tham khảo về cú pháp này?
Ziu

@Ziu Về cơ bản tôi đã tìm thấy nó từ khóa học Coursera của Oderky - coursera.org/learn/progfun1 . Khi bạn hoàn thành nó, bạn sẽ hiểu 'Loại => Loại' nghĩa là gì
Denys lobur

Câu trả lời:


325

Phương thức def evenđánh giá cuộc gọi và tạo chức năng mới mỗi lần (ví dụ mới Function1).

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

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

Với defbạn có thể nhận chức năng mới trên mỗi cuộc gọi:

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đánh giá khi được định nghĩa, def- khi được gọi:

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

Lưu ý rằng có một tùy chọn thứ ba : lazy val.

Nó đánh giá khi được gọi lần đầu tiên:

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

scala> even
scala.NotImplementedError: an implementation is missing

Nhưng trả về cùng một kết quả (trong trường hợp này là cùng một trường hợp FunctionN) mỗi lần:

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

Hiệu suất

val đánh giá khi xác định.

defđánh giá trên mỗi cuộc gọi, do đó hiệu suất có thể kém hơn so valvới nhiều cuộc gọi. Bạn sẽ có được hiệu suất tương tự với một cuộc gọi. Và không có cuộc gọi, bạn sẽ không nhận được chi phí nào def, vì vậy bạn có thể xác định cuộc gọi ngay cả khi bạn sẽ không sử dụng nó trong một số chi nhánh.

Với một lazy valđánh giá lười biếng: bạn có thể xác định nó ngay cả khi bạn sẽ không sử dụng nó trong một số chi nhánh và nó sẽ đánh giá một lần hoặc không bao giờ, nhưng bạn sẽ có được một chút chi phí từ việc kiểm tra khóa đôi khi truy cập vào lazy val.

Như @SargeBorsch lưu ý, bạn có thể xác định phương thức và đây là tùy chọn nhanh nhất:

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

Nhưng nếu bạn cần một hàm (không phải phương thức) để tạo thành hàm hoặc cho filter(even)trình biên dịch hàm bậc cao (như ) sẽ tạo ra một hàm từ phương thức của bạn mỗi khi bạn sử dụng nó làm hàm, do đó hiệu năng có thể kém hơn một chút so với val.


Bạn có thể vui lòng so sánh chúng về hiệu suất? Không quan trọng để đánh giá chức năng mỗi lần evenđược gọi.
Amir Karimi

2
defcó thể được sử dụng để định nghĩa một phương thức và đây là tùy chọn nhanh nhất. @ A.Karimi
Tên hiển thị

2
Để giải trí: vào ngày 2.12 , even eq even.
som-snytt

Có một khái niệm về các hàm nội tuyến như trong c ++ không? Tôi đến từ thế giới c ++, vì vậy hãy tha thứ cho sự thiếu hiểu biết của tôi.
animageofmine

2
@animageofmine Trình biên dịch Scala có thể thử các phương thức nội tuyến. Có @inlinethuộc tính cho điều này. Nhưng nó không thể thực hiện các hàm nội tuyến vì lệnh gọi hàm là một cuộc gọi đến applyphương thức ảo của một đối tượng hàm. JVM có thể phá hủy và thực hiện các cuộc gọi như vậy trong một số trường hợp, nhưng không nói chung.
senia

24

Xem xét điều này:

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

Bạn có thấy sự khác biệt? Nói ngắn gọn:

def : Đối với mỗi lệnh gọi đến even, nó gọi lại phần thân của evenphương thức. Nhưng với even2ví dụ val , hàm chỉ được khởi tạo một lần trong khi khai báo (và do đó nó in valở dòng 4 và không bao giờ lặp lại) và cùng một đầu ra được sử dụng mỗi lần nó truy cập. Ví dụ: thử làm điều này:

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

Khi xđược khởi tạo, giá trị được trả về Random.nextIntđược đặt làm giá trị cuối cùng của x. Lần sau xđược sử dụng lại, nó sẽ luôn trả về cùng một giá trị.

Bạn cũng có thể khởi tạo một cách lười biếng x. tức là lần đầu tiên nó được sử dụng, nó được khởi tạo và không khai báo. Ví dụ:

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

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Tôi nghĩ rằng lời giải thích của bạn có thể ngụ ý điều gì đó bạn không có ý định. Hãy thử gọi even2hai lần, một lần với 1và một lần với 2. Bạn sẽ nhận được câu trả lời khác nhau trong mỗi cuộc gọi. Vì vậy, trong khi printlncuộc gọi không được thực hiện trong các cuộc gọi tiếp theo, bạn không nhận được kết quả tương tự từ các cuộc gọi khác nhau even2. Về lý do tại sao printlnkhông được thực hiện lại, đó là một câu hỏi khác nhau.
melston

1
Điều đó thực sự rất thú vị. Giống như trong trường hợp val tức là chẵn2, val được ước tính thành một giá trị tham số. Vì vậy, có với một val bạn đánh giá chức năng, giá trị của nó. Println không phải là một phần của giá trị được đánh giá. Nó là một phần của đánh giá nhưng không phải là giá trị được đánh giá. Mẹo ở đây là giá trị được đánh giá thực sự là giá trị tham số hóa, phụ thuộc vào một số đầu vào. điều thông minh
MaatDeamon

1
@melston chính xác! đó là những gì tôi hiểu, vậy tại sao println không được thực thi lại trong khi đầu ra thay đổi?
aur

1
@aur những gì được trả về bởi yet2 thực sự là một hàm (biểu thức được ngoặc đơn ở cuối định nghĩa của chẵn2). Hàm đó thực sự được gọi với tham số bạn truyền tới chẵn2 mỗi khi bạn gọi nó.
melston

5

Xem cái này:

  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)

Đáng ngạc nhiên, điều này sẽ in 4 chứ không phải 9! val (chẵn var) được đánh giá ngay lập tức và được chỉ định.
Bây giờ thay đổi val thành def .. nó sẽ in 9! Def là một hàm gọi .. nó sẽ đánh giá mỗi lần nó được gọi.


1

val tức là "sq" là theo định nghĩa Scala là cố định. Nó được đánh giá ngay tại thời điểm khai báo, bạn không thể thay đổi sau này. Trong các ví dụ khác, trong đó chẵn2 cũng có giá trị, nhưng nó được khai báo với chữ ký hàm tức là "(Int => Boolean)", vì vậy nó không phải là kiểu Int. Đây là một hàm và giá trị của nó được đặt theo biểu thức sau

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

Theo thuộc tính Scala val, bạn không thể gán một hàm khác cho chẵn2, cùng quy tắc như sq.

Về lý do tại sao gọi hàm eval2 val không in "val" nhiều lần?

Mã nguồn gốc:

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

Chúng ta biết, trong câu lệnh cuối cùng của Scala về loại biểu thức trên (bên trong {..}) thực sự trở về phía bên trái. Vì vậy, bạn kết thúc việc thiết lập hàm chẵn2 thành "x => x% 2 == 0", khớp với loại bạn đã khai báo cho loại val2, tức là (Int => Boolean), vì vậy trình biên dịch rất vui. Bây giờ chẵn2 chỉ trỏ đến hàm "(x => x% 2 == 0)" (không phải bất kỳ câu lệnh nào khác trước đó là println ("val"), v.v. Gọi event2 với các tham số khác nhau sẽ thực sự gọi "(x => x% 2 == 0) "mã, vì chỉ có nó được lưu với event2.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Chỉ cần làm rõ điều này hơn, sau đây là phiên bản khác nhau của mã.

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

Chuyện gì sẽ xảy ra ? ở đây chúng ta thấy "bên trong fn cuối cùng" được in lặp đi lặp lại, khi bạn gọi chẵn2 ().

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

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

scala> 

1

Thực hiện một định nghĩa như def x = esẽ không đánh giá biểu thức e. Thay vào đó e được đánh giá bất cứ khi nào x được gọi.

Ngoài ra, Scala cung cấp một định nghĩa giá trị val x = e, đánh giá phía bên phải là một phần của việc đánh giá định nghĩa. Nếu sau đó x được sử dụng, nó sẽ được thay thế ngay lập tức bằng giá trị được tính toán trước của e, do đó biểu thức không cần phải được đánh giá lại.


0

Ngoài ra, Val là một đánh giá giá trị. Có nghĩa là biểu thức phía bên phải được đánh giá trong khi định nghĩa. Trong đó Def là bằng cách đánh giá tên. Nó sẽ không đánh giá cho đến khi nó được sử dụng.


0

Ngoài các câu trả lời hữu ích ở trên, những phát hiện của tôi là:

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

Những điều trên cho thấy rằng, def def Cảnh là một phương thức (với các tham số đối số bằng 0) trả về một hàm khác "Int => Int Int khi được gọi.

Việc chuyển đổi các phương thức thành các hàm được giải thích rõ ở đây: https://tpolecat.github.io/2014/06/09/methods-fifts.html


0

Trong 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 phương tiện call-by-name, đánh giá theo yêu cầu

val có nghĩa call-by-value, được đánh giá trong khi khởi tạo


Với một câu hỏi cũ này và với rất nhiều câu trả lời đã được gửi, thường rất hữu ích để giải thích câu trả lời của bạn khác với hoặc thêm vào thông tin được cung cấp trong các câu trả lời hiện có như thế nào.
jwvh
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.