API và lập trình chức năng


15

Từ sự tiếp xúc (hạn chế) của tôi đối với các ngôn ngữ lập trình chức năng, như Clojure, dường như việc đóng gói dữ liệu có vai trò ít quan trọng hơn. Thông thường các loại bản địa khác nhau như bản đồ hoặc bộ là loại tiền được ưu tiên đại diện cho dữ liệu, trên các đối tượng. Hơn nữa, dữ liệu đó nói chung là bất biến.

Ví dụ, đây là một trong những trích dẫn nổi tiếng từ Rich Hickey of Clojure nổi tiếng, trong một cuộc phỏng vấn về vấn đề này :

Fogus: Theo ý tưởng đó, một số người ngạc nhiên về việc Clojure không tham gia vào việc đóng gói ẩn dữ liệu trên các loại của nó. Tại sao bạn quyết định từ bỏ ẩn dữ liệu?

Hickey: Chúng ta hãy rõ ràng rằng Clojure nhấn mạnh mạnh vào lập trình để trừu tượng hóa. Tại một số điểm, ai đó sẽ cần phải có quyền truy cập vào dữ liệu. Và nếu bạn có một khái niệm về riêng tư, bạn cần có những khái niệm tương ứng về đặc quyền và sự tin tưởng. Và điều đó thêm vào cả tấn sự phức tạp và ít giá trị, tạo ra sự cứng nhắc trong một hệ thống và thường buộc mọi thứ phải sống ở những nơi họ không nên đến. Điều này là thêm vào sự mất mát khác xảy ra khi thông tin đơn giản được đưa vào các lớp. Trong phạm vi dữ liệu là bất biến, có rất ít tác hại có thể đến từ việc cung cấp quyền truy cập, ngoài việc ai đó có thể phụ thuộc vào thứ gì đó có thể thay đổi. Chà, được thôi, mọi người làm điều đó mọi lúc trong cuộc sống thực, và khi mọi thứ thay đổi, họ thích nghi. Và nếu chúng hợp lý, họ biết khi nào họ đưa ra quyết định dựa trên điều gì đó có thể thay đổi mà họ có thể cần phải thích nghi trong tương lai. Vì vậy, đó là một quyết định quản lý rủi ro, tôi nghĩ các lập trình viên nên tự do thực hiện. Nếu mọi người không có sự nhạy cảm với mong muốn lập trình trừu tượng và cảnh giác với việc kết hôn với các chi tiết thực hiện, thì họ sẽ không bao giờ trở thành lập trình viên giỏi.

Đến từ thế giới OO, điều này dường như làm phức tạp một số nguyên tắc được bảo tồn mà tôi đã học được trong nhiều năm. Chúng bao gồm Ẩn thông tin, Luật Demeter và Nguyên tắc truy cập thống nhất, để nêu tên một số. Chủ đề chung là đóng gói cho phép chúng ta xác định API để người khác biết họ nên và không nên chạm vào cái gì. Về bản chất, tạo một hợp đồng cho phép người duy trì một số mã tự do thực hiện các thay đổi và tái cấu trúc mà không cần lo lắng về cách nó có thể đưa lỗi vào mã của người tiêu dùng (nguyên tắc Mở / Đóng). Nó cũng cung cấp một giao diện sạch, được quản lý để các lập trình viên khác biết họ có thể sử dụng công cụ nào để sử dụng hoặc xây dựng dữ liệu đó.

Khi dữ liệu được phép truy cập trực tiếp, hợp đồng API đó đã bị hỏng và tất cả những lợi ích đóng gói đó dường như biến mất. Ngoài ra, dữ liệu bất biến nghiêm ngặt dường như khiến việc chuyển qua các cấu trúc cụ thể của miền (đối tượng, cấu trúc, bản ghi) ít hữu ích hơn theo nghĩa đại diện cho một trạng thái và tập hợp các hành động có thể được thực hiện trên trạng thái đó.

Làm thế nào để các cơ sở mã chức năng giải quyết các vấn đề dường như xuất hiện khi kích thước của một cơ sở mã phát triển lớn đến mức cần phải xác định API và rất nhiều nhà phát triển có liên quan đến việc làm việc với các phần cụ thể của hệ thống? Có ví dụ nào về tình huống này có sẵn để chứng minh cách xử lý này trong các loại cơ sở mã này không?


2
Bạn có thể định nghĩa một giao diện chính thức mà không có khái niệm về các đối tượng. Chỉ cần tạo chức năng của giao diện tài liệu chúng. Đừng cung cấp tài liệu để biết chi tiết thực hiện. Bạn vừa tạo một giao diện.
Scara95

@ Scara95 Điều đó không có nghĩa là tôi phải làm việc để vừa triển khai mã cho giao diện vừa viết đủ tài liệu về nó để cảnh báo người tiêu dùng phải làm gì và không nên làm gì? Điều gì nếu mã thay đổi và tài liệu trở nên cũ? Tôi thường thích mã tự viết tài liệu cho lý do này.
jameslk

