Câu hỏi Động lực để phân công Scala đánh giá cho Đơn vị thay vì giá trị được giao là gì?


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

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

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

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

bytesRead = in.read(buffer)

.. trả về Unit, không phải giá trị mới của bytesRead.

Có vẻ như một điều thú vị để rời 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?


76
2018-01-04 10:37


gốc


David Pollack đã đăng một số thông tin đầu tay, khá nhiều xác nhận bởi bình luận Martin Odersky tự mình để lại 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. - Daniel C. Sobral


Các câu trả lời:


Tôi ủng hộ việc phân công trả lại giá trị được gán 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.


75
2018-01-04 16:18



Có một lý do tại sao trình biên dịch Scala 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? - Matt R
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. - Martin Odersky
Đố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? - Phương Nguyễn
@ PhươngNguyễn Sự khác biệt là Nguyên tắc truy cập thống nhất. Trong C # / Java setters (thường) trả về void. Tại Scala foo_=(v: Foo) nên quay trở lại Foo nếu chuyển nhượng. - Alexey Romanov
@Martin Odersky: làm thế nào về sau: setters vẫn void (Unit), bài tập x = value được dịch thành tương đương x.set(value);x.get(value); trình biên dịch loại bỏ trong các giai đoạn tối ưu hóa get-các giá trị không được sử dụng. Nó có thể là một sự thay đổi chào đón trong một chính mới (vì không tương thích ngược) Scala phát hành và ít kích thích hơn cho người dùng. Bạn nghĩ sao? - Eugen Labun


Tôi không biết thông tin bên trong về những lý do thực sự, 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.

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

Giải pháp thay thế:

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.

CHỈNH SỬA

David Pollack có đã trả lời 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 tự nhận xét câu trả lời của mình, cho sự tin tưởng vào các vấn đề liên quan đến hiệu suất được đưa ra bởi Pollack.


17
2018-01-04 11:57



Vì vậy, có lẽ là for phiên bản vòng lặp sẽ là: for (bytesRead <- in.read(buffer) if (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! - oxbow_lakes


Đ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 xác hơn. Nói chính thức, phân công là một tuyên bố hoàn toàn phụ có hiệu lực và do đó nên quay 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
}

Các state_= phương thức trả về Unit (như dự kiến ​​cho một setter) chính xác vì trả về nhiệm vụ 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.


9
2018-01-04 15:14



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 gì họ không thể.) Tôi nghi ngờ tôi không rên rỉ các sắc thái của các khái niệm như một "tuyên bố hoàn toàn phụ có hiệu lực" chỉ được nêu ra. - Graham Lea
@ 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, rằng họ trả lại giá trị họ đặt ra. Điều này sẽ 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ĩ vậy. (Những gì bạn sẽ trở lại trong trường hợp lỗi? Null? - thay vì không. Không có? - sau đó loại của bạn sẽ là Option [T].) Tôi nghĩ rằng nó khó có thể phù hợp với điều đó. - Debilski


Có lẽ điều này là do tách truy vấn lệnh nguyên tắc?

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 ra sự phân biệt rõ ràng giữa các phương thức đối tượng có 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 gọn về lý do tại sao CQS là hữu ích: Hãy xem xét một ngôn ngữ F / OO lai giả định với một List lớp học có phương pháp Sort, Append, FirstLength. 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 chức năng hơn, một trong nhiều khả năng sẽ viết 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()

Đây dường như là 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 Append và Sort thay đổi danh sách, chúng phải trả về loại đơn vị, do đó ngăn chúng ta tạo lỗi bằng cách sử dụng biểu mẫu thứ hai khi chúng ta không nên. 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.


5
2018-01-04 16:38





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

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


4
2018-01-04 10:42



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 "huyền diệu" val ở đằng trước b) so với val a = 1; val b = 1;.
Điều này không liên quan gì đến tác dụng phụ, ít nhất là không theo ý nghĩa được mô tả ở đây: Tác dụng phụ (khoa học máy tính) - Feuermurmel


Nó không phải là kiểu tốt nhất để sử dụng một phép gán như là một 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.


4
2018-01-04 11:08



Tôi nghĩ rằng đây là một 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! - oxbow_lakes
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
OP không sử dụng '=' thay vì '==', anh ta đã sử dụng cả hai. Anh ta 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 một giá trị khác (-1 trong ví dụ) - IttayD
@deamon: nó sẽ biên dịch (trong Java ít nhất) nếu a và b là boolean. Tôi đã thấy người mới rơi vào cái bẫy này bằng cách sử dụng if (a = true). Một lý do nữa để thích đơn giản hơn nếu (a) (và rõ ràng hơn nếu sử dụng tên có ý nghĩa hơn!). - PhiLho


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, nhiệm vụ xuất hiện hai lần, nhưng ít nhất bytesRead nằm trong phạm vi nó thuộc về, và tôi không chơi với các thủ thuật chuyển nhượng vui ...


2
2018-01-04 21:55



Đ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