Làm thế nào để ngôn ngữ chức năng thuần túy xử lý mô-đun?


23

Tôi đến từ một nền tảng hướng đối tượng nơi tôi đã học được rằng các lớp hoặc ít nhất có thể được sử dụng để tạo ra một lớp trừu tượng cho phép tái chế mã dễ dàng, sau đó có thể được sử dụng để tạo đối tượng hoặc được sử dụng để kế thừa.

Ví dụ như tôi có thể có một lớp động vật và từ đó thừa hưởng mèo và chó và tất cả đều thừa hưởng nhiều đặc điểm giống nhau, và từ các lớp con đó, tôi có thể tạo ra các vật thể có thể chỉ định một giống vật hoặc thậm chí là tên của nó
Hoặc tôi có thể sử dụng các lớp để chỉ định nhiều phiên bản của cùng một mã xử lý hoặc chứa các thứ hơi khác nhau; như các nút trong cây tìm kiếm hoặc nhiều kết nối cơ sở dữ liệu khác nhau và những gì không.

Gần đây tôi đang chuyển sang lập trình chức năng, vì vậy tôi bắt đầu tự hỏi:
Làm thế nào để các ngôn ngữ chức năng thuần túy xử lý những thứ như vậy? Đó là, ngôn ngữ không có bất kỳ khái niệm về các lớp và đối tượng.


1
Tại sao bạn nghĩ rằng chức năng không có nghĩa là các lớp? Một số các lớp đầu tiên đến từ LISP - CLOS Nhìn vào không gian tên clojure và các loại hoặc mô-đunhaskell .

Tôi đã đề cập đến các ngôn ngữ chức năng mà KHÔNG có các lớp học, tôi biết rất rõ về một số ngôn ngữ mà DO
Cà phê điện

1
Caml là một ví dụ, ngôn ngữ chị em của nó OCaml thêm các đối tượng, nhưng bản thân Caml không có chúng.
Cà phê điện

12
Thuật ngữ "hoàn toàn chức năng" dùng để chỉ các ngôn ngữ chức năng duy trì tính minh bạch tham chiếu và không liên quan đến việc ngôn ngữ có bất kỳ tính năng hướng đối tượng nào hay không.
sepp2k

2
Bánh là một lời nói dối, tái sử dụng mã trong OO khó khăn hơn nhiều so với trong FP. Đối với tất cả những gì OO đã tuyên bố tái sử dụng mã trong nhiều năm qua, tôi đã thấy nó tuân theo tối thiểu nhiều lần. (bạn có thể chỉ nói rằng tôi phải làm việc đó sai, tôi cảm thấy thoải mái với tôi viết mã OO tốt như thế nào khi phải thiết kế và duy trì hệ thống OO trong nhiều năm qua, tôi biết chất lượng kết quả của riêng tôi)
Jimmy Hoffa

Câu trả lời:


19

Nhiều ngôn ngữ chức năng có một hệ thống mô-đun. (Nhân tiện, nhiều ngôn ngữ hướng đối tượng cũng làm như vậy.) Nhưng ngay cả khi không có một ngôn ngữ, bạn có thể sử dụng các hàm làm mô-đun.

JavaScript là một ví dụ tốt. Trong JavaScript, các hàm được sử dụng cả để thực hiện các mô-đun và thậm chí đóng gói hướng đối tượng. Trong Scheme, nguồn cảm hứng chính cho JavaScript, chỉ có các hàm. Các hàm được sử dụng để thực hiện hầu hết mọi thứ: các đối tượng, mô-đun (được gọi là các đơn vị trong Vợt), thậm chí cả các cấu trúc dữ liệu.

OTOH, Haskell và gia đình ML có một hệ thống mô-đun rõ ràng.

Định hướng đối tượng là về trừu tượng hóa dữ liệu. Đó là nó. Modularity, thừa kế, đa hình, thậm chí trạng thái đột biến là mối quan tâm trực giao.


8
Bạn có thể giải thích làm thế nào những điều đó làm việc chi tiết hơn một chút liên quan đến oop? Thay vì chỉ đơn giản nói rằng các khái niệm tồn tại ...
Cà phê điện

