Các ngôn ngữ lập trình chức năng không cho phép tác dụng phụ?


10

Theo Wikipedia, các ngôn ngữ lập trình chức năng , đó là Tuyên bố, chúng không cho phép tác dụng phụ. Lập trình khai báo nói chung, cố gắng giảm thiểu hoặc loại bỏ các tác dụng phụ.

Ngoài ra, theo Wikipedia, một tác dụng phụ có liên quan đến những thay đổi trạng thái. Vì vậy, theo ngôn ngữ lập trình chức năng, theo nghĩa đó, chúng thực sự loại bỏ các tác dụng phụ, vì chúng không lưu trạng thái.

Nhưng, ngoài ra, một tác dụng phụ có một định nghĩa khác. Tác dụng phụ

có sự tương tác có thể quan sát được với các chức năng gọi của nó hoặc thế giới bên ngoài bên cạnh việc trả về một giá trị. Ví dụ, một chức năng cụ thể có thể sửa đổi biến toàn cục hoặc biến tĩnh, sửa đổi một trong các đối số của nó, đưa ra một ngoại lệ, ghi dữ liệu vào màn hình hoặc tệp, đọc dữ liệu hoặc gọi các hàm hiệu ứng phụ khác.

Theo nghĩa đó, các ngôn ngữ lập trình chức năng thực sự cho phép các tác dụng phụ, vì có vô số ví dụ về các chức năng ảnh hưởng đến thế giới bên ngoài của chúng, gọi các chức năng khác, đưa ra các ngoại lệ, ghi vào tệp, v.v.

Vì vậy, cuối cùng, các ngôn ngữ lập trình chức năng có cho phép tác dụng phụ hay không?

Hoặc, tôi không hiểu những gì đủ điều kiện là "tác dụng phụ", vì vậy các ngôn ngữ mệnh lệnh cho phép chúng và Tuyên bố không. Theo những điều trên và những gì tôi nhận được, không có ngôn ngữ nào loại bỏ tác dụng phụ, vì vậy hoặc tôi đang thiếu một cái gì đó về tác dụng phụ, hoặc định nghĩa Wikipedia là không chính xác.

Câu trả lời:


26

Lập trình chức năng bao gồm nhiều kỹ thuật khác nhau. Một số kỹ thuật là tốt với tác dụng phụ. Nhưng một khía cạnh quan trọng là lý luận tương đương : Nếu tôi gọi một hàm trên cùng một giá trị, tôi luôn nhận được kết quả tương tự. Vì vậy, tôi có thể thay thế một cuộc gọi hàm bằng giá trị trả về và có hành vi tương đương. Điều này làm cho nó dễ dàng hơn để lý do về chương trình, đặc biệt là khi gỡ lỗi.

Nếu chức năng có tác dụng phụ, điều này không hoàn toàn giữ. Giá trị trả về không tương đương với lệnh gọi hàm, vì giá trị trả về không chứa các tác dụng phụ.

Giải pháp là ngừng sử dụng phụ ảnh hưởng và mã hóa những hiệu ứng trong giá trị trả về . Các ngôn ngữ khác nhau có hệ thống hiệu ứng khác nhau. Ví dụ, Haskell sử dụng các đơn nguyên để mã hóa các hiệu ứng nhất định như đột biến IO hoặc Trạng thái. Các ngôn ngữ C / C ++ / Rust có một hệ thống loại có thể không cho phép đột biến một số giá trị.

Trong một ngôn ngữ bắt buộc, một print("foo")chức năng sẽ in một cái gì đó và không trả lại gì. Trong một ngôn ngữ chức năng thuần túy như Haskell, một printhàm cũng lấy một đối tượng đại diện cho trạng thái của thế giới bên ngoài và trả về một đối tượng mới đại diện cho trạng thái sau khi đã thực hiện đầu ra này. Một cái gì đó tương tự như newState = print "foo" oldState. Tôi có thể tạo bao nhiêu trạng thái mới từ trạng thái cũ tùy thích. Tuy nhiên, chỉ có một sẽ được sử dụng bởi chức năng chính. Vì vậy, tôi cần phải sắp xếp các trạng thái từ nhiều hành động bằng cách xâu chuỗi các chức năng. Để in foo bar, tôi có thể nói một cái gì đó như print "bar" (print "foo" originalState).

Nếu trạng thái đầu ra không được sử dụng, Haskell sẽ không thực hiện các hành động dẫn đến trạng thái đó, vì đó là ngôn ngữ lười biếng. Ngược lại, sự lười biếng này chỉ có thể vì tất cả các hiệu ứng được mã hóa rõ ràng dưới dạng giá trị trả về.

Lưu ý rằng Haskell là ngôn ngữ chức năng duy nhất được sử dụng phổ biến sử dụng tuyến đường này. Các ngôn ngữ chức năng khác bao gồm. gia đình Lisp, gia đình ML và các ngôn ngữ chức năng mới hơn như Scala không khuyến khích nhưng vẫn cho phép các tác dụng phụ - chúng có thể được gọi là ngôn ngữ chức năng bắt buộc.

Sử dụng tác dụng phụ cho I / O có lẽ là tốt. Thông thường, I / O (ngoài việc đăng nhập) chỉ được thực hiện ở ranh giới bên ngoài của hệ thống của bạn. Không có giao tiếp bên ngoài xảy ra trong logic kinh doanh của bạn. Sau đó có thể viết lõi của phần mềm của bạn theo kiểu thuần túy, trong khi vẫn thực hiện I / O không tinh khiết ở lớp vỏ ngoài. Điều này cũng có nghĩa là lõi có thể không trạng thái.

