Là lập trình chức năng một superset của đối tượng định hướng?


26

Tôi càng lập trình nhiều chức năng, tôi càng cảm thấy như nó thêm một lớp trừu tượng giống như cách một lớp hành tây - tất cả bao gồm các lớp trước đó.

Tôi không biết điều này có đúng hay không nên từ bỏ các nguyên tắc OOP mà tôi đã làm việc trong nhiều năm, có ai có thể giải thích chức năng hoạt động như thế nào hoặc không mô tả chính xác bất kỳ trong số chúng: Đóng gói, Trừu tượng, Kế thừa, Đa hình

Tôi nghĩ rằng tất cả chúng ta đều có thể nói, vâng, nó được đóng gói thông qua các bộ dữ liệu, hoặc các bộ dữ liệu được tính kỹ thuật là thực tế của "lập trình chức năng" hay chúng chỉ là một tiện ích của ngôn ngữ?

Tôi biết Haskell có thể đáp ứng yêu cầu "giao diện", nhưng một lần nữa không chắc chắn nếu phương thức đó là một thực tế của chức năng? Tôi đoán rằng thực tế là functor có một cơ sở toán học mà bạn có thể nói rằng đó là một định nghĩa được xây dựng trong kỳ vọng của chức năng, có lẽ?

Xin vui lòng, chi tiết cách bạn nghĩ chức năng thực hiện hoặc không đáp ứng 4 nguyên tắc của OOP.

Chỉnh sửa: Tôi hiểu sự khác biệt giữa mô hình chức năng và mô hình hướng đối tượng là tốt và nhận ra có rất nhiều ngôn ngữ đa ngôn ngữ ngày nay có thể làm cả hai. Tôi thực sự chỉ tìm kiếm các định nghĩa về cách fp hoàn toàn (nghĩ theo chủ nghĩa thuần túy, như haskell) có thể làm bất kỳ điều gì trong 4 điều được liệt kê, hoặc tại sao nó không thể làm bất kỳ điều gì trong số chúng. tức là "Đóng gói có thể được thực hiện với việc đóng cửa" (hoặc nếu tôi sai trong niềm tin này, vui lòng nêu rõ lý do tại sao).


7
4 nguyên tắc đó không "làm" OOP. OOP chỉ đơn giản là "giải quyết" chúng bằng cách sử dụng các lớp, hiearchy lớp và các thể hiện của chúng. Nhưng tôi cũng muốn có một câu trả lời nếu có nhiều cách để đạt được những điều đó trong lập trình chức năng.
Euphoric

2
@Euphoric Tùy thuộc vào định nghĩa, nó tạo ra OOP.
Konrad Rudolph

2
@KonradRudolph Tôi biết rất nhiều người yêu cầu những điều này và những lợi ích mà họ mang lại như những đặc tính độc đáo của OOP. Giả sử "đa hình" có nghĩa là "đa hình phụ", tôi có thể đi với hai dạng sau là không thể tách rời với OOP. Nhưng tôi vẫn chưa gặp phải một định nghĩa hữu ích về đóng gói và trừu tượng loại trừ các phương pháp tiếp cận không OOP quyết định. Bạn có thể ẩn chi tiết và giới hạn quyền truy cập vào chúng ngay cả trong Haskell. Và Haskell cũng có tính đa hình ad-hoc, chỉ không phải là đa hình phụ - câu hỏi đặt ra là liệu bit "subtype" có quan trọng không?

1
@KonradRudolph Điều đó không làm cho nó dễ chấp nhận hơn. Nếu bất cứ điều gì, nó được khuyến khích để tăng cường và đưa ra những lý do để xem xét lại nó.

1
Trừu tượng là nội tại đối với bất kỳ chương trình nào, ít nhất là bất kỳ chương trình nào ngoài mã máy thô. Đóng gói đã có từ rất lâu trước OOP, và nó là bản chất của lập trình chức năng. Một ngôn ngữ chức năng không bắt buộc phải bao gồm cú pháp rõ ràng cho tính kế thừa hoặc đa hình. Tôi đoán rằng thêm vào 'không'.
sdenham

Câu trả lời:


44

