Functional Programming: Mengenal Semigroup dan Monoid

Abstraksi Penggabungan Dua Buah Tipe Data yang Sama

Jihad Dzikri Waspada
codewey
5 min readApr 4, 2019

--

Tulisan kali ini akan coba saya jelaskan dengan cepat. Sebelum memahami kedua konsep Semigroup dan Monoid, ada baiknya untuk memahami apa maksud dari Identity.

Identity

Identity merupakan suatu konsep yang, jika diaplikasikan dengan suatu value, akan langsung mengembalikan value itu sendiri.

const identity = x => xconst five = 5
log(identity(five)) // => 5

Sounds useless, right?

Semigroup

Semigroup bisa diartikan dengan: suatu value yang bisa “disatukan” dengan value lain yang setipe. Bahasa OOP-nya: Appendable. Contoh paling mudah dipahami adalah penambahan dan perkalian angka, juga penggabungan pada String dan Array.

> 1 + 2 = 3> 6 * 7 = 42> "gue elo " + "end" == "gue elo end"> [1].concat([2]) == [1, 2]

Terlihat pola yang serupa dari masing-masing contoh di atas dimana semua operand (1 dan 2 pada contoh pertama) dan hasil komputasi (3) memiliki tipe yang sama ⬇️

(+) :: Num -> Num -> Num
(*) :: Num -> Num -> Num
(+) :: String -> String -> String
concat :: [a] -> [a] -> [a]

Pada dasarnya, operasi penggabungan nilai pada Semigroup ini harus memenuhi 1 syarat saja, yaitu Associativity:

> (1 + 2) + 3 == 1 + (2 + 3)> (6 * 7) * 8 == 6 * (7 * 8)> ("gue " + "elo ") +  "end" == "gue " + ("elo " +  "end")> ([1].concat([2])).concat([3]) == [1].concat([2].concat([3]))

Monoid

Rumus Monoid ⬇️

Monoid = Semigroup + Identity

Kita akan bahas segera maksud dari rumus ini

Penambahan

Pada operasi penambahan, nilai Identity-nya adalah 0. Mengapa demikian? Seperti yang telah dijelaskan di awal artikel, suatu value yang “diaplikasikan” dengan Identity hanya akan mengembalikan value tersebut kembali. Thus:

1 + 0 = 1atau0 + 1 = 1

Dengan demikian, tipe data Number under addition memiliki Identity 0 dan + sebagai operator Semigroup-nya.

Pseudocode-nya bisa dituliskan seperti berikut:

Perkalian

Bisa ditebak kan, under multiplication, tipe data Number memliki Identity 1 karena semua angka yang dikalikan dengan 1 akan mengembalikan angka itu sendiri.

String

Tidak sebatas angka, tipe data String pun juga bisa memiliki instance Monoid karena bisa di-append dengan String lain dan memliki identity-nya.

Boolean

Boolean jugak? Iyes! under operation OR (disjunction) memliki identity false dan under operation AND (conjunction) memiliki identity true.

Array

Array juga demikian, ia memiliki Identity [] dan operator append untuk instance Semigroup-nya.

Ada instance lain? Let’s try Object!

Object

Object di Javascript juga bisa memiliki instance Monoid-nya dengan {} sebagai nilai identity-nya dan Object.assign sebagai operator Semigroup-nya

Masih banyak lagi sebenarnya tipe data tak-primitif yang bisa dijadikan instance Semigroup dan Monoid. Biar aku juga sekalian bisa belajar dari nulis artikel ini 😄🙏🏻

CSS Color

Untuk teknik color blending-nya, sebenarnya bebas aja mau kek gimana implementasinya. Tapi kita ambil contoh aja seperti yang didemonstrasikan di Meyerweb.

Bagaimana untuk identity-nya? Bingo! Di CSS, value transparent is a valid color.

JSX

Kalau dipikir-pikir, JSX juga bisa masuk kategori Monoid karena semua HTML element dan React component kita bertipe JSX.

Function

Untuk operator identity-nya, saya kira sudah jelas juga bahwa identity function adalah const id = x => x. Nah untuk operasi Semigroup-nya ini yang agak menantang.

First rule, hasilnya harus berupa function juga, karena Semigroup bernotasi a -> a -> a.

Kedua, ketika kita ingin membuat sesuatu tipe data instance dari Semigroup, kita harus bisa membuktikan bahwa append(h, append(f, g))) dan append(append(h, f), g) akan membuahkan hasil yang sama (hukum Associativity di awal artikel)

Kayaknya pake fungsi compose bisa karena kan compose bersifat associative! Yuk dibuktikan dulu:

// Andaikata
const f = x => x * 2
const g = y => y + 1
const h = z => String(z)
// Maka
// append(h, append(f, g)))
const _1 = compose(h, compose(f, g)))
log(_1(3)) // => '8'
// append(append(h, f), g)
const _2 = compose(compose(h, f), g)
log(_2(3)) // => '8'