Không quốc tịch có một số lợi thế thực tế, chẳng hạn như tăng tính hợp lý và khả năng mở rộng. Điều này rất phổ biến cho các phụ trợ ứng dụng web. Bất kỳ trạng thái được giữ bên ngoài, trong một cơ sở dữ liệu chia sẻ. Điều này giúp cân bằng tải dễ dàng: Tôi không phải gắn phiên với một máy chủ cụ thể. Nếu tôi cần thêm máy chủ thì sao? Chỉ cần thêm một cái khác, bởi vì nó sử dụng cùng một cơ sở dữ liệu. Nếu một máy chủ gặp sự cố thì sao? Tôi có thể làm lại bất kỳ yêu cầu chờ xử lý trên máy chủ khác. Tất nhiên, vẫn còn trạng thái - trong cơ sở dữ liệu. Nhưng tôi đã làm cho nó rõ ràng và trích xuất nó, và có thể sử dụng một cách tiếp cận chức năng thuần túy trong nội bộ nếu tôi muốn.


Cảm ơn các câu trả lời chi tiết. Những gì tôi giữ như kết luận, là các tác dụng phụ không ảnh hưởng đến giá trị chức năng, do lý do phương trình, đây là lý do tại sao "ngôn ngữ chức năng không cho phép / giảm thiểu tác dụng phụ". Các hiệu ứng được nhúng trong các giá trị hàm ảnh hưởng và thay đổi trạng thái đã từng được lưu - hoặc được lưu bên ngoài lõi của chương trình. Ngoài ra, I / O xảy ra ở ranh giới bên ngoài của logic kinh doanh.
codebot

3
@codebot, Theo tôi thì không. Nếu được thực hiện đúng cách, các tác dụng phụ trong lập trình chức năng sẽ được phản ánh trong kiểu trả về của hàm. Ví dụ: nếu một chức năng có thể thất bại (nếu một tệp cụ thể không tồn tại hoặc không thể thiết lập kết nối cơ sở dữ liệu), thì kiểu trả về của hàm sẽ đóng gói lỗi, thay vì hàm có ngoại lệ. Hãy xem Lập trình định hướng đường sắt để biết ví dụ ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach

"... chúng có thể được gọi là ngôn ngữ chức năng bắt buộc.": Simon Peyton Jones đã viết "... Haskell là ngôn ngữ lập trình mệnh lệnh tốt nhất thế giới."
Giorgio

5

Không có ngôn ngữ lập trình loại bỏ tác dụng phụ. Tôi nghĩ tốt hơn nên nói rằng các ngôn ngữ khai báo có chứa các tác dụng phụ trong khi các ngôn ngữ bắt buộc thì không. Tuy nhiên, tôi không chắc chắn rằng bất kỳ điều nào trong số này nói về tác dụng phụ có sự khác biệt cơ bản giữa hai loại ngôn ngữ và điều đó thực sự giống như những gì bạn đang tìm kiếm.

Tôi nghĩ rằng nó giúp minh họa sự khác biệt với một ví dụ.

a = b + c

Dòng mã trên có thể được viết bằng hầu như bất kỳ ngôn ngữ nào, vậy làm thế nào chúng ta có thể xác định liệu chúng ta đang sử dụng ngôn ngữ mệnh lệnh hay ngôn ngữ khai báo? Các thuộc tính của dòng mã đó khác nhau như thế nào trong hai lớp ngôn ngữ?

Trong một ngôn ngữ bắt buộc (C, Java, Javascript, & c.) Dòng mã đó chỉ đại diện cho một bước trong một quy trình. Nó không cho chúng ta biết bất cứ điều gì về bản chất cơ bản của bất kỳ giá trị nào. Nó cho chúng ta biết rằng tại thời điểm sau dòng mã này (nhưng trước dòng tiếp theo) asẽ bằng bcộng cnhưng nó không cho chúng ta biết bất cứ điều gì atheo nghĩa lớn hơn.

Trong một ngôn ngữ khai báo (Haskell, Scheme, Excel, & c.) Dòng mã đó nói lên nhiều điều hơn thế. Nó thiết lập một mối quan hệ bất biến giữa avà hai đối tượng khác sao cho nó sẽ luôn luôn là trường hợp abằng với bcộng c. Lưu ý rằng tôi đã bao gồm Excel trong danh sách các ngôn ngữ khai báo vì ngay cả khi bhoặc cthay đổi giá trị, thực tế vẫn sẽ vẫn abằng với tổng của chúng.

Để tâm trí của tôi này , không tác dụng phụ hoặc tiểu bang, là những gì làm cho hai loại ngôn ngữ khác nhau. Trong một ngôn ngữ bắt buộc, bất kỳ dòng mã cụ thể nào cũng không cho bạn biết gì về ý nghĩa tổng thể của các biến trong câu hỏi. Nói cách khác, a = b + cchỉ có nghĩa là trong một khoảnh khắc rất ngắn trong thời gian, ađã xảy ra bằng tổng của bc.

Trong khi đó, trong các ngôn ngữ khai báo, mỗi dòng mã thiết lập một sự thật cơ bản sẽ tồn tại trong suốt toàn bộ thời gian của chương trình. Trong các ngôn ngữ này, a = b + ccho bạn biết rằng không có vấn đề gì xảy ra trong bất kỳ dòng mã nào khác asẽ luôn bằng tổng bc.

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.