Brief by evilfactorylabs

Brief by evilfactorylabs • Untuk obrolan makan siangmu seputar dunia pemrograman. Terbit sebelum jam 13.37 WIB setiap hari Senin & Selasa.

Tentang functional programming

Tentang Imperatif, declarative, the how and the what.

Sebagai seorang pemrogram, bagaimanapun pada dasarnya keseharian kita adalah menulis kode. Dan seperti menulis buku, menulis program (kode) memiliki hal-hal yang menjadi pembeda. Jika dalam menulis buku pembedanya mungkin di aliran (genre), jenis buku, dsb; di menulis program, pembedanya antara lain seperti target platform, bahasa program yang dipilih, dsb.

Aktivitas menulis adalah tentang menuangkan "pikiran" dalam bentuk tulisan yang dapat dipahami oleh orang lain (dan mesin dalam konteks ini). Karena menyampaikan suatu pemikiran (algoritma?) dan instruksi, proses menulis tidak bisa terlepas dari paradigma yang dianutnya. Paradigma singkatnya adalah cara pandang terhadap sesuatu.

Ada beberapa paradigma dalam membuat program yang sudah populer, antara lain ada Procedural dan Object-oriented. Yang paling populer adalah Object-oriented (OOP), yang singkatnya tentang mengatur program sebagai objek.

OOP adalah bagian dari Imperative Programming, yang singkatnya adalah sebuah paradigma yang berfokus pada "how to execute" nya, dan yang mana menggunakan "alur kontrol" sebagai statement untuk mengubah state pada program.

Beberapa keuntungan menggunakan paradigma OOP adalah enkapsulasi, inheritance, delegation, dsb yang membuat program menjadi lebih mudah dipelihara & diorganisir. Juga, menggunakan paradigma OOP relatif lebih mudah dipahami, karena biasanya "implementasi nya" hampir serupa dengan konsep yang ada di dunia nyata, misal: dbInstance.Connect(&config) yang mana kodenya lebih mudah dipahami, bukan?

The imperative

Karena imperative relatif tentang "how to execute", terkadang kita melakukan hal-hal yang mungkin sederhana, dengan cara yang sedikit ribet. Seperti, jika kita memiliki sebuah array dengan nilai [1, 2, 3, 4, 5] dan kita hanya ingin mengambil nilai yang genapnya saja, biasanya, caranya seperti ini:

// payload
let array = [1, 2, 3, 4, 5]
             
// holder of collection of even numbers
let evenArray = []

// loop `array` to get each value
for (let i = 0; i < array.length; i++) {
  if (array[i] % 2 === 0) {
    evenArray.push(array[i])
  }
}

console.log(evenArray) // [2, 4]

Kode diatas adalah contoh, menggambarkan bahwa Imperative adalah tentang "how to execute" dan menggunakan alur kontrol (for, if diatas) untuk mengubah state pada program (evenArray) misalnya).

Tapi ini hanya tentang paradigma, tentang cara pandang terhadap sesuatu. Bagaimana bila kode diatas menjadi seperti ini misalnya:

// payload
let array = [1, 2, 3, 4, 5]
             
// holder of collection of even numbers
let evenArray = array.filter(value => value % 2 === 0)

console.log(evenArray) // [2, 4]

Kita tidak perlu "memberitahu" bagaimana (how) cara melakukan sesuatu terhadap apa yang diinginkan, melainkan hanya "memberitahu" apa (what) yang diinginkan. Dan mengambil perbandingan dari 2 contoh diatas, kita melakukan hal yang sama dengan cara yang berbeda.

Dan statement diatas adalah tentang Declarative Programming.

The declarative

Sebelum kita membahas lebih lengkap seputar functional (yang mana kategori dari declarative programming), kita akan membahas sedikit seputar declarative ini. Seperti yang sudah kita bahas, paradigma adalah tentang cara memandang sesuatu. Dan meskipun caranya berbeda, itu tidak masalah bila apa yang dihasilkan sesuai dengan apa yang diharapkan.

Sebelum lebih lanjut ke functional, mari kita lihat "syntax sugar" populer di JavaScript yang bernama JSX, untuk membuat DOM di JavaScript:

<button className='c-button'>
  {state.isLoggedIn ? 'Logout' : 'Login'}
</button> // <button class="c-button">Login</button>

Terlihat seperti sintaks HTML? Ya, namun itu dalam format JavaScript (.js) bukan (.html). Berikut contohnya ketika kita melakukannya tanpa JSX:

const button = document.createElement('button')

button.classList.add('c-button')
button.innerText = state.isLoggedIn ? 'Logout' : 'Login'

target.appendChild(button) // <button class="c-button">Login</button>