Lập trình chức năng không phải là một lớp trên OOP; đó là một mô hình hoàn toàn khác Có thể thực hiện OOP theo kiểu chức năng (F # được viết cho chính xác mục đích này) và ở đầu kia của quang phổ bạn có những thứ như Haskell, từ chối rõ ràng các nguyên tắc định hướng đối tượng.

Bạn có thể thực hiện đóng gói và trừu tượng hóa trong bất kỳ ngôn ngữ nào đủ nâng cao để hỗ trợ các mô-đun và chức năng. OO cung cấp các cơ chế đặc biệt để đóng gói, nhưng đó không phải là thứ vốn có của OO. Điểm của OO là cặp thứ hai mà bạn đề cập: tính kế thừa và tính đa hình. Khái niệm này được chính thức gọi là sự thay thế Liskov và bạn không thể có được nó mà không có sự hỗ trợ ở cấp độ ngôn ngữ cho lập trình hướng đối tượng. (Vâng, có thể giả mạo nó trong một số trường hợp, nhưng bạn mất rất nhiều lợi thế mà OO mang lại cho bàn.)

Lập trình chức năng không tập trung vào sự thay thế Liskov. Nó tập trung vào việc tăng mức độ trừu tượng và giảm thiểu việc sử dụng trạng thái và thói quen có thể thay đổi với "tác dụng phụ", đây là thuật ngữ mà các lập trình viên chức năng muốn sử dụng để tạo ra các thói quen thực sự làm gì đó (trái ngược với việc tính toán một cái gì đó) đáng sợ. Nhưng một lần nữa, chúng là những mô hình hoàn toàn riêng biệt, có thể được sử dụng cùng nhau hoặc không, tùy thuộc vào ngôn ngữ và kỹ năng của lập trình viên.


1
Vâng, tính kế thừa (trong những trường hợp đặc biệt hiếm khi cần thiết) có thể đạt được so với thành phần và nó sạch hơn so với thừa kế cấp độ loại. Đa hình là tự nhiên, đặc biệt là sự hiện diện của các loại đa hình. Nhưng tất nhiên tôi đồng ý rằng FP không liên quan gì đến OOP và các nguyên tắc của nó.
SK-logic

Luôn luôn có thể giả mạo nó - bạn có thể thực hiện các đối tượng bằng bất kỳ ngôn ngữ nào bạn chọn. Tôi đồng ý với mọi thứ khác mặc dù :)
Eliot Ball

5
Tôi không nghĩ thuật ngữ "tác dụng phụ" được đặt ra (hoặc chủ yếu được sử dụng) bởi các lập trình viên chức năng.
sepp2k

4
@ sepp2k: Anh ấy đã không nói rằng họ đã phát minh ra thuật ngữ này, chỉ là họ sử dụng nó với âm điệu gần giống như người ta thường sử dụng để nói đến những đứa trẻ không chịu rời khỏi bãi cỏ của chúng.
Aaronaught

16
@Aaronaught không phải là những đứa trẻ trên bãi cỏ của tôi làm phiền tôi, đó là tác dụng phụ đẫm máu của chúng! Nếu họ chỉ dừng lại đột biến trên khắp bãi cỏ của tôi, tôi sẽ không bận tâm đến họ.
Jimmy Hoffa

10

Tôi thấy trực giác sau đây hữu ích để so sánh OOP và FP.

Thay vì coi FP là một siêu thay thế của OOP, hãy nghĩ về OOP và FP như hai cách khác để xem xét một mô hình tính toán cơ bản tương tự mà bạn có:

  1. Một số thao tác được thực thi,
  2. một số đối số đầu vào cho hoạt động,
  3. một số dữ liệu / tham số cố định có thể ảnh hưởng đến định nghĩa của hoạt động,
  4. một số giá trị kết quả, và
  5. có thể là tác dụng phụ

Trong OOP, điều này được ghi lại bởi

  1. Một phương thức được thực thi,
  2. các đối số đầu vào của một phương thức,
  3. đối tượng mà phương thức được gọi, chứa một số dữ liệu cục bộ dưới dạng các biến thành viên,
  4. giá trị trả về của phương thức (có thể là void),
  5. tác dụng phụ của phương pháp.

Trong FP, điều này được ghi lại bởi

  1. Một đóng cửa được thực thi,
  2. các đối số đầu vào của bao đóng,
  3. các biến bị bắt của đóng cửa,
  4. giá trị trả lại của đóng cửa,
  5. tác dụng phụ có thể đóng cửa (trong các ngôn ngữ thuần túy như Haskell, điều này xảy ra theo cách rất được kiểm soát).

