Definisi Pemrograman Fungsional
Pengantar The Joy of Clojure mengatakan yang berikut:
Pemrograman fungsional adalah salah satu istilah komputasi yang memiliki definisi amorf. Jika Anda bertanya kepada 100 programmer tentang definisi mereka, Anda kemungkinan akan menerima 100 jawaban berbeda ...
Pemrograman fungsional menyangkut dan memfasilitasi penerapan dan komposisi fungsi ... Agar suatu bahasa dianggap fungsional, pengertian fungsinya harus kelas satu. Fungsi kelas satu dapat disimpan, diteruskan, dan dikembalikan sama seperti bagian data lainnya. Di luar konsep inti ini, [definisi FP mungkin meliputi] kemurnian, kekekalan, rekursi, kemalasan, dan transparansi referensial.
Pemrograman dalam Scala 2nd Edition hlm. 10 memiliki definisi berikut:
Pemrograman fungsional dipandu oleh dua ide utama. Gagasan pertama adalah bahwa fungsi adalah nilai kelas satu ... Anda dapat meneruskan fungsi sebagai argumen ke fungsi lain, mengembalikannya sebagai hasil dari fungsi, atau menyimpannya dalam variabel ...
Gagasan utama kedua pemrograman fungsional adalah bahwa operasi suatu program harus memetakan nilai input ke nilai output daripada mengubah data yang ada.
Jika kami menerima definisi pertama, maka satu-satunya hal yang perlu Anda lakukan untuk membuat kode Anda "fungsional" adalah mengubah loop Anda ke dalam. Definisi kedua termasuk kekekalan.
Fungsi Kelas Pertama
Bayangkan Anda saat ini mendapatkan Daftar Penumpang dari objek Bus Anda dan Anda mengulanginya mengurangi rekening bank setiap penumpang dengan jumlah ongkos bus. Cara fungsional untuk melakukan tindakan yang sama akan memiliki metode pada Bus, mungkin disebut forEachPassenger yang mengambil fungsi dari satu argumen. Kemudian Bus akan beralih dari penumpangnya namun yang terbaik dilakukan dan kode klien Anda yang menetapkan ongkos untuk perjalanan akan dimasukkan ke dalam fungsi dan diteruskan ke untuk setiap Penumpang. Voila! Anda menggunakan pemrograman fungsional.
Imperatif:
for (Passenger p : Bus.getPassengers()) {
p.debit(fare);
}
Fungsional (menggunakan fungsi anonim atau "lambda" di Scala):
myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })
Versi Scala yang lebih manis:
myBus = myBus.forEachPassenger(_.debit(fare))
Fungsi Non-kelas
Jika bahasa Anda tidak mendukung fungsi kelas satu, ini bisa menjadi sangat jelek. Di Java 7 atau sebelumnya, Anda harus menyediakan antarmuka "Objek Fungsional" seperti ini:
// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
public void accept(T t);
}
Kemudian kelas Bus menyediakan iterator internal:
public void forEachPassenger(Consumer<Passenger> c) {
for (Passenger p : passengers) {
c.accept(p);
}
}
Akhirnya, Anda meneruskan objek fungsi anonim ke Bus:
// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
}
}
Java 8 memungkinkan variabel lokal untuk ditangkap lingkup fungsi anonim, tetapi dalam versi sebelumnya, varibales tersebut harus dinyatakan final. Untuk menyiasatinya, Anda mungkin perlu membuat kelas pembungkus MutableReference. Berikut adalah kelas khusus integer yang memungkinkan Anda menambahkan penghitung lingkaran ke kode di atas:
public static class MutableIntWrapper {
private int i;
private MutableIntWrapper(int in) { i = in; }
public static MutableIntWrapper ofZero() {
return new MutableIntWrapper(0);
}
public int value() { return i; }
public void increment() { i++; }
}
final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
@Override
public void accept(final Passenger p) {
p.debit(fare);
count.increment();
}
}
System.out.println(count.value());
Bahkan dengan keburukan ini, kadang-kadang bermanfaat untuk menghilangkan logika yang rumit dan berulang dari loop yang tersebar di seluruh program Anda dengan menyediakan iterator internal.
Keburukan ini telah diperbaiki di Java 8, tetapi penanganan pengecualian yang diperiksa di dalam fungsi kelas satu masih benar-benar jelek dan Java masih membawa asumsi bisa berubah-ubah dalam semua koleksinya. Yang membawa kita ke tujuan lain yang sering dikaitkan dengan FP:
Kekekalan
Josh Bloch's Item 13 adalah "Prefer Immutability." Meskipun pembicaraan sampah umum bertentangan, OOP dapat dilakukan dengan objek yang tidak dapat diubah, dan melakukan hal itu membuatnya jauh lebih baik. Misalnya, String di Java tidak dapat diubah. StringBuffer, OTOH harus bisa berubah untuk membangun String yang tidak berubah. Beberapa tugas, seperti bekerja dengan buffer secara inheren membutuhkan kemampuan berubah-ubah.
Kemurnian
Setiap fungsi setidaknya harus dapat dipindah-pindahkan - jika Anda memberikan parameter input yang sama (dan seharusnya tidak memiliki input selain argumen aktualnya), ia harus menghasilkan output yang sama setiap kali tanpa menyebabkan "efek samping" seperti mengubah keadaan global, melakukan / O, atau melempar pengecualian.
Dikatakan bahwa dalam Pemrograman Fungsional, "beberapa kejahatan biasanya diperlukan untuk menyelesaikan pekerjaan." Kesucian 100% umumnya bukan tujuan. Meminimalkan efek sampingnya.
Kesimpulan
Sungguh, dari semua ide di atas, ketidakmampuan telah menjadi kemenangan tunggal terbesar dalam hal aplikasi praktis untuk menyederhanakan kode saya - apakah OOP, atau FP. Melewati fungsi ke iterator adalah kemenangan terbesar kedua. The Java 8 Lambdas dokumentasi memiliki penjelasan terbaik mengapa. Rekursi sangat bagus untuk memproses pohon. Kemalasan memungkinkan Anda bekerja dengan koleksi tak terbatas.
Jika Anda menyukai JVM, saya sarankan Anda melihat Scala dan Clojure. Keduanya adalah interpretasi mendalam tentang Pemrograman Fungsional. Scala adalah tipe-aman dengan sintaksis mirip-C, meskipun ia benar-benar memiliki banyak sintaksis yang sama dengan Haskell seperti halnya dengan C. Clojure bukan tipe-aman dan itu adalah Lisp. Saya baru-baru ini memposting perbandingan Java, Scala dan Clojure sehubungan dengan satu masalah refactoring tertentu. Perbandingan Logan Campbell menggunakan Game of Life termasuk Haskell dan juga Clojure yang diketik.
PS
Jimmy Hoffa menunjukkan bahwa kelas Bus saya bisa berubah. Daripada memperbaiki yang asli, saya pikir ini akan menunjukkan jenis refactoring pertanyaan ini. Ini dapat diperbaiki dengan membuat setiap metode pada Bus sebuah pabrik untuk menghasilkan Bus baru, setiap metode pada Penumpang sebuah pabrik untuk menghasilkan Penumpang baru. Jadi saya telah menambahkan jenis kembali ke segala sesuatu yang berarti saya akan menyalin java.util.function.Function Java 8 bukan antarmuka konsumen:
public interface Function<T,R> {
public R apply(T t);
// Note: I'm leaving out Java 8's compose() method here for simplicity
}
Kemudian di Bus:
public Bus mapPassengers(Function<Passenger,Passenger> c) {
// I have to use a mutable collection internally because Java
// does not have immutable collections that return modified copies
// of themselves the way the Clojure and Scala collections do.
List<Passenger> newPassengers = new ArrayList(passengers.size());
for (Passenger p : passengers) {
newPassengers.add(c.apply(p));
}
return Bus.of(driver, Collections.unmodifiableList(passengers));
}
Akhirnya, objek fungsi anonim mengembalikan keadaan yang dimodifikasi (bus baru dengan penumpang baru). Ini mengasumsikan bahwa p.debit () sekarang mengembalikan Penumpang kekal baru dengan lebih sedikit uang daripada yang asli:
Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
@Override
public Passenger apply(final Passenger p) {
return p.debit(fare);
}
}
Semoga sekarang Anda dapat membuat keputusan sendiri tentang seberapa fungsional Anda ingin membuat bahasa imperatif Anda, dan memutuskan apakah akan lebih baik untuk mendesain ulang proyek Anda menggunakan bahasa fungsional. Di Scala atau Clojure, koleksi dan API lainnya dirancang untuk memudahkan pemrograman fungsional. Keduanya memiliki interop Java yang sangat bagus, sehingga Anda dapat mencampur dan mencocokkan bahasa. Bahkan, untuk interoperabilitas Java, Scala mengkompilasi fungsi kelas pertamanya ke kelas anonim yang hampir kompatibel dengan antarmuka fungsional Java 8. Anda dapat membaca tentang detail di Scala in Depth sect. 1.3.2 .