My personal code the first time using Ramda

Functional Programming: Sekilas tentang Type Signature sebuah Function

lagi dan lagi, dalam ES6

Published in
6 min readJun 27, 2017

--

Belajar Functional Programming itu hukumnya haram kalau belum mengenal Type Signature. Karena memang nantinya Type Signature inilah yang akan membantu reasoning kita akan alur sebuah program. Ciri paling mencolok dari Type Signature yang sering digunakan adalah adanya tanda-tanda panah di atas sebuah function. Mungkin ada yang pernah lihat simbol macam ini?

add function (source)

Inilah yang sebentar lagi akan dibahas. Notasi di atas biasa disebut Type Signature, merujuk pada type system-nya Hindley-Milner yang banyak digunakan di berbagai bahasa FP, termasuk Haskell. Type Signature ini hanya salah satu bentuk standarisasi saja di antara bahasa-bahasa FP. Scala punya gaya Type Signature-nya sendiri. Namun di artikel ini (dan mungkin di artikel-artikel selanjutnya), saya akan mulai menggunakan Type Signature-nya si HM ini.

Penggunaan

Type Signature digunakan sebagai alat dokumentasi akan bagaimana sebuah pure function menerima input dan mengeluarkan output. Apa saja tipe data paramaternya? Apakah sebuah Integer? String? Atau array? Atau justru bisa semuanya?

Function Sederhana

// lima :: Number
const lima = 5
// numToString :: Number → String
const numToString = num => num.toString()

Function numToString di atas simple-nya adalah menerima input dengan tipe Number dan mengembalikan output dengan tipe String. Beberapa rule dasar yang mesti dipahami adalah:

  1. Nama function dengan tipe parameter dan/atau return-nya dipisah dengan simbol ::
  2. Nama function ditulis sebelum simbol ::
  3. Return type ditulis setelah simbol → (yang paling akhir) atau jika tidak memiliki pramater, setelah simbol ::

Function dengan Beberapa Parameter

// add :: Number → Number → Number
const add = (x, y) => x + y

Fungsi add menerima 2 input, yang keduanya harus bertipe Number, dan mengembalikan value dengan tipe Number juga. Ingat kembali, tipe yang ditulis paling kanan selalu merupakan tipe return value-nya.

// split :: String → String → [String]
const split = (separator, str) => str.split(separator)

Juga seperti contoh split ini, menerima 2 inputan bertipe String, dan mengembalikan kumpulan value bertipe String pula (array of strings).

Note: Best practice-nya, function yang memiliki paramater lebih dari satu dijadikan curried function. Contoh-contoh function yang memiliki lebih dari satu parameter di artikel ini saya asumsikan sudah curried semua agar penjelasan bisa lebih fokus pada esensi.

Higher Order Function

Bukan Functional Programming namanya kalau sebuah function tidak bisa menerima function juga dan/atau mengembalikan sebuah function.

// mathMsg :: (Number → Number → Number) → Number → Number → String
const mathMsg = (mathOp, x, y) => {
const result = mathOp(x, y)
return `The result of ${x} and ${y} is ${result}`
}

Wah lumayan banyak ya type signature-nya. Tapi sederhananya adalah, mathMsg yang mengembalikan String ini menerima sebuah function yang mana function tersebut memiliki 2 parameter bertipe Number dan mengembalikan Number, lalu parameter kedua dan ketiga dari mathMsg sendiri sama-sama menerima Number. Mudah kan?

Nah function yang di-pass inilah (dalam hal ini mathOp) jika dituliskan ke dalam Type Signature harus dibungkus dengan tanda kurung, sebagai pembeda function dengan tipe biasa.

Jika melihat type signature dari mathMsg di atas, kita bisa mensubstitusi mathOp dengan fungsi add di contoh sebelumnya karena memiliki type signature yang sama (Number → Number → Number).

Karena kesamaan Type Signature, fungsi add bisa di-pass sebagai argument pertama mathMsg

Generic?

Bagaimana kalau kita memiliki function yang general, yang artinya tidak sebatas tipe tertentu saja? Seperti

const identity = value => value

Jika saya pass sebuah Number ke dalam function identity, maka return type-nya adalah Number. Jika pass sebuah String, maka tipe kembalian String juga. Jika pass sebuah Array, maka tipe kembaliannya pun Array yang sama juga. Artinya, fungsi identity ini fleksibel, parameternya dapat menerima lebih dari satu macam tipe. Lantas bagaimana Type Signature-nya?

// identity :: a → a
const identity = value => value

a bisa apa saja, karena berlaku general. Contoh lain:

// head :: [a] → a
const head = xs => xs[0]
console.log(head([5, 6, 7]))
// => 5, dalam hal ini a bertipe Int
console.log(head('Kamu iya kamu'))
// => 'K', dalam hal ini a bertipe String
console.log(head([[1, 2], [3, 4]]))
// => [1, 2], dalam hal ini a bertipe [Int]

