Fitur Fungsional apa yang bernilai sedikit kebingungan OOP untuk manfaat yang mereka bawa?


13

Setelah mempelajari pemrograman fungsional dalam Haskell dan F #, paradigma OOP tampaknya terbelakang dengan kelas, antarmuka, objek. Aspek FP mana yang dapat saya bawa ke tempat kerja yang bisa dipahami rekan kerja saya? Apakah ada gaya KB yang layak untuk diajak bicara kepada bos saya tentang melatih kembali tim saya sehingga kami dapat menggunakannya?

Aspek yang mungkin dari KB:

  • Kekekalan
  • Aplikasi Parsial dan Kari
  • Fungsi Kelas Pertama (pointer fungsi / Objek Fungsional / Pola Strategi)
  • Evaluasi Malas (dan Monad)
  • Fungsi Murni (tanpa efek samping)
  • Ekspresi (vs. Pernyataan - setiap baris kode menghasilkan nilai alih-alih, atau selain menyebabkan efek samping)
  • Pengulangan
  • Pencocokan Pola

Apakah ini gratis untuk semua tempat kita dapat melakukan apa pun yang didukung oleh bahasa pemrograman hingga batas yang didukung oleh bahasa itu? Atau adakah pedoman yang lebih baik?


6
Saya memiliki pengalaman serupa. Setelah sekitar 2 bulan sakit saya mulai menemukan keseimbangan yang bagus dari "barang yang memetakan ke objek" dan "barang yang memetakan ke fungsi". Ini membantu untuk melakukan beberapa peretasan serius dalam bahasa yang mendukung keduanya. Pada akhirnya, keterampilan FP dan OOP saya jauh lebih baik
Daniel Gratzer

3
FWIW, Linq fungsional dan malas, dan Anda dapat mensimulasikan pemrograman fungsional dalam C # dengan menggunakan metode statis dan menghindari kegigihan negara.
Robert Harvey

1
Pada titik ini, Anda harus membaca sicp . Ini gratis dan ditulis dengan baik. Ini menawarkan perbandingan yang bagus antara kedua paradigma.
Simon Bergot

