Lập trình chức năng - Bất biến


12

Tôi đang cố gắng hiểu việc xử lý dữ liệu bất biến trong FP (cụ thể là trong F #, nhưng các FP khác cũng ổn) và phá vỡ thói quen cũ về tư duy toàn trạng thái (kiểu OOP). Một phần của câu trả lời được chọn cho câu hỏi ở đây đã nhắc lại việc tôi tìm kiếm bất kỳ bài viết nào xung quanh các vấn đề được giải quyết bằng các biểu diễn trạng thái trong OOP với các câu hỏi bất biến trong FP (Ví dụ: Hàng đợi với Nhà sản xuất & Người tiêu dùng). Bất kỳ suy nghĩ hoặc liên kết đều được chào đón? Cảm ơn trước.

Chỉnh sửa : Để làm rõ câu hỏi hơn một chút, các cấu trúc bất biến (ví dụ: queue) sẽ được chia sẻ đồng thời trên nhiều luồng (ví dụ: nhà sản xuất và người tiêu dùng) trong FP


Một cách để xử lý các vấn đề tương tranh là tạo các bản sao của hàng đợi mỗi lần (hơi tốn kém, nhưng hoạt động).
Công việc

Câu trả lời:


19

Mặc dù đôi khi nó diễn đạt theo cách đó, lập trình chức năng - không ngăn chặn các tính toán trạng thái. Những gì nó làm là buộc các lập trình viên phải làm cho nhà nước rõ ràng.

Ví dụ: chúng ta hãy lấy cấu trúc cơ bản của một số chương trình bằng cách sử dụng hàng đợi bắt buộc (trong một số ngôn ngữ giả):

q := Queue.new();
while (true) {
    if (Queue.is_empty(q)) {
        Queue.add(q, producer());
    } else {
        consumer(Queue.take(q));
    }
}

Cấu trúc tương ứng với cấu trúc dữ liệu hàng đợi chức năng (vẫn bằng ngôn ngữ bắt buộc, để giải quyết một sự khác biệt tại một thời điểm) sẽ giống như sau:

q := Queue.empty;
while (true) {
    if (q = Queue.empty) {
        q := Queue.add(q, producer());
    } else {
        (tail, element) := Queue.take(q);
        consumer(element);
        q := tail;
    }
}

Vì hàng đợi bây giờ là bất biến, nên đối tượng không thay đổi. Trong mã giả này, qchính nó là một biến; các bài tập q := Queue.add(…)q := taillàm cho nó trỏ đến một đối tượng khác. Giao diện của các hàm hàng đợi đã thay đổi: mỗi hàm phải trả về đối tượng hàng đợi mới là kết quả của hoạt động.

Trong một ngôn ngữ chức năng thuần túy, tức là trong một ngôn ngữ không có tác dụng phụ, bạn cần phải làm cho tất cả các trạng thái rõ ràng. Vì nhà sản xuất và người tiêu dùng có lẽ đang làm gì đó, trạng thái của họ cũng phải có trong giao diện của người gọi ở đây.

main_loop(q, other_state) {
    if (q = Queue.empty) {
        let (new_state, element) = producer(other_state);
        main_loop(Queue.add(q, element), new_state);
    } else {
        let (tail, element) = Queue.take(q);
        let new_state = consumer(other_state, element);
        main_loop(tail, new_state);
    }
}
main_loop(Queue.empty, initial_state)

Lưu ý làm thế nào bây giờ mọi phần của nhà nước được quản lý rõ ràng. Các hàm thao tác hàng đợi lấy một hàng đợi làm đầu vào và tạo ra một hàng đợi mới làm đầu ra. Các nhà sản xuất và người tiêu dùng cũng vượt qua trạng thái của họ.

Lập trình đồng thời không phù hợp rất tốt trong lập trình chức năng, nhưng nó phù hợp rất tốt xung quanh lập trình chức năng. Ý tưởng là chạy một loạt các nút tính toán riêng biệt và để chúng trao đổi thông điệp. Mỗi nút chạy một chương trình chức năng và trạng thái của nó thay đổi khi nó gửi và nhận tin nhắn.

Tiếp tục ví dụ, vì có một hàng đợi duy nhất, nó được quản lý bởi một nút cụ thể. Người tiêu dùng gửi cho nút đó một thông điệp để có được một phần tử. Các nhà sản xuất gửi cho nút đó một thông điệp để thêm một phần tử.

main_loop(q) =
    consumer->consume(q->take()) || q->add(producer->produce());
    main_loop(q)

Ngôn ngữ được công nghiệp hóa của một người dùng mà được đồng thời hóa là Erlang . Học Erlang chắc chắn là con đường dẫn đến sự giác ngộ - về lập trình đồng thời.

Mọi người chuyển sang ngôn ngữ không có tác dụng phụ ngay bây giờ!

¹ Thuật ngữ này có nhiều ý nghĩa; Ở đây tôi nghĩ rằng bạn đang sử dụng nó để có nghĩa là lập trình mà không có tác dụng phụ, và đó là ý nghĩa tôi cũng đang sử dụng.
² Lập trình với trạng thái ngầm là lập trình bắt buộc ; định hướng đối tượng là một mối quan tâm hoàn toàn trực giao.
³ Viêm, tôi biết, nhưng tôi có nghĩa là nó. Chủ đề với bộ nhớ chia sẻ là ngôn ngữ lắp ráp của lập trình đồng thời. Truyền tin nhắn dễ hiểu hơn rất nhiều và việc thiếu tác dụng phụ thực sự tỏa sáng ngay khi bạn giới thiệu đồng thời.
Và điều này xuất phát từ một ai đó không phải là một fan hâm mộ của Erlang, nhưng vì lý do khác.


2
+1 Câu trả lời đầy đủ hơn nhiều, mặc dù tôi cho rằng người ta có thể ngụy biện rằng Erlang không phải là ngôn ngữ thuần túy.
Rein Henrichs

1
@Rein Henrichs: Thật vậy. Trong thực tế, trong tất cả các ngôn ngữ chính hiện tại, Erlang là ngôn ngữ thực hiện nhất Định hướng đối tượng.
Jörg W Mittag

2
@ Jorg đồng ý. Mặc dù, một lần nữa, người ta có thể ngụy biện rằng FP và OO thuần túy là trực giao.
Rein Henrichs

Vì vậy, để thực hiện một hàng đợi bất biến trong một phần mềm đồng thời, các thông điệp cần phải được gửi và nhận giữa các nút. Nơi lưu trữ tin nhắn đang chờ xử lý?
mouviciel

Các phần tử hàng đợi @mouviciel được lưu trữ trong hàng đợi tin nhắn đến của nút. Cơ sở xếp hàng tin nhắn này là một tính năng cơ bản của cơ sở hạ tầng phân tán. Một thiết kế cơ sở hạ tầng thay thế hoạt động tốt cho đồng thời cục bộ nhưng không phải với các hệ thống phân tán là chặn người gửi cho đến khi người nhận sẵn sàng. Tôi nhận ra điều này không giải thích mọi thứ, nó sẽ mất một hoặc hai cuốn sách về lập trình đồng thời để giải thích điều này một cách đầy đủ.
Gilles 'SO- ngừng trở nên xấu xa'

4

Hành vi trạng thái trong ngôn ngữ FP được thực hiện như một sự chuyển đổi từ trạng thái trước sang trạng thái mới. Ví dụ, enqueue sẽ là một phép biến đổi từ hàng đợi và giá trị sang hàng đợi mới với giá trị được ghi lại. Dequeue sẽ là một chuyển đổi từ một hàng đợi thành một giá trị và một hàng đợi mới với giá trị bị loại bỏ. Các cấu trúc như các đơn nguyên đã được tạo ra để trừu tượng hóa sự chuyển đổi trạng thái này (và các kết quả tính toán khác) theo những cách hữu ích


3
Nếu đó là một hàng đợi mới cho mỗi hoạt động thêm / xóa, làm thế nào hai (hoặc nhiều) hoạt động không đồng bộ (luồng) chia sẻ hàng đợi? Đây có phải là một mô hình để trừu tượng hóa mới của hàng đợi?
venkram

Đồng thời là một câu hỏi hoàn toàn khác. Tôi không thể cung cấp một câu trả lời đầy đủ trong một bình luận.
Rein Henrichs

2
@Rein Henrichs: "không thể cung cấp câu trả lời đầy đủ trong một bình luận". Điều đó thường có nghĩa là bạn nên cập nhật câu trả lời để giải quyết các vấn đề liên quan đến bình luận.
S.Lott

Đồng thời cũng có thể là đơn âm, xem haskells Control.Concurrency.STM.
thay thế

1
@ S.Lott trong trường hợp này có nghĩa là OP nên hỏi một câu hỏi mới. Đồng thời là OT cho câu hỏi này, đó là về cấu trúc dữ liệu bất biến.
Rein Henrichs

2

... các vấn đề được giải quyết bằng các biểu diễn trạng thái trong OOP với các vấn đề bất biến trong FP (Ví dụ: Hàng đợi với Nhà sản xuất & Người tiêu dùng)

Câu hỏi của bạn là cái được gọi là "vấn đề XY". Cụ thể, khái niệm bạn trích dẫn (xếp hàng với nhà sản xuất & người tiêu dùng) thực sự là một giải pháp và không phải là "vấn đề" như bạn mô tả. Điều này giới thiệu một khó khăn bởi vì bạn đang yêu cầu thực hiện chức năng hoàn toàn của một cái gì đó vốn không tinh khiết. Vì vậy, câu trả lời của tôi bắt đầu với một câu hỏi: vấn đề bạn đang cố gắng giải quyết là gì?

Có nhiều cách để nhiều nhà sản xuất gửi kết quả của họ tới một người tiêu dùng chung. Có lẽ giải pháp rõ ràng nhất trong F # là biến người tiêu dùng thành đại lý (akaMailboxProcessor ) và đưa các nhà sản xuất Postkết quả của họ đến đại lý tiêu dùng. Điều này sử dụng một hàng đợi bên trong và nó không thuần túy (gửi tin nhắn trong F # là một tác dụng phụ không được kiểm soát, một tạp chất).

Tuy nhiên, có nhiều khả năng vấn đề tiềm ẩn là một cái gì đó giống như mô hình thu thập phân tán từ lập trình song song. Để giải quyết vấn đề này, bạn có thể tạo một mảng các giá trị đầu vào và sau đóArray.Parallel.map qua chúng và thu thập kết quả bằng cách sử dụng nối tiếp Array.reduce. Ngoài ra, bạn có thể sử dụng các hàm từ PSeqmô-đun để xử lý song song các phần tử của chuỗi.

Tôi cũng nên nhấn mạnh rằng không có gì sai với suy nghĩ nhà nước. Sự tinh khiết có những lợi thế nhưng nó chắc chắn không phải là thuốc chữa bách bệnh và bạn cũng nên tự nhận ra những thiếu sót của nó. Thật vậy, đây chính là lý do tại sao F # không phải là một ngôn ngữ chức năng thuần túy: vì vậy bạn có thể sử dụng tạp chất khi chúng thích hợp hơn.


1

Clojure có một khái niệm rất rõ về trạng thái và bản sắc, liên quan chặt chẽ đến sự tương tranh. Tính bất biến đóng một vai trò quan trọng, tất cả các giá trị trong Clojure là bất biến và có thể được truy cập thông qua các tài liệu tham khảo. Tài liệu tham khảo không chỉ là con trỏ đơn giản. Họ quản lý quyền truy cập vào giá trị và có nhiều loại trong số họ với các ngữ nghĩa khác nhau. Một tham chiếu có thể được sửa đổi để trỏ đến một giá trị mới (không thay đổi) và một thay đổi như vậy được đảm bảo là nguyên tử. Tuy nhiên, sau khi sửa đổi, tất cả các luồng khác vẫn hoạt động trên giá trị ban đầu, ít nhất là cho đến khi chúng truy cập lại tham chiếu.

Tôi đặc biệt khuyên bạn nên đọc một bài viết xuất sắc về trạng thái và bản sắc trong Clojure , nó giải thích các chi tiết tốt hơn nhiều sau đó tôi có thể.

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.