2012-05-25 28 views
6

Imagine mã này:Tại sao hàm được áp dụng một phần trì hoãn việc khởi tạo lớp trong Scala?

class Foo { 
    println("in Foo") 

    def foo(a: Int) = a + 1 
} 

Bây giờ, nếu chúng ta gọi:

new Foo().foo _ 

thể hiện của lớp Foo sẽ được tạo ra, như mong đợi:

in Foo 
res0: (Int) => Int = <function1> 

Tuy nhiên, nếu chúng ta gọi này :

new Foo().foo(_) 

constructor Foo sẽ không được gọi:

res1: (Int) => Int = <function1> 

Nếu chúng ta sau đó nói:

res1(7) 

đó là khi Foo được instantiated:

in Foo 
res2: Int = 8 

Tại sao Eta mở rộng so với ứng dụng chức năng phần tạo sự khác biệt trong phân lớp?

+0

Câu hỏi này đã có một câu trả lời, nhưng tôi không thể nhìn thấy nó nữa. Có ai đó xóa nó không? –

Trả lời

2

Cậu bé, đó là một cách tinh tế, nhưng theo như tôi có thể nói nó theo sau Scala spec hoàn toàn. Tôi sẽ trích dẫn từ phiên bản 2.9 của spec.

Ví dụ đầu tiên của bạn: như bạn một cách đúng đắn nói, bạn đang nhìn thấy sự bành trướng eta qua một trường hợp đặc biệt của một Value Method (§6.7):

The expression e _ is well-formed if e is of method type or if e is a call-by-name parameter. If e is a method with parameters, e _ represents e converted to a function type by eta expansion.

Thuật toán để mở rộng eta được đưa ra trong § 6.26.5 mà bạn có thể làm theo để cung cấp cho việc thay đổi sau đây cho biểu new Foo().x1 _:

{ 
    val x1 = new Foo(); 
    (y1: Int) => x1.(y1); 
} 

Điều này ngụ ý rằng khi mở rộng eta đang được sử dụng, tất cả các tiểu biểu thức được đánh giá tại điểm mà c đảo ngược diễn ra (nếu tôi đã hiểu ý nghĩa của cụm từ "biểu thức phụ tối đa" một cách chính xác) và biểu thức cuối cùng là tạo ra một hàm ẩn danh.

Trong ví dụ thứ hai của bạn, những ngoặc thêm có nghĩa là trình biên dịch sẽ xem xét §6.23 (đặc biệt là "giữ chỗ Cú pháp cho hàm Anonymous) và tạo ra một chức năng ẩn danh trực tiếp.

An expression (of syntactic category Expr) may contain embedded underscore symbols _ at places where identifiers are legal. Such an expression represents an anonymous function where subsequent occurrences of underscores denote successive parameters.

Trong trường hợp đó , và sau các thuật toán trong phần đó, biểu hiện của bạn kết thúc lên được điều này:

(x1: Int) => new Foo().foo(x1) 

sự khác biệt là tinh tế và, như đã giải thích rất tốt bởi @Antoras, chỉ thực sự cho thấy sự hiện diện của mã tác dụng phụ.

Lưu ý rằng có một sửa lỗi trong trường hợp liên quan đến các khối mã theo từng tên (xem, ví dụ: this question, this bugthis bug).

Postscript: Trong cả hai trường hợp, chức năng ẩn danh (x1:Int) => toto được mở rộng để

new scala.Function1[Int, Int] { 
    def apply(x1: Int): Int = toto 
} 
1

Bởi vì nó mở rộng để

(x: Int) => new Foo().foo(x) 

Vì vậy, bạn chỉ được tạo ra mà thể hiện của Foo khi bạn gọi hàm.

Và lý do tại sao người đầu tiên instantiates Foo ngay lập tức là bởi vì nó mở rộng để

private[this] val c: (Int) => Int = { 
    <synthetic> val eta$0$1: Foo = new Foo(); 
    ((a: Int) => eta$0$1.foo(a)) 
}; 
<stable> <accessor> def c: (Int) => Int = Foo.this.c; 

Foo là nhận được khởi tạo ở đây một lần c được định nghĩa.

2

Tôi không hoàn toàn chắc chắn, nhưng tôi nghĩ lý do tại sao có sự khác biệt, đó là Scala không phải là một ngôn ngữ lập trình hoàn toàn chức năng - nó cho phép tác dụng phụ:

scala> class Adder { var i = 0; def foo(a:Int)={i+=1;println(i);a+1} } 
defined class Adder 

scala> val curriedFunction = new Adder().foo _ 
curriedFunction: (Int) => Int = <function1> 

scala> val anonymousFunction = new Adder().foo(_) 
anonymousFunction: (Int) => Int = <function1>  

scala> curriedFunction(5) 
1 
res11: Int = 6 

scala> curriedFunction(5) 
2 
res12: Int = 6 

scala> anonymousFunction(5) 
1 
res13: Int = 6 

scala> anonymousFunction(5) 
1 
res14: Int = 6 

Các chức năng ẩn danh được xử lý như:

val anonymousFunction = x => new Adder().foo(x) 

trong khi chức năng cà ri được coi là:

val curriedFunction = { 
    val context = new Adder() 
    (a:Int) => context foo a 
} 

Hàm curried phù hợp với các hàm curried truyền thống được xử lý bằng các ngôn ngữ chức năng: Hàm curried là hàm được áp dụng cho một số dữ liệu và đánh giá hàm này được áp dụng một phần. Nói cách khác: Dựa trên một số dữ liệu, ngữ cảnh được tạo ra và được sử dụng sau này. Đây chính xác là những gì curriedFunction đang thực hiện. Bởi vì Scala cho phép trạng thái có thể thay đổi, bối cảnh có thể được thay đổi - một thực tế có thể dẫn đến hành vi bất ngờ như đã thấy trong câu hỏi.

Các ngôn ngữ hoàn toàn chức năng như Haskell không có vấn đề này vì chúng không cho phép các tác dụng phụ như vậy. Trong Scala người ta phải tự mình đảm bảo rằng bối cảnh được tạo ra bởi hàm curried thực sự thuần khiết.Nếu đây không phải là trường hợp và hành vi của các hàm thuần túy được yêu cầu, các hàm ẩn danh phải được sử dụng bởi vì chúng không lưu trữ một ngữ cảnh (có thể có vấn đề nếu việc tạo ngữ cảnh là tốn kém và phải được thực hiện thường xuyên).

Các vấn đề liên quan