Bạn phải ghi lại giao diện nào.
Scara95

3
Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.Không hẳn vậy. Điều duy nhất thay đổi là những thay đổi kết thúc trên một đối tượng mới. Đây là một chiến thắng lớn khi nói về lý luận về mã; vượt qua các đối tượng có thể thay đổi xung quanh có nghĩa là phải theo dõi xem ai có thể đột biến chúng, một vấn đề mở rộng với kích thước của mã.
Doval

Câu trả lời:


10

Trước hết, tôi sẽ đưa ra nhận xét thứ hai của Sebastian về thế nào là đúng chức năng, thế nào là gõ động. Tổng quát hơn, Clojure là một hương vị của ngôn ngữ và cộng đồng chức năng, và bạn không nên khái quát quá nhiều dựa trên nó. Tôi sẽ đưa ra một số nhận xét từ nhiều hơn về phối cảnh ML / Haskell.

Như Basile đề cập, khái niệm kiểm soát truy cập tồn tại trong ML / Haskell và thường được sử dụng. "Bao thanh toán" hơi khác so với các ngôn ngữ OOP thông thường; trong OOP, khái niệm về một lớp đóng vai trò đồng thời của kiểumô-đun , trong khi các ngôn ngữ chức năng (và thủ tục truyền thống) xử lý các trực giao này.

Một điểm khác là ML / Haskell rất nặng về thuốc generic với kiểu xóa, và điều này có thể được sử dụng để cung cấp một hương vị khác của "ẩn thông tin" so với đóng gói OOP. Khi một thành phần chỉ biết loại mục dữ liệu là tham số loại, thành phần đó có thể được trao một cách an toàn các giá trị của loại đó, và nó sẽ bị ngăn không làm gì nhiều với chúng vì nó không biết và không thể biết loại cụ thể của chúng (không có phổ biến instanceofhoặc thời gian chạy trong các ngôn ngữ này). Mục blog này là một trong những ví dụ giới thiệu yêu thích của tôi cho các kỹ thuật này.

Tiếp theo: trong thế giới FP, việc sử dụng các cấu trúc dữ liệu trong suốt làm giao diện cho các thành phần mờ / đóng gói là rất phổ biến. Ví dụ, các mẫu trình thông dịch rất phổ biến trong FP, trong đó các cấu trúc dữ liệu được sử dụng làm cây cú pháp mô tả logic và được cung cấp cho mã "thực thi" chúng. Nhà nước, nói đúng, sau đó tồn tại một cách phù du khi trình thông dịch chạy tiêu thụ cấu trúc dữ liệu. Ngoài ra, việc triển khai của trình thông dịch có thể thay đổi miễn là nó vẫn liên lạc với khách hàng theo cùng một kiểu dữ liệu.

Cuối cùng và lâu nhất: đóng gói / ẩn thông tin là một kỹ thuật , không phải là kết thúc. Hãy suy nghĩ một chút về những gì nó cung cấp. Đóng gói là một kỹ thuật để điều chỉnh hợp đồngthực hiện một đơn vị phần mềm. Tình huống điển hình là thế này: việc triển khai hệ thống thừa nhận các giá trị hoặc trạng thái, theo hợp đồng của nó, không nên tồn tại.

Khi bạn nhìn vào nó theo cách này, chúng tôi có thể chỉ ra rằng FP cung cấp, ngoài việc đóng gói, một số công cụ bổ sung có thể được sử dụng cho cùng một kết quả:

  1. Bất biến như mặc định phổ biến. Bạn có thể trao các giá trị dữ liệu trong suốt cho mã của bên thứ ba. Họ không thể sửa đổi chúng và đưa chúng vào trạng thái không hợp lệ. (Câu trả lời của Karl đưa ra quan điểm này.)
  2. Các hệ thống loại tinh vi với các loại dữ liệu đại số cho phép bạn kiểm soát tốt cấu trúc của các loại của mình mà không cần viết nhiều mã. Bằng cách sử dụng các phương tiện này một cách thận trọng, bạn thường có thể thiết kế các loại trong đó "trạng thái xấu" là không thể. (Slogan: "Làm cho các trạng thái bất hợp pháp không thể diễn tả được." ) Thay vì sử dụng đóng gói để gián tiếp kiểm soát tập hợp các trạng thái được chấp nhận của một lớp, tôi chỉ muốn nói với trình biên dịch những gì chúng là và đảm bảo chúng cho tôi!
  3. Mẫu phiên dịch, như đã đề cập. Một chìa khóa để thiết kế một loại cây cú pháp trừu tượng tốt là:
    • Hãy thử và thiết kế kiểu dữ liệu cây cú pháp trừu tượng sao cho tất cả các giá trị là "hợp lệ".
    • Không, điều đó làm cho trình thông dịch phát hiện rõ ràng các kết hợp không hợp lệ và từ chối chúng một cách rõ ràng.

