Trong lập trình chức năng, các biến cục bộ có biến đổi cục bộ không có tác dụng phụ vẫn được coi là thực hành xấu không?


23

Có phải các biến cục bộ có thể thay đổi trong một hàm chỉ được sử dụng nội bộ, (ví dụ: hàm không có tác dụng phụ, ít nhất là không cố ý) vẫn được coi là "không có chức năng"?

ví dụ: trong kiểm tra kiểu khóa học "Lập trình chức năng với Scala" coi bất kỳ varviệc sử dụng nào là xấu

Câu hỏi của tôi, nếu chức năng không có tác dụng phụ, việc viết mã kiểu bắt buộc vẫn không được khuyến khích?

ví dụ, thay vì sử dụng đệ quy đuôi với mẫu tích lũy, có gì sai khi thực hiện một vòng lặp cục bộ và tạo một biến đổi cục bộListBuffer và thêm vào nó, miễn là đầu vào không bị thay đổi?

Nếu câu trả lời là "có, họ luôn nản lòng, ngay cả khi không có tác dụng phụ" thì lý do là gì?


3
Tất cả các lời khuyên, khuyên nhủ, vv về chủ đề mà tôi từng nghe đề cập đến trạng thái có thể thay đổi được chia sẻ là nguồn gốc của sự phức tạp. Có phải khóa học chỉ dành cho người mới bắt đầu? Sau đó, nó có thể là một sự đơn giản hóa có chủ ý tốt.
Kilian Foth

3
@KilianFoth: Trạng thái có thể thay đổi được chia sẻ là một vấn đề trong bối cảnh đa luồng, nhưng trạng thái có thể thay đổi không chia sẻ có thể dẫn đến các chương trình cũng khó lý luận.
Michael Shaw

1
Tôi nghĩ rằng sử dụng một biến có thể thay đổi cục bộ không nhất thiết là thực tiễn xấu, nhưng nó không phải là "phong cách chức năng": Tôi nghĩ mục đích của khóa học Scala (mà tôi đã học trong mùa thu năm ngoái) là để dạy bạn lập trình theo kiểu chức năng. Một khi bạn có thể phân biệt rõ ràng giữa phong cách chức năng và mệnh lệnh, bạn có thể quyết định khi nào nên sử dụng (trong trường hợp ngôn ngữ lập trình của bạn cho phép cả hai). varluôn luôn không có chức năng. Scala có vals lười biếng và tối ưu hóa đệ quy đuôi, cho phép tránh hoàn toàn vars.
Giorgio

Câu trả lời:


17

Một điều thực tế tồi tệ nhất ở đây là tuyên bố rằng một cái gì đó là một chức năng thuần túy khi nó không.

Nếu các biến có thể thay đổi được sử dụng theo cách thực sự và hoàn toàn khép kín, thì hàm này hoàn toàn bên ngoài và mọi người đều vui vẻ. Haskell trên thực tế hỗ trợ điều này một cách rõ ràng , với hệ thống loại thậm chí đảm bảo rằng các tham chiếu có thể thay đổi không thể được sử dụng bên ngoài chức năng tạo ra chúng.

Điều đó nói rằng, tôi nghĩ rằng nói về "tác dụng phụ" không phải là cách tốt nhất để xem xét nó (và đó là lý do tại sao tôi nói "thuần túy" ở trên). Bất cứ điều gì tạo ra sự phụ thuộc giữa chức năng và trạng thái bên ngoài đều khiến mọi thứ trở nên khó khăn hơn và bao gồm những điều như biết thời gian hiện tại hoặc sử dụng trạng thái có thể thay đổi được che giấu theo cách không an toàn theo luồng.


16

Vấn đề không phải là tính đột biến mỗi se, đó là sự thiếu minh bạch tham chiếu.

