Sự thay thế lập trình chức năng cho một giao diện là gì?


15

Nếu tôi muốn lập trình theo kiểu "chức năng", tôi sẽ thay thế giao diện bằng gì?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Có lẽ là một Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

Lý do duy nhất tôi đang sử dụng một giao diện ở nơi đầu tiên là vì tôi luôn muốn có sẵn các thuộc tính / phương thức nhất định.


Chỉnh sửa: Một số chi tiết hơn về những gì tôi đang suy nghĩ / cố gắng.

Giả sử, tôi có một phương thức có ba chức năng:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

Với một ví dụ của Bartôi có thể sử dụng phương pháp này:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

Nhưng đó là một chút đau đớn khi tôi phải đề cập đến barba lần trong một cuộc gọi. Thêm vào đó, tôi thực sự không có ý định cho người gọi cung cấp các chức năng từ các trường hợp khác nhau

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

Trong C #, an interfacelà một cách để giải quyết vấn đề này; nhưng đó có vẻ như là một cách tiếp cận hướng đối tượng. Tôi tự hỏi liệu có giải pháp chức năng nào hơn không: 1) chuyển nhóm chức năng lại với nhau và 2) đảm bảo các chức năng có liên quan chính xác với nhau.


Bạn sẽ không. Các giao diện cho kiểu dữ liệu là hoàn toàn tốt (mặc dù bạn sẽ thích các đối tượng bất biến).
Telastyn

1
Chương 2 của SICP là khá nhiều về điều này.
dùng16764 ngày

7
Sau khi đọc lại câu hỏi của bạn, tôi tò mò chức năng cụ thể nào bạn đang cố gắng thực hiện? Những gì bạn dường như đang yêu cầu là làm thế nào để thực hiện chương trình hiệu quả phụ theo phong cách oo đối với một thể hiện trong một phong cách chức năng, điều đó không có ý nghĩa ..
Jimmy Hoffa

Câu trả lời sẽ phụ thuộc vào ngôn ngữ. Trong Clojure, bạn có thể sử dụng clojure.org/prot Protocol , trong đó khu vực mềm duy nhất là các loại tham số mà các chức năng phải hoạt động - chúng là một đối tượng - đó là tất cả những gì bạn biết.
Công việc

1
Làm cho nó đơn giản: một cấu trúc có chứa các con trỏ phương thức đó, cộng với một hàm để khởi tạo nó từ một thể hiện đối tượng. Tại sao lại là Haskell? ;)
mlvljr

Câu trả lời:


6

Đừng coi lập trình chức năng là một veneer mỏng so với lập trình bắt buộc; có nhiều hơn chỉ là một sự khác biệt cú pháp.

Trong trường hợp này, bạn có một GetIDphương thức, hàm ý tính duy nhất của các đối tượng. Đây không phải là một cách tiếp cận tốt để viết chương trình chức năng. Có lẽ bạn có thể cho chúng tôi biết vấn đề bạn đang cố gắng giải quyết và chúng tôi có thể cho bạn lời khuyên có ý nghĩa hơn.


3
"Bạn phải suy nghĩ bằng tiếng Nga."
Đnn

Đủ công bằng; vấn đề thực sự của tôi không đặc biệt thú vị. Tôi đã có những thứ hoạt động tốt trong C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay nếu bạn thực sự muốn xem mã), nhưng sẽ rất vui khi làm theo kiểu nhiều chức năng hơn trong khi vẫn đang sử dụng C #.
Đnn

1
@ Ðаn Đó thực sự là một trích dẫn của Firefox (phim) (vì nó tuyệt vời nếu có)? Hoặc nó được sử dụng ở một nơi khác?
icc97

2
Theo như tôi đồng ý, đó là một sự thay đổi hoàn toàn từ mô hình lập trình sang lập trình hàm, có nhiều dòng mã lớn hơn được viết theo cách thức bắt buộc. Vì vậy, rất nhiều trường hợp góc được viết cho các hệ thống lớn sẽ được tìm thấy với lập trình mệnh lệnh. Có rất nhiều thực tiễn tốt trong lập trình mệnh lệnh và biết liệu những kỹ năng đó có thể được dịch hay nếu chúng không phải là vấn đề trong FP là một câu hỏi đáng giá. Hoàn toàn có thể viết mã khủng khiếp trong FP, vì vậy những loại câu hỏi này cũng cần làm nổi bật những phần hay của FP.
icc97

11

Haskell và các dẫn xuất của nó có kiểu chữ tương tự như giao diện. Mặc dù có vẻ như bạn đang hỏi về cách thực hiện đóng gói, đó là một câu hỏi liên quan đến các hệ thống loại. Hệ thống loại hindley Milner là phổ biến trong các ngôn ngữ chức năng và nó có các loại dữ liệu thực hiện điều này cho bạn theo nhiều cách khác nhau giữa các ngôn ngữ.


5
+1 cho kiểu chữ - sự khác biệt chính giữa kiểu chữ Haskell và giao diện Java là kiểu chữ được liên kết với kiểu sau khi cả hai được khai báo riêng. Bạn có thể sử dụng loại cũ thông qua "giao diện" mới dễ dàng như bạn có thể sử dụng "giao diện" cũ để truy cập loại mới. Để ẩn dữ liệu, bạn ẩn việc thực hiện kiểu trong một mô-đun. Ít nhất là theo Bertrand Meyer của danh tiếng Eiffel , một lớp OOP một loại mô-đun.
Steve314

