lisan ini adalah terjemahan dari esai dengan judul “Can Your Programming Language Do This?” karya Joel Spolsky. Walaupun terdapat beberapa fakta yang sudah kurang tepat (terutama mengenai Java) ide utama dari tulisan ini masih tepat, dan penting untuk direnungkan. Ingat juga bahwa tulisan ini dibuat pada tahun 2006. Telah terdapat banyak perubahan dalam dunia pemrograman sejak saat itu.

Langsung saja, tulisan terjemahannya dimulai setelah paragraf ini.


Suatu hari, kamu sedang menelusuri kodemu, dan lalu menemukan dua blok yang kelihatannya hampir sama persis. Bahkan sebenarnya kedua blok tersebut sama persis, kecuali pada bagian di mana satu merujuk ke “Spaghetti” dan yang satunya lagi merujuk ke “Chocolate Moose.”

// Contoh sederhana:

alert("I'd like some Spaghetti!");
alert("I'd like some Chocolate Moose!");

Contoh yang dipakai kebetulan dibuat dalam Javascript, tetapi kalaupun kamu tidak mengerti Javascript, contoh ini seharusnya tidak terlalu susah untuk dimengerti.

Kode yang berulang seperti ini terasa salah, sehingga kamu membuat sebuah fungsi:

function SwedishChef( food )
{
    alert("I'd like some " + food + "!");
}

SwedishChef("Spaghetti");
SwedishChef("Chocolate Moose");

Ok, contohnya sangat sederhana, tetapi kamu dapat membayangkan contoh yang lebih substantif. Kode di atas lebih baik karena banyak hal, yang kesemuanya sudah pernah kamu dengar jutaan kali. Maintainability, Readability, Abstraction = Baik!

Sekarang kamu melihat ada dua blok kode lain yang nampaknya hampir sama persis, kecuali salah satu blok memanggil fungsi BoomBoom dan yang satunya lagi memanggil fungsi PutInPot. Selain itu, kedua kode sama sekali tidak berbeda.

alert("get the lobster");
PutInPot("lobster");
PutInPot("water");

alert("get the chicken");
BoomBoom("chicken");
BoomBoom("coconut");

Sekarang kamu perlu mencari cara untuk mengirimkan sebuah argumen ke fungsi, yang mana argumen itu sendiri adalah sebuah fungsi. Kemampuan ini adalah hal yang sangat penting, karena hal ini meningkatkan kesempatan kita untuk mencari kode-kode umum yang dapat dimasukkan ke dalam sebuah fungsi.

function Cook( i1, i2, f )
{
    alert("get the " + i1);
    f(i1);
    f(i2);
}

Cook( "lobster", "water", PutInPot );
Cook( "chicken", "coconut", BoomBoom );

Lihat! Kita mengirimkan sebuah fungsi sebagai argumen.

Dapatkah bahasa pemrogramanmu melakukan hal ini?

Tunggu dulu… Misalkan kamu belum membuat fungsi PutInPot atau BoomBoom. Bukankah akan sangat enak jika kamu dapat langsung membuat fungsi tersebut langsung daripada harus mendeklarasikannya terlebih dahulu di tempat lain?

Cook( "lobster", 
      "water", 
      function(x) { alert("pot " + x); }  );
Cook( "chicken", 
      "coconut", 
      function(x) { alert("boom " + x); } );

Wow, berguna sekali fitur ini. Perhatikan bahwa saya membuat fungsi secara langsung, dan bahkan saya tidak memberikan nama untuk fungsi tersebut, hanya mengangkat fungsi tersebut dan melemparkannya ke fungsi lainnya.

Begitu kamu mulai berpikir dalam konteks fungsi anonim sebagai argumen, kamu akan mulai menyadari bahwa sangat banyak kode di berbagai tempat yang melakukan sesuatu, misalnya, melakukan sesuatu terhadap setiap elemen di dalam array.