2 Kode diatas hanyalah contoh bagaimana kita ingin membuat tombol di JavaScript menggunakan paradigma "what" dengan membuat tombol di JavaScript menggunakan paradigma "how'.

Ya, contoh diatas hanyalah tentang abstraksi. Tapi bukankah programming adalah selalu tentang abstraksi?

Functional Programming

Functional programming adalah salah satu yang menggunakan paradigma declarative, yang mana singkatnya adalah tentang bagaimana "menganggap" sesuatu sebagai fungsi matematika.

Kita tidak sedang membahas kalkulus disini, tapi jika mengambil contoh .filter sebelumnya, fungsi  "matematika" tersebut kurang lebih seperti ini:

Filterable f => (a → Boolean) → f a → f a

Yang singkatnya, parameter dari method filter harus berjenis function yang memberikan return berjenis boolean dan return dari function filter ini nilainya harus sama dengan Filterable (yang mana adalah "array" yang ingin dilakukan filter tersebut). Berikut ilustrasinya:

array      .filter(value =>                     value % 2 === 0)
^^^^^              ^^^^^                        ^^^^^^^^^^^^^^^
Filterable         a -> Boolean (predicate)     f a

Berikut contoh kode dari ilustrasi diatas menggunakan Reason, agar lebih mudah memahami maksud dari "rumus" tersebut:

type filterable = array(int);
type predicate = int => bool;

let filterEven: predicate = value => value mod 2 == 0;

let array: filterable = [|1, 2, 3, 4, 5|];
let evenArray: filterable = Belt.Array.keep(array, filterEven);

Js.log(evenArray); // [2, 4]

Jangan pusing dulu terkait "fungsi matametika" ini, contoh diatas hanyalah ilustrasi untuk menjelaskan sedikit seputar "fungsi matematika" tersebut.

The why?

Layak disebutkan bahwa Functional Programming (FP) bukan untuk mengganti OOP, alias, jika bisa berjalan beriringan mengapa harus berjalan sendirian?

Ada beberapa "prinsip" terkait menggunakan paradigma FP, salah satunya adalah menghindari side effect alias fungsi harus murni (Pure function). Pure function singkatnya Output akan selalu bernilai sama berdasarkan Input yang diberikan.

Contoh:

// pure function
function pureGetTimestamp (date) {
  return new Date(date).valueOf()
}

// non-pure function
function nonPureGetTimestamp () {
  return Date.now()
}

const now = new Date()

console.log(pureGetTimestamp(now) === nonPureGetTimestamp()) // true

Dimana bedanya? Meskipun sama-sama menghasilkan waktu menggunakan format epoch, fungsi pertama akan selalu menghasilkan timestamp yang sama sedangkan yang kedua tidak.

Dibaris terakhir, nilai console.log bernilai true. Bagaimana jika kita eksekusi untuk kedua kalinya? Pastinya menjadi false. Kode diatas hanya sebagai gambaran "pure vs non-pure" di function. Yang mana fungsi pertama akan selalu menghasilkan nilai yang sama, fungsi kedua (meskipun tidak ada input, tapi hanya untuk contoh) akan selalu menghasilkan nilai yang berbeda.

Kembali ke pembahasan, salah satu hal yang sulit dihindari terkait side effect adalah operasi I/O dan salah satunya adalah terhadap database. Karena pada dasarnya "proses write" ke database adalah "effect" disamping proses yang dilakukan oleh proses itu sendiri (misal seperti saveUserProfile(payload)) yang mana meskipun inputnya jelas, namun outputnya tidak ter-prediksi.

Maka dari itu FP tidak bisa mengganti OOP, melainkan bisa berjalan beriringan. Dan ya, karena di OOP tidak ada prinsip Pure function dan juga "state" disimpan dalam bentuk properties.

Currying, Monad, Monoid, blablabla...

3 sebutan diatas adalah "jargon" yang biasa ditemukan di Functional Programming. Pada dasarnya itu adalah tentang paradigma, spesifiknya adalah tentang bagaimana cara "memanggil" fungsi yang ada.

Misal seperti ini, kita ambil contoh Currying. Kita ingin membuat utilitas untuk mem-parse Date. Kita ambil contoh imperative, hal pertama adalah untuk mengambil nilai date menggunakan format epoch-ish:

const parseDate = date => {
  return new Date(date).valueOf()
}

const now = "Mon Feb 17 2020 10:09:25 GMT+0700"

parseDate(now) // 1581908965000

Sekarang, bagaimana bila ingin mengambil versi toLocaleString nya saja? Sederhana, gunakan "parameter":

const parseDate = (date, type) => {
  let $date = new Date(date)
  
  if (type === 'ts') {
    return $date.valueOf()
  } else if (type === 'localeString') {
    return $date.toLocaleString()
  }
    
  return $date
}

