Pipes dan Filters

9.2 Pipes dan Filters

Satu contoh dari paradigma coroutine adalah masalah produsen-konsumen. Mungkin kita punya sebuah fungsi yang secara berkelanjutan menghasilkan nilai dan fungsi lain yang secara berkelanjutan memakai nilai tersebut. Disini terdapat 2 fungsi seperti analogi diatas:

function producer () while true do local x = io.read() -- produce new value send(x) -- send to consumer

end end

function consumer () while true do

local x = receive() -- receive from producer io.write(x, "\n") -- consume new value end

end

Masalahnya disini adalah bagaimana mencocokkan pengiriman dengan penerimaan. Ini adalah sebuah pilihan siapa yang mempunyai masalah utama. Keduanya produsen dan konsumen aktif,keduanya memiliki loop utama dan keduanya menerima bahwa yang lain adalah pelayanan. Untuk contoh,hal yang mudah untuk merubah struktur suatu fungsi,kembalikan putaran loop dan rubah menjadi pasif. Tetapi,perubahan struktur mungkin jauh dari mudah dalam scenario lain yang sebenarnya.

Coroutine memberikan sebuah alat yang ideal untuk mencocokkan produsen dan konsumen karena pasangan resume-yield merubah tipe hubungan antara pemanggil dan terpanggil. Ketika sebuah coroutine memanggil yield,ini tidak masuk ke dalam sebuah fungsi baru; ini kembali ke panggilan yang ditunda(resume). Kemiripannya,pemanggilan resume tidak memulai fungsi baru,tetapi memanggil yield. Properti ini nyata yang kita butuhkan untuk mencocokkan pengiriman dengan penerimaan dalam suatu jalur yang setiap aksinya menghasilkan satu sisi bersifat master dan yang lain bersifat slave. Jadi,receive memanggil produsen untuk memproduksi nilai baru dan send menghasilkan nilai balik ke konsumen:

function receive () local status,value= coroutine.resume(producer) return value end

function send (x) coroutine.yield(x) end

Tentu saja,producer harus jadi sebuah coroutine:

producer = coroutine.create( function ()

while true do local x = io.read() -- produce new value

send(x) end

end)

Pada design ini,program mulai memanggil consumer,ketika consumer butuh sebuah barang, consumer memanggil producer, yang akan berjalan hingga barang diberikan ke consumer, dan kemudian berhenti sampai consumer memulai kembali. Oleh Karena itu,kita menyebutnya consumer-driven desain.

Kita dapat memperluas desain ini dengan penyaring,dimana pekerjaannya duduk antara producer dan consumer melakukan beberapa macam transformasi data. Sebuah penyaring adalah sebuah consumer dan producer pada waktu yang sama,jadi memanggil producer untuk mendapat nilai baru dan menghasilkan transformasi nilai ke consumer. Sebagai contoh,kita dapat menambahkan sebuah penyaring kedalam kode sebelumnya dengan memasukan sebuah baris baru disetiap awal baris. Kode lengkapnya seperti dibawah ini: Kita dapat memperluas desain ini dengan penyaring,dimana pekerjaannya duduk antara producer dan consumer melakukan beberapa macam transformasi data. Sebuah penyaring adalah sebuah consumer dan producer pada waktu yang sama,jadi memanggil producer untuk mendapat nilai baru dan menghasilkan transformasi nilai ke consumer. Sebagai contoh,kita dapat menambahkan sebuah penyaring kedalam kode sebelumnya dengan memasukan sebuah baris baru disetiap awal baris. Kode lengkapnya seperti dibawah ini:

function send (x) coroutine.yield(x) end

function producer () return coroutine.create(function ()

while true do

local x = io.read() -- produce new value send(x)

end

end) end

function filter (prod) return coroutine.create(function ()

local line = 1 while true do

local x = receive(prod) -- get new value x = string.format("%5d %s", line, x) send(x) -- send it to consumer line = line + 1

end

end) end function consumer (prod)

while true do local x = receive(prod) -- get new value io.write(x, "\n") -- consume new value end

end

Terakhir kita buat sedikit komponen sederhana yang dibutuhkan,hubungkan mereka dan mulai final consumer:

p = producer()

f = filter(p) consumer(f)

Atau lebih baik lagi: