Sự khác biệt giữa:
def even: Int => Boolean = _ % 2 == 0
và
val even: Int => Boolean = _ % 2 == 0
Cả hai có thể được gọi là như thế even(10)
.
Sự khác biệt giữa:
def even: Int => Boolean = _ % 2 == 0
và
val even: Int => Boolean = _ % 2 == 0
Cả hai có thể được gọi là như thế even(10)
.
Câu trả lời:
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 def
bạ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 val
vớ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
.
even
được gọi.
def
có 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
even eq even
.
@inline
thuộ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 apply
phươ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.
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 even
phương thức. Nhưng với even2
ví 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
even2
hai lần, một lần với 1
và 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 println
cuộ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 println
không được thực hiện lại, đó là một câu hỏi khác nhau.
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.
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>
Thực hiện một định nghĩa như def x = e
sẽ 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.
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.
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
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
Int => Boolean
nghĩa là gì? Tôi nghĩ cú pháp xác định làdef foo(bar: Baz): Bin = expr