Wih masuk pak eko! 🎉 Untuk yang belum tahu apa itu compose

⚠️ Tapi tunggu dulu. Sekilas keliatan benar tapi ada sesuatu yang terlewatkan. Jika kita flashback pada contoh-contoh sebelumnya, operasi concat selalu berhasil jika argument pertama ditaruh di argument kedua dan sebaliknya (walaupun hasil akhirnya berbeda).

// Addition
sumAddition.concat(1, 2) // works
sumAddition.concat(2, 1) // works
// Array
array.concat([1], [2]) // works
array.concat([2], [1]) // works
// CSS Colour (See the implementation! ⬆️)
cssColour.concat('transparent', '#aaa') // works
cssColour.concat('#aaa', 'transparent') // works

Tapi untuk function composition ini gak selamanya benar. Suppose:

// length :: [a] -> Int
const length = xs => xs.length
// toUpper :: String -> String
const toUpper = str => str.toUpperCase()
funcMonoid.concat(length, toUpper)('Jihad') // works, 5
funcMonoid.concat(toUpper, length)('Jihad') // error!
// Uncaught TypeError: str.toUpperCase is not a function

So gimana? 🤔

Dari fakta ini, kita bisa menarik kesimpulan bahwa instance Monoid untuk Function hanya berlaku untuk function yang endomorphic. Endomorphism adalah suatu fungsi yang tipe argument dan return value-nya sama.

endoFunc :: a -> a

Dengan ini kita bisa menjamin bahwa function composition tersebut akan selalu berhasil saat runtime.

Dan untuk function yang tidak endomorphic, kita tetap bisa membuat instance Monoid-nya, namun dengan syarat return value dari function tersebut harus memiliki instance dari Monoid juga. Syarat berikutnya, kedua function tersebut harus memiliki tipe yang sama. Pada contoh di bawah ini, kedua function-nya memiliki tipe Int -> String.

Kegunaan

Kalau saya pribadi, saya nggak melihat keuntungan menggunakan Monoid di Javascript mainly karena ekosistem JS yang gak tepat aja: bukan statically-typed language. Kalaupun bahasa tersebut statically-typed (think of Typescript, Kotlin, Java) tetep belum tentu bisa menerapkan Monoid se-simple Haskell atau Purescript.

Tapi kalau temen-temen mau liat kegunaan umum Monoid ini (dan lihat betapa verbose-nya jika digunakaan di bahasa selain Haskell/Purescript), bisa mampir ke link-link di bawah ini:

  1. https://dev.to/gcanti/getting-started-with-fp-ts-semigroup-2mf7
  2. https://dev.to/gcanti/getting-started-with-fp-ts-monoid-ja0

Dan ada komentar menarik di sini: https://dev.to/jbristow/comment/78dl 😄

Emang gimana sih gimana kalau implementasi di Haskell?

Simple banget!

Dengan begini, kita meng-overload operator concat (di-aliaskan dengan <>) dengan berbagai macam tipe data, sehingga tipe data yang telah memiliki instance Monoid dapat beroperasi dengan <>, seperti di bawah ini:

Sum 1 <> Sum 2 == Sum 3
Prod 2 <> Prod 9 == Prod 18
"Jihad" <> "Pita" == "JihadPita"
Color "transparent" <> Color "#aaa" == Color "#aaa"

Nah karena <> sudah polymorphic dengan berbagai macam tipe data, kita ndak perlu menghafal berbagai macam nama method/function hanya untuk menggabungkan sesuatu. Beberapa minggu yang lalu saya sedang latihan parser dengan Haskell (menggunakan Parsec), dan saya ingin menggabungkan 2 buah parser menjadi satu. Gak perlu buka dokumentasi, cukup dengan intuisi, saya gunakan <> dan ternyata berhasil-berhasil aja karena String sudah implement instance Monoid 😉

openBracket :: Parser String
openBracket = string "["
space :: Parser String
space = string " "
openBracketFollowedBySpace :: Parser String
openBracketFollowedBySpace = openBracket <> space

Apa yang Bisa Dipelajari?

My takeaway: Functional Programming paradigm memungkinkan kita untuk membuat abstraksi sampai ke hal-hal yang sangat mendasar. Dalam hal Semigroup, kita mengabstraksi operasi penggabungan dari 2 buah tipe data. Kedepannya insyaallah saya akan menulis artikel tentang abstraksi FP soal operasional aritmatik dasar seperti +, -, *, dan / menggunakan typeclass Semiring, Ring, dan EuclideanRing (tapi gak tau kapan ya) 😄

Jika temen-temen tertarik bagaimana membuat abstraksi di FP, saya sangat rekomendasikan untuk nonton video ini:

Terima kasih semoga bermanfaat 🙂

--

--

Jihad Dzikri Waspada
codewey

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