var a = [1,2,3];

for (i=0; i<a.length; i++)
{
    a[i] = a[i] * 2;
}

for (i=0; i<a.length; i++)
{
    alert(a[i]);
}

Melakukan sesuatu terhadap setiap elemen dalam array cukup umum, dan kamu dapat menuliskan fungsi yang melakukannya untukmu:

function map(fn, a)
{
    for (i = 0; i < a.length; i++)
    {
        a[i] = fn(a[i]);
    }
}

Sekarang kode barusan dapat ditulis kembali seperti berikut:

map( function(x){return x*2;}, a );
map( alert, a );

Satu hal lagi yang umum dilakukan terhadap array adalah menggabungkan semua nilai array entah bagaimanapun caranya.

function sum(a)
{
    var s = 0;
    for (i = 0; i < a.length; i++)
        s += a[i];
    return s;
}

function join(a)
{
    var s = "";
    for (i = 0; i < a.length; i++)
        s += a[i];
    return s;
}

alert(sum([1,2,3]));
alert(join(["a","b","c"]));

sum dan join sangat mirip sekali, dan kamu mungkin ingin membungkus inti dari fungsi ke dalam sebuah fungsi generik yang menggabungkan elemen-elemen dalam array menjadi sebuah nilai baru:

function reduce(fn, a, init)
{
    var s = init;
    for (i = 0; i < a.length; i++)
        s = fn( s, a[i] );
    return s;
}

function sum(a)
{
    return reduce( function(a, b){ return a + b; }, 
                   a, 0 );
}

function join(a)
{
    return reduce( function(a, b){ return a + b; }, 
                   a, "" );
}

Banyak bahasa-bahasa lama yang sama sekali tidak memiliki cara untuk melakukan hal ini. Beberapa bahasa lain memiliki cara untuk melakukan ini, tetapi dengan sangat sulit (contohnya, C memiliki function pointer, tetapi kamu tetap harus mendeklarasikan dan membuata fungsi yang akan dikirimkan di tempat lain). Bahasa berorientasi objek belum benar-benar yakin bahwa kamu diperbolehkan untuk melakukan sesuatu dengan fungsi.

Java mengharuskan kamu membuat sebuah objek baru dengan hanya satu method yang disebut functor jika kamu ingin memperlakukan sebuah fungsi layaknya objek kelas pertama. Kombinasikan hal ini dengan fakta bahwa banyak bahasa berorientasi objek yang mewajibkan pembuatan sebuah file baru untuk tiap class, maka sebentar saja hal ini akan sangat melelahkan. Jika bahasa pemrogramanmu mengharuskanmu untuk menggunakan functor, kamu tidak mendapatkan semua kelebihan dari lingkungan pemrograman modern. Cari tahu apakah kamu bisa mendapatkan uangmu kembali.

Berapa keuntungan yang benar-benar kamu dapatkan ketika menuliskan fungsi yang tidak melakukan apa-apa selain menelusuri array dan melakukan sesuatu terhadap elemen-elemen array tersebut?

Mari kembali ke fungsi map. Ketika kamu ingin melakukan seuatu terhadap semua elemen array secara berurutan, faktanyta adalah biasanya urutan operasinya tidak penting. Kamu dapat membaca arraynya dari depan atau belakang, dan hasilnya akan sama saja kan? Bahkan jika kamu memiliki dua buah CPU mungkin kamu dapat menulis sedikit kode untuk membuat masing-masing CPU memproses setengah dari elemen array, dan mendadak map menjadi dua kali lebih cepat.

Atau mungkin, misalnya, kamu memiliki ratusan ribu server yang tersebar di beberapa data center di seluruh dunia, dan kamu memiliki sebuah array yang sangat besar, yang berisi, contohnya, seluruh konten yang ada di Internet. Sekarang kamu dapat menjalankan map di ribuan komputer, yang masing-masing akan menyelesaikan sebagian kecil dari masalah yang ada.