Sidenote - Các mô-đun và đơn vị là hai cấu trúc khác nhau trong Vợt - các mô-đun có thể so sánh với các không gian tên và các đơn vị được sắp xếp giữa các không gian tên và giao diện OO. Các tài liệu đi sâu vào chi tiết hơn về sự khác biệt
Jack

@Jack: Tôi không biết rằng Vợt cũng có một khái niệm gọi là module. Tôi nghĩ thật không may khi Vợt có một khái niệm gọi modulelà không phải là một mô-đun, và một khái niệm là một mô-đun nhưng không được gọi module. Dù sao, bạn đã viết: "các đơn vị là một nửa giữa các không gian tên và giao diện OO". Chà, đó không phải là định nghĩa của mô-đun là gì sao?
Jörg W Mittag

Các mô-đun và đơn vị là cả hai nhóm tên ràng buộc với các giá trị. Các mô-đun có thể có các phụ thuộc vào các bộ ràng buộc cụ thể khác , trong khi các đơn vị có thể có các phụ thuộc vào một số các ràng buộc chung mà bất kỳ mã nào khác sử dụng đơn vị phải cung cấp. Các đơn vị được tham số hóa trên các ràng buộc, mô-đun không. Một mô-đun phụ thuộc vào một ràng buộc mapvà một đơn vị phụ thuộc vào một ràng buộc mapkhác nhau ở chỗ mô-đun phải tham chiếu đến một số mapràng buộc cụ thể , chẳng hạn như một từ racket/base, trong khi những người dùng khác nhau của đơn vị có thể đưa ra các định nghĩa khác nhau mapcho đơn vị.
Jack

4

Có vẻ như bạn đang hỏi hai câu hỏi: "Làm thế nào bạn có thể đạt được tính mô đun trong các ngôn ngữ chức năng?" đã được xử lý trong các câu trả lời khác và "làm thế nào bạn có thể tạo ra sự trừu tượng trong các ngôn ngữ chức năng?" mà tôi sẽ trả lời.

Trong các ngôn ngữ OO, bạn có xu hướng tập trung vào danh từ "động vật", "máy chủ thư", "ngã ba vườn của anh ấy", v.v ... Ngược lại, các ngôn ngữ chức năng nhấn mạnh động từ, "đi bộ", "để lấy thư" , "Để sản xuất", v.v.

Do đó, không có gì ngạc nhiên khi sự trừu tượng trong các ngôn ngữ chức năng có xu hướng vượt qua các động từ hoặc hoạt động hơn là trên mọi thứ. Một ví dụ mà tôi luôn hướng tới khi tôi cố gắng giải thích điều này là phân tích cú pháp. Trong các ngôn ngữ chức năng, một cách tốt để viết các trình phân tích cú pháp là bằng cách chỉ định một ngữ pháp và sau đó diễn giải nó. Trình thông dịch tạo ra một sự trừu tượng trong quá trình phân tích cú pháp.

Một ví dụ cụ thể khác về điều này là một dự án tôi đã làm việc cách đây không lâu. Tôi đã viết một cơ sở dữ liệu trong Haskell. Tôi đã có một "ngôn ngữ nhúng" để chỉ định các hoạt động ở mức thấp nhất; ví dụ, nó cho phép tôi viết và đọc những thứ từ phương tiện lưu trữ. Tôi đã có một "ngôn ngữ nhúng" khác, riêng biệt để chỉ định các hoạt động ở mức cao nhất. Sau đó, tôi đã có, về cơ bản là một thông dịch viên, để chuyển đổi các hoạt động từ cấp cao hơn sang cấp thấp hơn.

Đây là một hình thức trừu tượng đáng chú ý chung, nhưng nó không phải là hình thức duy nhất có sẵn trong các ngôn ngữ chức năng.


4

Mặc dù "lập trình chức năng" không truyền đạt được ý nghĩa sâu rộng đối với các vấn đề về tính mô đun, các ngôn ngữ cụ thể giải quyết việc lập trình rộng lớn theo nhiều cách khác nhau. Tái sử dụng mã và trừu tượng hóa tương tác ở chỗ bạn càng ít lộ diện thì càng khó sử dụng lại mã. Đặt sự trừu tượng sang một bên, tôi sẽ giải quyết hai vấn đề về khả năng sử dụng lại.