5

Có một vài cách để cho phép một hàm xử lý nhiều đầu vào.

Đầu tiên và phổ biến nhất: Đa hình tham số.

Điều này cho phép một hàm hoạt động trên các loại tùy ý:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

Điều này thật tuyệt, nhưng không cung cấp cho bạn công văn động mà giao diện OO có. Đối với Haskell này có typeclass, Scala có ẩn ý, ​​vv

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Giữa hai cơ chế này, Bạn có thể thể hiện tất cả các loại hành vi phức tạp và thú vị trên các loại bạn.


1
Bạn có thể thêm gợi ý tô sáng sintax khi ngôn ngữ trong câu trả lời không khớp với ngôn ngữ trong câu hỏi. Xem chỉnh sửa đề xuất của tôi ví dụ.
hugomg

1

Nguyên tắc cơ bản là trong các chức năng lập trình FP thực hiện công việc giống như các đối tượng thực hiện trong lập trình OO. Bạn có thể gọi các phương thức của họ (tốt, dù sao cũng là phương thức "gọi") và họ trả lời theo một số quy tắc nội bộ được đóng gói. Đặc biệt, mọi ngôn ngữ FP phong nha ngoài kia cho phép bạn có "các biến thể hiện" trong hàm của mình với các bao đóng / phạm vi từ vựng.

var make_OO_style_counter = function(){
   return {
      counter: 0
      increment: function(){
          this.counter += 1
          return this.counter;
      }
   }
};

var make_FP_style_counter = function(){
    var counter = 0;
    return fucntion(){
        counter += 1
        return counter;
    }
};

Bây giờ câu hỏi tiếp theo là bạn có ý nghĩa gì với một giao diện? Một cách tiếp cận là sử dụng các giao diện danh nghĩa (nó phù hợp với giao diện nếu nó nói như vậy) - cách tiếp cận này thường phụ thuộc rất nhiều vào ngôn ngữ bạn đang sử dụng vì vậy hãy để nó cho ngôn ngữ sau. Cách khác để xác định một giao diện là cách cấu trúc, xem những gì tham số nhận và trả lại. Đây là loại giao diện mà bạn có xu hướng nhìn thấy trong các ngôn ngữ động, gõ vịt và nó rất phù hợp với tất cả các FP: giao diện chỉ là các loại tham số đầu vào cho các chức năng của chúng tôi và các loại chúng trả về để tất cả các chức năng khớp với đúng loại phù hợp với giao diện!

Do đó, cách đơn giản nhất để biểu diễn một đối tượng khớp với giao diện là chỉ cần có một nhóm các chức năng. Bạn thường nhận được sự xấu xí của việc chuyển các chức năng một cách riêng biệt bằng cách đóng gói chúng trong một số loại hồ sơ:

var my_blarfable = {
 get_name: function(){ ... },
 set_name: function(){ ... },
 get_id:   function(){ ... }
}

do_something(my_blarfable)

Sử dụng các chức năng trần trụi hoặc hồ sơ của các chức năng sẽ đi một chặng đường dài trong việc giải quyết hầu hết các vấn đề phổ biến của bạn theo cách "không có chất béo" mà không có hàng tấn nồi hơi. Nếu bạn cần một cái gì đó cao cấp hơn thế, đôi khi các ngôn ngữ cung cấp cho bạn các tính năng bổ sung. Một ví dụ mọi người đề cập là các lớp loại Haskell. Các lớp loại về cơ bản liên kết một loại với một trong các bản ghi các hàm đó và cho phép bạn viết các thứ để từ điển ẩn và được tự động chuyển sang các hàm bên trong nếu thích hợp.

-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
    blarg_name :: String,
    blarg_id   :: Integer
}

do_something :: BlargDict -> IO()
do_something blarg_dict = do
   print (blarg_name blarg_dict)
   print (blarg_id   blarg_dict)

-- Typeclass version   
class Blargable a where
   blag_name :: a -> String
   blag_id   :: a -> String

do_something :: Blargable a => a -> IO
do_something blarg = do
   print (blarg_name blarg)
   print (blarg_id   blarg)

Tuy nhiên, một điều quan trọng cần lưu ý về kiểu chữ là các từ điển được liên kết với các loại chứ không phải với các giá trị (như những gì xảy ra trong từ điển và các phiên bản OO). Điều này có nghĩa là hệ thống loại không cho phép bạn trộn "loại" [1]. Nếu bạn muốn có một danh sách "blargables" hoặc hàm nhị phân tham gia vào blargables thì typeclass sẽ buộc mọi thứ phải cùng loại trong khi cách tiếp cận từ điển sẽ cho phép bạn có các nguồn gốc khác nhau (phiên bản nào tốt hơn phụ thuộc rất nhiều vào bạn đang làm)

[1] Có nhiều cách nâng cao để thực hiện "các loại tồn tại" nhưng thường không đáng để gặp rắc rối.


0

Tôi nghĩ nó sẽ là ngôn ngữ cụ thể. Tôi đến từ một nền tảng lispy. Trong nhiều trường hợp giao diện với trạng thái phá vỡ mô hình chức năng ở một mức độ. Vì vậy, CLOS, ví dụ, là nơi LISP ít chức năng hơn và gần với một ngôn ngữ bắt buộc hơn. Nói chung, các tham số chức năng được yêu cầu kết hợp với các phương thức cấp cao hơn có thể là những gì bạn đang tìm kiếm.

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
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.