Memakai Currying Pada Closure Di Groovy

Berdasarkan informasi dari Wikipedia, currying adalah sebuah teknik transformasi fungsi pada bidang matematika dan ilmu komputer yang diperkenalkan oleh Moses Schonfinkel dan dipopulerkan oleh Haskell Curry.   Closure pada Groovy mendukung currying, tapi sebenarnya apa itu currying?   Saya akan mengawali ilustrasi dengan sebuah pemanggilan closure biasa seperti berikut ini:

def query = { String namaModel, boolean barisTunggal, Map where ->
   println "Melakukan query untuk mencari $namaModel"
   where.each { k,v -> println "Dengan kondisi $k = $v" }
   if (barisTunggal) {
       "Data $namaModel"
   } else {
       ["Data $namaModel #1", "Data $namaModel #2"]
   }
}

//
// Output:
// Melakukan query untuk mencari mahasiswa
// Dengan kondisi nama = Jocki
// Dengan kondisi lulus = true
// Data mahasiswa
//
println query('mahasiswa', true, [nama: 'Jocki', lulus: true])

//
// Output:
// Melakukan query untuk mencari mahasiswa
// Dengan kondisi nama = Jocki
// [Data mahasiswa #1, Data mahasiswa #2]
//
println query('mahasiswa', false, [nama: 'Jocki'])

//
// Output:
// Melakukan query untuk mencari kelas
// Dengan kondisi kode = 1AC
// Data kelas
//
println query('kelas', true, [kode: '1AC'])

Pada kode program di atas, anggap saja closure query dipakai untuk membaca data dari database.   Ia membutuhkan argumen berupa sebuah namaModel yang mewakili nama tabel yang akan dibaca.   Ia juga memiliki argumen barisTunggal yang jika bernilai true akan selalu mengembalikan sebuah record; sebaliknya jika bernilai false, maka yang dikembalikan adalah sebuah List berisi satu atau lebih record.   Saya juga dapat menyertakan kondisi pencarian dalam bentuk sebuah Map.

Sekilas, pemanggilan closure query terlihat kompleks karena harus memberikan banyak parameter.   Oleh sebab itu, saya dapat menggunakan currying untuk me-‘reduksi‘ closure tersebut menjadi lebih terspesialisasi (dengan cara memastikan parameter tertentu selalu sama nilainya).   Sebagai contoh, saya dapat menghasilkan queryMahasiswa dan queryKelas yang khusus untuk mencari tabel-nya masing-masing, seperti pada kode program berikut ini:

def query = { String namaModel, boolean barisTunggal, Map where ->
   println "Melakukan query untuk mencari $namaModel"
   where.each { k,v -> println "Dengan kondisi $k = $v" }
   if (barisTunggal) {
       "Data $namaModel"
   } else {
       ["Data $namaModel #1", "Data $namaModel #2"]
   }
}

def queryMahasiswa = query.curry('mahasiswa')
println queryMahasiswa(true, [nama: 'Jocki', lulus: true])
println queryMahasiswa(false, [nama: 'Jocki'])

def queryKelas = query.curry('kelas')
println queryKelas(true, [kode: '1AC'])

Kode program di atas menghasilkan output yang sama seperti sebelumnya, tetapi kini terdapat queryMahasiswa dan queryKelas yang dihasilkan dari query, dimana parameter pertama-nya selalu di-isi dengan nilai yang telah ditentukan (yakni 'mahasiswa' dan 'kelas').   Ini disebut juga dengan partial function application.

Pada contoh berikut ini, saya kembali melakukan ‘spesialisasi’ lagi dimana terdapat closure yang hanya mengembalikan record tunggal dan juga terdapat closure yang mengembalikan lebih dari satu record:

def query = { String namaModel, boolean barisTunggal, Map where ->
   println "Melakukan query untuk mencari $namaModel"
   where.each { k,v -> println "Dengan kondisi $k = $v" }
   if (barisTunggal) {
       "Data $namaModel"
   } else {
       ["Data $namaModel #1", "Data $namaModel #2"]
   }
}

def cariSeluruhMahasiswa = query.curry('mahasiswa', false)
def cariMahasiswa = query.curry('mahasiswa', true)
def cariSeluruhKelas = query.curry('kelas', false)
def cariKelas = query.curry('kelas', true)

println cariMahasiswa([nama: 'Jocki', lulus: true])
println cariSeluruhMahasiswa([nama: 'Jocki'])
println cariKelas([kode: '1AC'])
Contoh Penggunaan Currying

Contoh Penggunaan Currying

Seluruh contoh program di atas mengembalikan hasil yang sama dan memiliki implementasi kode program yang satu (dimana isi closure query tidak berubah).   Walaupun demikian, versi terakhir di atas terlihat lebih mudah dimengerti dan lebih rapi karena argumen yang dilewatkan lebih sedikit.

