Xử lý các vấn đề nhà nước trong lập trình chức năng


18

Tôi đã học cách lập trình chủ yếu từ quan điểm OOP (như hầu hết chúng ta, tôi chắc chắn), nhưng tôi đã dành rất nhiều thời gian để cố gắng học cách giải quyết vấn đề theo cách chức năng. Tôi nắm rất rõ cách giải quyết các vấn đề tính toán với FP, nhưng khi gặp các vấn đề phức tạp hơn, tôi luôn thấy mình trở lại cần các đối tượng có thể thay đổi. Ví dụ: nếu tôi đang viết một trình giả lập hạt, tôi sẽ muốn các "đối tượng" hạt có vị trí có thể thay đổi để cập nhật. Các vấn đề "trạng thái" vốn đã được giải quyết bằng cách sử dụng các kỹ thuật lập trình chức năng như thế nào?


4
Bước đầu tiên có khả năng nhận ra rằng các vấn đề không phải là trạng thái vốn có.
Telastyn

4
Một số vấn đề vốn đã có trạng thái, như viết vào cơ sở dữ liệu hoặc vẽ gui. Lấy ví dụ giả lập hạt của tôi, điều gì sẽ là một cách khác để suy nghĩ về nó? Trả lại các hạt mới mỗi khi vị trí của chúng cập nhật để tránh trạng thái dường như không hiệu quả đối với tôi, và không phải là một mô hình tốt của thế giới thực.
Andrew Martin

4
Có lẽ ngoại trừ ví dụ về cơ sở dữ liệu, những vấn đề đó vốn không phải là trạng thái. Ví dụ, đối với lập trình GUI, bạn thực sự sử dụng trạng thái có thể thay đổi như một mô hình thời gian nghèo nàn, tiềm ẩn ; lập trình phản ứng chức năng cho phép bạn mô hình hóa thời gian một cách rõ ràng mà không cần dựa vào trạng thái bằng cách cung cấp các luồng sự kiện bạn có thể kết hợp.
Tikhon Jelvis

1
Có một giải pháp đơn giản hơn: khi bạn gặp phải một vấn đề không dễ mô hình hóa bằng các kỹ thuật FP, đừng sử dụng lập trình chức năng để giải quyết nó. Công cụ phù hợp cho công việc và tất cả những thứ đó ...
Mason Wheeler

1
@AndrewMartin Không phải là một mô hình tốt của thế giới thực? Toán học được sử dụng trong vật lý để mô hình hóa thế giới thực hoàn toàn là chức năng. Với một trình thu gom rác tốt, việc phân bổ một đối tượng rẻ như việc trả một con trỏ và thời gian thu thập tỷ lệ thuận với số lượng đối tượng sống . Nếu có bất cứ điều gì, tôi đặt cược nguồn thiếu hiệu quả chính trong lập trình chức năng là sử dụng các cấu trúc dữ liệu không hiệu quả trong bộ nhớ cache. Danh sách được liên kết và cây nhị phân không chính xác là con của hiệu quả bộ đệm.
Doval

Câu trả lời:


20

Các chương trình chức năng xử lý trạng thái rất tốt, nhưng đòi hỏi một cách nhìn khác. Đối với ví dụ về vị trí của bạn, một điều cần xem xét là có vị trí của bạn là một hàm của thời gian thay vì một giá trị cố định . Điều này hoạt động tốt cho các hạt theo một đường toán học cố định, nhưng bạn yêu cầu một chiến lược khác để xử lý một sự thay đổi trong đường dẫn, chẳng hạn như sau một vụ va chạm.

Chiến lược cơ bản ở đây là bạn tạo các hàm có trạng thái và trả về trạng thái mới . Vì vậy, một trình mô phỏng hạt sẽ là một hàm lấy một Sethạt làm đầu vào và trả về một Sethạt mới sau một bước thời gian. Sau đó, bạn chỉ cần liên tục gọi hàm đó với đầu vào được đặt thành kết quả trước đó.


5
+1 Bạn có thể có trạng thái trong FP, không phải là trạng thái có thể thay đổi .
jhewlett

1
Cảm ơn cho cái nhìn sâu sắc này. Những lo lắng của tôi về sự kém hiệu quả đã bị cản trở bởi @logc; các chi tiết kỹ thuật về cách thức chuyển đổi trạng thái là một vấn đề triển khai ở mức độ thấp mà chính ngôn ngữ được cho là phải giải quyết. Tôi đã xem Rich Hickey giải thích cách anh ấy làm điều này với Clojure trong một video.
Andrew Martin

1
@jhewlett: Nói chính xác hơn: FP không có trạng thái, thậm chí trạng thái có thể thay đổi, nhưng họ không đại diện cho nó bằng các biến có thể thay đổi.
Giorgio

9

Theo ghi nhận của @KarlBielefeldt, cách tiếp cận chức năng cho vấn đề như vậy là xem nó như trả về một trạng thái mới từ trạng thái trước đó. Bản thân các hàm không chứa bất kỳ thông tin nào, vì vậy chúng sẽ luôn cập nhật trạng thái m thành trạng thái n .