Dengan begitu sekarang, misalnya, menuliskan kode yang sangat cepat untuk mencari sesuatu di dalam seluruh konten yang ada di Internet menjadi semudah memanggil fungsi map dengan string pencarian sebagai argumennya.

Hal yang sangat menarik yang ingin saya pastikan kamu perhatikan di sini adalah bahwa begitu kamu melihat map dan reduce sebagai fungsi yang dapat digunakan oleh siapa saja, dan semua orang menggunakannya, kamu hanya perlu membuat satu orang super-jenius untuk menuliskan kode kompleks yang menjalankan map dan reduce pada berjibun komputer yang terhubung secara paralel, dan semua kode lama yang berjalan dengan benar ketika hanya menggunakan loop akan tetap bekerja dengan baik, tetapi miliaran kali lebih cepat, yang artinya kode-kode lama ini dapat digunakan untuk menyelesaikan masalah besar dalam sekejap.

Saya ulangi lagi. Dengan mengabstrakkan konsep dari looping, anda dapat mengimplementasikan looping seperti apapun, termasuk looping sedemikian rupa sehingga dapat menangani tambahan hardware tanpa masalah.

Dan sekarang kamu mengerti sesuatu yang saya tuliskan beberapa waktu lalu, di mana saya mengeluh tentang mahasiswa Ilmu Komputer yang tidak pernah diajari apapun selain Java:

Tanpa mengerti pemrograman fungsional kamu tidak mungkin menciptakan MapReduce, algoritma yang membuat Google dapat berkembang dalam skala masif. Istilah Map dan Reduce datang dari Lisp dan pemrograman fungsional. MapReduce, setelah direnungkan, gamblang sekali untuk orang-orang yang ingat kelas pemrograman 6.001 atau sejenisnya bahwa program fungsional murni tidak memiliki efek samping dan karenanya dapat diparalelkan dengan sangat mudah. Fakta bahwa Google menciptakan MapReduce, dan Microsoft tidak, dapat menjelaskan bagaimana Microsoft masih berusaha membuat fitur pencarian dasar bekerja, sementara Google telah bergerak untuk menyelesaikan masalah berikutnya: membangun Skynet^H^H^H^H^H^H superkomputer paralel terbesar di dunia. Saya kira Microsoft tidak benar-benar mengerti betapa tertinggalnya mereka dalam hal ini. .

Ok. Mudah-mudahan kamu sudah yakin sekarang, bahwa bahasa pemrograman dengan fungsi kelas pertama memungkinkan kita untuk mencari lebih banyak kesempatan untuk abstraksi, yang artinya kode kita menjadi lebih kecil, padat, reusable, dan scalable. Banyak aplikasi di Google menggunakan MapReduce dan semua aplikasi tersebut menuai keuntungan ketika seseorang melakukan optimasi terhadap MapReduce atau memperbaiki bug.

Dan sekarang saya akan sedikit sentimentil, dan mendebat bahwa lingkungan pemrograman yang paling produktif adalah lingkungan yang memungkinkan kita untuk bekerja pada beberapa level abstraksi yang berbeda. FOTRAN bahkan tidak memungkinkan kita untuk membuat fungsi. C memiliki function pointer, tetapi hal ini sangaaaaaat jelek dan tidak anonim dan harus diimplementasikan di tempat lain alih-alih tempat kita akan menggunakan fungsi tersebut. Java memaksa kita menggunakan functor, yang bahkan lebih jelek dari function pointer. Seperti yang dikatakan oleh Steve Yegge, Java adalah Kerjaan Kata Benda.

Koreksi: Terakhir kali saya menggunakan FOTRAN 27 tahun yang lalu. Rupanya FOTRAN memiliki fungsi. Mungkin saya tertukar antara FOTRAN dengan GW-BASIC.

comments powered by Disqus

Daftar Isi