Apa alternatif pemrograman fungsional untuk sebuah antarmuka?

15

Jika saya ingin memprogram dalam gaya "fungsional", dengan apa saya akan mengganti antarmuka?

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

Mungkin a Tuple<>?

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

Satu-satunya alasan saya menggunakan antarmuka di tempat pertama adalah karena saya ingin selalu ingin properti / metode tertentu tersedia.


Sunting: Beberapa detail tentang apa yang saya pikirkan / coba.

Katakanlah, saya punya metode yang membutuhkan tiga fungsi:

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

Dengan contoh Barsaya dapat menggunakan metode ini:

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);

Tapi itu agak menyebalkan karena saya harus menyebutkan bartiga kali dalam satu panggilan. Plus, saya tidak benar-benar berniat bagi penelepon untuk memasok fungsi dari berbagai kejadian

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

Dalam C #, an interfaceadalah salah satu cara untuk menghadapinya; tapi itu sepertinya pendekatan yang sangat berorientasi objek. Saya bertanya-tanya apakah ada solusi yang lebih fungsional: 1) melewati kelompok fungsi bersama, dan 2) memastikan fungsi terkait satu sama lain dengan benar.

.Аn
sumber
Kamu tidak akan Antarmuka untuk tipe data benar-benar baik-baik saja (meskipun Anda akan menyukai objek yang tidak berubah).
Telastyn
1
Bab 2 SICP cukup banyak tentang hal ini.
user16764
7
Setelah membaca ulang pertanyaan Anda, saya ingin tahu fungsi spesifik apa yang Anda coba selesaikan? Apa yang tampaknya Anda tanyakan adalah bagaimana melakukan omin program-side-effectful gaya terhadap contoh dalam gaya fungsional, yang tidak masuk akal ..
Jimmy Hoffa
Jawabannya akan tergantung pada bahasa. Di Clojure Anda dapat menggunakan clojure.org/protocols , di mana satu-satunya area lunak adalah jenis parameter yang fungsi-fungsinya harus operasikan - mereka adalah objek - itu saja yang Anda ketahui.
Ayub
1
Sederhanakan: struct yang berisi pointer metode tersebut, ditambah fungsi untuk menginisialisasi dari instance objek. Kenapa Haskell? ;)
mlvljr

Jawaban:

6

Jangan memperlakukan pemrograman fungsional sebagai lapisan tipis di atas pemrograman imperatif; ada lebih dari sekedar perbedaan sintaksis.

Dalam hal ini, Anda memiliki GetIDmetode, yang menyiratkan keunikan objek. Ini bukan pendekatan yang baik untuk menulis program fungsional. Mungkin Anda bisa memberi tahu kami masalah yang Anda coba selesaikan dan kami bisa memberi Anda saran yang lebih bermakna.

dan_waterworth
sumber
3
"Kamu harus berpikir dalam bahasa Rusia."
4аn
Cukup adil; masalah saya yang sebenarnya tidak terlalu menarik. Saya sudah mendapatkan hal-hal yang berfungsi dengan baik di C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay jika Anda benar-benar ingin melihat kode), tetapi akan menyenangkan untuk dilakukan dengan gaya yang lebih fungsional saat masih menggunakan C #.
Pada
1
@ Ðаn Apakah itu benar-benar kutipan Firefox (film) (karena itu hebat jika ya)? Atau digunakan di tempat lain?
icc97
2
Ketika saya setuju itu adalah perubahan paradigma lengkap dari Pemrograman Imperitif ke Fungsional, ada banyak urutan besarnya lebih banyak baris kode yang ditulis dengan cara yang imperatif. Jadi, lebih banyak lagi kasus sudut ditulis untuk sistem besar akan ditemukan dengan pemrograman imperitif. Ada banyak praktik yang baik dalam pemrograman imperatif dan mengetahui apakah keterampilan itu dapat diterjemahkan atau apakah itu bukan masalah dalam FP adalah pertanyaan yang layak. Sangat mungkin untuk menulis kode mengerikan di FP, jadi pertanyaan-pertanyaan semacam ini harus menyoroti bagian-bagian baik dari FP juga.
icc97
11

Haskell dan turunannya memiliki kacamata ketik yang mirip dengan antarmuka. Meskipun sepertinya Anda bertanya tentang bagaimana melakukan enkapsulasi, yang merupakan pertanyaan tentang sistem tipe. Sistem tipe hindley Milner adalah umum dalam bahasa fungsional, dan memiliki tipe data yang melakukan ini untuk Anda dalam berbagai cara di berbagai bahasa.

Jimmy Hoffa
sumber
5
+1 untuk typeclasses - perbedaan utama antara typeclass Haskell dan antarmuka Java adalah bahwa typeclass dikaitkan dengan jenis setelah keduanya dideklarasikan secara terpisah. Anda dapat menggunakan tipe lama melalui "antarmuka" baru semudah Anda dapat menggunakan "antarmuka" lama untuk mengakses tipe baru. Untuk menyembunyikan data, Anda menyembunyikan implementasi tipe dalam modul. Paling tidak menurut Bertrand Meyer dari ketenaran Eiffel , kelas OOP adalah sejenis modul.
Steve314
5

