diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-02-20 22:15:21 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-02-20 22:15:21 +0700 |
| commit | e80cf067599f56b3cbbac6162f160629a7862bcf (patch) | |
| tree | cc617ddf02ea7cfec8b7522aa63c5021e04292f7 | |
| parent | c30505ff0526c014d9f9b7ae2ecb58bcac68f168 (diff) | |
| download | golang-id-web-e80cf067599f56b3cbbac6162f160629a7862bcf.tar.xz | |
blog: terjemahkan artikel "Strings, bytes, runes and characters in Go"
| -rw-r--r-- | content/blog/index.adoc | 3 | ||||
| -rw-r--r-- | content/blog/strings/index.adoc | 441 |
2 files changed, 444 insertions, 0 deletions
diff --git a/content/blog/index.adoc b/content/blog/index.adoc index b441e04..248e128 100644 --- a/content/blog/index.adoc +++ b/content/blog/index.adoc @@ -32,6 +32,9 @@ * link:/blog/errors-are-values[Error adalah nilai], 12 Januari 2015, _Rob Pike_ +* link:/blog/strings[String, byte, rune, dan karakter dalam Go], + 23 Oktober 2013. _Rob Pike_ + * link:/blog/slices[Array, slice (dan string): Mekanisme 'append'] 26 September 2013. _Rob Pike_ diff --git a/content/blog/strings/index.adoc b/content/blog/strings/index.adoc new file mode 100644 index 0000000..e5e5a26 --- /dev/null +++ b/content/blog/strings/index.adoc @@ -0,0 +1,441 @@ += String, byte, rune, dan karakter dalam Go +:author: Rob Pike +:date: 23 Oktober 2013 + +== Pendahuluan + +Pada +link:/blog/slices[blog sebelumnya] +dijelaskan bagaimana slice bekerja dalam Go, menggunakan sejumlah contoh untuk +mengilustrasikan mekanisme di balik implementasinya. +Dengan latar belakang tersebut, artikel ini mendiskusikan string dalam Go. +Pertama, string tampak terlalu simpel untuk sebuah artikel, namun untuk +menggunakannya dengan baik membutuhkan pemahaman tidak hanya bagaimana cara +ia bekerja, tetapi juga perbedaan antara sebuah byte, karakter, dan rune, +perbedaan antara Unicode dan UTF-8, perbedaan antara sebuah string dan literal +string, dan perbedaan lain yang lebih halus. + +Salah satu cara untuk mengkaji topik ini yaitu dengan membayangkannya sebagai +sebuah jawaban dari pertanyaan yang sering diajukan, "Saat saya mengindeks +string pada Go pada posisi _n_, kenapa saya tidak mendapatkan karakter +ke-_n_?" +Seperti yang akan kita lihat nantinya, pertanyaan ini mengarahkan kita pada +banyak hal tentang bagaimana teks bekerja dalam dunia nyata. + +Sebuah pendahuluan yang bagus terhadap masalah ini, independen terhadap Go, +yaitu blog dari Joel Spolsky, +http://www.joelonsoftware.com/articles/Unicode.html[The Absolute Minimum Every +Software Developer Absolutely, Positively Must Know About Unicode and +Character Sets (No Excuses!)]. +Banyak poin-poin yang diangkat dalam tulisan tersebut akan diulang di sini. + + +== Apa itu string? + +Mari kita mulai dengan beberapa dasar. + +Dalam Go, sebuah string yaitu slice dari byte yang _read-only_. +Jika Anda tidak yakin tentang apa itu slice dari byte atau bagaimana ia +bekerja, mohon baca +link:/blog/slices[blog sebelumnya]; +kita asumsikan Anda telah membacanya. + +Penting juga diperjelas di sini bahwa sebuah string menyimpan _beragam_ byte. +Ia tidak harus menyimpan teks Unicode, teks UTF-8, atau format lainnya. +Selama menyangkut isi dari string, isinya sudah pasti slice dari byte. + +Berikut sebuah literal string (lebih lanjut tentang ini akan kita bahas nanti) +yang menggunakan notasi heksadesimal lepas `\xNN` untuk mendefinisikan sebuah +konstanta string yang menyimpan beberapa nilai byte tertentu. +(Rentang byte untuk nilai heksadesimal yaitu 00 sampai FF, inklusif.) + +---- +const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" +---- + + +== Mencetak string + +Karena beberapa byte pada contoh string sebelumnya bukanlah karakter ASCII +yang valid, bahkan bukan UTF-8 yang valid, mencetak string tersebut secara +langsung akan menghasilkan keluaran yang kacau. +Perintah pencetakan sederhana + +---- +fmt.Println(sample) +---- + +menghasilkan keluaran kacau berikut (yang tampilannya beragam tergantung +lingkungan Anda): + +---- +��=� ⌘ +---- + +Untuk mengetahui apa yang disimpan string, kita perlu memecahnya dan memeriksa +satu persatu. +Ada beberapa cara untuk melakukan hal ini. +Cara yang paling kentara yaitu dengan melakukan pengulangan pada isi string +dan mengambil byte satu-per-satu, seperti berikut: + +---- +for i := 0; i < len(sample); i++ { + fmt.Printf("%x ", sample[i]) +} +---- + +Pengindeksan pada string mengakses byte, bukan karakter. +Kita akan kembali pada topik tersebut lebih rinci nanti di bawah. +Untuk sekarang, fokus pada byte dahulu. +Berikut keluaran dari pengulangan byte-per-byte: + +---- +bd b2 3d bc 20 e2 8c 98 +---- + +Perhatikan bagaimana setiap byte sesuai dengan nilai heksadesimal yang mengisi +string sebelumnya. + +Cara singkat untuk menghasilkan keluaran yang baik untuk string yang kacau +seperti di atas yaitu dengan menggunakan format %x (heksadesimal) pada +`fmt.Printf`. +Ia akan mencetak byte dari string secara sekuensial sebagai bilangan +heksadesimal, dua per byte. + +---- +fmt.Printf("%x\n", sample) +---- + +Bandingkan keluarannya dengan yang di atas: + +---- +bdb23dbc20e28c98 +---- + +Sebuah trik untuk menambahkan "spasi" pada format, dengan menempatkan sebuah +spasi antara `%` dan `x`. +Bandingkan format string yang digunakan di sini dengan yang di atas, + +---- + fmt.Printf("% x\n", sample) +---- + +dan perhatikan bagaimana byte ditulis dengan spasi, membuat hasilnya lebih +mudah dilihat: + +---- +bd b2 3d bc 20 e2 8c 98 +---- + +Ada lagi. +Format `%q` akan mengindahkan urutan byte yang tidak bisa dicetak dalam sebuah +string sehingga keluarannya tidak ambigu. + +---- + fmt.Printf("%q\n", sample) +---- + +Teknik ini cukup berguna saat kebanyakan string tidak bisa dibaca sebagai teks +namun ada kemungkinan yang bisa dibaca juga; ia menghasilkan: + +---- +"\xbd\xb2=\xbc ⌘" +---- + +Jika kita lihat lebih rinci, ditemukan sebuah karakter ASCII sama-dengan, +bersama dengan sebuah spasi, dan pada akhir muncul simbol Swedia untuk "Place +of Interest". +Simbol tersebut memiliki nilai Unicode U+2318, dienkod sebagai UTF-8 oleh byte +setelah spasi (nilai heksa 20): `e2 8c 98`. + +Jika kita tidak terbiasa atau bingung dengan nilai di dalam string, kita dapat +menggunakan tanda "tambah" pada format `%q`. +Tanda ini menyebabkan pencetakan yang meng-"escape" (melepas) tidak hanya +urutan yang tidak dapat dicetak, namun juga byte yang bukan ASCII, yang +diinterpretasi dengan UTF-8. +Hasilnya yaitu nilai Unicode dari UTF-8 yang merepresentasikan data selain +ASCII dalam string: + +---- + fmt.Printf("%+q\n", sample) +---- + +Dengan format tersebut, nilai Unicode dari simbol Swedia di-"escape" dengan +`\u`: + +---- +"\xbd\xb2=\xbc \u2318" +---- + +Teknik pencetakan ini perlu diketahui saat men-_debug_ isi dari string, +dan berguna dalam diskusi kita di bawah. +Semua metode-metode tersebut selain dapat digunakan pada string juga dapat +digunakan pada slice dari byte. + +Berikut seluruh opsi pencetakan sebelumnya, dalam sebuah program yang dapat +Anda jalankan (dan ubah): + +---- +package main + +import "fmt" + +func main() { + const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" + + fmt.Println("Println:") + fmt.Println(sample) + + fmt.Println("Byte loop:") + for i := 0; i < len(sample); i++ { + fmt.Printf("%x ", sample[i]) + } + fmt.Printf("\n") + + fmt.Println("Printf with %x:") + fmt.Printf("%x\n", sample) + + fmt.Println("Printf with % x:") + fmt.Printf("% x\n", sample) + + fmt.Println("Printf with %q:") + fmt.Printf("%q\n", sample) + + fmt.Println("Printf with %+q:") + fmt.Printf("%+q\n", sample) +} +---- + +(Latihan: Ubah contoh di atas menggunakan slice dari byte bukan string. +Petunjuk: Gunakan konversi untuk membuat slice.) + +(Latihan: Lakukan pengulangan pada string menggunakan format %q untuk setiap +byte. +Apa yang dapat Anda pelajari dari keluarannya?) + + +== UTF-8 dan literal string + +Seperti yang kita lihat sebelumnya, pengindeksan pada string menghasilkan +byte-byte, bukan karakter: sebuah string hanyalah sekumpulan byte. +Ini artinya saat kita menyimpan sebuah nilai karakter dalam sebuah string, +kita menyimpan representasi karakter tersebut byte-per-byte. +Mari kita lihat contoh yang lebih terkontrol untuk melihat bagaimana hal +tersebut terjadi. + +Berikut sebuah program sederhana yang mencetak konstanta string per karakter +dengan tiga cara, satu dengan string polos, kedua dengan string ASCII, dan +ketiga dengan heksadesimal. +Supaya tidak bingung, kita buat sebuah "string mentah", yang dikurung +dengan _backtick_, supaya hanya mengandung literal teks. +(String biasa, dengan tanda kutip-ganda, bisa berisi seurutan "escape" seperti +yang kita perlihatkan di atas.) + +---- +func main() { + const placeOfInterest = `⌘` + + fmt.Printf("plain string: ") + fmt.Printf("%s", placeOfInterest) + fmt.Printf("\n") + + fmt.Printf("quoted string: ") + fmt.Printf("%+q", placeOfInterest) + fmt.Printf("\n") + + fmt.Printf("hex bytes: ") + for i := 0; i < len(placeOfInterest); i++ { + fmt.Printf("%x ", placeOfInterest[i]) + } + fmt.Printf("\n") +} +---- + +Keluarannya adalah: + +---- +plain string: ⌘ +quoted string: "\u2318" +hex bytes: e2 8c 98 +---- + +yang mengingatkan kita bahwa karakter Unicode untuk nilai U+2318, simbol ⌘ +untuk "Place of Interest" direpresentasikan oleh byte `e2 8c 98`, dan byte +tersebut adalah _encoding_ UTF-8 dari nilai heksadesimal 2318. + +Bergantung pada kebiasaan Anda dengan UTF-8, semua ini tampak jelas atau +membingungkan, namun perlu waktu sebentar untuk menjelaskan bagaimana +representasi UTF-8 dari string dibuat. +Fakta sederhananya adalah: ia dibuat saat sumber kode Go ditulis. + +Sumber kode dalam Go _didefinisikan_ sebagai teks UTF-8; tidak ada +representasi lain yang dibolehkan. +Hal ini menyatakan bahwa saat kita menulis teks berikut dalam sumber kode + +---- +`⌘` +---- + +editor akan menyimpan _encoding_ UTF-8 dari simbol ⌘ ke dalam berkas. +Saat kita mencetak nilai heksadesimal, kita hanya mencetak data yang editor +simpan dalam berkas. + +Sumber kode Go adalah UTF-8, sehingga +_sumber kode untuk literal string yaitu teks UTF-8_. +Jika literal string tersebut tidak berisi urutan "escape" maka string yang +dibuat akan menyimpan persis sumber teks di antara tanda kutip. +Maka secara definisi dan secara konstruksi, string mentah akan selalu berisi +representasi UTF-8 yang valid. +Hal yang sama, sebuah literal string biasa akan selalu berisi UTF-8 yang +valid, kecuali bila ia berisi "escape" UTF-8 seperti bagian sebelumnya. + +Beberapa orang menyangka bahwa string pada Go selalu UTF-8, belum tentu: hanya +literal string yang UTF-8. +Seperti yang kita lihat pada bagian sebelumnya, _nilai_ dari string dapat +berisi beragam nilai byte; _literal_ string selalu berisi teks UTF-8 selama +tidak ada "escape" pada tingkat byte. + +Sebagai kesimpulan, string dapat berisi byte apa pun, namun saat membangun +sebuah literal string, byte-byte tersebut (hampir selalu) UTF-8. + + +== Poin kode, karakter, dan rune + +Sejauh ini kita telah berhati-hati dalam menggunakan kata "byte" dan +"karakter". +Hal ini sebagian karena string menyimpan byte, sebagian lagi karena ide +tentang "karakter" cukup susah didefinisikan. +Standar Unicode menggunakan istilah "poin kode" untuk mengacu pada item yang +direpresentasikan oleh nilai tunggal. +Poin kode U+2318 misalnya, dengan nilai heksadesimal 2318, merepresentasikan +simbol ⌘. +(Untuk informasi lebih lanjut tentang poin kode tersebut, lihat +http://unicode.org/cldr/utility/character.jsp?a=2318[halaman Unicode].) + +Contoh lainnya, poin kode Unicode U+0061 adalah huruf kecil Latin 'A': a. + +Lalu bagaimana dengan huruf kecil 'A' dengan aksen, à? +Itu adalah sebuah karakter, dan juga poin kode (U+00E0), namun ia juga +memiliki representasi lain. +Misalnya kita dapat "menggabungkan" poin kode aksen non-tirus, U+0300, dan +menempelkan ke huruf kecil a, U+0061, untuk membuat karakter yang sama à. +Pada umumnya, sebuah karakter bisa direpresentasikan oleh sejumlah urutan poin +kode yang berbeda. + +Konsep dari karakter dalam komputer menjadi ambigu, atau membingungkan, jadi +kami pakai secara hati-hati. +Supaya hal-hal tersebut lebih dapat digunakan, ada beberapa teknik +_normalisasi_ yang menjamin bahwa sebuah karakter selalu direpresentasikan +oleh poin kode yang sama, namun subjek tersebut membuat kita terlalu jauh dari +topik bahasan yang sekarang. +Blog selanjutnya akan mencoba menjelaskan bagaimana pustaka Go mengatasi +masalah normalisasi ini. + +"Poin kode" terlalu panjang, jadi Go memperkenalkan istilah yang lebih singkat +untuk konsep ini: _rune_. +Istilah ini muncul dalam pustaka dan sumber kode, dan maknanya sama dengan +"poin kode", dengan sebuah tambahan informasi yang menarik. + +Bahasa Go mendefinisikan kata `rune` sebagai alias dari tipe `int32`, sehingga +program jelas kapan sebuah nilai integer merepresentasikan sebuah poin kode. +Lebih lanjut lagi, apa yang Anda bayangkan tentang sebuah konstanta karakter +disebut dengan _konstanta rune_ dalam Go. +Tipe dan nilai dari ekspresi + +---- +'⌘' +---- + +adalah rune dengan nilai integer `0x2318`. + +Sebagai kesimpulan, berikut beberapa poin penting: + +* Sumber kode Go selalu UTF-8. +* Sebuah string menyimpan byte yang beragam. +* Literal string, tanpa "escape" pada tingkat byte, selalu menyimpan seurutan + UTF-8 yang valid. +* Urutan tersebut merepresentasikan poin kode Unicode, yang disebut + dengan rune. +* Tidak ada jaminan dalam Go bahwa karakter dalam string dinormalisasi. + + +== Pengulangan `range` + +Selain aksioma bahwa sumber kode Go adalah UTF-8, hanya ada satu cara dalam Go +yang memperlakukan UTF-8 secara khusus, yaitu saat melakukan pengulangan +`for range` pada sebuah string. + +Kita telah melihat apa yang terjadi dengan pengulangan `for` biasa, +perbedaannya, `for range` men-_decode_ satu rune UTF-8 dalam setiap iterasi. +Di setiap pengulangan, indeks dari pengulangan yaitu posisi rune, yang diukur +dalam byte, dan nilai dari pengulangan yaitu poin kodenya. +Berikut contoh penggunaan format `Printf`, `%#U`, yang memperlihatkan nilai +poin kode Unicode yang mencetak representasinya: + +---- +const nihongo = "日本語" +for index, runeValue := range nihongo { + fmt.Printf("%#U starts at byte position %d\n", runeValue, index) +} +---- + +Keluarannya memperlihatkan bagaimana setiap poin kode memakai beberapa byte: + +---- +U+65E5 '日' starts at byte position 0 +U+672C '本' starts at byte position 3 +U+8A9E '語' starts at byte position 6 +---- + +(Latihan: Taruh seurutan byte UTF-8 yang tidak valid ke dalam string. +Apa yang terjadi pada setiap iterasi pengulangan?) + + +== Pustaka + +Pustaka standar Go menyediakan dukungan untuk memroses teks UTF-8. +Jika pengulangan `for range` tidak cukup, bisa jadi fasilitas yang Anda +butuhkan disediakan oleh sebuah paket dalam pustaka tersebut. + +Paket tersebut adalah +https://golang.org/pkg/unicode/utf8/[`unicode/utf8`], +yang berisi fungsi-fungsi yang membantu untuk memvalidasi, membedah, dan +menggabungkan string-string UTF-8. +Berikut contoh program yang sama dengan `for range` di atas, namun dengan +menggunakan fungsi `DecodeRuneInString` yang ada dalam paket tersebut. +Nilai kembalian dari fungsi tersebut adalah rune dengan ukuran byte UTF-8 yang +di-_encode_. + +---- +const nihongo = "日本語" +for i, w := 0, 0; i < len(nihongo); i += w { + runeValue, width := utf8.DecodeRuneInString(nihongo[i:]) + fmt.Printf("%#U starts at byte position %d\n", runeValue, i) + w = width +} +---- + +Jalankan kode tersebut untuk melihat bahwa ia mencetak keluaran yang sama +dengan pengulangan `for range`. +Pengulang `for range` dan `DecodeRuneInString` didefinisikan menghasilkan +urutan iterasi yang sama. + +Lihat +https://golang.org/pkg/unicode/utf8/[dokumentasi] +paket `unicode/utf8` untuk melihat fasilitas lain yang disediakan paket +tersebut. + +== Kesimpulan + +Untuk menjawab pertanyaan pada bagian awal: String dibangun dari kumpulan byte +sehingga pengindeksan string menghasilkan byte, bukan karaketer. +Sebuah string bisa jadi tidak menyimpan karakter. +Pada kenyataannya, definisi "karakter" itu ambigu dan adalah sebuah kesalahan +untuk mencoba menyelesaikan keambiguan tersebut dengan mendefinisikan bahwa +string terbuat dari kumpulan karakter. + +Ada banyak hal yang dapat dijelaskan tentang Unicode, UTF-8, dan dunia +pemrosesan teks multibahasa, namun ia bisa ditunda sampai artikel selanjutnya. +Untuk saat sekarang, kami berharap Anda lebih paham bagaimana perilaku string +pada Go dan, walaupun ia bisa mengandung beragam byte, UTF-8 ialah bagian +inti dari rancangan string. |