Các ngôn ngữ OOP được nhập tĩnh theo truyền thống được sử dụng phân nhóm danh nghĩa, có nghĩa là mã được thiết kế cho lớp / mô-đun / giao diện A chỉ có thể xử lý lớp / mô-đun / giao diện B khi B đề cập rõ ràng A. Các ngôn ngữ trong họ lập trình chức năng chủ yếu sử dụng phân nhóm cấu trúc, có nghĩa là mã được thiết kế cho A có thể xử lý B bất cứ khi nào B có tất cả các phương thức và / hoặc các trường của A. B có thể được tạo bởi một nhóm khác trước khi cần một lớp / giao diện tổng quát hơn A. Ví dụ như trong OCaml, phân nhóm cấu trúc áp dụng cho hệ thống mô-đun, hệ thống đối tượng giống OOP và các loại biến thể đa hình khá độc đáo của nó.

Sự khác biệt nổi bật nhất giữa OOP và FP wrt. tính mô đun là "đơn vị" mặc định trong OOP kết hợp với nhau như một đối tượng hoạt động khác nhau trên cùng một trường hợp giá trị, trong khi "đơn vị" mặc định trong gói bó lại với nhau như một chức năng hoạt động giống nhau cho các trường hợp giá trị khác nhau. Trong FP, vẫn rất dễ dàng để kết hợp các hoạt động lại với nhau, ví dụ như các mô-đun. (BTW, cả Haskell và F # đều không có hệ thống mô-đun ML-gia đình đầy đủ.) Vấn đề Biểu hiệnlà nhiệm vụ tăng dần cả hai thao tác mới hoạt động trên tất cả các giá trị (ví dụ: đính kèm một phương thức mới vào các đối tượng hiện có) và các trường hợp mới của các giá trị mà tất cả các hoạt động sẽ hỗ trợ (ví dụ: thêm một lớp mới có cùng giao diện). Như đã thảo luận trong bài giảng Ralf Laemmel đầu tiên bên dưới (có ví dụ mở rộng trong C #), việc thêm các hoạt động mới là vấn đề trong các ngôn ngữ OOP.

Sự kết hợp giữa OOP và FP trong Scala có thể khiến nó trở thành một trong những ngôn ngữ mạnh mẽ nhất. tính mô đun. Nhưng OCaml vẫn là ngôn ngữ yêu thích của tôi và theo ý kiến ​​cá nhân, chủ quan của tôi, nó không thua Scala. Hai bài giảng Ralf Laemmel dưới đây thảo luận về giải pháp cho vấn đề biểu hiện trong Haskell. Tôi nghĩ rằng giải pháp này, mặc dù hoạt động hoàn hảo, gây khó khăn cho việc sử dụng dữ liệu kết quả với tính đa hình tham số. Giải quyết vấn đề biểu hiện với các biến thể đa hình trong OCaml, được giải thích trong bài báo Jaques Garrigue được liên kết dưới đây, không có thiếu sót này. Tôi cũng liên kết đến các chương sách giáo khoa so sánh việc sử dụng mô đun không OOP và OOP trong OCaml.

Dưới đây là các liên kết cụ thể của Haskell và OCaml đang mở rộng về Vấn đề Biểu hiện :


2
bạn có phiền giải thích thêm về những tài nguyên này làm gì không và tại sao bạn lại đề xuất những tài nguyên này khi trả lời câu hỏi? "Câu trả lời chỉ liên kết" không được chào đón tại Stack Exchange
gnat

2
Tôi vừa cung cấp một câu trả lời thực tế thay vì chỉ liên kết, như là một chỉnh sửa.
lukstafi

0

Trên thực tế, mã OO ít tái sử dụng hơn và đó là do thiết kế. Ý tưởng đằng sau OOP là hạn chế các hoạt động trên các phần dữ liệu cụ thể đối với một mã đặc quyền nhất định trong lớp hoặc ở vị trí thích hợp trong hệ thống phân cấp thừa kế. Điều này hạn chế các tác động bất lợi của tính đột biến. Nếu cấu trúc dữ liệu thay đổi, chỉ có rất nhiều vị trí trong mã có thể chịu trách nhiệm.

Với tính không thay đổi, bạn không quan tâm ai có thể vận hành trên bất kỳ cấu trúc dữ liệu cụ thể nào, bởi vì không ai có thể thay đổi bản sao dữ liệu của bạn. Điều này làm cho việc tạo các chức năng mới để làm việc trên các cấu trúc dữ liệu hiện có dễ dàng hơn nhiều. Bạn chỉ cần tạo các hàm và nhóm chúng thành các mô-đun có vẻ phù hợp theo quan điểm tên miền. Bạn không phải lo lắng về nơi phù hợp với chúng trong hệ thống phân cấp thừa kế.

Một loại tái sử dụng mã khác là tạo các cấu trúc dữ liệu mới để hoạt động trên các hàm hiện có. Điều này được xử lý trong các ngôn ngữ chức năng bằng cách sử dụng các tính năng như chung chung và các lớp loại. Ví dụ, lớp loại Ord của Haskell cho phép bạn sử dụng sorthàm trên bất kỳ loại nào với một Ordthể hiện. Trường hợp dễ tạo ra nếu chúng chưa tồn tại.

Lấy Animalví dụ của bạn và xem xét thực hiện một tính năng cho ăn. Việc thực hiện OOP đơn giản là duy trì một tập hợp các Animalđối tượng và lặp qua tất cả chúng, gọi feedphương thức trên mỗi đối tượng.

Tuy nhiên, mọi thứ trở nên khó khăn khi bạn đi vào chi tiết. Một Animalđối tượng tự nhiên biết loại thức ăn nào nó ăn, và nó cần bao nhiêu để cảm thấy no. Nó không tự nhiên biết thực phẩm được giữ ở đâu và có sẵn bao nhiêu, vì vậy một FoodStoređối tượng đã trở thành một sự phụ thuộc của mọi người Animal, như là một trường của Animalđối tượng, hoặc được truyền vào như một tham số của feedphương thức. Thay phiên, để giữ cho Animallớp gắn kết hơn, bạn có thể di chuyển feed(animal)đến FoodStoređối tượng hoặc bạn có thể tạo ra một sự ghê tởm của một lớp được gọi là một AnimalFeederhoặc một số như vậy.

Trong FP, không có xu hướng cho các lĩnh vực của một Animalluôn luôn được nhóm lại với nhau, điều này có một số ý nghĩa thú vị cho việc tái sử dụng. Giả sử bạn có một danh sách các Animalhồ sơ, với các lĩnh vực như name, species, location, food type, food amount, vv Bạn cũng có một danh sách các FoodStorehồ sơ với các lĩnh vực như location, food type, và food amount.

Bước đầu tiên trong việc cho ăn có thể là ánh xạ từng danh sách các bản ghi đó vào danh sách các (food amount, food type)cặp, với số âm cho số lượng của động vật. Sau đó, bạn có thể tạo các hàm để thực hiện tất cả các loại với các cặp này, như tổng số lượng của từng loại thực phẩm. Các chức năng này không hoàn toàn thuộc về một Animalhoặc một FoodStoremô-đun, nhưng cả hai đều có thể tái sử dụng cao.

Bạn kết thúc với một loạt các chức năng làm những thứ hữu ích với [(Num A, Eq B)]khả năng tái sử dụng và mô đun, nhưng bạn gặp khó khăn trong việc tìm ra nơi để đặt chúng hoặc những gì để gọi chúng là một nhóm. Hiệu quả là các mô-đun FP khó phân loại hơn, nhưng việc phân loại ít quan trọng hơn nhiều.


-1

Một trong những giải pháp phổ biến là chia mã thành các mô-đun, đây là cách nó được thực hiện trong JavaScript:

    media.podcast = (function(name) {
    var fileExtension = 'mp3';        

     function determineFileExtension() {
         console.log('File extension is of type ' + fileExtension);
     }

     return {
         download: function(episode) {
            console.log('Downloading ' + episode + ' of ' + name);
            determineFileExtension();
        }
    }    
}('Astronomy podcast'));

Các bài viết đầy đủ giải thích mô hình này trong JavaScript , ngoài rằng có nhiều cách khác để xác định một mô-đun, chẳng hạn như RequireJS , CommonJS , Google Closure. Một ví dụ khác là Erlang, nơi bạn có cả mô-đunhành vi thực thi API và mẫu, đóng vai trò tương tự như Giao diện trong OOP.

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.