Lập trình không đồng bộ trong các ngôn ngữ chức năng


31

Tôi chủ yếu là một lập trình viên C / C ++, điều đó có nghĩa là phần lớn kinh nghiệm của tôi là với các mô hình hướng đối tượng và hướng đối tượng. Tuy nhiên, như nhiều lập trình viên C ++ biết, C ++ đã chuyển trọng tâm qua nhiều năm sang phong cách chức năng, cuối cùng là việc bổ sung lambdas và đóng cửa trong C ++ 0x.

Bất kể, trong khi tôi có kinh nghiệm đáng kể về mã hóa theo kiểu chức năng bằng C ++, tôi có rất ít kinh nghiệm với các ngôn ngữ chức năng thực tế như Lisp, Haskell, v.v.

Gần đây tôi đã bắt đầu nghiên cứu các ngôn ngữ này, bởi vì ý tưởng "không có tác dụng phụ" trong các ngôn ngữ chức năng thuần túy luôn khiến tôi tò mò, đặc biệt là liên quan đến các ứng dụng của nó để tính toán đồng thời và tính toán phân tán.

Tuy nhiên, đến từ nền tảng C ++, tôi bối rối không biết làm thế nào mà philsophy "không có tác dụng phụ" này hoạt động với lập trình không đồng bộ. Theo lập trình không đồng bộ, tôi có nghĩa là bất kỳ kiểu khung / API / mã hóa nào gửi các trình xử lý sự kiện do người dùng cung cấp để xử lý các sự kiện xảy ra không đồng bộ (bên ngoài luồng chương trình.) Điều này bao gồm các thư viện không đồng bộ như Boost.ASIO hoặc thậm chí chỉ là C cũ đơn giản trình xử lý tín hiệu hoặc trình xử lý sự kiện Java GUI.

Một điểm chung của tất cả những điểm chung là bản chất của lập trình không đồng bộ dường như đòi hỏi phải tạo ra các hiệu ứng phụ (trạng thái), để dòng chính của chương trình nhận ra rằng một trình xử lý sự kiện không đồng bộ đã được gọi. Thông thường, trong một khung như Boost.ASIO, một trình xử lý sự kiện thay đổi trạng thái của một đối tượng, do đó hiệu ứng của sự kiện được lan truyền vượt quá thời gian sống của chức năng xử lý sự kiện. Thực sự, một người xử lý sự kiện có thể làm gì khác? Nó không thể "trả lại" một giá trị cho điểm gọi, vì không có điểm gọi. Trình xử lý sự kiện không phải là một phần của luồng chính của chương trình, vì vậy cách duy nhất nó có thể có bất kỳ ảnh hưởng nào đến chương trình thực tế là thay đổi một số trạng thái (hoặc khác longjmpsang điểm thực thi khác).

Vì vậy, có vẻ như lập trình không đồng bộ là tất cả về việc tạo ra các tác dụng phụ không đồng bộ. Điều này dường như hoàn toàn mâu thuẫn với các mục tiêu của lập trình chức năng. Làm thế nào hai mô hình này được điều hòa (trong thực tế) trong các ngôn ngữ chức năng?


3
Ồ, tôi vừa mới viết một câu hỏi thích điều này và không biết làm thế nào để đặt nó và sau đó thấy điều này trong các gợi ý!
Amogh Talpallikar

Câu trả lời:


11

Tất cả logic của bạn là âm thanh, ngoại trừ việc tôi nghĩ rằng sự hiểu biết của bạn về lập trình chức năng là một chút quá cực đoan. Trong lập trình chức năng trong thế giới thực, giống như lập trình hướng đối tượng, hoặc bắt buộc là về tư duy và cách bạn tiếp cận vấn đề. Bạn vẫn có thể viết chương trình theo tinh thần lập trình chức năng trong khi sửa đổi trạng thái ứng dụng.

