1 poin oleh GN⁺ 2025-01-08 | 1 komentar | Bagikan ke WhatsApp
  • Metaprogramming waktu kompilasi (comptime) Zig dapat dibaca seperti kode runtime, tetapi juga dapat menghasilkan kode eksekusi seefisien fungsi C yang ditulis manual untuk kode berulang seperti penjumlahan field struct
  • Alih-alih sintaks generik terpisah, penggunaan comptime T: type dan anytype untuk menangani pemrograman generik adalah penggunaan utama Zig comptime
  • Runtime, comptime, dan sistem build berbagi bahasa Zig yang sama, sehingga fungsi seperti Fizz Buzz pun dapat dijalankan saat kompilasi dan dibuat menjadi array yang sudah dihitung sebelumnya
  • Compiler terlebih dahulu mengevaluasi bagian yang memungkinkan, lalu hanya menyisakan bagian yang membutuhkan nilai runtime sebagai bytecode keluaran yang nantinya menjadi machine code
  • Zig comptime dapat menyelesaikan masalah yang mirip dengan pembuatan kode berbasis teks, tetapi tidak mengganti keyword secara arbitrer seperti macro C, sehingga mengurangi beban membaca dan debugging kode

Cara membaca Zig comptime

  • Comptime Zig adalah fitur metaprogramming, tetapi saat membaca perilaku akhirnya, kita tidak perlu terlalu memikirkan perbedaan antara waktu kompilasi dan runtime
  • Butir ketiga dari zig zen, “Favor reading code over writing code”, menjadi tolok ukur penting
    • Metaprogramming berbasis macro atau pembuatan kode dapat menciptakan dua lapisan: kode sumber dan kode yang diperluas
    • Lapisan tidak langsung seperti ini membuat membaca kode, debugging, dan mengubah perilaku menjadi sulit
  • Kode runtime yang menjumlahkan array [3]i64 = .{1,2,3} dibandingkan berdampingan dengan kode comptime yang menjumlahkan field a, b, c dari struct MyStruct
    • inline for (comptime std.meta.fieldNames(MyStruct)) digunakan untuk mengiterasi nama field
    • @field(my_struct, field_name) digunakan untuk mengakses field memakai nama field yang diketahui pada waktu kompilasi
  • Contoh penjumlahan field struct terlihat seperti reflection runtime, tetapi di executable yang tersisa adalah kode seefisien fungsi penjumlahan untuk tipe struct tersebut yang ditulis manual
  • Proses transformasi pada contoh lebih merupakan penjelasan konseptual, bukan reproduksi persis dari implementasi Zig
  • Tim inti Zig sedang mengerjakan debugger yang dapat mengeksekusi bertahap kode campuran comptime dan runtime seperti pada contoh

Menangani generik dengan comptime

  • Zig tidak memiliki fitur generik terpisah
  • Untuk membuat tipe generik, tulis fungsi yang menerima tipe dan mengembalikan tipe
    • Contoh: pub fn GenericMyStruct(comptime T: type) type
    • Field a, b, c pada struct yang dikembalikan semuanya menggunakan tipe T
  • Fungsi generik juga dapat ditulis dengan cara yang sama
    • Contoh: fn quadratic(comptime T: type, a: T, b: T, c: T, x: T) T
    • Argumen tipe dapat dinyatakan eksplisit seperti quadratic(f32, ...) dan quadratic(i64, ...)
  • Jika tipe argumen tidak penting bagi bagian lain dari signature fungsi, tipe khusus anytype dapat digunakan untuk inferensi
  • Cara ini saja tidak dapat menjelaskan keseluruhan comptime, tetapi menjadi titik awal untuk memahami pekerjaan generik yang umum di Zig

