2010-01-04 9 views
67

Động lực để phân định Scala đánh giá cho Đơn vị thay vì giá trị được chỉ định là gì?Động lực để phân định Scala đánh giá cho Đơn vị thay vì giá trị được chỉ định là gì?

Một mô hình phổ biến ở I/O lập trình là để làm những việc như thế này:

while ((bytesRead = in.read(buffer)) != -1) { ... 

Nhưng điều này là không thể trong Scala vì ...

bytesRead = in.read(buffer) 

.. trả về đơn vị, không giá trị mới của bytesRead.

Có vẻ như một điều thú vị để thoát khỏi một ngôn ngữ chức năng. Tôi tự hỏi tại sao nó được thực hiện như vậy?

+0

David Pollack đã đăng một số thông tin đầu tay, được xác nhận rất nhiều bởi lời nhận xét của Martin Odersky về câu trả lời của mình. Tôi nghĩ rằng người ta có thể an toàn acceppt câu trả lời của Pollack. –

Trả lời

66

Tôi ủng hộ việc phân công trả lại giá trị được chỉ định thay vì đơn vị. Martin và tôi đã đi qua lại trên nó, nhưng lập luận của ông là đặt một giá trị trên ngăn xếp chỉ để bật nó ra 95% thời gian là một sự lãng phí các mã byte và có tác động tiêu cực đến hiệu suất.

+4

Có lý do tại sao Scala không trình biên dịch không thể xem liệu giá trị của nhiệm vụ có thực sự được sử dụng hay không và tạo ra bytecode hiệu quả cho phù hợp? –

+39

Nó không phải là dễ dàng như vậy trong sự hiện diện của setters: Mỗi setter phải trả lại một kết quả, đó là một nỗi đau để viết. Sau đó, trình biên dịch phải tối ưu hóa nó đi, đó là khó để làm qua các cuộc gọi. –

+0

Đối số của bạn có ý nghĩa, nhưng java & C# là chống lại điều đó. Tôi đoán bạn đang làm một cái gì đó kỳ lạ với mã byte được tạo ra, sau đó làm thế nào một nhiệm vụ trong Scala được biên dịch vào tập tin lớp và ngược lại để trở thành Java như thế nào? –

5

Tôi đoán đây là để giữ cho chương trình/ngôn ngữ không bị tác dụng phụ.

Những gì bạn mô tả là việc sử dụng có chủ ý một hiệu ứng phụ mà trong trường hợp chung được coi là một điều xấu.

+0

Heh. Scala không có tác dụng phụ? :) Ngoài ra, hãy tưởng tượng một trường hợp như 'val a = b = 1' (tưởng tượng" ma thuật "' val' trước 'b') so với' val a = 1; val b = 1; '. –

4

Đây không phải là kiểu tốt nhất để sử dụng phép gán làm biểu thức boolean. Bạn thực hiện hai điều cùng một lúc dẫn đến lỗi. Và tránh sử dụng "=" thay vì "==" bằng cách sử dụng hạn chế của Scalas.

+2

Tôi nghĩ đây là lý do rác rưởi! Khi OP được đăng, mã vẫn biên dịch và chạy: nó không làm những gì bạn có thể mong đợi. Đó là một trong những gotcha hơn, không phải một ít! –

+1

Nếu bạn viết một cái gì đó như nếu (a = b) nó sẽ không biên dịch. Vì vậy, ít nhất lỗi này có thể tránh được. – deamon

+1

OP không sử dụng '=' thay vì '==', anh ta đã sử dụng cả hai. Ông hy vọng nhiệm vụ trả về một giá trị mà sau đó có thể được sử dụng, ví dụ, để so sánh với giá trị khác (-1 trong ví dụ) – IttayD

16

Tôi không có thông tin bên trong về các lý do thực tế, nhưng sự nghi ngờ của tôi rất đơn giản. Scala làm cho các vòng bên có hiệu quả trở nên khó xử khi sử dụng sao cho các lập trình viên sẽ tự nhiên thích đọc hiểu hơn.

Điều này thực hiện theo nhiều cách. Ví dụ: bạn không có vòng lặp for nơi bạn khai báo và biến đổi một biến. Bạn không thể (dễ dàng) biến đổi trạng thái trên một vòng lặp while cùng một lúc bạn kiểm tra điều kiện, điều đó có nghĩa là bạn thường phải lặp lại đột biến ngay trước nó, và ở cuối nó. Các biến được khai báo bên trong khối while không hiển thị trong điều kiện thử nghiệm while, điều này làm cho do { ... } while (...) ít hữu ích hơn nhiều. Và cứ thế.

Cách giải quyết:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

Đối với bất cứ điều gì nó là giá trị.

Như một lời giải thích thay thế, có lẽ Martin Odersky đã phải đối mặt với một vài lỗi rất xấu xí xuất phát từ việc sử dụng như vậy, và quyết định cấm nó từ ngôn ngữ của mình.

EDIT

David Pollackanswered với một số sự kiện thực tế, được xác nhận rõ ràng bởi thực tế là Martin Odersky mình nhận xét câu trả lời của mình, đem lại tin vào lập luận vấn đề hiệu suất liên quan đến đưa ra bởi Pollack.

+3

Vì vậy, có lẽ phiên bản vòng lặp 'for' sẽ là:' for (bytesRead <- in.read (buffer) nếu (bytesRead)! = -1' là tuyệt vời ngoại trừ việc nó sẽ không hoạt động vì không có 'foreach' và 'withFilter' có sẵn! –