Trong thực tế, bạn phải sửa đổi trạng thái ứng dụng để thực sự làm bất cứ điều gì. Các anh chàng Haskell sẽ cho bạn biết các chương trình của họ là "thuần túy" bởi vì họ bao gồm tất cả các thay đổi trạng thái của họ trong một đơn nguyên. Tuy nhiên, các chương trình của họ vẫn tương tác với thế giới bên ngoài. (Nếu không thì vấn đề là gì!)

Chức năng lập trình nhấn mạnh "không có tác dụng phụ" khi nó có ý nghĩa. Tuy nhiên, để thực hiện lập trình trong thế giới thực, như bạn đã nói, bạn cần phải sửa đổi trạng thái của thế giới. (Ví dụ: trả lời các sự kiện, ghi vào đĩa, v.v.)

Để biết thêm thông tin về lập trình không đồng bộ trong các ngôn ngữ chức năng, tôi khuyên bạn nên xem xét các quy trình làm việc không đồng bộ của F # mô hình lập trình trình . Nó cho phép bạn viết các chương trình chức năng trong khi ẩn tất cả các chi tiết lộn xộn của quá trình chuyển đổi luồng trong thư viện. (Theo cách rất giống với các phong cách đơn nguyên của Haskell.)

Nếu 'phần thân' của luồng chỉ đơn giản là tính toán một giá trị, thì sinh ra nhiều luồng và để chúng tính toán các giá trị song song vẫn nằm trong mô hình chức năng.


5
Ngoài ra: nhìn vào Erlang giúp. Ngôn ngữ rất đơn giản, thuần túy (tất cả dữ liệu là bất biến) và tất cả là về xử lý không đồng bộ.
9000

Về cơ bản sau khi hiểu được lợi ích của cách tiếp cận chức năng và chỉ thay đổi trạng thái khi có vấn đề sẽ tự động đảm bảo rằng ngay cả khi bạn làm việc nói gì đó như Java, bạn sẽ biết khi nào nên sửa đổi trạng thái và cách kiểm soát những điều đó.
Amogh Talpallikar

Không đồng ý - thực tế là chương trình được tạo từ các hàm 'thuần' không có nghĩa là nó không lặp lại với thế giới bên ngoài, điều đó có nghĩa là mọi hàm trong một chương trình cho một bộ đối số sẽ luôn trả về cùng một kết quả và điều này là (độ tinh khiết) là một vấn đề lớn, bởi vì, từ quan điểm thực tế - chương trình như vậy sẽ ít lỗi hơn, 'dễ kiểm chứng hơn', việc thực hiện thành công các chức năng có thể được chứng minh bằng toán học.
Gill Bates

8

Đây là một câu hỏi hấp dẫn. Theo tôi, điều thú vị nhất là cách tiếp cận được áp dụng trong Clojure và được giải thích trong video này:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

Về cơ bản, "giải pháp" được đề xuất như sau:

  • Bạn viết hầu hết mã của mình dưới dạng các hàm "thuần" cổ điển với cấu trúc dữ liệu không thay đổi và không có tác dụng phụ
  • Các tác dụng phụ được phân lập thông qua việc sử dụng các tham chiếu được quản lý để kiểm soát thay đổi theo quy tắc bộ nhớ giao dịch phần mềm (tức là tất cả các cập nhật của bạn về trạng thái có thể thay đổi diễn ra trong một giao dịch bị cô lập phù hợp)
  • Nếu bạn xem quan điểm này về thế giới, bạn có thể xem các "sự kiện" không đồng bộ là tác nhân kích hoạt cập nhật giao dịch của trạng thái có thể thay đổi trong đó bản cập nhật là một hàm thuần túy.

Có lẽ tôi đã không thể hiện ý tưởng rõ ràng như những người khác đã làm, nhưng tôi hy vọng điều này mang lại ý tưởng chung - về cơ bản, nó sử dụng hệ thống STM đồng thời để cung cấp "cầu nối" giữa lập trình chức năng thuần túy và xử lý sự kiện không đồng bộ.


6

Một lưu ý: một ngôn ngữ chức năng là thuần túy, nhưng thời gian chạy của nó thì không.