Menjalankan kode Zig yang sama pada waktu kompilasi

  • Zig menggunakan bahasa yang sama untuk runtime, comptime, dan sistem build
  • Contoh Fizz Buzz terlebih dahulu menulis fungsi fizzBuzz(writer) sebagai fungsi output runtime biasa
    • Mengiterasi dari 1 sampai 100
    • Mencetak fizzbuzz untuk kelipatan 3 dan 5, fizz untuk kelipatan 3, buzz untuk kelipatan 5, dan angka untuk sisanya
  • Jika fungsi fizzBuzz yang sama dijalankan di dalam blok comptime, seluruh output dapat dihitung sebelumnya
    • Pertama menghitung jumlah byte yang dibutuhkan dengan std.io.countingWriter(std.io.null_writer)
    • Membuat array buffer: [cw.bytes_written]u8 dengan panjang yang sudah dihitung
    • Mencetak kembali ke std.io.fixedBufferStream(&buffer) untuk membuat nilai full_fizzbuzz
  • Jika hanya bagian inti yang diukur, versi yang sudah dihitung sebelumnya berjalan sekitar 9 kali lebih cepat
    • Contohnya sendiri kecil, sehingga waktu eksekusi total lebih banyak dipengaruhi faktor lain
  • Comptime dan runtime memiliki perbedaan pada nilai dan fitur yang dapat diakses
    • Nilai comptime_int, comptime_float, dan type hanya dapat diakses di comptime
    • Sebagian fungsi hanya menerima argumen comptime sehingga pada praktiknya khusus comptime
    • System call atau fitur yang menggunakannya hanya dapat diakses saat runtime
  • Kode yang tidak menggunakan fitur khusus seperti ini berperilaku sama di comptime dan runtime

Memahami comptime sebagai evaluasi parsial

  • Comptime dapat dipandang sebagai evaluasi parsial (partial evaluation) yang terjadi selama proses kompilasi
  • Evaluasi parsial adalah cara ketika hanya sebagian argumen diberikan kepada fungsi: ekspresi yang hanya memakai nilai yang sudah diketahui diganti dan dihitung terlebih dahulu, lalu fungsi baru dibuat untuk menerima argumen tersisa yang belum diketahui
  • Contoh penjumlahan array menunjukkan proses evaluasi secara bertahap
    • Loop for dipecah menjadi pernyataan individual untuk setiap iterasi
    • array[0], array[1], array[2] masing-masing diganti menjadi 1, 2, 3
    • sum diperbarui dari 0 → 1 → 3 → 6
    • Pada akhirnya disederhanakan hingga bentuk std.debug.print(..., .{6})
  • Contoh penjumlahan field struct juga dapat dibaca dengan cara yang sama
    • Karena inline for dijalankan di comptime, ia dibentangkan menjadi pernyataan individual untuk field "a", "b", "c"
    • @field(my_struct, "a") berubah menjadi my_struct.a
    • Fungsi akhirnya memiliki bentuk langsung sum += my_struct.a, sum += my_struct.b, sum += my_struct.c
  • Cara ini mengekspresikan maksud langsung di dalam kode, sekaligus mengurangi kebutuhan meninggalkan komentar agar fungsi penjumlahan diperbarui manual ketika field berubah

Evaluasi waktu kompilasi dan emisi kode runtime

  • Zig comptime juga dapat dilihat sebagai kombinasi evaluasi waktu kompilasi dan emisi kode runtime
  • Kode input dieksekusi oleh compiler
    • Pernyataan yang dapat diketahui pada waktu kompilasi langsung dievaluasi
    • Pernyataan yang membutuhkan nilai runtime ditambahkan ke kode output
  • Sudut pandang ini paling dekat dengan cara kerja nyata compiler Zig
    • Zig terlebih dahulu mem-parse sintaks
    • Kode diubah menjadi bytecode untuk virtual machine
    • VM mengevaluasi bagian yang memungkinkan, dan bagian yang perlu diproses saat runtime dipancarkan sebagai bytecode baru
    • Bytecode baru kemudian diubah menjadi machine code
  • Conditional seperti if yang memiliki input runtime memancarkan kedua jalur
  • Dead code tidak menjalani analisis semantik
    • Bahkan jika menulis fungsi yang salah, error kompilasi mungkin tidak muncul sampai fungsi itu benar-benar digunakan
    • Karakteristik ini membuat kompilasi lebih efisien dan memungkinkan kompilasi kondisional yang alami tanpa #ifdef
  • Nama tipe di Zig juga merupakan ekspresi yang mengevaluasi nilai type di comptime
    • Nama tipe yang tampak sederhana, seperti tipe argumen fungsi, juga merupakan hasil evaluasi comptime
    • Karakteristik ini memungkinkan contoh generik sebelumnya
    • Jika perlu, tipe dapat dihitung dengan ekspresi yang lebih kompleks
  • Analisis statis Zig lebih kompleks dibanding banyak bahasa statically typed lain
    • Untuk mengetahui semua tipe, sebagian besar compiler harus dijalankan
    • Sampai tooling berhasil mengejar, fitur editor seperti code completion tidak selalu bekerja dengan baik

