Được đóng cửa với các tác dụng phụ có được coi là phong cách chức năng của thành phố không?


9

Nhiều ngôn ngữ lập trình hiện đại hỗ trợ một số khái niệm về đóng , tức là một đoạn mã (một khối hoặc một hàm) mà

  1. Có thể được coi là một giá trị và do đó được lưu trữ trong một biến, được chuyển qua các phần khác nhau của mã, được định nghĩa trong một phần của chương trình và được gọi trong một phần hoàn toàn khác của cùng một chương trình.
  2. Có thể nắm bắt các biến từ bối cảnh được xác định và truy cập chúng khi nó được gọi sau này (có thể trong một bối cảnh hoàn toàn khác).

Dưới đây là một ví dụ về việc đóng cửa được viết bằng Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Hàm bằng chữ x => x >= lowerBoundchứa biến tự do lowerBound, được đóng (ràng buộc) bởi đối số của hàm filterListcó cùng tên. Việc đóng được truyền cho phương thức thư viện filter, có thể gọi nó nhiều lần như một hàm bình thường.

Tôi đã đọc rất nhiều câu hỏi và câu trả lời trên trang web này và, theo tôi hiểu, thuật ngữ đóng thường được tự động liên kết với lập trình chức năng và phong cách lập trình chức năng.

Định nghĩa của lập trình hàm trên wikipedia đọc:

Trong khoa học máy tính, lập trình chức năng là một mô hình lập trình coi việc tính toán là việc đánh giá các hàm toán học và tránh trạng thái và dữ liệu đột biến. Nó nhấn mạnh việc áp dụng các chức năng, trái ngược với phong cách lập trình mệnh lệnh, trong đó nhấn mạnh đến những thay đổi về trạng thái.

và hơn thế nữa

[...] trong mã chức năng, giá trị đầu ra của hàm chỉ phụ thuộc vào các đối số được nhập vào hàm [...]. Loại bỏ các tác dụng phụ có thể giúp dễ hiểu và dự đoán hành vi của chương trình hơn, đây là một trong những động lực chính cho sự phát triển của lập trình chức năng.

Mặt khác, nhiều cấu trúc bao đóng được cung cấp bởi các ngôn ngữ lập trình cho phép một bao đóng để nắm bắt các biến không cục bộ và thay đổi chúng khi đóng được gọi, do đó tạo ra hiệu ứng phụ trên môi trường mà chúng được xác định.

Trong trường hợp này, các bao đóng thực hiện ý tưởng đầu tiên về lập trình chức năng (các hàm là các thực thể hạng nhất có thể được di chuyển xung quanh như các giá trị khác) nhưng bỏ qua ý tưởng thứ hai (tránh tác dụng phụ).

Việc sử dụng các bao đóng này với các tác dụng phụ được coi là kiểu chức năng hay là các bao đóng được coi là một cấu trúc tổng quát hơn có thể được sử dụng cho cả kiểu lập trình chức năng và không chức năng? Có tài liệu nào về chủ đề này không?

LƯU Ý QUAN TRỌNG

Tôi không đặt câu hỏi về tính hữu ích của tác dụng phụ hay việc đóng cửa với tác dụng phụ. Ngoài ra, tôi không quan tâm đến một cuộc thảo luận về những lợi thế / bất lợi của việc đóng cửa có hoặc không có tác dụng phụ.

Tôi chỉ quan tâm để biết nếu sử dụng các bao đóng như vậy vẫn được coi là phong cách chức năng bởi người đề xuất lập trình chức năng hay ngược lại, việc sử dụng chúng không được khuyến khích khi sử dụng một phong cách chức năng.


3
Trong một phong cách / ngôn ngữ hoàn toàn chức năng, các tác dụng phụ là không thể ... vì vậy tôi đoán đó sẽ là vấn đề: ở mức độ tinh khiết nào mà người ta coi mã là chức năng?
Steven Evers

@SnOrfus: Ở mức 100%?
Giorgio

1
Tác dụng phụ là không thể trong một ngôn ngữ chức năng thuần túy, do đó sẽ trả lời câu hỏi của bạn.
Steven Evers

@SnOrfus: Trong nhiều ngôn ngữ, việc đóng cửa được coi là một cấu trúc lập trình chức năng, vì vậy tôi đã hơi bối rối. Dường như với tôi rằng (1) việc sử dụng của họ nói chung hơn là chỉ thực hiện FP và (2) sử dụng các bao đóng không tự động đảm bảo một người đang sử dụng một kiểu chức năng.
Giorgio

Downvoter có thể để lại một lưu ý về cách cải thiện câu hỏi này?
Giorgio

Câu trả lời:


7

Không; định nghĩa của mô hình chức năng là về việc thiếu trạng thái và hoàn toàn thiếu tác dụng phụ. Nó không phải là về các chức năng bậc cao, đóng cửa, thao tác danh sách được hỗ trợ ngôn ngữ hoặc các tính năng ngôn ngữ khác ...