const now = "Mon Feb 17 2020 10:09:25 GMT+0700"

parseDate(now, 'ts') // 1581908965000
parseDate(now, 'localeString') // "2/17/2020, 10:09:25 AM"

Semakin banyak type yang diinginkan, semakin banyak pula "control flow" yang dibuat. Bagaimana bila menggunakan "declarative" khususnya menggunakan "gaya" currying?

// curry-ing basically function that return function
// until the last argument (function) being executed
// which is the next argument refer to previous argument

// `op` below is refering to `date` value
const parseDate = (date) => (op) => new Date(date)[op]()

const now = "Mon Feb 17 2020 10:09:25 GMT+0700"

parseDate(now)('valueOf') // 1581908965000
parseDate(now)('toLocaleString') // "2/17/2020, 10:09:25 AM"

Yap, ingat tentang "what" vs "how" kan? Ini cuma masalah paradigma saja, bukan? Dan ya, meskipun kode yang versi imperatif bisa saja menggunakan cara yg seperti declarative juga, tapi ini sebagai contoh aja gambaran seputar currying.

Jargon-jargon seperti "Monoid", "Monad" dsb bisa dipelajari selengkapnya diluar tulisan ini, disini kita hanya berbicara sebatas bagaimana menggunakan paradigma FP dalam membuat sebuah program.

Declarative what

Sebenarnya kita sudah terbiasa melakukan hal-hal yang bersifat deklaratif, contohnya adalah SQL statement. Untuk mengambil data di database yang menggunakan SQL, kita biasa menggunakan query berikut misalnya:

SELECT name, email from `users` where `id` = 1337;

Lihat? Kita hanya melakukan "what" bukan "how". Atau juga, untuk melakukan styling di CSS:

body {
  font-family: "Comic Sans Ms";
}

Dan membuat element di HTML?

<button>click me</button>

Functional programming adalah salah satu kategori dari Declarative Programming, yang intinya lebih fokus ke "what" daripada "how" nya.

Benefit

Relatif, dan pastinya ada banyak. Yang pastinya program menjadi lebih ter-prediksi; lebih transparan, dan lebih mudah dipahami. Pure function membuat proses testing lebih mudah, menghindari side effect membuat program menjadi lebih ter-prediksi, dan "menyembunyikan" hal-hal detail membuat program menjadi lebih mudah dipahami, bukan?

Hey, siapa yang peduli dengan dengan Gecko Renderer & kernel XNU hanya untuk bisa melihat foto kucing di 9gag?

Komik diatas adalah salah satu ilustrasi seputar "abstraksi" yakni tentang menyembunyikan hal-hal detail. Yang intinya, tentang "I want this" not "how to do that".

Tantangan

Karena ini tentang paradigma, tentunya tidak mudah untuk memahami FP terlebih jika sudah terbiasa dengan OOP. Dan plus, bila membandingkan dengan hal-hal terkait OOP (alias how to this OOP in FP).

Selain itu, ada beberapa "jargon" yang mungkin better to know agar menggunakan paradigma FP ini lebih idiomatic.

Penutup

Sebenarnya kita sudah terbiasa menggunakan hal-hal seputar FP, namun di bagian lebih "general" nya yakni di Declarative Programming.

Mempelajari (dan menggunakan FP) benar-benar mengubah cara pandang saya dalam membuat menulis kode program. Melatih dalam "melakukan hal sesederhana" mungkin, menulis kode yang se-sedikit mungkin, dsb.

Sekali lagi, FP bukan untuk menggantikan OOP. Jika OOP memudahkan untuk membuat program yang modular, FP memudahkan untuk membuat modular tersebut menjadi composable (contoh seperti Date yang imperatif menggunakan prototype dipadukan dengan menggunakan "currying" yg so functional).

Sudah banyak bahasa program yang menggunakan gaya functional dalam semantiknya seperti Elm, Reason, Haskell, Elixir, Clojure, dll. Silahkan pelajari lebih lanjut seputar bahasa pemrograman tersebut bila tertarik menulis kode program yang "bernuansa" functional.

Sebagai penutup, FP adalah tentang what daripada how, jadi, "kalau bisa ribet, kenapa mesti sederhana?"

Menikmati tulisan ini?

Blog ini tidak menampilkan iklan, yang berarti blog ini didanai oleh pembaca seperti kamu. Gabung bersama Loading... yang telah membantu blog ini agar terus bisa mencakup tulisan yang lebih berkualitas dan bermanfaat!

Pendukung

Dukung Mengapa saya harus mendukung?
You've successfully subscribed to Brief by evilfactorylabs
Great! Next, complete checkout for full access to Brief by evilfactorylabs
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.