Hubungan dengan pembuatan kode berbasis teks

  • Bentuk Zig comptime berbeda dari pembuatan kode berbasis teks, tetapi dapat menyelesaikan masalah serupa
  • Fungsi pembuat kode writeSumFn mencetak source code fungsi sumFields dengan writer.print
    • Kode yang dijalankan di generator berpadanan dengan bagian comptime Zig
    • Kode yang dicetak oleh generator berpadanan dengan kode runtime
  • Versi comptime Zig mengekspresikan logika yang sama secara lebih langsung
    • Mendapatkan nama field dengan std.meta.fieldNames(MyStruct)
    • Menyusun kode akses field dengan inline for dan @field
  • Pembuatan kode yang menggunakan informasi tipe sebagai input menjadi lebih sederhana di Zig
    • Pendekatan pembuatan teks harus menyelesaikan secara terpisah dari mana mendapatkan informasi nama tipe dan nama field
    • Zig dapat menangani tipe itu sendiri dan informasi field secara langsung di comptime
  • Jika menggunakan input seperti spesifikasi eksternal, isi file dapat disertakan dengan @embedFile lalu di-parse seperti biasa
  • Dalam contoh struct generik, comptime menangani referensi nama tipe lebih langsung daripada pembuatan kode teks
    • Pembuatan teks harus merangkai nama seperti MyStruct_{s} sebagai string dan menjaganya tetap konsisten
    • Comptime menggunakan fungsi secara langsung seperti GenericMyStruct(T)
  • Ada juga pengecualian
    • Tipe yang nama field-nya ditentukan di comptime dapat dibuat
    • Dalam kasus ini, fungsi bawaan harus dipanggil dengan spesifikasi yang berisi daftar definisi field
    • Deklarasi seperti method tidak dapat didefinisikan pada tipe seperti itu
    • Ini tidak membatasi daya ekspresi kode itu sendiri, tetapi membatasi bentuk API yang dapat diekspos ke kode lain
  • Sebagian besar pekerjaan masuk akal yang mungkin dilakukan dengan macro teks seperti di C juga mungkin dilakukan dengan comptime, tetapi bentuk kodenya bisa sangat berbeda
  • Zig tidak menyediakan fitur untuk mengganti keyword dengan nama lain seperti macro teks

Bacaan lanjutan dan contoh dari standard library

  • Zig sendiri bukan bahasa yang hanya bergantung pada comptime, dan Anda dapat mempelajari lebih lanjut di situs web resmi
  • Referensi bahasa membahas fitur konkret comptime
  • Standard library Zig memiliki contoh penggunaan comptime
    • Fungsi format yang digunakan std.debug.print adalah fungsi generik yang kuat
      • Banyak bahasa mem-parse format string saat runtime dan dapat menambahkan validator terpisah untuk mendeteksi error lebih awal
      • Zig mem-parse format string di comptime untuk membuat kode output yang efisien dan melakukan semua validasi saat kompilasi
    • ArrayList adalah container generik yang relatif sederhana tetapi berfitur lengkap
    • Fungsi main Zig dapat memiliki beberapa tipe return, dan ini tidak ditangani oleh magic compiler, melainkan oleh kode comptime biasa