Với cách giải thích này, một đối tượng có thể được xem như là một tập hợp các bao đóng (các phương thức của nó) tất cả đều nắm bắt các biến không cục bộ giống nhau (các biến thành viên của đối tượng chung cho tất cả các bao đóng trong tập hợp). Khung nhìn này cũng được hỗ trợ bởi thực tế là trong các ngôn ngữ hướng đối tượng, các bao đóng thường được mô hình hóa thành các đối tượng với chính xác một phương thức.

Tôi nghĩ rằng các quan điểm khác nhau bắt nguồn từ thực tế là chế độ xem hướng đối tượng được tập trung vào các đối tượng (dữ liệu) trong khi chế độ xem chức năng được tập trung vào các chức năng / đóng (các hoạt động).


8

Nó phụ thuộc vào người bạn yêu cầu định nghĩa về OOP. Hỏi năm người và bạn có thể sẽ nhận được sáu định nghĩa. Wikipedia nói :

Nỗ lực tìm kiếm một định nghĩa đồng thuận hoặc lý thuyết đằng sau các đối tượng chưa được chứng minh là rất thành công

Vì vậy, bất cứ khi nào ai đó đưa ra một câu trả lời rất dứt khoát, hãy mang nó với một hạt muối.

Điều đó nói rằng, có một lập luận tốt để được đưa ra rằng, vâng, FP một siêu mẫu của OOP như một mô hình. Cụ thể là định nghĩa của Alan Kay về thuật ngữ lập trình hướng đối tượng không mâu thuẫn với khái niệm này (nhưng của Kristen Nygaard thì có ). Tất cả những gì Kay thực sự quan tâm là mọi thứ đều là một đối tượng và logic đó được thực hiện bằng cách chuyển các thông điệp giữa các đối tượng.

Có thể thú vị hơn cho câu hỏi của bạn, các lớp và đối tượng có thể được nghĩ về các hàm và các bao đóng được trả về bởi các hàm (đóng vai trò là các lớp và hàm tạo cùng một lúc). Điều này rất gần với lập trình dựa trên nguyên mẫu và trên thực tế, JavaScript cho phép thực hiện chính xác điều đó.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Tất nhiên JavaScript cho phép thay đổi các giá trị bất hợp pháp trong lập trình chức năng thuần túy nhưng cũng không bắt buộc trong định nghĩa nghiêm ngặt về OOP.)

Câu hỏi quan trọng hơn là: Đây có phải là một phân loại có ý nghĩa của OOP? Có hữu ích khi nghĩ về nó như là một tập hợp con của lập trình chức năng? Tôi nghĩ rằng trong hầu hết các trường hợp, nó không phải là.


1
Tôi cảm thấy nó có thể có ý nghĩa trong suy nghĩ thông qua nơi nên vẽ đường thẳng khi tôi chuyển đổi mô hình. Nếu nó nói rằng không có cách nào để đạt được đa hình subtypal trong fp, thì tôi sẽ không bao giờ cố gắng sử dụng fp trong việc mô hình hóa một cái gì đó phù hợp với nó. Tuy nhiên, nếu có thể, tôi có thể dành thời gian để đạt được cách làm tốt (mặc dù cách tốt có thể không thực hiện được) khi làm việc nhiều trong không gian fp nhưng muốn đa hình phụ trong một vài không gian thích hợp. Rõ ràng nếu phần lớn hệ thống phù hợp với nó, tuy nhiên, tốt hơn là sử dụng OOP.
Jimmy Hoffa

6

FP như OO không phải là một thuật ngữ được xác định rõ. Có những trường với các định nghĩa khác nhau, đôi khi mâu thuẫn. Nếu bạn có những gì họ có chung, bạn có thể:

  • lập trình chức năng là lập trình với các chức năng hạng nhất

  • Lập trình OO là lập trình với tính đa hình bao gồm kết hợp với ít nhất một dạng hạn chế của quá tải được giải quyết động. (Một lưu ý bên lề: trong vòng tròn OO, đa hình thường được hiểu là đa hình bao gồm , trong khi các trường FP thường có nghĩa là đa hình tham số .)

Mọi thứ khác hoặc có mặt ở nơi khác, hoặc vắng mặt trong một số trường hợp.

FP và OO là hai công cụ xây dựng trừu tượng. Mỗi người trong số họ đều có điểm mạnh và điểm yếu riêng (ví dụ họ có hướng mở rộng ưa thích khác nhau trong vấn đề biểu hiện), nhưng về bản chất thì không mạnh hơn cái kia. Bạn có thể xây dựng một hệ thống OO qua hạt nhân FP (CLOS là một trong những hệ thống như vậy). Bạn có thể sử dụng khung OO để lấy các hàm hạng nhất (xem cách các hàm lambda được định nghĩa trong C ++ 11 chẳng hạn).


Tôi nghĩ bạn có nghĩa là 'các hàm hạng nhất' chứ không phải là 'các hàm bậc nhất'.
dan_waterworth

Errr ... C ++ 11 lambdas hầu như không phải là hàm hạng nhất: Mỗi lambda có loại ad-hoc riêng (cho tất cả các mục đích thực tế, một cấu trúc ẩn danh), không tương thích với loại con trỏ hàm gốc. Và std::function, mà cả hai con trỏ hàm và lambdas có thể được gán, được quyết định chung chung, không hướng đối tượng. Điều này không có gì đáng ngạc nhiên, bởi vì thương hiệu đa hình hạn chế hướng đối tượng (đa hình phụ) hoàn toàn không mạnh hơn đa hình tham số (ngay cả Hindley-Milner, chứ chưa nói đến toàn hệ thống F-omega).
pyon

Tôi không có nhiều kinh nghiệm với các ngôn ngữ chức năng thuần túy nhưng nếu bạn có thể định nghĩa các lớp phương thức tĩnh trong các bao đóng và chuyển chúng xung quanh sang các bối cảnh khác nhau, tôi sẽ nói rằng bạn (có lẽ lúng túng) ít nhất là một nửa có các tùy chọn kiểu chức năng. Có rất nhiều cách để làm việc xung quanh các thông số nghiêm ngặt trong hầu hết các ngôn ngữ.
Erik Reppen

2

Không; OOP có thể được xem như là một siêu thay đổi của lập trình thủ tục và về cơ bản khác với mô hình chức năng bởi vì nó có trạng thái được thể hiện trong các trường mẫu. Trong mô hình chức năng, các biến là các hàm được áp dụng trên dữ liệu không đổi để có được kết quả mong muốn.

Trên thực tế, bạn có thể coi lập trình chức năng là một tập hợp con của OOP; nếu bạn làm cho tất cả các lớp học của bạn bất biến, bạn có thể xem xét bạn có một số loại lập trình chức năng.


3
Các lớp không thay đổi không tạo ra các hàm bậc cao hơn, liệt kê các mức hiểu hoặc đóng. Fp không phải là tập hợp con.
Jimmy Hoffa

1
@Jimmy Hoffa: Bạn có thể dễ dàng mô phỏng hàm oreder cao hơn bằng cách tạo một lớp có một phương thức duy nhất hoặc nhiều đối tượng thuộc loại tương tự và cũng trả về một đối tượng thuộc loại tương tự này (loại có phương thức và không có trường) . Danh sách tổng hợp không phải là một cái gì đó liên quan đến ngôn ngữ lập trình không phải mô hình (Smalltalk hỗ trợ nó và là OOP). Các bao đóng có mặt trong C # và cũng sẽ được chèn vào Java.
m3th0dman

vâng, C # có các bao đóng, nhưng đó là vì nó đa mô hình, các bao đóng được thêm vào cùng với các phần fp khác cho C # (mà tôi biết ơn mãi mãi) nhưng sự hiện diện của chúng trong một ngôn ngữ oop không làm cho chúng trở nên oop. Điểm hay về hàm bậc cao hơn mặc dù, đóng gói một phương thức trong một lớp không cho phép hành vi tương tự.
Jimmy Hoffa

2
Vâng, nhưng nếu sử dụng đóng cửa để thay đổi trạng thái, bạn vẫn sẽ lập trình trong mô hình chức năng chứ? Vấn đề là - mô hình chức năng là về việc thiếu trạng thái không phải về các hàm bậc cao, đệ quy hoặc đóng.
m3th0dman

suy nghĩ thú vị về định nghĩa của fp .. Tôi sẽ phải suy nghĩ thêm về điều này, cảm ơn vì đã chia sẻ những quan sát của bạn.
Jimmy Hoffa

