Kuy Ngobrol Dikit Soal Generics

Using Typescript for simplicity

Jihad Dzikri Waspada
codewey
4 min readMay 8, 2019

--

A hole to fill in (source)

Reusability adalah komponen yang sangat penting dalam membangun sebuah software. Dengan code reuse, LoC (lines of code) dapat dikurangi sehingga maintainance aplikasi jadi relatif lebih gampang.

Less code == Less bugs

Salah satu cara untuk mencapai reusability adalah dengan menggunakan konsep Generics. Beberapa bahasa pemrograman seperti Typescript, Java, C++, OCaml, ReasonML, Purescript, dan Haskell, memeliki konsep Generics. Generics memungkinkan kita untuk membuat function atau data structure yang gak kaku sama satu tipe data aja, melainkan dengan tipe data apapun dia ayok-ayok aja: a.k.a terserah yang manggil. Tentunya bisa dibatasi dengan constraints (akan dibahas nanti).

Oh iya, konsep Generics ini hanya berguna jika kita menggunakan statically-typed language. Oleh karenanya membahas Generics dengan Javascript tidaklah relevan karena Javascript tidak memiliki type system.

✈️ Let’s Start with Function

Ketika bicara Generics, contoh yang paling gampang adalah dengan menggunakan identity function: sebuah unary function (memiliki 1 argument) yang langsung mengembalikan argument-nya:

const id = x => xid(1)       === 1
id('jihad') === 'jihad'
id([1,2]) === [1,2]
// etc

Tanpa Generics, kita musti overload atau bahkan redefine function ini dengan semua macam tipe yang ada di muka bumi.

const idNumber = (x: number): number => x
const idString = (x: string): string => x
...

ini belum termasuk overloading untuk array, object, function, atau bahkan data structure kita sendiri.

Dengan Generics, semua jadi jauh lebih mudah:

const id = <T>(x: T): T => x

Maksudnya: id menerima variable x yang memiliki tipe T, dan mengembalikan tipe T. Ketika T disubstitusi dengan number, maka bunyinya jadi: id menerima variable x yang memiliki tipe number, dan mengembalikan tipe number. Begitupun seterusnya dengan string, array, dll.

id<number>(1)       === 1
id<string>('jihad') === 'jihad'
id<number>('jihad') // error

Typecheck! Oh iya, satu lagi, jika diperhatikan, tipe T muncul di 3 tempat pada definisi identity function yang kita buat:

const id = <T>(x: T): T => x
^ ^ ^
1 2 3

Ketika menjalankan id(‘jihad’), type inference bilang bahwa ‘jihad’ bertipe string. Dengan kata lain, T pada posisi kedua sekarang bisa diisi dengan string. Karena salah satu T sudah diketahui bernilai string, maka semua T bisa disubstitusi dengan string sehinnga kita gak perlu lagi menggunakan <> untuk type assertion. Type can be inferred from anywhere.

typeof id('string') === 'string'
typeof id(1) === 'number'

🔗 Generic Data Structure

Sampai di sini kita udah tau gimana caranya buat function yang polymorphic pakek konsep Generics. Sebenernya gak cuman function, kita juga bisa buat data structure kita sendiri generics juga: misal pas kita mau bikin sebuah “template” data:

Baik createRemoteData maupun RemoteData, keduanya generic. Gak usah jauh-jauh, data structure dasar kayak Array di TS juga syudah generic. Kita bisa buat array of integers, array of string, array of arrays, array of objects, dll

Pun yang agak kompleks kayak struktur data Tree:

🚧 Constraints

Generics is cool. Sampai suatu saat, kita sadar bahwa dalam beberapa kasus kita juga ingin membatasi tipe data apa saja yang boleh digunakan. Misal seperti sebuah function generics yang mengembalikan semua key dari suatu object.

const keys = <T>(obj: T) => Object.keys(obj)

Ada satu masalah di sini: T tidak boleh bernilai null atau undefined. Malah kalau dipikir-pikir, kita hanya ingin T ini bertipe object.

Object.keys(null)
// Uncaught TypeError: Cannot convert undefined or null to object

Gimana solusinya? Di Typescript, kita harus menggunakan keyword extends.

const keys = <T extends {}>(obj: T) => Object.keys(obj)

Di sini compiler akan bilang: saya gak mau tau apa itu T, asalkan dia bentuknya object, saya terima. Saya juga gak peduli apapun key dan value-nya. Seng penting object.

keys({}) === []
keys({ id: 1 }) === ['id']
keys(1) // error

Easy peasy 🎉

Kesimpulan

Konsep Generics dalam bahasa pemrograman sangat sangat membantu kita untuk achieve reusability. Dengan Generics pula, kita bisa dengan mudah membuat function dan data structure yang polymorphic: bisa nerima berbagai macam tipe data.

Namun ada pertanyaan besar: “Kan katanya generics bisa nerima tipe data apa aja, bisa gak generics nerima generics lain?”. Semacam Higher-Order Generics gitu 🤔

Maksudnya kek gimana Pak Jihad? Kek gini:

Fungsi map punya 3 buah type variable: F, A, dan B. Di argument kedua, thing memiliki tipe F<A>, dan fungsi map ini mengembalikan tipe F<B>. Di sini F seolah-olah berperan sebagai suatu struktur data yang menerima type variable, mirip Array dan Tree di atas. Harapannya sih dengan fungsi ini saya bener-bener bisa substitusi F dengan Array maupun Tree, dan A atau B dengan primitive types seperti number dan string.

Kira-kira bisa gak ya? Kalau iya apa keuntungannya? Simak artikel berikut kalau mau tau lebih lanjut 😉

https://www.jihadwaspada.com/post/generic-di-atas-generic-higher-kinded-type/

Terima kasih semoga bermanfaat 🙂

If you find this article useful, don’t hesitate to hit the clap button so that your friends could know this story.

--

--

Jihad Dzikri Waspada
codewey

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