Ada beberapa cara untuk memungkinkan suatu fungsi menangani beberapa input.

Pertama dan paling umum: Polimorfisme Parametrik.

Ini memungkinkan fungsi bertindak pada tipe arbitrer:

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

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

Yang bagus, tetapi tidak memberi Anda pengiriman dinamis yang dimiliki antarmuka OO. Untuk Haskell ini memiliki kacamata ketik, Scala memiliki implisit, dll

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 <+>

Di antara kedua mekanisme ini, Anda dapat mengekspresikan semua jenis perilaku yang kompleks dan menarik pada tipe Anda.

Daniel Gratzer
sumber
1
Anda dapat menambahkan petunjuk highlight sintax ketika bahasa dalam jawaban tidak cocok dengan bahasa dalam pertanyaan. Lihat edit yang saya sarankan misalnya.
hugomg
1

Aturan dasar praktis adalah bahwa dalam fungsi pemrograman FP melakukan pekerjaan yang sama seperti objek dalam pemrograman OO. Anda dapat memanggil metode mereka (yah, metode "panggilan") dan mereka merespons sesuai dengan beberapa aturan internal yang dienkapsulasi. Secara khusus, setiap bahasa FP yang layak di luar sana memungkinkan Anda memiliki "variabel instan" dalam fungsi Anda dengan penutupan / pelingkupan leksikal.

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;
    }
};

Sekarang pertanyaan selanjutnya adalah apa yang Anda maksud dengan antarmuka? Salah satu pendekatan adalah menggunakan antarmuka nominal (sesuai dengan antarmuka jika ia mengatakan demikian) - pendekatan ini biasanya sangat tergantung pada bahasa apa yang Anda gunakan jadi mari kita tinggalkan untuk yang terakhir. Cara lain untuk mendefinisikan antarmuka adalah cara struktural, melihat parameter apa yang diterima dan dikembalikan. Ini adalah jenis antarmuka yang cenderung Anda lihat dalam bahasa yang dinamis dan diketik dengan bebek dan sangat cocok dengan semua FP: antarmuka hanyalah jenis parameter input untuk fungsi kami dan jenis yang dikembalikan sehingga semua fungsi sesuai dengan jenis yang benar sesuai dengan antarmuka!

Oleh karena itu, cara paling mudah untuk merepresentasikan objek yang cocok dengan antarmuka adalah dengan hanya memiliki sekelompok fungsi. Anda biasanya menyiasati keburukan dari melewatkan fungsi secara terpisah dengan mengemasnya dalam semacam catatan:

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

do_something(my_blarfable)

Menggunakan fungsi-fungsi telanjang atau catatan-catatan fungsi akan sangat membantu dalam menyelesaikan sebagian besar masalah umum Anda dengan cara "bebas lemak" tanpa berton-ton boilerplate. Jika Anda membutuhkan sesuatu yang lebih maju dari itu, terkadang bahasa memberi Anda fitur tambahan. Salah satu contoh yang disebutkan orang adalah kelas tipe Haskell. Jenis kelas pada dasarnya mengaitkan jenis dengan salah satu catatan fungsi dan memungkinkan Anda menulis hal-hal sehingga kamus implisit dan secara otomatis diteruskan ke fungsi dalam yang sesuai.

-- 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)

Satu hal penting yang perlu diperhatikan tentang typeclasses adalah bahwa kamus dikaitkan dengan jenis, dan bukan dengan nilai-nilai (seperti apa yang terjadi dalam kamus dan versi OO). Ini berarti Anda bahwa sistem tipe tidak memungkinkan Anda mencampur "tipe" [1]. Jika Anda ingin daftar "blargables" atau fungsi biner yang diambil ke blargables maka typeclasses akan membatasi semuanya menjadi jenis yang sama sementara pendekatan kamus akan membiarkan Anda memiliki blargables dari asal yang berbeda (versi mana yang lebih baik banyak tergantung pada apa yang Anda berada perbuatan)

[1] Ada beberapa cara canggih untuk melakukan "tipe eksistensial" tetapi biasanya tidak sepadan dengan masalahnya.

hugomg
sumber
0

Saya pikir ini akan menjadi spesifik bahasa. Saya berasal dari latar belakang lispy. Dalam banyak kasus antarmuka dengan keadaan memecah model fungsional ke tingkat tertentu. Jadi CLOS, misalnya, adalah tempat LISP kurang fungsional dan lebih dekat ke bahasa imperatif. Secara umum, parameter fungsi yang diperlukan yang dikombinasikan dengan metode level yang lebih tinggi mungkin adalah yang Anda cari.

;; 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)))
ipaul
sumber