Một thứ trong suốt tham chiếu và một tham chiếu đến nó luôn phải bằng nhau, do đó, một hàm trong suốt tham chiếu sẽ luôn trả về cùng một kết quả cho một tập hợp đầu vào nhất định và một "biến" trong suốt tham chiếu thực sự là một giá trị chứ không phải là một biến, vì nó không thể thay đổi. Bạn có thể tạo một hàm trong suốt tham chiếu có một biến có thể thay đổi bên trong; đó không phải là vấn đề Tuy nhiên, có thể khó hơn để đảm bảo chức năng được minh bạch tham chiếu, tùy thuộc vào những gì bạn đang làm.

Có một ví dụ tôi có thể nghĩ về nơi mà tính biến đổi phải được sử dụng để làm một việc rất có chức năng: ghi nhớ. Ghi nhớ là các giá trị bộ đệm từ một hàm, vì vậy chúng không phải được tính toán lại; nó tham chiếu trong suốt, nhưng nó sử dụng đột biến.

Nhưng trong tính minh bạch tham chiếu chung và tính bất biến đi đôi với nhau, ngoài biến số biến đổi cục bộ trong chức năng minh bạch và ghi nhớ tham chiếu, tôi không chắc có bất kỳ ví dụ nào khác không phải là trường hợp này.


4
Quan điểm của bạn về ghi nhớ là rất tốt. Lưu ý rằng Haskell nhấn mạnh mạnh vào tính minh bạch tham chiếu cho lập trình, nhưng hành vi đánh giá lười biếng giống như ghi nhớ liên quan đến một lượng đột biến đáng kinh ngạc được thực hiện bởi thời gian chạy ngôn ngữ đằng sau hậu trường.
CA McCann

@CA McCann: Tôi nghĩ những gì bạn nói là rất quan trọng: trong ngôn ngữ chức năng, bộ thực thi có thể sử dụng đột biến để tối ưu hóa tính toán nhưng không có cấu trúc trong ngôn ngữ cho phép lập trình viên sử dụng đột biến. Một ví dụ khác là một vòng lặp while với biến vòng lặp: trong Haskell, bạn có thể viết hàm đệ quy đuôi có thể được thực hiện với một biến có thể thay đổi (để tránh sử dụng ngăn xếp), nhưng những gì lập trình viên thấy là các đối số hàm bất biến được truyền từ một gọi tiếp theo
Giorgio

@Michael Shaw: +1 cho "Vấn đề không thể thay đổi theo từng se, đó là sự thiếu minh bạch tham chiếu." Có thể bạn có thể trích dẫn ngôn ngữ Sạch trong đó bạn có các loại duy nhất: những ngôn ngữ này cho phép khả năng biến đổi nhưng vẫn đảm bảo tính minh bạch tham chiếu.
Giorgio

@Giorgio: Tôi thực sự không biết gì về Clean, mặc dù thỉnh thoảng tôi vẫn nghe thấy nó được đề cập. Có lẽ tôi nên nhìn vào nó.
Michael Shaw

@Michael Shaw: Tôi không biết nhiều về Clean, nhưng tôi biết rằng nó sử dụng các loại duy nhất để đảm bảo tính minh bạch tham chiếu. Về cơ bản, bạn có thể sửa đổi một đối tượng dữ liệu với điều kiện là sau khi sửa đổi, bạn không có tham chiếu đến giá trị cũ. IMO điều này minh họa quan điểm của bạn: tính minh bạch tham chiếu là điểm quan trọng nhất và tính bất biến chỉ là một cách có thể để đảm bảo nó.
Giorgio

8

Thật không tốt khi đun sôi điều này thành "thực hành tốt" so với "thực hành xấu". Scala hỗ trợ các giá trị có thể thay đổi bởi vì chúng giải quyết một số vấn đề tốt hơn nhiều so với các giá trị bất biến, cụ thể là các giá trị lặp trong tự nhiên.

Đối với phối cảnh, tôi khá chắc chắn rằng thông qua CanBuildFromhầu hết tất cả các cấu trúc bất biến được cung cấp bởi scala thực hiện một số loại đột biến trong nội bộ. Vấn đề là những gì họ phơi bày là bất biến. Giữ càng nhiều giá trị càng tốt càng tốt giúp chương trình dễ dàng lý luận hơnít bị lỗi hơn .