2

Câu trả lời:

Wikipedia có một bài viết tuyệt vời về Lập trình chức năng với một số ví dụ bạn yêu cầu. @Konrad Rudolph đã cung cấp liên kết đến bài viết OOP .

Tôi không nghĩ một mô hình là một siêu tập hợp khác. Chúng là những quan điểm khác nhau về lập trình và một số vấn đề được giải quyết tốt hơn từ góc độ này và một số từ góc độ khác.

Câu hỏi của bạn phức tạp hơn bởi tất cả các triển khai của FP và OOP. Mỗi ngôn ngữ có những câu hỏi riêng có liên quan đến bất kỳ câu trả lời hay nào cho câu hỏi của bạn.

Tăng dần tiếp tuyến:

Tôi thích ý tưởng rằng một ngôn ngữ như Scala cố gắng cung cấp cho bạn những điều tốt nhất của cả hai thế giới. Tôi lo lắng rằng nó cũng mang lại cho bạn sự phức tạp của cả hai thế giới.

Java là ngôn ngữ OO, nhưng phiên bản 7 đã thêm tính năng "dùng thử với tài nguyên" có thể được sử dụng để bắt chước một kiểu đóng. Ở đây, nó bắt chước cập nhật một biến cục bộ "a" ở giữa một hàm khác, mà không làm cho nó hiển thị cho hàm đó. Trong trường hợp này, nửa đầu của hàm khác là hàm tạo ClosTry () và nửa sau là phương thức close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Đầu ra:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Điều này có thể hữu ích cho mục đích dự định của nó là mở một luồng, ghi vào luồng và đóng nó một cách đáng tin cậy hoặc chỉ đơn giản là ghép hai hàm theo cách mà bạn không quên gọi hàm thứ hai sau khi thực hiện một số công việc giữa chúng . Tất nhiên, nó mới và khác thường đến nỗi một lập trình viên khác có thể loại bỏ khối thử mà không nhận ra họ đang phá vỡ thứ gì đó, vì vậy hiện tại nó là một kiểu chống mẫu, nhưng thú vị là nó có thể được thực hiện.

Bạn có thể diễn đạt bất kỳ vòng lặp nào trong hầu hết các ngôn ngữ bắt buộc dưới dạng đệ quy. Đối tượng và các biến có thể được thực hiện bất biến. Việc kiểm tra có thể được viết để giảm thiểu tác dụng phụ (mặc dù tôi cho rằng chức năng thực sự là không thể thực hiện được trên máy tính - thời gian cần thiết để thực thi và tài nguyên bộ xử lý / đĩa / hệ thống mà nó tiêu thụ là tác dụng phụ không thể tránh khỏi). Một số ngôn ngữ chức năng có thể được thực hiện để làm nhiều nếu không phải tất cả các hoạt động hướng đối tượng là tốt. Chúng không phải loại trừ lẫn nhau, mặc dù một số ngôn ngữ có những hạn chế (như không cho phép bất kỳ cập nhật biến nào) ngăn các mẫu nhất định (như các trường có thể thay đổi).

Đối với tôi, phần hữu ích nhất của lập trình hướng đối tượng là ẩn dữ liệu (đóng gói), coi các đối tượng đủ tương tự như nhau (đa hình) và thu thập dữ liệu và phương thức của bạn hoạt động trên dữ liệu đó cùng nhau (đối tượng / lớp). Kế thừa có thể là lá cờ đầu của OOP, nhưng với tôi nó là phần ít quan trọng nhất và ít được sử dụng nhất.

Các phần hữu ích nhất của lập trình chức năng là tính bất biến (mã thông báo / giá trị thay vì biến), hàm (không có tác dụng phụ) và đóng.

Tôi không nghĩ nó hướng đối tượng, nhưng tôi phải nói rằng một trong những điều hữu ích nhất trong khoa học máy tính là khả năng khai báo một giao diện, sau đó có nhiều phần chức năng và dữ liệu thực hiện giao diện đó. Tôi cũng muốn có một vài dữ liệu có thể thay đổi để làm việc, vì vậy tôi đoán rằng tôi không hoàn toàn thoải mái với các ngôn ngữ chức năng độc quyền, mặc dù tôi cố gắng hạn chế khả năng biến đổi và tác dụng phụ trong tất cả các thiết kế chương trình của mình.

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.