1 komentar

 
GN⁺ 2025-01-08
Opini Hacker News
  • Saya berharap isu-isu yang sudah ditemukan dalam pemrograman waktu kompilasi dibahas lebih mendalam, bukan dipuji begitu saja tanpa kritik
    Pemrograman bertahap bukan hal baru, dan keluarga Lisp pun telah melakukan hal serupa selama puluhan tahun, tetapi dalam prosesnya banyak kompromi desain dan masalah terungkap. Misalnya, generics dengan cara seperti ini merusak parametricity, sehingga sulit melakukan inferensi hanya dari signature tipe sebuah fungsi. Juga belum jelas bagaimana Zig menangani tipe generik rekursif, dan kapan serta dalam urutan apa pemeriksaan tipe dan komputasi waktu kompilasi saling berinteraksi adalah pilihan penting. Tulisan itu mengatakan kode waktu kompilasi dapat menghasilkan bukan hanya nilai tetapi juga kode, tetapi tidak membahas hygiene. Untuk pembahasan terkait, https://typesanitizer.com/blog/zig-generics.html bagus

    • Saya cukup menyukai Zig dan telah mengikutinya selama beberapa tahun sambil sesekali memakainya, tetapi comptime terutama bersinar untuk kasus seperti generics, inisialisasi waktu kompilasi atas struktur data kompleks, dan pembuatan kode per target
      Namun dalam situasi lain, ketika melihat comptime di kode Zig, kadang saya merasa “ah…”. Seperti macro Lisp, fitur ini mudah dipakai untuk menghindari masalah yang sebenarnya tidak ada, atau yang akan hilang jika struktur kode ditata lebih baik. Contoh di artikel asli yang mengiterasi field struct lalu menjumlahkan nilainya memang tipikal cara orang memakai comptime, tetapi dalam banyak kasus lebih baik memakai struktur data yang benar-benar bisa diiterasi, seperti std.enums.EnumArray
    • Saya penasaran, yang dimaksud parametricity rusak itu pada level programmer praktis memahami perilaku fungsi, atau pada level inferensi dalam sistem teori tipe seperti kalkulus lambda bertipe?
      Secara teoritis kekhawatirannya jelas ada, tetapi seberapa penting itu dalam bahasa praktis adalah perkara terpisah. Template C++ juga merusak parametricity melalui spesialisasi template, tetapi dalam praktik biasanya bukan masalah besar dan malah memungkinkan optimisasi. Misalnya implementasi seperti std::vector<bool> yang benar-benar menyimpan tiap elemen sebagai 1 bit hanya mungkin dengan fleksibilitas seperti ini
    • Sebagai penulis, tulisan ini dibuat untuk merapikan penjelasan setelah saya gagal menjelaskan dengan baik di sebuah pertemuan, jadi fokusnya memang pada penjelasan ketimbang kritik
      Menurut saya comptime ala Zig adalah kompromi langsung antara kemampuan inferensi vs daya ekspresi dibanding signature fungsi yang didefinisikan lebih statis. Itu hanya memengaruhi kode yang melakukan inferensi semacam itu lewat tipe, dan merupakan bagian yang bisa dipilih sesuai kebutuhan. Bahkan ketika signature tipe saja tidak cukup, biasanya setelah membaca beberapa puluh baris source, cara pakainya menjadi jelas, dan saya tidak pernah bingung lama. Untuk contoh tipe generik rekursif, jika memakai nama yang sedang dideklarasikan akan muncul “dependency loop detected”, tetapi ada cara mengakalinya, dan contoh generik di tulisan itu juga merujuk dirinya sendiri. Pemeriksaan tipe terjadi selama comptime; misalnya @compileLog("Hi") pertama akan dieksekusi, tetapi jika setelah itu terjadi error tipe pada const a: u32 = "42";, eksekusi tidak sampai ke log kedua. Masalah halus bisa muncul pada pemeriksaan tipe antara comptime dan runtime, tetapi biasanya hanya terlihat pada kode yang cukup rumit dan mudah diselesaikan; tim inti juga mengetahuinya, jadi saya berharap akan ditangani sebelum 1.0. Apa persisnya yang dimaksud dengan hygiene masih perlu dijelaskan lebih lanjut
    • Bisa melakukan inferensi hanya dari deklarasi fungsi adalah hal yang terpisah dari eksekusi waktu kompilasi
      Jika sebuah fungsi punya tujuan logis yang jelas serta nama dan nama parameter yang baik, deklarasinya saja sudah bisa cukup untuk inferensi. Bagian-bagian yang bisa ditentukan programmer seperti label parameter dan nama tipe juga bisa dianggap sebagai bagian dari nama. Saya setuju dengan kesimpulan pada judul artikel yang ditautkan, tetapi tulisannya terasa lebih seperti tulisan tim debat yang ingin mencetak poin daripada argumen yang bermakna. Kerangka yang lebih baik adalah fleksibilitas vs kompleksitas. Sistem generik yang tetap, jika dirancang baik, lebih sederhana daripada yang dapat diprogram, tetapi kurang fleksibel. Kelebihan Zig adalah bahasa metaprogramming-nya hampir sama dengan bahasa umumnya, dan bahasa umum itu sendiri juga sederhana, sehingga biaya kompleksitas tambahan berkurang. Namun karena kode waktu kompilasi dan kode runtime bercampur serta petunjuknya sedikit, muncul kompleksitas yang membuat programmer sulit membedakan apa yang dieksekusi kapan. Mungkin menarik jika language server atau plugin editor menyediakan “comptime shader” yang memberi warna latar berbeda pada kode comptime
    • Agak mengecewakan bahwa topik ini dibahas berdasarkan demo 15 menit dan kurang memahami kompromi yang sebenarnya. Tulisan Varun Gandhi yang ditautkan itu luar biasa
      Dari pengalaman dengan Rust, banyak hal yang ingin dilakukan orang dengan const generics tampaknya akan lebih mudah jika ada fitur seperti comptime. Mengizinkan aritmetika pada const generics sambil mempertahankan parametricity sulit diimplementasikan, dan jika yang sebenarnya diinginkan hanya semacam “trait fungsi hash dengan ukuran output N”, mungkin tidak masalah mengorbankan parametricity untuk tujuan itu dan menjadikan trait pada N sebagai tahap pembuatan kode sebelumnya. Namun macro Rust terlalu fleksibel dan merepotkan untuk dipakai seperti itu. Meski begitu, begitu polimorfisme parametrik diganti dengan fitur pembuatan kode yang naif, jalan penuh kesulitan pun terbuka
  • Di D, ini sudah ada sejak 17 tahun lalu. Fitur D terus berpindah ke bahasa lain
    Zig menandai blok yang akan dijalankan saat kompilasi dengan kata kunci comptime, tetapi di D, yang menentukan eksekusi bukan kata kunci, melainkan apakah itu ekspresi konstanta. Misalnya, int s = sum(3, 4); dijalankan saat runtime, sedangkan enum e = sum(3, 4); dijalankan pada waktu kompilasi. Jika menghindari global non-konstan, input/output, dan pemanggilan fungsi sistem seperti malloc(), cukup banyak fungsi bisa dijalankan pada waktu kompilasi tanpa perubahan. Berkat manajemen memori otomatis D, alokasi memori juga dimungkinkan

    • Salah satu pemanfaatan yang saya sukai adalah pembuatan tabel statis
      Dulu, untuk membuat tabel statis, orang menulis program terpisah, tetapi dengan eksekusi fungsi pada waktu kompilasi, itu tidak lagi diperlukan. Nilai inisialisasi array tytab berasal dari lambda yang menghitung dan mengembalikan array. Contoh lengkap ada di https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba.... Kegunaan umum CTFE lainnya adalah membuat DSL
    • Saya tidak ingin melewatkan kesempatan untuk mengucapkan terima kasih karena telah membuat D. Namun saya penasaran dengan batasan eksekusi waktu kompilasi
      Jika mesin tempat compiler berjalan berbeda dari mesin tempat program akan dijalankan, bagaimana compiler D menjamin kebenarannya? Misalnya, saya penasaran bagaimana compiler tahu bahwa int s = sum(100000, 1000000) akan bernilai sama di semua mesin x86. Bisa saja ada perbedaan halus antar-generasi CPU; bagaimana menjamin bahwa perhitungan yang benar-benar dilakukan di host akan memberikan hasil yang sama di mesin target, atau apakah diasumsikan bahwa jika arsitekturnya sama maka host dan target cukup mirip?
    • ImportC milik D juga bisa melakukan CTFE dengan kode C
      Dengan int sum(int a, int b) { return a + b; }, bisa dipakai seperti _Static_assert(sum(3, 4) == 7, "look ma, check at compile time!");. Saya tidak tahu mengapa standar C tidak menambahkan ini, dan ini bekerja dengan baik
    • Agar adil, Zig juga bisa memanggil fungsi yang sama baik dalam konteks runtime maupun konteks comptime
      square(2) adalah pemanggilan runtime, sedangkan comptime square(3) adalah pemanggilan pada waktu kompilasi. Pemanggilan comptime akan menghasilkan error kompilasi jika ada sesuatu yang tidak kompatibel dengan waktu kompilasi, dan saya menganggap ini fitur penting. Sebab ini menjadi sinyal peringatan ketika kode yang diharapkan berjalan pada waktu kompilasi secara tidak sengaja terdorong menjadi evaluasi runtime karena perubahan argumen input
  • Zig terlihat menarik, tetapi saya berharap ada operator overloading
    Sebagian besar argumen yang menentang operator overloading tidak terlalu meyakinkan. Klaim bahwa kita tidak tahu apa yang sebenarnya terjadi di balik layar tidak cukup, karena fungsi bernama add pun bisa dibuat melakukan perkalian. iostreams atau boost::spirit di C++ sering dijadikan contoh penyalahgunaan, tetapi hal seperti itu tidak banyak terlihat di bahasa lain yang memiliki operator overloading, dan tampaknya lebih merupakan masalah khas C++

    • Solusi ala OCaml sepertinya cocok untuk penggunaan Zig
      Di OCaml, operator bisa didefinisikan ulang, tetapi hanya di dalam konteks modul lain. Misalnya, jika + didefinisikan ulang di modul Vec3, bisa ditulis Vec3.(a + b + c + d) atau let open Vec3 in a + b + c + d. Saat membaca source, tidak ada pertanyaan “+ ini berasal dari mana?”, dan ini jauh lebih baik daripada a.add(b).add(c).add(d). Namun Zig sudah mulai mengeras, jadi perubahan besar untuk menyelesaikan masalah seperti ini tampaknya sulit masuk
    • Orang mungkin tidak tahu seberapa banyak keajaiban yang berjalan di balik fungsi-fungsi __ Python dan PHP
      Pendekatan Zig terasa segar, dan kemampuan untuk menelusuri kode lebih penting daripada beberapa detik yang dibutuhkan untuk mengetik beberapa karakter tambahan
    • Saya rasa banyak alasan orang tidak suka operator overloading berkaitan dengan performa runtime yang tak terduga
      Solusi ideal adalah bahasa menyediakan operator buatan pengguna yang secara jelas menunjukkan bahwa itu overloading. Misalnya, dengan bentuk prefiks/sufiks seperti let c = a |+| b, orang yang membaca kode bisa tahu bahwa operasi |+| sebenarnya adalah pemanggilan fungsi. Ini tetap bisa disalahgunakan, tetapi dapat mengurangi salah satu kekhawatiran utama
    • Di C++, saya hanya memakai operator overloading untuk matematika vektor/matriks, dan dalam kasus itu memang sangat berguna
      Jika bahasa menyediakan sintaks matematika vektor secara langsung seperti bahasa shading, itu sepertinya baik-baik saja. Zig setidaknya memiliki tipe @Vector() yang agak mirip dengan Vector Extension milik Clang, tetapi sayangnya bukan Extended Vector Extension. Lihat https://ziglang.org/documentation/master/#Vector dan https://clang.llvm.org/docs/LanguageExtensions.html#vectors-...
    • Mungkin operator aljabar linear dasar untuk array angka sebaiknya langsung dibangun ke dalam bahasa, bukan melalui overloading
      Saya tidak begitu tahu apakah proposal seperti itu sudah ada
  • fieldNames terlihat sangat mirip dengan fieldPairs di Nim, dan merupakan konstruksi yang luar biasa praktis
    Ini membuat serialisasi yang efisien menjadi sangat mudah, dan belakangan saya memakai fieldPairs untuk mengimplementasikan pemeriksaan thread-safety pada waktu kompilasi untuk tipe dalam sekitar 20 baris. Saya rasa fitur seperti ini seharusnya menjadi fitur standar bahasa pemrograman. Salah satu hal yang paling saya rindukan di Rust ada di sini, dan terakhir kali saya mencobanya, itu terbatas pada macro tanpa tipe. Terlalu banyak batasannya. Kita hanya bisa berharap struct dari crate sudah mengimplementasikan serde, dan tidak bisa membuat sendiri secara programatis struct dengan field yang sama

    • Dulu pernah ada diskusi tentang refleksi waktu kompilasi, dan mungkin fitur seperti ini juga bisa tercakup, tetapi tampaknya menghilang bersama drama terkait
      Cukup disayangkan. Hal-hal seperti serde akan jauh lebih mudah diimplementasikan jika ada refleksi waktu kompilasi
    • Dengan refleksi waktu kompilasi, kita bisa membuat framework seperti ORM atau web framework
      Satu-satunya kompromi adalah library seperti itu harus disertakan dalam bentuk kode sumber
  • Setelah pernah menulis pustaka parser C yang sudah cukup lengkap, saya jadi tidak begitu yakin dengan klaim bahwa bahasa itu sendiri harus punya metaprogramming
    Kalau ingin menghasilkan struct, serialisasi, properti, instrumentasi, dan sebagainya, cukup tulis program C biasa yang memproses file sumber lalu mengeluarkan file sumber, dan jalankan dulu dari skrip build. Saya penasaran bagaimana program-program meta seperti ini di-debug dan diuji. Punya saya adalah program C biasa, jadi memakai debugger dan alat yang sama seperti kode lain

    • Justru pekerjaan seperti itulah yang tidak ingin dilakukan orang
    • C#, lebih tepatnya Roslyn/dotnet, menyediakan ini dengan cukup baik
      Karena kompiler itu sendiri ditulis dalam bahasa yang sama, kita bisa memasukkan plugin yang mengakses AST secara read-only dan mengeluarkan sumber C#. Untuk debugging, perlu sedikit usaha tambahan untuk membuat framework pengujian yang bagus, tetapi setelah itu kita bisa menjalankan kompiler yang dipasangi plugin di dalam framework unit test standar dan melihatnya dengan debugger interaktif
    • Ini sama dengan pendekatan yang didukung Ryan Fleury dan lainnya, dan merupakan cara yang cukup baik
      “Eksekusi arbitrer pada waktu kompilasi di C: cl /nologo /Zi metaprogram.c && metaprogram.exe, cl /nologo /Zi program.c. Kode waktu kompilasi berjalan pada kecepatan native, bisa di-debug, sepenuhnya prosedural dan arbitrer. Kompiler tidak perlu menjalankan kode untuk Anda.” https://x.com/ryanjfleury/status/1875824288487571873
    • Saya tidak terlalu tahu Zig, tetapi kekuatan Lisp ada pada S-expression, dengan kata lain memanipulasi AST
      Untuk melakukan itu di C, Anda harus menulis parser C lengkap untuk program C yang memproses file sumber
    • Dulu saya pernah melakukan hal serupa di Python bersama numba JIT
      Saya menulis kode Python yang menghasilkan kode Python, lalu membuatnya dikompilasi lagi. Ini rapuh dan merupakan kekacauan yang mengerikan, dan kebutuhan melakukan hal semacam ini adalah salah satu alasan besar saya meninggalkan Python. Ini mirip seperti bertanya kenapa tidak meneruskan semua argumen fungsi sebagai string saja. Ada orang yang memakai kode berbasis string, tetapi itu seharusnya jarang, dan bahasa harus menyediakan cara untuk menghindarinya
  • Salah satu pola yang menarik adalah bisa membuat struct pada waktu kompilasi
    Saya pernah bereksperimen membuat file JSON di PyTorch, membacanya dengan @embedFile di Zig, lalu membuat struct yang memiliki metode run tertentu untuk mengimplementasikan jaringan neural. Secara teori, ini memungkinkan kompiler mengoptimalkan jaringan neural secara langsung. Saya belum berhasil membuktikan manfaat besar, tetapi seluruh jaringan juga berada di stack sehingga tidak ada alokasi dinamis. Saya belum yakin apakah itu bagus

    • Hal seperti ini pernah saya lakukan dengan menulis generator kode memakai Python, bukan comptime
      Saya tidak yakin comptime Zig akan sangat cepat, dan saya juga tidak ingin menjalankan parser JSON yang menghasilkan struct setiap kali
    • Saya penasaran apa dampaknya terhadap waktu kompilasi
  • Ini menarik, tetapi dalam kasus seperti ini saya cenderung setuju dengan komentar semacam “D melakukannya lebih dulu”

    • Benar, dan itu dilakukan secara higienis, bukan dengan cara preprocessor
  • Kalau Anda terkesan dengan comptime milik Zig, Nim yang memiliki evaluasi kode pada waktu kompilasi dan sistem makro AST penuh juga sangat layak dilihat

    • Nim adalah bahasa yang menyenangkan, tetapi sepertinya saya tidak akan mempertimbangkannya untuk pekerjaan “serius”
      Ada masalah ekosistem seperti bahasa niche lain, ditambah maintainer yang menimbulkan pro-kontra, kontributor inti yang tampaknya tidak bertahan lama, serta pendanaan utama dari perusahaan kripto. Sepuluh tahun lalu hal-hal seperti ini mungkin tidak mengganggu saya, tetapi sekarang berbeda
    • Zig punya kelebihan berupa tidak adanya exception
      Nim juga tampaknya berusaha menjauh dari exception, tetapi exception mencemari fungsi dan membuat kita tetap harus memikirkannya meskipun tidak memakai fungsi yang melempar exception[1]. Hidup terlalu singkat untuk berurusan dengan alur kontrol yang tidak terlihat. [1]: https://github.com/status-im/nim-stew
  • Zig secara keseluruhan adalah bahasa yang cukup bagus dan melakukan apa yang dibutuhkan
    Sangat penting untuk tidak keluar dari tujuannya, dan karena itu saya kurang suka beberapa bahasa dipakai di mana-mana hanya karena kebetulan sudah dipakai

  • Saya berharap ada sesuatu yang menggabungkan kemampuan metaprogramming Zig dengan ekosistem, komunitas, dan keamanan Rust yang sangat besar
    Dari sisi desain bahasa saja, saya jauh lebih suka Zig daripada Rust, tetapi sebagai programmer hobi yang belum mahir, setidaknya untuk saat ini saya tidak percaya diri bisa menulis sesuatu yang benar-benar berguna atau dapat diandalkan dengan Zig

    • Sebagai orang yang terbiasa dengan Rust, saya sempat mencoba Zig sebentar lalu cepat menyerah
      Karena kompiler tidak membantu menemukan masalah-masalah semacam itu pada waktu kompilasi. Saya tahu Zig memang tidak menjanjikan hal itu, tetapi bagi saya itu kekurangan yang menentukan, jadi sepertinya Zig bukan bahasa yang cocok untuk saya. Di sisi lain, konsep comptime lebih saya sukai daripada macro Rust
    • Saya berharap komunitas Rust menjauh dari Zig. Bercanda, tetapi sebagian besar saya serius