Operasi map pada sebuah array pun demikian. Kita tidak tahu tipe data di dalam array tersebut, pun tidak tahu hasil kembalian dari function yang ingin di-pass ke dalam fungsi map. Oleh karena itu variable acak (seperti a, b, c) dapat digunakan dalam kasus seperti ini.

Contoh arbitrary variables dari Hindley-Milner Type System

Hal yang sama berlaku juga untuk operasi filter dan reduce pada array.

Filter & Reduce Type Signature

Perhatikan bagaimana tipe kembalian fungsi reduce haruslah sama dengan tipe nilai init.

Instance

Bagi kamu yang sudah mengerti konsep Functor, kita bisa bisa membuat Type Signature map dari sebuah Functor:

// fmap :: Functor f => (a → b) → f a → f b
const fmap = fn => functor => functor.map(fn)

Maksud dari notasi di atas adalah: semua f yang ditulis di Type Signature fmap, haruslah berupa Functor.

// (Int → String) → Maybe Int → Maybe String
fmap(x => x.toString(), Maybe(5))
// anggap fmap itu singkatan functor-map

Begitupun Monad, kita dapat membuat notasi fungsi bind yang general untuk Monad:

// mbind :: Monad m => (a → m b) → m a → m b
const mbind = fn => monad => monad.bind(fn)
// anggap mbind itu singkatan monad-bind

Dan untuk kamu yang sudah mengerti konsep Applicative Functor, kita bisa sama-sama membuat Type Signature dari pure, ap dan liftA:

Ingat bahwa Applicative itu subclass dari Functor

Berbagai macam Type Signature fungsi-fungsi dari Applicative Functor

Kesimpulannya dari ketiga contoh instance di atas adalah bahwa simbol => berperan sebagai pemisah antara nama instance dan “the real type signature” dari sebuah fungsi.

Reasoning & Optimisasi

Seperti yang telah dijelsakan di atas, Type Signature ini dapat digunakan untuk reasoning alur sebuah program, terutama terhadap sebuah fungsi hasil komposisi dari beberapa pure function. Untuk memahami konsep komposisi dalam Functional Programming, kamu dapat membacanya disini.

Saya ingin membuktikan bahwa kedua fungsi dibawah ini adalah sama

// Ingat bahwa Type Signature dari head adalah
// head :: [a] → a
compose(head, map(fn)) === compose(fn, head)

compose yang sebelah kiri maksutnya adalah: Diberikan sebuah array lalu saya map terhadap function fn, kemudian saya ambil elemen pertama dari hasil map tersebut. Sedangkan yang sebelah kanan adalah: Diberikan sebuah array, saya ambil elemen pertama terlebih dahulu, lalu saya proses dengan function fn. Pertanyaannya adalah, apakah hasilnya sama?

Mari kita buktikan dengan Type Signature.

Note: Ingat compose mengevaluasi function dari kanan ke kiri.

// head :: [a] → a
// fn :: a → b
// map(fn) :: [a] → [b]
// [a] → [b] → b === [a] → a → b
compose(head, map(fn)) === compose(fn, head)
-- Lalu kita analisa INPUT dan OUTPUT nya saja// [a] → b === [a] → b
compose(head, map(fn)) === compose(fn, head)

Tadaa! Terbukti bahwa kedua fungsi tersebut menghasilkan OUTPUT yang sama. Yang lebih performan yang mana? Jelas yang sebelah kanan karena tidak perlu mengiterasi array-nya terlebih dahulu. Nah, jika ada teman programmer yang menulis fungsi seperti compose yang sebelah kiri, kita langsung bisa refactor fungsi tersebut dengan fungsi yang lebih performan (compose yang sebelah kanan) melalui pembuktian dari type signature-nya.

Kesimpulan

Dengan adanya Type Signature, pure function yang kita buat dapat terdokumentasikan dengan lebih baik. Pun ketika kita sedang menggunakan pure function yang dibuat oleh orang lain, kita jadi langsung paham apa yang harus kita lakukan dengan fungsi tersebut. Reasoning alur program pun menjadi lebih terbantu.

Untuk lebih lanjutnya, kamu bisa mampir-mampir ke dokumentasi Ramda, salah satu library Javascript terpopuler. Dokumentasinya sangat bagus untuk dipelajari.

Penutup

Terima kasih sudah bersedia membaca artikel ini. Jika terdapat penjelasan yang kurang mudah dicerna, mohon feedback-nya di kolom komentar. Harapannya artikel-artikel selanjutnya dapat menjadi lebih baik.

Happy coding! 😃

--

--

Software Developer @Chordify, Utrecht. NOTE: Please navigate to https://jihadwaspada.com. I no longer write on Medium