Tôi đọc Hàm Scala (một phần của Chuyến tham quan khác của Scala ). Trong bài đăng đó, ông tuyên bố:
Phương thức và chức năng không giống nhau
Nhưng anh không giải thích bất cứ điều gì về nó. Anh ấy đang cố nói gì vậy?
Tôi đọc Hàm Scala (một phần của Chuyến tham quan khác của Scala ). Trong bài đăng đó, ông tuyên bố:
Phương thức và chức năng không giống nhau
Nhưng anh không giải thích bất cứ điều gì về nó. Anh ấy đang cố nói gì vậy?
Câu trả lời:
Jim đã nhận được khá nhiều điều này trong bài đăng trên blog của mình , nhưng tôi đang đăng một bản tóm tắt ở đây để tham khảo.
Trước tiên, hãy xem những gì Đặc tả Scala cho chúng ta biết. Chương 3 (loại) cho chúng tôi biết về Loại chức năng (3.2.9) và Loại phương thức (3.3.1). Chương 4 (khai báo cơ bản) nói về Tuyên bố giá trị và định nghĩa (4.1), Khai báo biến và định nghĩa (4.2) và Khai báo hàm và định nghĩa (4.6). Chương 6 (biểu thức) nói về Hàm ẩn danh (6.23) và Giá trị phương thức (6.7). Thật kỳ lạ, các giá trị hàm được nói đến một lần vào ngày 3.2.9, và không ở đâu khác.
Một Loại Chức năng là (xấp xỉ) một loại hình thức (T1, ..., Tn) => U , mà là một viết tắt cho các đặc điểm FunctionN
trong thư viện chuẩn. Các hàm và giá trị phương thức ẩn danh có các loại hàm và các loại hàm có thể được sử dụng như một phần của khai báo giá trị, biến và hàm và định nghĩa. Trong thực tế, nó có thể là một phần của kiểu phương thức.
Một phương pháp Loại là một loại không có giá trị . Điều đó có nghĩa là không có giá trị - không có đối tượng, không có thể hiện - với một kiểu phương thức. Như đã đề cập ở trên, Giá trị Phương thức thực sự có Loại Chức năng . Một kiểu phương thức là một def
khai báo - tất cả mọi thứ về một def
ngoại trừ cơ thể của nó.
Giá trị khai báo và định nghĩa và khai báo biến và định nghĩa là val
và var
tờ khai, bao gồm cả hai loại và giá trị - có thể, tương ứng, Chức năng Loại và chức năng Anonymous hoặc giá trị Phương pháp . Lưu ý rằng, trên JVM, các (giá trị phương thức) này được triển khai với cái mà Java gọi là "phương thức".
Một tuyên bố chức năng là một def
tuyên bố, bao gồm cả loại và cơ thể . Phần loại là Kiểu phương thức và phần thân là biểu thức hoặc khối . Điều này cũng được triển khai trên JVM với cái mà Java gọi là "phương thức".
Cuối cùng, Hàm ẩn danh là một thể hiện của Loại chức năng (nghĩa là một thể hiện của đặc điểm FunctionN
) và Giá trị phương thức là điều tương tự! Điểm khác biệt là Giá trị phương thức được tạo từ các phương thức, bằng cách đặt trước một dấu gạch dưới ( m _
là giá trị phương thức tương ứng với "khai báo hàm" ( def
) m
) hoặc bằng một quá trình gọi là mở rộng eta , giống như một quá trình tự động từ phương thức để hoạt động.
Đó là những gì thông số kỹ thuật nói, vì vậy hãy để tôi đưa ra điều này: chúng tôi không sử dụng thuật ngữ đó! Nó dẫn đến quá nhiều nhầm lẫn giữa cái gọi là "khai báo hàm" , là một phần của chương trình (chương 4 - khai báo cơ bản) và "hàm ẩn danh" , là một biểu thức và "kiểu hàm" , đó là, cũng là một loại - một đặc điểm.
Thuật ngữ dưới đây và được sử dụng bởi các lập trình viên Scala có kinh nghiệm, thực hiện một thay đổi so với thuật ngữ của đặc tả: thay vì nói khai báo hàm , chúng tôi nói phương thức . Hoặc thậm chí khai báo phương pháp. Hơn nữa, chúng tôi lưu ý rằng khai báo giá trị và khai báo biến cũng là phương pháp cho các mục đích thực tế.
Vì vậy, với sự thay đổi ở trên về thuật ngữ, đây là một lời giải thích thực tế về sự khác biệt.
Một chức năng là một đối tượng trong đó bao gồm một trong những FunctionX
đặc điểm, chẳng hạn như Function0
, Function1
, Function2
, vv Nó có thể bao gồm cả PartialFunction
là tốt, mà thực sự mở rộng Function1
.
Hãy xem chữ ký loại cho một trong những đặc điểm sau:
trait Function2[-T1, -T2, +R] extends AnyRef
Đặc điểm này có một phương thức trừu tượng (nó cũng có một vài phương thức cụ thể):
def apply(v1: T1, v2: T2): R
Và điều đó cho chúng ta biết tất cả những gì cần biết về nó. Một hàm có một apply
phương thức nhận N tham số của các loại T1 , T2 , ..., TN và trả về một kiểu nào đó R
. Nó là biến thể đối với các tham số mà nó nhận được và đồng biến thể trên kết quả.
Phương sai đó có nghĩa là a Function1[Seq[T], String]
là một kiểu con của Function1[List[T], AnyRef]
. Trở thành một kiểu con có nghĩa là nó có thể được sử dụng thay cho nó. Người ta có thể dễ dàng thấy rằng nếu tôi sẽ gọi f(List(1, 2, 3))
và mong đợi sự AnyRef
trở lại, một trong hai loại trên sẽ hoạt động.
Bây giờ, sự giống nhau của một phương thức và một hàm là gì? Chà, nếu f
là một hàm và m
là một phương thức cục bộ trong phạm vi, thì cả hai có thể được gọi như thế này:
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
Các cuộc gọi này thực sự khác nhau, bởi vì cuộc gọi đầu tiên chỉ là một cú pháp cú pháp. Scala mở rộng nó thành:
val o1 = f.apply(List(1, 2, 3))
Mà, tất nhiên, là một phương thức gọi đối tượng f
. Các hàm cũng có các loại cú pháp cú pháp khác với lợi thế của nó: hàm chữ (thực tế là hai trong số chúng) và (T1, T2) => R
chữ ký loại. Ví dụ:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
Một điểm tương đồng khác giữa một phương thức và một hàm là cái trước có thể dễ dàng chuyển đổi thành cái sau:
val f = m _
Scala sẽ mở rộng điều đó , giả sử m
loại là (List[Int])AnyRef
vào (Scala 2.7):
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
Trên Scala 2.8, nó thực sự sử dụng một AbstractFunction1
lớp để giảm kích thước lớp.
Lưu ý rằng người ta không thể chuyển đổi theo cách khác - từ một chức năng sang một phương thức.
Tuy nhiên, các phương thức có một lợi thế lớn (tốt, hai - chúng có thể nhanh hơn một chút): chúng có thể nhận được các tham số loại . Chẳng hạn, trong khi f
ở trên có thể nhất thiết chỉ định loại List
nhận được ( List[Int]
trong ví dụ), m
có thể tham số hóa nó:
def m[T](l: List[T]): String = l mkString ""
Tôi nghĩ rằng điều này bao gồm khá nhiều thứ, nhưng tôi sẽ vui lòng bổ sung câu trả lời này cho bất kỳ câu hỏi nào còn tồn tại.
val f = m
trình biên dịch như val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
bạn nên chỉ ra rằng this
bên trong apply
phương thức không tham chiếu đến AnyRef
đối tượng, mà là đối tượng trong đó phương thức val f = m _
được đánh giá ( bên ngoài this
, có thể nói là ), vì this
là một trong những giá trị được thu thập bởi bao đóng (như ví dụ return
như được chỉ ra bên dưới).
Một sự khác biệt thực tế lớn giữa một phương thức và một chức năng là những gì return
có nghĩa là. return
chỉ bao giờ trở lại từ một phương thức. Ví dụ:
scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
val f = () => { return "test" }
^
Trở về từ một hàm được định nghĩa trong một phương thức thực hiện trả về không cục bộ:
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
Trong khi đó trả về từ một phương thức cục bộ chỉ trả về từ phương thức đó.
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
for (a <- List(1, 2, 3)) { return ... }
? Điều đó được khử đường đến một đóng cửa.
return
trả về một giá trị từ hàm và một số dạng escape
hoặc break
hoặc continue
trả về từ các phương thức.
Hàm Một hàm có thể được gọi với một danh sách các đối số để tạo kết quả. Hàm có danh sách tham số, phần thân và loại kết quả. Các hàm là thành viên của một lớp, đặc điểm hoặc đối tượng singleton được gọi là các phương thức . Các hàm được định nghĩa bên trong các hàm khác được gọi là các hàm cục bộ. Các hàm với loại kết quả của Đơn vị được gọi là thủ tục. Các hàm ẩn danh trong mã nguồn được gọi là hàm chữ. Trong thời gian chạy, các ký tự hàm được khởi tạo thành các đối tượng được gọi là các giá trị hàm.
Lập trình trong Scala Phiên bản thứ hai. Martin Oderky - Chiếc thìa Lex - Bill Venners
Giả sử bạn có một Danh sách
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
Xác định một phương pháp
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
Xác định hàm
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
Phương pháp chấp nhận đối số
scala> m1(2)
res3: Int = 4
Xác định hàm với val
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
Đối số cho chức năng là Tùy chọn
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
Đối số về phương pháp là bắt buộc
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
Kiểm tra Hướng dẫn sau để giải thích việc chuyển các khác biệt với các ví dụ như ví dụ khác về hàm Phương thức Vs, Sử dụng hàm làm Biến, tạo hàm trả về hàm
Có một bài viết hay ở đây mà hầu hết các mô tả của tôi được thực hiện. Chỉ là một so sánh ngắn về Chức năng và Phương pháp liên quan đến sự hiểu biết của tôi. Hy vọng nó giúp:
Chức năng : Chúng cơ bản là một đối tượng. Chính xác hơn, các hàm là các đối tượng với một phương thức áp dụng; Do đó, chúng chậm hơn một chút so với các phương thức vì chi phí hoạt động. Nó tương tự như các phương thức tĩnh theo nghĩa là chúng độc lập với một đối tượng được gọi. Một ví dụ đơn giản về hàm giống như dưới đây:
val f1 = (x: Int) => x + x
f1(2) // 4
Dòng trên không có gì ngoài việc gán một đối tượng cho đối tượng khác như object1 = object2. Trên thực tế, object2 trong ví dụ của chúng ta là một hàm ẩn danh và phía bên trái có kiểu đối tượng vì điều đó. Do đó, bây giờ F1 là một đối tượng (Chức năng). Hàm ẩn danh thực sự là một thể hiện của Function1 [Int, Int] có nghĩa là một hàm có 1 tham số kiểu Int và giá trị trả về của kiểu Int. Gọi F1 mà không có đối số sẽ cho chúng ta chữ ký của hàm ẩn danh (Int => Int =)
Các phương thức : Chúng không phải là các đối tượng mà được gán cho một thể hiện của một lớp, tức là một đối tượng. Chính xác giống như phương thức trong java hoặc các hàm thành viên trong c ++ (như Raffi Khatchadourian đã chỉ ra trong một nhận xét cho câu hỏi này ) và v.v. Một ví dụ đơn giản về phương thức giống như dưới đây:
def m1(x: Int) = x + x
m1(2) // 4
Dòng trên không phải là gán giá trị đơn giản mà là định nghĩa của phương thức. Khi bạn gọi phương thức này với giá trị 2 như dòng thứ hai, x được thay thế bằng 2 và kết quả sẽ được tính và bạn nhận được 4 làm đầu ra. Ở đây bạn sẽ gặp lỗi nếu chỉ đơn giản là viết m1 vì đây là phương thức và cần giá trị đầu vào. Bằng cách sử dụng _ bạn có thể gán một phương thức cho một hàm như dưới đây:
val f2 = m1 _ // Int => Int = <function1>
Đây là một bài viết tuyệt vời của Rob Norris giải thích sự khác biệt, đây là một TL; DR
Các phương thức trong Scala không phải là các giá trị, nhưng các hàm là. Bạn có thể xây dựng một hàm ủy nhiệm cho một phương thức thông qua mở rộng (được kích hoạt bởi dấu gạch dưới dấu gạch dưới).
với định nghĩa sau:
một phương thức là một cái gì đó được định nghĩa bằng def và một giá trị là thứ bạn có thể gán cho một val
Tóm lại ( trích từ blog ):
Khi chúng ta định nghĩa một phương thức, chúng ta thấy rằng chúng ta không thể gán nó cho a val
.
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int
scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
val f = add1
Cũng lưu ý các loại của add1
, mà không giống bình thường; bạn không thể khai báo một biến kiểu (n: Int)Int
. Phương thức không phải là giá trị.
Tuy nhiên, bằng cách thêm toán tử postfix mở rộng (được phát âm là et eta), chúng ta có thể biến phương thức thành một giá trị hàm. Lưu ý các loại f
.
scala> val f = add1 _
f: Int => Int = <function1>
scala> f(3)
res0: Int = 4
Tác dụng của _
là thực hiện tương đương như sau: chúng tôi xây dựng một Function1
thể hiện ủy nhiệm cho phương thức của chúng tôi.
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>
scala> g(3)
res18: Int = 4
Trong Scala 2.13, không giống như các hàm, các phương thức có thể lấy / trả về
Tuy nhiên, các hạn chế này được gỡ bỏ trong dotty (Scala 3) bởi các loại hàm đa hình # 4672 , ví dụ, phiên bản dotty 0.23.0-RC1 cho phép cú pháp sau
Loại tham số
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
Tham số ngầm định ( tham số ngữ cảnh )
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
Các loại phụ thuộc
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
Để biết thêm ví dụ, xem các bài kiểm tra / run / polymorphic-tests.scala
Thực tế, một lập trình viên Scala chỉ cần biết ba quy tắc sau để sử dụng đúng chức năng và phương thức:
def
và hàm chữ được định nghĩa bởi =>
các hàm. Nó được định nghĩa trong trang 143, Chương 8 trong cuốn sách Lập trình ở Scala, ấn bản thứ 4.someNumber.foreach(println)
Sau bốn phiên bản Lập trình trong Scala, vấn đề là mọi người phải phân biệt hai khái niệm quan trọng: hàm và giá trị hàm vì tất cả các phiên bản không đưa ra lời giải thích rõ ràng. Đặc tả ngôn ngữ quá phức tạp. Tôi thấy các quy tắc trên là đơn giản và chính xác.