Tên của lập trình hàm xuất phát từ khái niệm toán học của các hàm - các cuộc gọi lặp lại trên cùng một đầu vào luôn cho cùng một đầu ra - hàm nullipotent. Điều này chỉ có thể đạt được nếu dữ liệu là bất biến . Để dễ dàng phát triển, các hàm trở nên có thể thay đổi (hàm thay đổi, dữ liệu vẫn không thay đổi) và do đó, khái niệm hàm bậc cao (hàm trong toán học, ví dụ như đạo hàm) - một hàm lấy đầu vào là hàm khác. Đối với khả năng các hàm được mang theo và chuyển qua làm đối số, các hàm hạng nhất đã được thông qua; Theo những điều này, để tăng cường hơn nữa năng suất, đóng cửa xuất hiện.

Tất nhiên, đây là một quan điểm rất đơn giản.


3
Các hàm bậc cao hoàn toàn không liên quan gì đến tính biến đổi, cũng không phải các hàm hạng nhất. Tôi có thể vượt qua các chức năng và xây dựng các chức năng khác từ chúng trong Haskell một cách dễ dàng, mà không có bất kỳ trạng thái đột biến cũng như tác dụng phụ nào.
tdammers

@tdammers Có lẽ tôi đã không thể hiện rất rõ ràng; Khi tôi nói các chức năng trở nên có thể thay đổi, tôi không đề cập đến khả năng biến đổi dữ liệu mà thực tế là hành vi của một chức năng có thể được thay đổi (bởi một chức năng bậc cao).
m3th0dman

Hàm bậc cao hơn không phải thay đổi hàm hiện có; hầu hết các ví dụ trong sách giáo khoa kết hợp các hàm hiện có thành các hàm mới mà không sửa đổi chúng - mapví dụ, lấy một hàm, áp dụng nó vào danh sách và trả về danh sách kết quả. mapkhông sửa đổi bất kỳ đối số nào của nó, nó không thay đổi hành vi của hàm mà nó lấy làm đối số, nhưng nó chắc chắn là hàm bậc cao hơn - nếu bạn áp dụng một phần, chỉ với tham số hàm, bạn đã xây dựng một chức năng mới hoạt động trên một danh sách, nhưng vẫn không có đột biến xảy ra.
tdammers

@tdammers: Chính xác: tính biến đổi chỉ được sử dụng trong các ngôn ngữ bắt buộc hoặc đa mô hình. Mặc dù những cái này có thể có khái niệm về hàm bậc cao (hoặc phương thức) và của một bao đóng, chúng từ bỏ tính bất biến. Điều này có thể hữu ích (tôi không nói người ta không nên làm điều đó) nhưng câu hỏi của tôi là liệu bạn vẫn có thể gọi chức năng này. Điều đó có nghĩa là, nói chung, đóng cửa không phải là một khái niệm chức năng nghiêm ngặt.
Giorgio

@Giorgio: hầu hết các ngôn ngữ FP cổ điển làm có mutability; Haskell là người duy nhất tôi có thể nghĩ ra khỏi đỉnh đầu mà không cho phép khả năng biến đổi chút nào. Tuy nhiên, tránh trạng thái đột biến là một giá trị quan trọng trong FP.
tdammers

9

Không. "Kiểu chức năng" ngụ ý lập trình không có hiệu ứng phụ.

Để biết lý do tại sao, hãy xem bài viết trên blog của Eric Lippert về ForEach<T>phương pháp mở rộng và tại sao Microsoft không bao gồm một phương pháp trình tự như trong Linq :

Tôi phản đối triết lý khi cung cấp một phương pháp như vậy, vì hai lý do.

Lý do đầu tiên là làm như vậy vi phạm các nguyên tắc lập trình chức năng mà tất cả các toán tử trình tự khác dựa trên. Rõ ràng mục đích duy nhất của một cuộc gọi đến phương pháp này là gây ra tác dụng phụ. Mục đích của biểu thức là tính toán một giá trị, không gây ra tác dụng phụ. Mục đích của một tuyên bố là gây ra tác dụng phụ. Trang web cuộc gọi của điều này sẽ trông rất giống một biểu thức (mặc dù, phải thừa nhận, vì phương thức này không có giá trị trả về, nên biểu thức chỉ có thể được sử dụng trong một biểu thức tuyên bố của ngữ cảnh.) Nó không phù hợp với tôi làm cho toán tử trình tự một và duy nhất chỉ hữu ích cho các tác dụng phụ của nó.

Lý do thứ hai là làm như vậy không thêm sức mạnh đại diện mới cho ngôn ngữ. Làm điều này cho phép bạn viết lại mã hoàn toàn rõ ràng này:

foreach(Foo foo in foos){ statement involving foo; }

vào mã này:

foos.ForEach((Foo foo)=>{ statement involving foo; });

trong đó sử dụng gần như chính xác các nhân vật theo thứ tự hơi khác nhau. Tuy nhiên, phiên bản thứ hai khó hiểu hơn, khó gỡ lỗi hơn và giới thiệu ngữ nghĩa đóng, do đó có khả năng thay đổi tuổi thọ đối tượng theo những cách tinh tế.