Sê-ri "Thiết kế với các loại" F # này giúp đọc khá tốt về một số chủ đề này, đặc biệt là # 2. (Đó là nơi liên kết "làm cho các quốc gia bất hợp pháp không thể diễn tả được" ở trên xuất phát.) Nếu bạn xem xét kỹ, bạn sẽ lưu ý rằng trong phần thứ hai, họ trình bày cách sử dụng đóng gói để ẩn các hàm tạo và ngăn khách hàng xây dựng các trường hợp không hợp lệ. Như tôi đã nói ở trên, nó một phần của bộ công cụ!


9

Tôi thực sự không thể phóng đại mức độ mà tính đột biến gây ra vấn đề trong phần mềm. Nhiều thực hành được đánh trống trong đầu chúng ta đang bù đắp cho những vấn đề mà tính đột biến gây ra. Khi bạn biến mất khả năng biến đổi, bạn không cần những thực hành đó nhiều như vậy.

Khi bạn có sự bất biến, bạn sẽ biết cấu trúc dữ liệu của mình sẽ không thay đổi từ bên dưới bạn trong thời gian chạy, do đó bạn có thể tạo cấu trúc dữ liệu phái sinh của riêng mình để sử dụng khi bạn thêm các tính năng vào chương trình của mình. Cấu trúc dữ liệu gốc không cần biết gì về các cấu trúc dữ liệu phái sinh này.

Điều này có nghĩa là cấu trúc dữ liệu cơ sở của bạn có xu hướng cực kỳ ổn định. Cấu trúc dữ liệu mới sắp xếp được lấy từ nó xung quanh các cạnh khi cần thiết. Thật khó để giải thích cho đến khi bạn thực hiện một chương trình chức năng quan trọng. Bạn chỉ thấy mình quan tâm đến quyền riêng tư ngày càng ít đi và suy nghĩ về việc tạo ra các cấu trúc dữ liệu chung chung bền vững ngày càng nhiều.


Một điều tôi muốn thêm là biến số bất biến khiến các lập trình viên dính vào cấu trúc dữ liệu phân tán và phân tán, nếu có một cấu trúc nào cả. Tất cả dữ liệu được cấu trúc để tạo ra một nhóm logic, để dễ dàng khám phá và duyệt qua, không phải để vận chuyển. Đây là một tiến trình logic bạn sẽ thực hiện khi bạn đã thực hiện đủ chương trình chức năng.
Xephon

8

Theo tôi, xu hướng của Clojure chỉ sử dụng băm và nguyên thủy không phải là một phần của di sản chức năng, mà là một phần của di sản năng động của nó. Tôi đã thấy các xu hướng tương tự trong Python và Ruby (cả hướng đối tượng, mệnh lệnh và động, mặc dù cả hai đều hỗ trợ khá tốt cho các hàm bậc cao hơn), nhưng không phải, nói, Haskell (được gõ tĩnh, nhưng hoàn toàn là chức năng , với các cấu trúc đặc biệt cần thiết để thoát khỏi sự bất biến).

Vì vậy, câu hỏi bạn cần đặt ra không phải là, làm thế nào để các ngôn ngữ chức năng xử lý các API lớn, mà là các ngôn ngữ động làm như thế nào. Câu trả lời là: tài liệu tốt và rất nhiều bài kiểm tra đơn vị. May mắn thay, các ngôn ngữ năng động hiện đại thường đi kèm với sự hỗ trợ rất tốt cho cả hai; ví dụ, cả Python và Clojure đều có cách nhúng tài liệu vào chính mã, không chỉ nhận xét.


Về các ngôn ngữ chức năng được gõ tĩnh, (hoàn toàn), không có cách nào (đơn giản) để thực hiện một hàm với kiểu dữ liệu như trong lập trình OO. Vì vậy, vấn đề tài liệu nào. Vấn đề là bạn không cần hỗ trợ ngôn ngữ để xác định giao diện.
Scara95

5
@ Scara95 Bạn có thể giải thích ý của bạn bằng cách "mang một hàm với kiểu dữ liệu" không?
Sebastian Redl

6

Một số ngôn ngữ chức năng cung cấp khả năng đóng gói hoặc ẩn chi tiết triển khai trong các kiểu dữ liệumô-đun trừu tượng .

Ví dụ, OCaml có các mô-đun được xác định bởi một tập hợp các loại và giá trị trừu tượng được đặt tên (đáng chú ý là các hàm hoạt động trên các loại trừu tượng này). Vì vậy, theo một nghĩa nào đó, các mô-đun của Ocaml đang giới thiệu lại các API. Ocaml cũng có functor, đang chuyển đổi một số mô-đun thành một mô-đun khác, do đó cung cấp chương trình chung. Vì vậy, các mô-đun là thành phần.

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.