8

Điều này xảy ra như là một phần của Scala có một hệ thống kiểu "chính thức chính xác" hơn. Nói chính thức, phân công là một tuyên bố hoàn toàn có tác dụng phụ và do đó phải trả lại Unit.Điều này có một số hậu quả tốt đẹp; ví dụ:

class MyBean { 
    private var internalState: String = _ 

    def state = internalState 

    def state_=(state: String) = internalState = state 
} 

Phương pháp state_= trả Unit (như sẽ được dự kiến ​​cho một setter) chính vì nhiệm vụ trả Unit.

Tôi đồng ý rằng đối với các mẫu kiểu C như sao chép luồng hoặc tương tự, quyết định thiết kế đặc biệt này có thể hơi rắc rối. Tuy nhiên, nó thực sự tương đối không có ý nghĩa nói chung và thực sự góp phần vào sự thống nhất tổng thể của hệ thống kiểu.

+0

Cảm ơn, Daniel Tôi nghĩ rằng tôi sẽ thích nó nếu sự nhất quán là cả hai nhiệm vụ VÀ setters trả về giá trị! (Không có lý do họ không thể.) Tôi nghi ngờ tôi không grokking sắc thái của các khái niệm –

+2

@Graham: Nhưng sau đó, bạn phải tuân theo sự nhất quán và đảm bảo trong tất cả những người định cư của bạn tuy nhiên họ có thể phức tạp, họ trả lại giá trị mà họ đã đặt. phức tạp trong một số trường hợp và trong trường hợp khác chỉ là sai, tôi nghĩ. (Bạn sẽ trở lại trong trường hợp lỗi? null? - thay vì không. Không? - thì kiểu của bạn sẽ là Option [T].) với điều đó. – Debilski

5

Có lẽ điều này là do nguyên tắc command-query separation?

CQS có xu hướng phổ biến tại giao điểm của OO và các kiểu lập trình hàm, vì nó tạo sự khác biệt rõ ràng giữa các phương pháp đối tượng hoặc không có tác dụng phụ (tức là thay đổi đối tượng). Áp dụng CQS cho các bài tập thay đổi đang thực hiện nó nhiều hơn bình thường, nhưng ý tưởng tương tự cũng được áp dụng.

Một minh họa ngắn về việc tại sao CQS rất hữu ích: Xem xét một ngôn ngữ giả lai F/OO với một lớp List có phương pháp Sort, Append, First, và Length. Trong phong cách OO bắt buộc, người ta có thể muốn viết một hàm như thế này:

func foo(x): 
    var list = new List(4, -2, 3, 1) 
    list.Append(x) 
    list.Sort() 
    # list now holds a sorted, five-element list 
    var smallest = list.First() 
    return smallest + list.Length() 

Trong khi đó trong phong cách thêm chức năng, người ta sẽ nhiều khả năng ghi một cái gì đó như thế này:

func bar(x): 
    var list = new List(4, -2, 3, 1) 
    var smallest = list.Append(x).Sort().First() 
    # list still holds an unsorted, four-element list 
    return smallest + list.Length() 

Những dường như cố gắng để làm điều tương tự, nhưng rõ ràng một trong hai là không chính xác, và không biết nhiều hơn về hành vi của các phương pháp, chúng ta không thể biết cái nào.

Sử dụng CQS, tuy nhiên, chúng tôi sẽ nhấn mạnh rằng nếu AppendSort thay đổi danh sách, chúng phải trả về loại đơn vị, do đó ngăn chúng tôi tạo lỗi bằng cách sử dụng biểu mẫu thứ hai. Sự hiện diện của các tác dụng phụ do đó cũng trở nên ngầm định trong chữ ký phương thức.

2

Nhân tiện: Tôi tìm thấy sự ngu ngốc trong khi-lừa ban đầu, ngay cả trong Java. Tại sao không phải như thế này?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { 
    //do something 
} 

Cấp, việc chuyển nhượng xuất hiện hai lần, nhưng ít nhất bytesRead là trong phạm vi nó thuộc về, và tôi không chơi với thủ đoạn phân buồn cười ...

+0

Điều đó trong khi lừa là một trò chơi khá phổ biến, nó thường xuất hiện trong mọi ứng dụng đọc qua bộ đệm. Và nó luôn luôn giống như phiên bản của OP. – TWiStErRob

1

Bạn có thể có một cách giải quyết cho điều này miễn là bạn có một loại tham chiếu cho indirection. Trong một thực hiện ngây thơ, bạn có thể sử dụng sau đây cho các loại tùy ý.

case class Ref[T](var value: T) { 
    def := (newval: => T)(pred: T => Boolean): Boolean = { 
    this.value = newval 
    pred(this.value) 
    } 
} 

Sau đó, dưới sự hạn chế mà bạn sẽ phải sử dụng ref.value để truy cập tài liệu tham khảo sau đó, bạn có thể viết while ngữ của bạn như

val bytesRead = Ref(0) // maybe there is a way to get rid of this line 

while ((bytesRead := in.read(buffer)) (_ != -1)) { // ... 
    println(bytesRead.value) 
} 

và bạn có thể làm việc kiểm tra chống bytesRead trong một cách ngầm hơn mà không cần phải gõ nó.