Iklan

Memahami Closure Di Groovy

Groovy memungkinkan developer untuk mendefinisikan closure, misalnya seperti pada kode program berikut ini:

class Latihan {
  def sebuahClosure = {
    println "Ini di dalam closure"
  }
}

def latihan = new Latihan()
latihan.sebuahClosure()

// outputnya adalah:
// Ini di dalam closure

Salah satu yang hal yang bisa menjebak adalah fakta bahwa def sebuahClosure adalah sebuah variabel yang menampung closure, bukan sebuah method!  Biar lebih jelas, saya akan menambahkan sebuah method di class tersebut sehingga terlihat seperti pada kode program berikut ini:

class Latihan {
  def sebuahClosure = {
    println "Ini di dalam closure"
  }

  void sebuahMethod() {
    println "Ini di dalam method"
  }
}

def latihan = new Latihan()
latihan.sebuahClosure()
latihan.sebuahMethod()

// outputnya adalah:
// Ini di dalam closure
// Ini di dalam method

Loh, terus apa bedanya sebuah closure dan sebuah method biasa?  Biar jelas, saya akan mengubah definisi class di atas menjadi seperti berikut ini:

class Latihan {
  def sebuahClosure = {
    println "Ini di dalam closure"
  }

  void sebuahMethod() {
    println "Ini di dalam method"
  }

  void proses(Closure argumen) {
    print "PROSES: "
    argumen()
  }
}

def latihan = new Latihan()

Berdasarkan kode program di atas, bila saya memberikan perintah seperti berikut ini:

latihan.proses(latihan.sebuahClosure)

Saya akan memperoleh hasil berupa:  PROSES: Ini di dalam closure. Hal ini memperlihatkan bahwa saya dapat melewatkan sebuah closure sebagai argumen dalam sebuah method karena closure tersebut di-“simpan” oleh sebuah variabel.

Tetapi bila saya memberikan perintah berikut ini:

latihan.proses(latihan.sebuahMethod)

Saya akan mendapatkan pesan kesalahan!!!  Apakah tidak bisa melewatkan sebuah method sebagai argumen?  Bisa, tetapi harus dalam bentuk seperti berikut ini:

latihan.proses(latihan.&sebuahMethod)

Bagaimana bila sebuah closure memiliki parameter?  Saya dapat menggunakan operator -> untuk memberikan parameter pada closure.   Hal ini terlihat pada kode program berikut ini:

def tambah = { nilai1, nilai2 ->
  println "${nilai1} + ${nilai2} = ${nilai1+nilai2}"
}

def kurang = { nilai1, nilai2 ->
  println "${nilai1} - ${nilai2} = ${nilai1-nilai2}"
}

void proses(nilai1, nilai2, Closure argumen) {
  print "PROSES: "
  argumen(nilai1, nilai2)
}

proses(10, 20, tambah)
proses(40, 30, kurang)

// Hasilnya adalah:
// PROSES: 10 + 20 = 30
// PROSES: 40 - 30 = 10

Ok, saya akan mengingat ini bila suatu saat nanti saya menemukan kode program dengan operator ->.

Btw, saya juga sering menemukan apa yang disebut dengan inline closure.  Melanjutkan dari kode program di atas, saya menambah baris seperti berikut ini:

proses(10, 20, tambah)
proses(40, 30, kurang)
proses(50, 60) {  nilai1, nilai2 ->
  println("${nilai1} * ${nilai2} = ${nilai1*nilai2}"
}

// Hasilnya adalah:
// PROSES: 10 + 20 = 30
// PROSES: 40 - 30 = 10
// PROSES: 50 * 60 = 3000

Bila parameter terakhir adalah sebuah Closure, saya dapat langsung memberikan definisi closure tersebut setelah pemanggilan method.

Berbekal pemahaman berdasarkan contoh di atas, saya akhirnya bisa memahami contoh kode program Griffon yang awalnya seolah penuh blok ajaib, misalnya yang terlihat berikut ini:

void mvcGroupInit(Map args) {
  ...
  execOutsideUI {
     String text = model.loadedFile.text
     execInsideUIAsync {
        model.fileText = text
     }
  }
}

Kapan memakai jQuery.proxy()?

Saat membuat widget untuk JQuery UI, saya menemukan contoh yang tepat untuk penerapan jQuery.proxy() sebagai pengganti closure.   Sebagai contoh, ini adalah kode widget saya secara garis besar:

(function($) {

  $.widget("thesolidsnake.jlSimpleTableEditor", {

    _create: function() {
       [[ NILAI this DISINI AKAN MERUJUK PADA PLUGIN SAYA ]]
       this.element;
       this.options;
       ...
    },

    ... // diabaikan

  }
})(jQuery);

Selama berada di method seperti _create, saya dapat memakai this untuk mendapatkan elemen yang diproses dengan this.element. Saya juga dapat memakai this untuk mendapatkan options (parameter) apa saja yang diberikan oleh pengguna dengan this.options.

Saya kemudian membuat sebuah elemen secara dinamis dan melakukan binding pada elemen tersebut, seperti pada kode program berikut ini:

(function($) {

  $.widget("thesolidsnake.jlSimpleTableEditor", {

    _create: function() {
       [[ Nilai this merujuk pada plugin saya ]]
       this.element;
       this.options;
       ...
       $("#test").bind("click", function() {
          [[ Nilai this merujuk pada elemen yang sedang di-klik ]]
          this.element;   // Tidak ditemukan, undefined
          this.options;   // Tidak ditemukan, undefined
       });
    },

    ... // diabaikan

  }
})(jQuery);

Permasalahannya sekarang dalam anonymous function tersebut, this tidak lagi merujuk ke objek plugin melainkan merujuk ke elemen yang sedang di-klik.

Bila saya ingin tetap mengakses this.element atau this.options di dalam anonymous  function tersebut, maka salah satu alternatif yang bisa saya ditempuh adalah dengan memakai closure.  Misalnya, saya bisa membuat sebuah variabel self yang berisi this, kemudian variabel self ini dapat diakses di anonymous  function tersebut, seperti yang terlihat seperti berikut ini:

(function($) {

  $.widget("thesolidsnake.jlSimpleTableEditor", {

    _create: function() {
       [[ Nilai this merujuk pada plugin saya ]]
       this.element;
       this.options;
       ...
       var self = this;
       $("#test").bind("click", function() {
          [[ Nilai this merujuk pada elemen yang sedang di-klik ]]
          this.element;   // Tidak ditemukan, undefined
          this.options;   // Tidak ditemukan, undefined
          self.element;   // Bekerja sesuai dengan yang diharapkan
          self.options;   // Bekerja sesuai dengan yang diharapkan
          ... // diabaikan
       });
    },

    ... // diabaikan

  }
})(jQuery);

Selain memakai closure, ada sebuah cara lain lagi yang lebih hemat memori, yaitu dengan menggunakan jQuery.proxy().  Method ini pada dasarnya akan memanggil Function.apply() di JavaScript yang memang ditujukan untuk memberi makna pada nilai this.  Contoh versi yang memakai jQuery.proxy() akan terlihat seperti berikut ini:

(function($) {

  $.widget("thesolidsnake.jlSimpleTableEditor", {

    _create: function() {
       [[ Nilai this merujuk pada plugin saya ]]
       this.element;
       this.options;
       ...
       $("#test").bind("click", $.proxy(function() {
          ...
          this.element;   // Nilainya sesuai dengan yang diharapkan
          this.options;   // Nilainya sesuai dengan yang diharapkan
          ... // diabaikan
       }, this));
    },

    ... // diabaikan

  }
})(jQuery);

Anonymous Function Di PHP

PHP sejak versi 5.3 sudah mendukung anonymous function dan closure.  Sementara itu, Java hingga versi 7 masih belum mendukung closure secara penuh (selain dengan variabel final).  Fitur ini akan disertakan pada Java 8 nanti dengan memakai lambda expression.

Apa itu closure?  Artikel Memakai Closure Di JavaScript menjelaskan tentang closure dan contoh penerapannya di JavaScript.  Pada tulisan ini, saya akan mencoba menggunakan fitur closure di PHP.

Sebagai contoh, saya ingin menghasilkan menu dari array berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");
?>

Saya mendefinisikan sebuah anonymous function yang akan menghasilkan HTML berdasarkan string di setiap elemen array, lalu menyimpan anonymous function tersebut ke sebuah variabel, seperti pada kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $prosesMenu = function($namaMenu, $indexMenu) {
     print "<div style='background-color: #00ccff; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }
?>

Sekarang, saya dapat memproses setiap elemen array di $daftarMenu dengan anonymous function $prosesMenu dengan menggunakan array_walk seperti pada kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");  
  $prosesMenu = function($namaMenu, $indexMenu) {
     print "<div style='background-color: #00ccff; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Pada contoh di atas, saya baru menggunakan anonymous function dan belum memakai closure.  Sekarang, seandainya saya ingin memberikan pewarnaan yang berbeda untuk menu yang sedang aktif, maka saya perlu melakukan perubahan kode program sehingga terlihat seperti berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");  
  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Bila program PHP tersebut dijalankan, menu pertama (index 0) akan selalu di-highlight karena nilai $menuAktif selalu adalah 0.  Seandainya saya bisa mendefinisikan $menuAktif  secara langsung, apakah saya bisa memberikan kode program berikut ini:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $menuAktif = 1;

  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Ternyata variabel $menuAktif belum dapat diakses secara langsung oleh anonymous function!  Menu dengan index 1 (urutan kedua) tidak akan di-highlight.  PHP mensyaratkan penggunaan keyword use bila ingin memakai variabel di parent scope di closure.  Dengan demikian, saya harus mengubah kode program di atas menjadi:

<?php
  $daftarMenu = array("FILE", "EDIT", "SOURCE", "REFACTOR");

  $menuAktif = 1;

  $prosesMenu = function($namaMenu, $indexMenu, $menuAktif) use ($menuAktif) {
     if ($menuAktif==$indexMenu) {
        $backgroundColor = "#cc00ff";
     } else {
        $backgroundColor = "#00ccff";
     }
     print "<div style='background-color: $backgroundColor; margin: 5px; padding: 5px; float: left;'>$namaMenu</div>";
  }

  array_walk($daftarMenu, $prosesMenu);
?>

Sekarang nilai $menuAktif di dalam anonymous function adalah 1.  Dengan closure, saya dapat memakai variabel $menuAktif di dalaman sebuah anonymous function walaupun variabel $menuAktif bukanlah variabel global.

Memakai Closure Di JavaScript

Belakangan ini, saya kerap terlibat pada pengembangan front-end dan mau tidak mau saya harus berhadapan dengan JavaScript.  Walaupun namanya mirip-mirip Java, tapi fiturnya jelas berbeda.  Salah satunya adalah JavaScript memiliki apa yang disebut sebagai closure.   Awalnya, sangat sulit bagi saya untuk mengerti apa itu closure dan bagaimana penerapannya.  Sejuta kata-kata yang saya temui serasa tidak bisa dicerna.  Tapi beruntung, sebuah kasus dalam development membuat saya memahami closure dan penerapannya.

Saya memiliki HTML + jQuery sederhana yang beri 4 tombol (<button>) seperti berikut ini:

<html>
  <head>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
  <script>
     $(document).ready(function(){
       $("#tombolA").click(function(){alert("Solid");});
       $("#tombolB").click(function(){alert("Solid");});
       $("#tombolC").click(function(){alert("Snake");});
       $("#tombolD").click(function(){alert("Snake");});
     });
  </script>
  </head>
  <body>
    <button id="tombolA">Tombol A</button>
    <button id="tombolB">Tombol B</button>
    <button id="tombolC">Tombol C</button>
    <button id="tombolD">Tombol D</button>
  </body>
</html>

Tombol A dan Tombol B bila ditekan akan memunculkan pesan “Solid”.  Sementara itu, Tombol C dan Tombol D bila ditekan akan memunculkan pesan “Snake”.  Jika dilihat-lihat, saya membuat 4 function berbeda untuk itu!  Ini berarti jika saya mau mengganti pesan “Solid” menjadi “Liquid”, saya harus mengubah 2 function yg berbeda…  Kurang efisien, bukan?

Saya menginginkan hanya 1 function yang bisa dipakai oleh keempatnya.  Dan function ini harus memiliki parameter untuk menampung nilai seperti “Solid”, “Snake”, dan sebagainya.  Kira-kira saya ingin seperti begini:

function buatPesan(pesan) {
  alert(pesan);
}
$(document).ready(function(){
  $("#tombolA,#tombolB").click(buatPesan("Solid"));  // KODE PROGRAM INI MASIH SALAH
  $("#tombolC,#tombolD").click(buatPesan("Snake"));  // KODE PROGRAM INI MASIH SALAH
});

Tapi sayangnya kode program di atas masih salah.  Saya tidak bisa memanggil sebuah function begitu saja di click(), tapi saya harus mengembalikan sebuah function (dalam bentuk nama function).

Beruntungnya, JavaScript mendukung inner function (function di dalam function).  Jika saya mengembalikan sebuah inner function, maka telah terjadi closure, dimana variabel yang ada di outer function (function diluarnya) akan tetapi di-ingat!  Jadi, saya bisa membuat kode program seperti berikut ini:

function buatPesan(pesan) {
  function f() {
    alert(pesan);
  }
  return f;
}
$(document).ready(function(){
  $("#tombolA,#tombolB").click(buatPesan("Solid"));
  $("#tombolC,#tombolD").click(buatPesan("Snake"));
});

Pada kode program di atas, sebuah fungsi yang sama dapat dipakai ulang untuk setiap tombol yang ada.  Dan saya tidak membutuhkan variabel global dan sebagainya, karena dengan closure, nilai parameter yang diberikan akan tetap diingat oleh JavaScript.