Suka Ngoding? Belajarlah Bahasa Haskell Sekali Seumur Hidup

Pernah gak sih sewaktu ngoding dengan cara functional masih ke bawa-bawa mikir dengan cara 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, saya jelasin di bagian bawah.

Buat yang belum tau apa itu functional paradigm vs imperative paradigm, bisa baca di Wikipedia atau StackOverflow. So basically, programming paradigms itu ada empat, imperative, object, declarative, dan functional.

Imperative programming itu seperti ngeprogram di C/C++ atau Java, di bahasa itu kita dibolehin buat ngerubah-rubah variabel di alamat memori yang sama dan berpikir seperti step-by-tep, obeject you know lah everything treat as an object and its behaviours, dan declarative kita ngedeskripsiik mau apa dan komputer yang nyari jalan keluarnya (contoh SQL), sedangkan functional hampir sama dengan declarative plus kita dilarang melakukan side-effect sebisa mungkin.

So, jadi kenapa imperative sama functional itu beda (banget)?

Tiga tahun pertama kuliah, saya belajar programing dengan gaya procedural dan object. Bahasa pertama Pascal, terus JS, C/C++, PHP, Java, dan terakhir Python. Jadi udah kebiasa banget ngoding dengan cara mikir step by step atau mecah progblem jadi beberpa object dan behaviours-nya. Nah baru setelah itu belajar functional programing.

Functional programming is like..

Functional programming is like..

Functional programing pertama saya saat itu Clojure. Saya sempat mikir, apaan ini ngoding, kok aneh banget?? Cara mikir di FP beda jauh dengan cara mikir procedural.

Meskipun Clojure functional style, tetapi di Clojure hampir mungkin ngoding style imperative. Untuk itu, saya belajar Haskell, yang katanya purely functional.

Contoh Masalah: Faktorial

Contoh deh, problem yang paling sering intro imperative vs functional itu n! atau n factorial.

Berapa faktorial 5?

Nah pertama kali ngoding di Clojure saya masih mikir cara-cara imperative, yaitu looping.

Pattren 1: Looping

Pattern ini umum digunakan di pemrograman imperative. Clojure meskipun functional dan tanpa side-effect, bisa ngelakuin pattern ini.

Clojure

(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.

Python

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

Meskipun demikian, pattern ke-2 ini memiliki kelemahan, yaitu untuk recursive yang besar stacknya akan mengalami stack overflow. Ini yang saya maksud stack-nya terbatas.

Dengan function yang masih faktorial2, berikut adalah hasil dari 10000 faktorial.

Clojure

(faktorial2 10000)
;; => java.lang.StackOverflowError: null

Haskell

faktorial2 10000
-- >> 284625968091...

Python

print(faktorial2(10000))
# >> RecursionError: maximum recursion depth exceeded in comparison

Di Clojure stack pada pattern ke-2 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

Nah, cara berpikir functional tidak seperti imperative yang procedural, tetapi lebih kepada cara perpikir first-class function, high-order function, declarative, atau recursive.

Cara berpikir declarative adalah cara berpikir tentang bagaimana mengolah sesuatu tanpa mendeskripsikan secara ekplisit step-by-step nya. So, abstraksi di functional penting banget. Di functional programming pastikan kita tahu apa input-nya dan ekptektasi output-nya.

Meskipun sulit, cara ini justru memiliki keunggulan banyak. Yang paling keren adalah kemudahan testing dan pararell programming jadi simpel. Karena dengan pattern ini fungsi harus bebas dari mutasi (side-effect free).

Ok. Cukup! Berikut ini adalah solusi yang sama dengan pattern functional.

Clojure

(defn faktorial3
  [n]
  (apply *' (range 1 (inc n))))
 
(faktorial3 5)
;; => 120
 
(faktorial3 10000)
;; => 284625968091...

Haskell

faktorial3 n = foldr (*) 1 [1..n]
 
faktorial3 5
-- >> 120
 
faktorial3 10000
-- >> 284625968091...

Menariknya, sejak Python v2.2, Python memiliki Lambda function. Yang artinya memungkinkan memakai pattern functional.

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...

Terus Kenapa?

Dari kode di atas, saya secara subjektif bisa ngambil kesimpulan:

Clojure memang functional lang, tetapi memungkinkan kita untuk berpikir secara imperative di beberapa case. Selain itu, Clojure juga memungkinkan untuk ada side-effect. Artinya apa? Clojure tidak pure functional. Kenapa demikian? karena di production code side-effect itu kadang-kadang diperlukan.

Python bisa segalanya, tetapi Python lebih cenderung mengadopsi imperative paradigm. Saran saya, jika ingin belajar imperative style udah paling ener jika Python sebagai bahasa pertama, karena dengan Python kita bisa belajar multi-paradigm sekaligus. Ini penting, karena imperative saja tidak akan cukup menyelesaikan problem yang cukup kompleks.

Haskell sangat cocok dijadikan bahasa pertama jika ingin berpikir in a functional way. Haskell memaksa programmer untuk itu, berbeda dengan Clojure yang kadang-kadang bisa memecahkan solusi dengan cara-cara style imperative.

Oh ya, menurut saya, belajar functional lebih sulit dibandingkan belajar imperative. Tetapi, sekali kita “ngeh” functional, jadi males lagi ngoding style imperative atau object oriented.

Kenapa Functional Thinking Penting (Banget!)

Menurut saya, programming itu adalah cara berpikir dan tujuan dari programming adalah menyelesaikan masalah. Sudah seharusnya programmer tidak terlalu terfokus terhadap distraction seperti “bagaimana menulis-nya”, tetapi sudah harus fokus di masalah bisnisnya.

Saya sangat merekomendasikan untuk berhenti sejenak dan melihat video Are We There Yet? dari Rich Hickey.

Selanjutnya alasan kenapa harus belajar FP adalah karena FP addressed most stressful problem in the future, scaling!. Menurut More Law

The observation that the number of transistors in a dense integrated circuit doubles approximately every two years.

Clock speed pada CPU segitu-gitu aja, tetepi core terus nambah (baca). Jelas programming untuk multi core akan sangat diperlukan.

CPU Trend

http://www.gotw.ca/publications/concurrency-ddj.htm

Bahasa pemrograman imperative memang memungkinkan untuk diproses secara pararell, tetapi itu sulit dan harus hati-hati katanya (saya belum pernah ngoding pararell btw). Berbeda dengan functional yang 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.

So, Why 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 ingin belajar functional programming apalagi datang dari imperative style, jangan menyerah. Awalnya emang aneh, tetapi nanti akan terbiasa. Lupakan cara-cara imperative jika ingin belajar FP.

Saya belajar Haskell dari EdX intro to FP dan Learn You a Haskell.

Dengan belajar Haskell, kita akan dapat keuntungan all-in-one, improve progrmaming skill, improve better way of thinking, dan belajar gaya programming masa depan.

Bacaan lanjutan:
https://docs.python.org/2/howto/functional.html
blog.newrelic.com/2015/04/01/python-programming-styles/
http://www.gotw.ca/publications/concurrency-ddj.htm

0 comments… add one

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.