Tôi nghĩ rằng bạn thấy điều này không hiệu quả vì bạn cho rằng trạng thái trước đó phải được lưu trong bộ nhớ trong khi tính toán trạng thái mới. Lưu ý rằng sự lựa chọn giữa việc viết một trạng thái hoàn toàn mới hoặc viết lại trạng thái cũ là một chi tiết triển khai theo quan điểm của một ngôn ngữ chức năng.

Chẳng hạn, giả sử tôi có một danh sách một triệu số nguyên và muốn tăng một phần mười lên một đơn vị. Sao chép toàn bộ danh sách với một số mới ở vị trí thứ mười của nó là lãng phí, bạn đã đúng; nhưng nó chỉ là cách khái niệm để mô tả hoạt động cho trình biên dịch ngôn ngữ hoặc trình thông dịch. Trình biên dịch hoặc trình thông dịch có thể tự do lấy danh sách đầu tiên và chỉ ghi đè lên vị trí thứ mười.

Ưu điểm của việc mô tả hoạt động theo cách này là trình biên dịch có thể giải thích về tình huống khi nhiều luồng muốn cập nhật cùng một danh sách ở các vị trí khác nhau. Nếu thao tác được mô tả là "đi đến vị trí này và ghi đè lên những gì bạn tìm thấy", thì đó là lập trình viên, không phải trình biên dịch, người chịu trách nhiệm đảm bảo rằng ghi đè không va chạm.

Với tất cả những gì đã nói, ngay cả trong Haskell cũng có một đơn vị Nhà nước giúp mô hình hóa các tình huống trong đó "giữ trạng thái" là một giải pháp trực quan hơn cho một vấn đề. Nhưng xin vui lòng lưu ý một số vấn đề mà bạn thấy " vốn đã có trạng thái, như viết vào cơ sở dữ liệu " có các giải pháp bất biến như Datomic . Điều này có thể gây ngạc nhiên cho đến khi bạn hiểu nó là một khái niệm, không nhất thiết phải nhận ra nó.


4
Tôi nghĩ đoạn trích về việc cập nhật một danh sách lớn là sai lệch; Tôi không biết bất kỳ trình biên dịch nào sẽ thực sự tối ưu hóa cho bạn. Ngay cả khi trình biên dịch có thể làm điều đó, nó chỉ có thể xảy ra trong trường hợp bạn không giữ các phiên bản trước của danh sách. Các thực giải pháp là sử dụng một cấu trúc danh sách dữ liệu mà không cần sao chép toàn bộ điều để thay đổi một yếu tố duy nhất.
Doval

@Doval: "Ngay cả khi trình biên dịch có thể làm điều đó, thì chỉ có thể trong trường hợp bạn không giữ các phiên bản trước của danh sách.": Điều này nhắc nhở tôi về các loại duy nhất trong Clean.
Giorgio

4

Đăng ký vào mô hình tinh thần phù hợp giúp người ta suy nghĩ tốt hơn và quản lý nhà nước. Trong tâm trí của tôi, mô hình tinh thần tốt nhất là cuốn sách lật . Một khi các nhấp chuột này, bạn sẽ hiểu rằng FP dựa rất nhiều vào các cấu trúc dữ liệu liên tục nắm bắt trạng thái của thế giới và các chức năng đó được sử dụng để chuyển trạng thái đó mà không có bất kỳ đột biến nào.

Rich Hickey chiếu sáng những ý tưởng này:

những cuộc nói chuyện khác nhưng điều này sẽ gửi bạn đi đúng hướng.


3

Khi viết các ứng dụng lớn và vừa phải, tôi thường thấy hữu ích khi phân biệt giữa các phần của ứng dụng có trạng thái và các phần không trạng thái.

Các lớp / cấu trúc dữ liệu trong phần trạng thái lưu trữ dữ liệu của ứng dụng và các chức năng trong phần này hoạt động với kiến ​​thức ngầm về dữ liệu của ứng dụng.

Các lớp / cấu trúc dữ liệu / hàm trong phần không trạng thái có sẵn để hỗ trợ các khía cạnh thuật toán thuần túy của ứng dụng. Họ không có kiến ​​thức ngầm về dữ liệu của ứng dụng. Họ làm việc trong một tính chất hoàn toàn chức năng. Các phần trạng thái của ứng dụng có thể gặp phải sự thay đổi trạng thái do tác dụng phụ của các chức năng đang chạy trong phần không trạng thái của ứng dụng.

Phần khó nhất là tìm ra các lớp / hàm nào sẽ được đặt trong phần không trạng thái và các lớp / hàm nào sẽ được đặt trong phần trạng thái và có kỷ luật để đặt chúng vào các tệp / thư viện riêng biệt.


Làm thế nào để trả lời câu hỏi này? (không bỏ phiếu)
kravemir

@kravemir, cho dù bạn viết một ứng dụng bằng OOP hay FP, bạn phải hiểu trạng thái của ứng dụng nằm ở đâu.
R Sahu
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.