Điều này không có nghĩa là bạn nhất thiết phải tránh các cấu trúc và giá trị có thể thay đổi bên trong khi bạn gặp vấn đề phù hợp hơn với khả năng biến đổi.

Với ý nghĩ đó, rất nhiều vấn đề thường yêu cầu các biến có thể thay đổi (như lặp) có thể được giải quyết tốt hơn với rất nhiều hàm bậc cao hơn mà các ngôn ngữ như Scala cung cấp (map / filter / Fold). Hãy nhận ra những điều đó.


2
Đúng, tôi gần như không bao giờ cần một vòng lặp for khi sử dụng các bộ sưu tập của Scala. map, filter, foldLeftforEach làm các việc lừa hầu hết thời gian, nhưng khi họ không, có thể cảm thấy tôi "OK" để quay trở lại mã bắt buộc brute force là tốt đẹp. (miễn là không có tác dụng phụ tất nhiên)
Eran Medan

3

Ngoài các vấn đề tiềm ẩn với an toàn luồng, bạn cũng thường mất rất nhiều loại an toàn. Các vòng lặp bắt buộc có kiểu trả về Unitvà có thể mất khá nhiều biểu thức cho đầu vào. Các hàm bậc cao hơn và thậm chí đệ quy có ngữ nghĩa và kiểu chính xác hơn nhiều.

Bạn cũng có nhiều tùy chọn hơn để xử lý container chức năng hơn với các vòng lặp bắt buộc. Với mệnh lệnh, về cơ bản bạn có for, whilevà thay đổi nhỏ trên hai như do...whileforeach.

Trong chức năng, bạn có tổng hợp, đếm, lọc, tìm, FlatMap, gấp, groupBy, last IndexWhere, map, maxBy, minBy, phân vùng, quét, sortBy, sortWith, span và TakeWhile, chỉ để đặt tên cho một vài cái phổ biến hơn từ Scala thư viện chuẩn. Khi bạn đã quen với việc có sẵn những thứ đó, forcác vòng lặp bắt buộc dường như quá cơ bản để so sánh.

Lý do thực sự duy nhất để sử dụng khả năng biến đổi cục bộ là rất thỉnh thoảng cho hiệu suất.


2

Tôi sẽ nói rằng nó là chủ yếu là ok. Hơn nữa, tạo cấu trúc theo cách này có thể là một cách tốt để cải thiện hiệu suất trong một số trường hợp. Clojure đã khắc phục vấn đề này bằng cách cung cấp các cấu trúc dữ liệu thoáng qua .

Ý tưởng cơ bản là cho phép các đột biến cục bộ trong một phạm vi hạn chế, và sau đó đóng băng cấu trúc trước khi trả lại nó. Bằng cách này, người dùng của bạn vẫn có thể suy luận về mã của bạn như thể nó là thuần túy, nhưng bạn có thể thực hiện các phép biến đổi tại chỗ khi bạn cần.

Như liên kết nói:

Nếu một cái cây rơi trong rừng, nó có tạo ra âm thanh không? Nếu một hàm thuần biến đổi một số dữ liệu cục bộ để tạo ra giá trị trả về bất biến, điều đó có ổn không?


2

Không có biến đột biến cục bộ nào có một lợi thế - nó làm cho hàm thân thiện hơn với các luồng.

Tôi đã bị đốt cháy bởi một biến cục bộ như vậy (không phải trong mã của tôi, tôi cũng không có nguồn) gây ra tham nhũng dữ liệu có xác suất thấp. An toàn chủ đề không được đề cập theo cách này hay cách khác, không có trạng thái nào tồn tại qua các cuộc gọi và không có tác dụng phụ. Nó đã không xảy ra với tôi rằng nó có thể không an toàn, theo đuổi tham nhũng dữ liệu ngẫu nhiên 1 trong 100.000 là một nỗi đau của hoàng gia.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.