Generic di atas Generic: Higher-Kinded Type
Artikel ini merupakan artikel lanjutan dari materi Generics dari Medium saya menggunakan Typescript.
Generics
Array
Sebelum membahas lebih dalam tentang Higher-Kinded Type, ada baiknya kita mengingat-ingat kembali apa itu Generics. Dan saya rasa gak ada data structure generic yang lebih sederhana dan umum digunakan di Typescript kecuali Array.
Karena struktur data ini generic, kita bisa membuat function yang generic pula untuk mengubah nilai yang ada di dalamnya tanpa memandang apakah ia bertipe number
, string
, object
, atau yang lainnya. Let’s say, kita lagi butuh function yang bisa mengubah array menjadi array of tuple.
So, function tuplifyArray
ini bersifat polymorphic: dimana ia mengubah Array<number>
menjadi Array<[number, number]>
pada contoh pertama dan mengubah Array<string>
menjadi Array<[string, string]>
pada contoh kedua, gak pandang type di dalamnya 🔥 So far so good.
Tree
Beberapa bulan kemudian, Project Manager dateng bawa berita duka: kita diminta untuk menambahkan struktur data Tree karena ternyata requirement proyek sudah mulai melebar.
Dasar emang PM ini plin-plan dan kurang bisa nego sama client, dia sekarang minta juga untuk dibuatkan fungsi tuplifyTree
persis seperti fungsi tuplifyArray
yang sudah dibuat tadi.
Kejadian ini membuat kita berasumsi jauh ke depan bagaimana jika suatu saat nanti lingkup projek menjadi semakin lebar dan harus menghadirkan beberapa data structure generic baru beserta function tuplify
-nya. Data structure tersebut bisa berupa Stack<T>
, Queue<T>
, LinkedList<T>
, Stream<T>
, you name it.
And this is the time: untuk membuat abstraksi dari solusi yang diinginkan, sebagai seorang developer kita harus bisa melihat pola dari masalah yang ada terlebih dahulu. Mari kita bandingkan function type tuplifyArray
dengan tuplifyTree
, maka kita akan menemukan bahwa kedua function type tersebut ternyata memiliki pola yang sangat mirip:
Yang membedakan keduanya hanyalah type Array
dan Tree
. AHA-moment datang 💡, sekarang kita mulai membayangkan solusi yang lebih generic, yang mengasbtraksi (tidak lagi terikat dengan) Array
maupun Tree
:
Namun sayangnya, compiler Typescript menolak syntax ini dengan pesan error: Type 'F' is not generic
. Error muncul karena keterbatasan compiler Typescript yang tidak memperbolehkan sebuah type variable menerima type lain.
Nah kemampuan type variable menerima type lain (disebut type constructor) inilah yang disebut Higher-Kinded Type. Namun sayangnya Typescript hanya bisa mengabstraksi type, belum bisa mengabstraksi type constructor.
Kind, type of type
Kind adalah cara untuk mengekspresikan (concrete) type dan type constructor dengan symbol *
. Anggap saja Kind ini adalah type-nya type 😄
*
adalah kind untuk concrete types. string
, number
, Date
, Array<number>
, Tree<string>
masuk dalam kategori ini.
* -> *
adalah kind untuk type constructor yang menerima satu type parameter. Kita bisa membacanya dengan “sesuatu yang menerima *
(concrete type) dan mengembalikan *
(concrete type pula)“. Contohnya adalah Array
, dan Tree
di atas.
* -> * -> *
adalah kind untuk type constructor dengan dua type parameter. Record
salah satu contohnya, karena kita harus menyuplai dua type paramater untuk menghasilkan sebuah concrete type: Record<string, number>
.
* -> * -> * -> *
adalah kind untuk type constructor dengan tiga type parameter, dsb.
Di salah satu bahasa yang mendukung HKT seperti Purescript (atau Haskell), permasalah tuplify
dapat diekspresikan dengan:
Type parameter f
bisa disubstitusi dengan Array
dan Tree
, artinya f
memiliki Kind * -> *
. Dan type paramater t
-nya adalah concrete type (Kind *
). Anggap aja class
di atas itu seperti interface
di Typescript dan instance
itu seperti class
:p
Namun tetap masih kurang akurat: perhatikan return type baris 14-15. Return type dari fungsi tuplify
bisa berupa apa saja asalkan ia subtype dari Tupleable
. Artinya notasi Tree<T> → Array<[T, T]>
di baris 14 type-checked! Sedangkan kita ingin menjaga type constructor yang sama di return type: Array<T> → Array<[T, T]>
atau Tree<T> → Tree<[T, T]>
saja.
Maunya sih begini tapi gak bisa 😅
Selain itu, konsep Higher-Kinded Type memungkinkan kita untuk membuat factory type, yang berarti satu type dapat menghasilkan banyak type atau data structure.
Penutup
Ada beberapa library yang mencoba mensimulasikan konsep HKT ke dalam Typescript. Yang paling terkenal adalah fp-ts. Dulu kami pernah menggunakan library ini di production, tapi kami merasa terlalu verbose dan kurang cocok dengan tim sehingga penggunaannya kami minimalisir. Ada juga alternatif lain yang mungkin lebih simple, hkts, belum pernah nyoba tapi hehe.
Akhirul kalam, semoga artikel ini dapat menambah wawasan temen-temen soal type.
Arriverdeci!