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 print
hà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.