Ví dụ, thời gian chạy của Haskell liên quan đến hàng đợi, ghép kênh, thu gom rác, v.v ... tất cả đều không thuần túy.

Một ví dụ điển hình là sự lười biếng. Haskell hỗ trợ đánh giá lười biếng (thực tế đó là mặc định). Bạn tạo ra một giá trị lười biếng bằng cách chuẩn bị một thao tác, sau đó bạn có thể tạo nhiều bản sao của giá trị này và nó vẫn "lười biếng" miễn là không bắt buộc. Khi kết quả là cần thiết hoặc nếu thời gian chạy tìm thấy một thời gian, giá trị thực sự được tính toán và trạng thái của đối tượng lười biếng thay đổi để phản ánh rằng không còn cần phải thực hiện tính toán (một lần nữa) để có kết quả. Bây giờ nó có sẵn thông qua tất cả các tài liệu tham khảo, vì vậy trạng thái của đối tượng đã thay đổi, mặc dù đó là một ngôn ngữ thuần túy.


2

Tôi bối rối không biết làm thế nào mà philsophy "không có tác dụng phụ" này hoạt động với lập trình không đồng bộ. Theo lập trình không đồng bộ, ý tôi là ...

Đó sẽ là điểm, sau đó.

Một âm thanh, không có kiểu hiệu ứng phụ không tương thích với các khung phụ thuộc vào trạng thái. Tìm một khuôn khổ mới.

Ví dụ, tiêu chuẩn WSGI của Python cho phép chúng tôi xây dựng không có ứng dụng hiệu ứng phụ.

Ý tưởng là các "thay đổi trạng thái" khác nhau được phản ánh bởi một môi trường của các giá trị có thể được xây dựng tăng dần. Mỗi yêu cầu là một đường dẫn của các biến đổi.


"Cho phép xây dựng không có ứng dụng phụ" Tôi nghĩ rằng có một từ bị thiếu ở đó ở đâu đó.
Christopher Mahan

1

Học được cách đóng gói từ Borland C ++ sau khi học C, khi Borland C ++ thiếu các mẫu kích hoạt tính tổng quát, mô hình định hướng đối tượng khiến tôi không yên tâm. Một cách tự nhiên hơn để tính toán dữ liệu dường như lọc qua các đường ống. Luồng ngoài có danh tính riêng biệt và độc lập với luồng đầu vào bất biến bên trong, thay vì được coi là hiệu ứng phụ, tức là mọi nguồn dữ liệu (hoặc bộ lọc) đều tự động từ các nguồn khác. Phím nhấn (một sự kiện ví dụ) đã ràng buộc các kết hợp đầu vào của người dùng không đồng bộ với các mã phím có sẵn. Các hàm hoạt động dựa trên các đối số tham số đầu vào và trạng thái được đóng gói bởi lớp chỉ là lối tắt để tránh việc chuyển các đối số lặp đi lặp lại một cách rõ ràng giữa các tập hợp nhỏ của hàm, bên cạnh việc đặt trước bối cảnh bị ràng buộc ngăn chặn việc lạm dụng các đối số đó khỏi bất kỳ hàm tùy ý nào.

Tuân thủ cứng nhắc đến mô hình cụ thể gây ra sự bất tiện để đối phó với sự trừu tượng bị rò rỉ, ví dụ. thời gian chạy thương mại như JRE, DirectX, .net đề xuất mục tiêu hướng đối tượng quan trọng nhất. Để hạn chế sự bất tiện này, các ngôn ngữ hoặc lựa chọn các đơn nguyên tinh vi học thuật như Haskell, hoặc hỗ trợ đa mô hình linh hoạt như F # cuối cùng đã có. Trừ khi đóng gói là hữu ích từ một số trường hợp sử dụng thừa kế, cách tiếp cận đa mô hình có thể là sự thay thế vượt trội so với một số mẫu lập trình cụ thể, đôi khi phức tạp.

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.