4
FP dan OOP pada saat yang sama dalam arti orthogonal dan dalam arti ganda. OOP adalah tentang abstraksi data, FP adalah tentang (tidak adanya) efek samping. Apakah Anda memiliki efek samping atau tidak adalah ortogonal terhadap bagaimana Anda mengabstraksi data Anda. Kalkulus Lambda adalah fungsional dan berorientasi objek misalnya. Ya, FP biasanya menggunakan Tipe Data Abstrak, bukan objek, tetapi Anda bisa juga menggunakan objek bukan tanpa FP. OTOH, ada juga hubungan yang dalam: fungsi isomorfik untuk objek dengan hanya satu metode (itulah cara mereka "dipalsukan" di Jawa, dan diterapkan di Java8, ...
Jörg W Mittag

3
Saya pikir aspek terkuat dari pertanyaan Anda berkaitan dengan keterbacaan. "Seberapa banyak gaya Pemrograman Fungsional yang pantas untuk dibawa bekerja di toko Object Oriented?" Atau fitur Fungsional apa yang patut sedikit membingungkan OOP untuk manfaat yang mereka bawa.
GlenPeterson

Jawaban:


13

Pemrograman fungsional adalah paradigma yang berbeda dari pemrograman berorientasi objek (pola pikir yang berbeda, dan cara berpikir yang berbeda tentang program). Anda mulai menyadari bahwa di sini ada lebih dari satu cara (berorientasi objek) untuk memikirkan masalah dan solusinya. Ada yang lain (pemrograman prosedural dan generik datang ke pikiran). Bagaimana Anda bereaksi terhadap pengetahuan baru ini, apakah Anda menerima dan mengintegrasikan alat dan pendekatan baru ini ke dalam keahlian Anda, akan menentukan apakah Anda tumbuh dan menjadi pengembang yang lebih lengkap dan terampil.

Kita semua terlatih untuk menangani dan merasa nyaman dengan tingkat kompleksitas tertentu. Saya suka menyebutnya batas hrair seseorang (dari Watership Down, seberapa tinggi Anda dapat menghitung). Merupakan hal yang hebat untuk memperluas pikiran Anda, kemampuan Anda untuk mempertimbangkan lebih banyak pilihan, dan memiliki lebih banyak alat untuk mendekati dan menyelesaikan masalah. Tetapi itu adalah perubahan, dan itu menarik Anda keluar dari zona nyaman Anda.

Satu masalah yang mungkin Anda temui adalah Anda menjadi kurang puas untuk mengikuti kerumunan "semuanya adalah objek". Anda mungkin harus mengembangkan kesabaran ketika Anda bekerja dengan orang-orang yang mungkin tidak mengerti (atau ingin memahami) mengapa pendekatan fungsional untuk pengembangan perangkat lunak bekerja dengan baik untuk masalah-masalah tertentu. Sama seperti pendekatan pemrograman generik bekerja dengan baik untuk masalah-masalah tertentu.

Semoga berhasil!


3
Saya ingin menambahkan, bahwa seseorang dapat memperoleh pemahaman yang lebih baik tentang beberapa konsep OOP tradisional ketika bekerja dengan bahasa fungsional seperti Haskell atau Clojure. Secara pribadi saya menyadari bagaimana polimorfisme benar-benar konsep penting (Antarmuka di Jawa atau typeclasses di Haskell), sementara pewarisan (apa yang saya pikir sebagai konsep pendefinisian) adalah semacam abstraksi yang aneh.
wirrbel

6

Pemrograman fungsional menghasilkan sangat praktis, sederhana, produktivitas dalam penulisan kode sehari-hari: beberapa fitur mendukung kesederhanaan, yang hebat karena semakin sedikit kode yang Anda tulis, semakin sedikit kegagalan yang Anda lakukan, dan semakin sedikit pemeliharaan yang diperlukan.

Menjadi ahli matematika, saya menemukan hal-hal fungsional yang mewah sangat menarik, tetapi biasanya berguna ketika merancang aplikasi: struktur ini dapat mengkodekan dalam struktur program banyak invarian dari program, tanpa mewakili invarian ini dengan variabel.

Kombinasi favorit saya mungkin terlihat sangat sepele, namun saya percaya itu memiliki dampak produktivitas yang sangat tinggi. Kombinasi ini adalah Aplikasi Parsial dan Currying dan Fungsi Kelas Pertama yang saya akan beri label ulang untuk for-loop lagi : alih-alih melewati tubuh loop ke fungsi iterasi atau pemetaan. Saya baru-baru ini disewa untuk pekerjaan C ++ dan saya perhatikan, saya benar-benar kehilangan kebiasaan menulis for-loop!

Kombinasi Rekursi dan Pencocokan Pola memusnahkan kebutuhan pola desain Pengunjung tersebut . Bandingkan saja kode yang Anda perlukan untuk memprogram seorang evaluator ekspresi boolean: dalam bahasa pemrograman fungsional apa pun ini harus sekitar 15 baris kode, dalam OOP hal yang benar untuk dilakukan adalah menggunakan pola desain Pengunjung tersebut , yang mengubah contoh mainan itu menjadi esai yang luas. Keuntungan sudah jelas dan saya tidak mengetahui adanya ketidaknyamanan.


2
Saya setuju sepenuhnya tetapi saya telah mendapat dorongan balik dari orang-orang yang di seluruh industri cenderung setuju: Mereka tahu mereka adalah pola pengunjung, mereka telah melihat dan menggunakannya berkali-kali sehingga kode di dalamnya adalah sesuatu yang mereka pahami dan kenal, pendekatan lain meskipun sangat sederhana dan mudah adalah asing dan karenanya lebih sulit bagi mereka. Ini adalah fakta yang tidak menguntungkan dari industri yang telah memiliki 15+ tahun OOP yang membentur setiap kepala programer bahwa 100+ baris kode lebih mudah bagi mereka untuk memahami daripada 10 hanya karena mereka telah menghafal 100+ baris setelah mengulanginya lebih dari satu dekade
Jimmy Hoffa

1
-1 - Lebih banyak kode singkat tidak berarti Anda sedang menulis kode "kurang". Anda sedang menulis kode yang sama menggunakan lebih sedikit karakter. Jika ada, Anda membuat lebih banyak kesalahan karena kodenya (sering) lebih sulit dibaca.
Telastyn

8
@ Telastyn: Terse tidak sama dengan tidak dapat dibaca. Selain itu, massa yang sangat besar dari boilerplate yang kembung memiliki cara mereka sendiri untuk tidak dapat dibaca.
Michael Shaw

1
@Telastyn Saya pikir Anda baru saja menyentuh inti sebenarnya di sini, ya singkat bisa menjadi buruk dan tidak dapat dibaca, kembung bisa menjadi buruk dan juga tidak dapat dibaca, tetapi kuncinya bukan panjang variabel dan kode yang ditulis secara membingungkan. Kuncinya adalah seperti yang Anda sebutkan di atas jumlah operasi, saya tidak setuju bahwa jumlah operasi tidak berkorelasi dengan pemeliharaan, saya pikir melakukan lebih sedikit hal (dengan kode yang ditulis dengan jelas) tidak menguntungkan keterbacaan dan pemeliharaan. Jelas melakukan jumlah hal yang sama dengan fungsi huruf tunggal dan nama variabel tidak akan membantu, FP yang baik membutuhkan operasi yang jauh lebih sedikit masih ditulis dengan jelas
Jimmy Hoffa

2
@ user949300: jika Anda ingin contoh yang sama sekali berbeda, bagaimana dengan ini Java 8 contoh ?: list.forEach(System.out::println);Dari sudut pandang FP pandang, printlnadalah fungsi mengambil dua argumen, target PrintStreamdan nilai Objecttetapi Collection's forEachmetode mengharapkan fungsi dengan satu argumen hanya yang bisa diterapkan ke setiap elemen. Jadi argumen pertama terikat pada instance yang ditemukan dalam System.outmenghasilkan ke fungsi baru dengan satu argumen. Ini lebih sederhana daripadaBiConsumer<…> c=PrintStream::println; PrintStream a1=System.out; list.forEach(a2 -> c.accept(a1, a2));
Holger

5

Anda mungkin harus membatasi bagian mana dari pengetahuan Anda yang Anda gunakan di tempat kerja, cara Superman harus berpura-pura menjadi Clark Kent untuk menikmati fasilitas kehidupan normal. Tetapi mengetahui lebih banyak tidak akan pernah menyakiti Anda. Yang mengatakan, beberapa aspek Pemrograman Fungsional sesuai untuk toko Berorientasi Objek, dan aspek-aspek lain mungkin layak dibicarakan dengan bos Anda sehingga Anda dapat meningkatkan tingkat pengetahuan rata-rata toko Anda dan mendapatkan untuk menulis kode yang lebih baik sebagai hasilnya.

FP dan OOP tidak saling eksklusif. Lihatlah Scala. Beberapa orang berpikir itu adalah yang terburuk karena itu adalah FP yang tidak murni, tetapi beberapa berpikir itu yang terbaik untuk alasan yang sama.

Satu per satu, berikut adalah beberapa aspek yang bekerja sangat baik dengan OOP:

  • Pure Functions (tanpa efek samping) - Setiap bahasa pemrograman yang saya ketahui mendukung ini. Mereka membuat kode Anda jauh, lebih mudah untuk dipikirkan dan harus digunakan kapan pun praktis. Anda tidak harus menyebutnya FP. Sebut saja praktik pengkodean yang baik.

  • Kekekalan: String dapat dikatakan sebagai objek Java yang paling umum digunakan dan tidak dapat diubah. Saya membahas Immutable Java Objects dan Immutable Java Collections di blog saya. Beberapa di antaranya mungkin berlaku untuk Anda.

  • Fungsi Kelas Pertama (pointer fungsi / Objek Fungsional / Pola Strategi) - Java telah memiliki versi mutan yang pincang sejak versi 1.1 dengan sebagian besar kelas API (dan ada ratusan) yang mengimplementasikan antarmuka Listener. Runnable mungkin adalah objek fungsional yang paling umum digunakan. Fungsi Kelas Satu lebih berfungsi untuk mengkodekan dalam bahasa yang tidak mendukungnya secara asli, tetapi terkadang sepadan dengan usaha ekstra saat menyederhanakan aspek lain dari kode Anda.

  • Rekursi berguna untuk memproses pohon. Di toko OOP, itu mungkin merupakan penggunaan rekursi yang tepat dan tepat. Menggunakan rekursi untuk bersenang-senang di OOP mungkin harus disukai jika tanpa alasan lain daripada kebanyakan bahasa OOP tidak memiliki ruang stack secara default untuk membuat ini ide yang baik.

  • Ekspresi (vs Pernyataan - setiap baris kode menghasilkan nilai alih-alih, atau selain menyebabkan efek samping) - Satu-satunya operator evaluatif dalam C, C ++, dan Java adalah Operator Ternary . Saya membahas penggunaan yang sesuai di blog saya. Anda mungkin menemukan Anda menulis beberapa fungsi sederhana yang sangat dapat digunakan kembali dan evaluatif.

  • Malas Evaluasi (dan Monads) - sebagian besar terbatas pada inisialisasi malas di OOP. Tanpa fitur bahasa untuk mendukungnya, Anda mungkin menemukan beberapa API yang bermanfaat, tetapi menulis sendiri itu sulit. Maksimalkan penggunaan stream Anda - lihat antarmuka Writer dan Reader untuk contoh.

  • Aplikasi Parsial dan Kari - Tidak praktis tanpa fungsi kelas satu.

  • Pencocokan Pola - umumnya tidak disarankan dalam OOP.

Singkatnya, saya tidak berpikir pekerjaan harus menjadi gratis untuk semua tempat Anda dapat melakukan apa pun yang didukung oleh bahasa pemrograman hingga batas yang didukung oleh bahasa itu. Saya pikir keterbacaan oleh rekan kerja Anda harus menjadi tes lakmus Anda untuk kode yang dibuat untuk disewa. Di mana hal itu paling membuat Anda resah, saya ingin memulai pendidikan di tempat kerja untuk memperluas wawasan rekan kerja Anda.


Sejak mempelajari FP, saya terbiasa merancang hal-hal untuk memiliki antarmuka yang lancar yang menghasilkan apa yang mirip dengan ekspresi, sebuah fungsi yang memiliki satu pernyataan yang melakukan banyak hal. Ini yang paling dekat dengan Anda, tetapi pendekatan yang mengalir secara alami dari kemurnian ketika Anda menemukan Anda tidak lagi memiliki metode batal, menggunakan metode ekstensi statis di C # bantu ini sangat. Dengan cara itu, titik ekspresi Anda adalah satu-satunya poin yang tidak saya setujui, semua yang lain sesuai dengan pengalaman saya sendiri belajar FP dan bekerja .NET day job
Jimmy Hoffa

Apa yang benar-benar mengganggu saya dalam C # sekarang adalah bahwa saya tidak dapat menggunakan delegasi alih-alih antarmuka satu metode karena 2 alasan sederhana: 1. Anda tidak dapat membuat lamba rekursif tanpa peretasan (menugaskan untuk membatalkan dulu dan kemudian ke lambda kedua) atau Y- Combinator (yang jelek sekali di C #). 2. tidak ada alias jenis yang dapat Anda gunakan dalam lingkup proyek, sehingga tanda tangan delegasi Anda dengan cepat menjadi tidak terkelola. Jadi hanya untuk 2 alasan bodoh ini saya tidak dapat menikmati C # lagi, karena satu-satunya hal yang saya dapat membuatnya bekerja adalah dengan menggunakan antarmuka satu-metode yang hanya pekerjaan tambahan yang tidak perlu.
Trident D'Gao

@bonomo Java 8 memiliki java.util.function.BiConsumer generik yang mungkin berguna dalam C #: public interface BiConsumer<T, U> { public void accept(T t, U u); }Ada antarmuka fungsional lain yang berguna di java.util.fungsi.
GlenPeterson

@bonomo Hei aku mengerti, ini adalah rasa sakit dari Haskell. Setiap kali Anda membaca seseorang berkata "Belajar FP membuat saya lebih baik di OOP" berarti mereka belajar Ruby atau sesuatu yang tidak murni dan deklaratif seperti Haskell. Haskell menjelaskan bahwa OOP adalah level rendah yang tidak berguna. Rasa sakit terbesar yang Anda temui adalah bahwa inferensi tipe berbasis kendala tidak dapat diputuskan ketika Anda tidak berada dalam sistem tipe HM, jadi inferensi tipe berbasis cosntraint sama sekali tidak dilakukan: blogs.msdn.com/b/ericlippert/archive / 2012/03/09 / ...
Jimmy Hoffa

1
Most OOP languages don't have the stack space for it Betulkah? Yang Anda perlukan hanyalah 30 level rekursi untuk mengelola milyaran node dalam pohon biner seimbang. Saya cukup yakin ruang stack saya cocok untuk banyak level lebih dari ini.
Robert Harvey

3

Selain pemrograman fungsional dan pemrograman berorientasi objek, ada juga pemrograman deklaratif (SQL, XQuery). Mempelajari setiap gaya membantu Anda mendapatkan wawasan baru, dan Anda akan belajar memilih alat yang tepat untuk pekerjaan itu.

Tapi ya, bisa sangat frustasi untuk menulis kode dalam bahasa, dan tahu bahwa jika Anda menggunakan sesuatu yang lain, Anda bisa menjadi jauh lebih produktif untuk domain masalah tertentu. Namun, bahkan jika Anda menggunakan bahasa seperti Java, dimungkinkan untuk menerapkan konsep dari FP ke kode Java Anda, meskipun dengan cara bundaran. Kerangka kerja Guava misalnya melakukan beberapa hal ini.


2

Sebagai seorang programmer saya pikir Anda tidak boleh berhenti belajar. Yang mengatakan, sangat menarik bahwa belajar FP menodai keterampilan OOP Anda. Saya cenderung menganggap belajar OOP sebagai belajar cara mengendarai sepeda; Anda tidak pernah lupa bagaimana melakukannya.

Ketika saya mempelajari seluk beluk FP, saya mendapati diri saya berpikir lebih matematis dan mendapatkan perspektif yang lebih baik tentang cara saya menulis perangkat lunak. Itu pengalaman pribadi saya.

Ketika Anda mendapatkan lebih banyak pengalaman, konsep pemrograman inti akan jauh lebih sulit hilang. Jadi saya sarankan Anda santai di FP sampai konsep OOP benar-benar dipadatkan dalam pikiran Anda. FP adalah pergeseran paradigma yang pasti. Semoga berhasil!


4
Belajar OOP seperti belajar merangkak. Tetapi begitu Anda berdiri dengan mantap, Anda hanya akan merangkak ketika Anda terlalu mabuk. Tentu saja Anda tidak bisa melupakan bagaimana melakukannya, tetapi biasanya Anda tidak mau. Dan itu akan menjadi pengalaman yang menyakitkan untuk berjalan dengan crawler ketika Anda tahu Anda bisa berlari.
SK-logic

@ SK-logika, saya suka metafora Anda
Trident D'Gao

@ SK-Logic: Seperti apa belajar pemrograman imperatif? Menyeret diri sendiri di atas perut Anda?
Robert Harvey

@RobertHarvey mencoba menggali bawah tanah dengan sendok berkarat dan setumpuk kartu punch.
Jimmy Hoffa

0

Sudah ada banyak jawaban bagus, jadi pertanyaan saya akan menjawab sebagian pertanyaan Anda; yaitu, saya mengambil dasar pemikiran pertanyaan Anda, karena OOP dan fitur fungsional tidak saling eksklusif.

Jika Anda menggunakan C ++ 11, ada banyak fitur pemrograman fungsional yang dibangun ke perpustakaan bahasa / standar yang bersinergi (cantik) dengan OOP. Tentu saja, saya tidak yakin seberapa baik TMP akan diterima oleh bos atau rekan kerja Anda, tetapi intinya adalah Anda bisa mendapatkan banyak fitur ini dalam beberapa bentuk dalam bahasa non-fungsional / OOP, seperti C ++.

Menggunakan template dengan waktu rekursi kompilasi bergantung pada 3 poin pertama Anda,

  • Kekekalan
  • Pengulangan
  • Pencocokan Pola

Dalam nilai template yang tidak dapat diubah (konstanta waktu kompilasi), setiap iterasi dilakukan menggunakan rekursi, dan percabangan dilakukan menggunakan (lebih atau kurang) pencocokan pola, dalam bentuk resolusi kelebihan beban.

Adapun poin lainnya, menggunakan std::binddan std::functionmemberi Anda aplikasi fungsi parsial, dan pointer fungsi adalah bawaan untuk bahasa. Objek yang bisa dipanggil adalah objek fungsional (serta aplikasi fungsi parsial). Perhatikan bahwa dengan objek yang dapat dipanggil, maksud saya objek yang menentukannya operator ().

Evaluasi malas dan fungsi murni akan sedikit lebih sulit; untuk fungsi murni, Anda dapat menggunakan fungsi lambda yang hanya menangkap berdasarkan nilai, tetapi ini tidak ideal.

Terakhir, inilah contoh penggunaan rekursi kompilasi-waktu dengan aplikasi fungsi parsial. Ini adalah contoh yang agak dibuat-buat, tetapi menunjukkan sebagian besar poin di atas. Itu akan secara rekursif mengikat nilai-nilai dalam tuple yang diberikan ke fungsi yang diberikan dan menghasilkan objek fungsi (callable)

#include <iostream>
#include <functional>

//holds a compile-time index sequence
template<std::size_t ... >
struct index_seq
{};

//builds the index_seq<...> struct with the indices (boils down to compile-time indexing)
template<std::size_t N, std::size_t ... Seq>
struct gen_indices
  : gen_indices<N-1, N-1, Seq ... >
{};

template<std::size_t ... Seq>
struct gen_indices<0, Seq ... >
{
    typedef index_seq<Seq ... > type;
};


template <typename RType>
struct bind_to_fcn
{
    template <class Fcn, class ... Args>
    std::function<RType()> fcn_bind(Fcn fcn, std::tuple<Args...> params)
    {
        return bindFunc(typename gen_indices<sizeof...(Args)>::type(), fcn, params);
    }

    template<std::size_t ... Seq, class Fcn, class ... Args>
    std::function<RType()> bindFunc(index_seq<Seq...>, Fcn fcn, std::tuple<Args...> params)
    {
        return std::bind(fcn, std::get<Seq>(params) ...);
    }
};

//some arbitrary testing function to use
double foo(int x, float y, double z)
{
    return x + y + z;
}

int main(void)
{
    //some tuple of parameters to use in the function call
    std::tuple<int, float, double> t = std::make_tuple(1, 2.04, 0.1);                                                                                                                                                                                                      
    typedef double(*SumFcn)(int,float,double);

    bind_to_fcn<double> binder;
    auto other_fcn_obj = binder.fcn_bind<SumFcn>(foo, t);
    std::cout << other_fcn_obj() << std::endl;
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.