1
Mặc dù, tôi đồng ý với anh ta ... lập luận của anh ta yếu đi một chút khi xem xét ParallelQuery<T>.ForAll(...). Việc thực hiện như vậy IEnumerable<T>.ForEach(...)là cực kỳ hữu ích để gỡ lỗi các ForAllcâu lệnh (thay thế ForAllbằng ForEachvà loại bỏ AsParallel()và bạn có thể dễ dàng bước qua / gỡ lỗi hơn)
Steven Evers

Rõ ràng Joe Duffy có những ý tưởng khác nhau. : D
Robert Harvey

Scala cũng không thực thi độ tinh khiết: bạn có thể chuyển một bao đóng không thuần túy cho một hàm bậc cao. Ấn tượng của tôi là ý tưởng về việc đóng cửa không dành riêng cho lập trình chức năng nhưng nó là một ý tưởng tổng quát hơn.
Giorgio

5
@Giorgio: Đóng cửa không phải là thuần khiết để vẫn được coi là đóng cửa. Tuy nhiên, chúng phải thuần túy để được coi là "Phong cách chức năng".
Robert Harvey

3
@Giorgio: Thật sự hữu ích khi có thể đóng cửa xung quanh trạng thái có thể thay đổi và tác dụng phụ và chuyển nó sang chức năng khác. Nó chỉ trái với mục tiêu của lập trình chức năng. Tôi nghĩ rằng rất nhiều sự nhầm lẫn đến từ sự hỗ trợ ngôn ngữ tao nhã cho lambdas là phổ biến trong các ngôn ngữ chức năng.
GlenPeterson

0

Chắc chắn lập trình hàm đưa các hàm hạng nhất lên cấp độ khái niệm tiếp theo, nhưng khai báo các hàm ẩn danh hoặc truyền các hàm cho các hàm khác không nhất thiết là một điều lập trình hàm. Trong C, mọi thứ là một số nguyên. Một số, một con trỏ tới dữ liệu, một con trỏ tới một hàm ... tất cả chỉ là ints. Bạn có thể chuyển con trỏ hàm đến các hàm khác, tạo danh sách các con trỏ hàm ... Heck, nếu bạn làm việc trong ngôn ngữ lắp ráp, các hàm thực sự chỉ là địa chỉ trong bộ nhớ nơi lưu trữ các khối lệnh của máy. Đặt một hàm là một chi phí phụ cho những người cần một trình biên dịch để viết mã. Vì vậy, các chức năng là "hạng nhất" theo nghĩa đó trong một ngôn ngữ hoàn toàn không có chức năng.

Nếu điều duy nhất bạn làm là tính toán các công thức toán học trong REPL, thì bạn có thể hoàn toàn hoạt động với ngôn ngữ của mình. Nhưng hầu hết các chương trình kinh doanh đều có tác dụng phụ. Mất tiền trong khi chờ đợi một chương trình dài hoàn thành là một tác dụng phụ. Thực hiện bất kỳ hành động bên ngoài nào: ghi vào tệp, cập nhật cơ sở dữ liệu, ghi nhật ký các sự kiện theo thứ tự, v.v ... yêu cầu thay đổi trạng thái. Chúng tôi có thể tranh luận về việc liệu trạng thái có thực sự thay đổi hay không nếu bạn gói gọn các hành động này trong các trình bao bọc bất biến làm giảm tác dụng phụ để mã của bạn không phải lo lắng về chúng. Nhưng nó giống như thảo luận về việc một cái cây có gây ra tiếng động hay không nếu nó rơi vào rừng mà không có ai ở đó để nghe. Thực tế là cây bắt đầu thẳng đứng và kết thúc trên mặt đất. Trạng thái được thay đổi khi công cụ được hoàn thành, ngay cả khi chỉ báo cáo rằng công cụ đã được thực hiện.

Vì vậy, chúng ta chỉ còn lại một thang đo độ tinh khiết chức năng, không phải màu đen và trắng, mà là màu xám. Và trên quy mô đó, càng ít tác dụng phụ, càng ít biến đổi, càng tốt (nhiều chức năng hơn).

Nếu bạn hoàn toàn yêu cầu tác dụng phụ hoặc trạng thái có thể thay đổi trong mã chức năng khác của bạn, bạn cố gắng đóng gói hoặc nó từ phần còn lại của chương trình của bạn một cách tốt nhất có thể. Sử dụng một bao đóng (hoặc bất cứ thứ gì khác) để tiêm các tác dụng phụ hoặc trạng thái có thể thay đổi vào các hàm thuần túy khác là phản đề của lập trình chức năng. Ngoại lệ duy nhất có thể là nếu việc đóng cửa là cách hiệu quả nhất để gói gọn các tác dụng phụ từ mã mà nó được truyền vào. Nó vẫn không phải là "lập trình chức năng" nhưng nó có thể là tủ quần áo bạn có thể có trong một số tình huống.

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.