Pernah gak sih sewaktu ngoding functional masih terbawa cara berpikir dengan imperative??
Saya sendiri, hal seperti itu masih kebawa-bawa, apalagi kalo nyangkut yang problemnya udah ribet dikit. Udah deh, cara mikirnya jadi auto imperative.
Functional programming itu penting, kenapa?
Berikut penjelasan saya.
Apa sih functional paradigm? Apa sih imperative paradigm?
Ini penjelasan Wikipedia atau StackOverflow.
Jadi pada intinya, programming paradigms itu bisa dilihat dari empat cara pandang, imperative, object, declarative, dan functional.
Imperative programming itu seperti menulis di bahasa di C/C++, di bahasa itu kita dibolehkan untuk mengganti variabel di alamat memori yang sama dan atau seperti berpikir seperti step-by-step.
Obeject oriented hampir mirip dengan imperative, bedanya object memiliki abstraksi dan enkapsulasi daripada code. Insetead of membayangkan step by step objek membayangkan instruksi sebagai delegasi.
Declarative kita mendeskripsikan apa yang kita cari. Contohnya SQL.
Sedangkan functional hampir sama dengan declarative, bedanya di sini tidak ada side-effect langsung.
Tapi, kenapa imperative dan functional?
Tiga tahun pertama kuliah, saya belajar programing dengan gaya imperative dan object. Dari bahasa pertama Pascal, terus ke JS, C/C++, PHP, Java, dan terakhir Python.
Karenanya, saya selalu berpikir ketika coding dengan cara detail oriented, step by step atau mecah progblem jadi beberpa object dan behaviours-nya.
Ketika coding dengan Clojure, saya agak sakit kepala. (Meskipun Clojure tidak pure funtional). Cara berpikir di FP beda jauh dengan cara mikir imperative.
Lalu kenapa harus Haskell, tidak Clojure saja?
Itu alasannya, di Clojure masih membolehkan kita berpikir imperative, sedangkan Haskell purely functional.
Contoh: Faktorial
Untuk lebih jelasnya, mari ambil contoh problem faktorial.
Faktorial 5
Pattern 1: Looping
Nah pertama kali coding di Clojure saya masih berpikir menyelesaikan ini dengan cara imperative, yaitu looping.
(defn faktorial
[n]
(loop [answer 1
start 1
akhir n]
(if (<= start akhir)
(recur (* answer start) (+ start 1) akhir)
answer)))
(faktorial 5)
;; => 120
Di Python kode di atas hampir sama dengan kode di bawah, hanya saja Python melakukan beberapa kali mutasi.
def faktorial(n):
answer = 1
start = 1
akhir = n
for i in range(akhir):
answer *= start
start += 1
return answer
print(faktorial(5))
# >> 120
Pattern 2: Recursive
Pattern recursive merupakan cara yang natural digunakan pada functional style sebagai pengganti looping (di FP, looping itu tidak ada).
Di imperative, pattern ini jarang digunakan dan sepertinya kurang natural. Dan kalaupun bisa, biasanya stack-nya terbatas.
Clojure
(defn faktorial2
[n]
(if (pos? n)
(*' n (faktorial2 (dec n)))
1))
(faktorial2 5)
;; => 120
Haskell
faktorial2 n =
if n > 0
then n * faktorial2 (n - 1)
else 1
faktorial2 5
-- >> 120
Python
def faktorial2(n):
if (n > 0):
return n * faktorial2(n - 1)
else:
return 1
print(faktorial2(5))
# >> 120
Dan iya, pattern ini memiliki kelemahan, yaitu untuk recursive yang besar stack-nya akan mengalami stack overflow.
Berikut contoh stackoverflow di Clojure dan Python:
(faktorial2 10000)
;; => java.lang.StackOverflowError: null
print(faktorial2(10000))
# >> RecursionError: maximum recursion depth exceeded in comparison
Di Haskell, dengan jumlah yang sama tidak terjadi stackoverflow.
faktorial2 10000
-- >> 284625968091...
Di Clojure stack pada pattern 2 recursive ini memungkinkan tak terbatas, yaitu dengan menggunakan pattern fn/recur. Dengan pattern yang sama, caranya hanya mengganti pemanggilan nama function dengan recur dan sedikir perubahan.
(defn faktorial2A
([n] (faktorial2A n n))
([n answer]
(if (> n 1)
(recur (dec n) (*' answer (dec n)))
answer)))
(faktorial2A 10000)
;; => 284625968091...
Pattern 3: Functional
Tools cara berpikir functional adalah cara perpikir denga memandang jika function itu first-class, high-order, declarative, atau recursive.
Cara berpikir ini memungkinkan kita untuk tiday worry ketika membuat testing. Karena kita yakin jika function tidak memiliki side-effect.
Selain itu, implikasi lainnya adalah ketika pemrosesan secara pararell.
Dengan contoh yang sama, berikut adalah menyelesaikan dengan cara functional.
Clojure
(defn faktorial3
[n]
(apply *' (range 1 (inc n))))
(faktorial3 5)
;; => 120
(faktorial3 10000)
;; => 284625968091...
Haskell
faktorial3 5
-- >> 120
faktorial3 10000
-- >> 284625968091.
Python
from functools import reduce
def faktorial3(n):
return reduce(lambda x, y: x * y, range(1, (n + 1)))
print(faktorial3(5))
# >> 120
print(faktorial3(5))
# >> 284625968091...
Lebih lama kita memakai cara functional, kita akan aware pada intinya functional itu memiliki tiga pattern, map, filter, dan reduce/fold.
Tapi, why like this?
Dari contoh di atas, secara subjektif saya bisa ber-kesimpulan:
Clojure memang functional, tetapi memungkinkan kita untuk berpikir secara imperative di beberapa case. Selain itu, Clojure juga memungkinkan untuk ada side-effect. Di production code side-effect itu kadang-kadang diperlukan.
Python adalah multi paradigm language, tetapi Python lebih cenderung mengadopsi imperative paradigm. Ini penting, karena satu paradigm saja tidak akan cukup menyelesaikan problem yang cukup kompleks.
Haskell sangat cocok dijadikan bahasa pertama jika ingin berpikir purely funtional. Saya tidak yakin jika Haskell bida dipakai untuk production grade.
Menurut saya, belajar functional lebih sulit dibandingkan belajar imperative. Tetapi, sekali kita "ngeh" functional, kita jadi males koding secara imperative.
Saya sangat merekomendasikan untuk berhenti sejenak dan melihat video Are We There Yet? dari Rich Hickey; beliau adalah penulis Clojure.
Alasan lain kenapa harus belajar FP adalah karena FP addressed most stressful problem in the future, yaitu skalabilitas.
The observation that the number of transistors in a dense integrated circuit doubles approximately every two years.
More Law
Clock speed pada CPU sepertinya tidak lagi doubling, di sisi lain core CPU terus nambah source. Jelas programming untuk multi core akan sangat diperlukan.
Karena functional tidak memiliki side-effect, setiap function bebas di eksekusi di mana saja, tanpa harus memikirkan apakah ini akan merubah kondisi global.
Banyak bahasa pemrograman sekarang mengarah ke sana contohnya seperti Java. Sejak Java 8, Java memiliki lambda function untuk meng-address masalah ini.
Kenapa Haskell?
Haskell menurut saya sangat cocok untuk dijadikan bahasa pemrograman functional pertama (atau bahkan bahasa pemrograman pertama). Karena Haskell memaksa untuk berpikir functional dan matematis.
Sedikit tips dari saya, jika Anda pernah koding sebelumnya, Coba lupakan cara-cara imperative. Karena ini sedikit menggangu ketika belajar Haskell.
Saya sendiri belajar Haskell dari EdX intro to FP dan Learn You a Haskell.
Bacaan lanjutan:
- https://docs.python.org/2/howto/functional.html
- https://blog.newrelic.com/2015/04/01/python-programming-styles/
- http://www.gotw.ca/publications/concurrency-ddj.htm