Parameter dalam kurung siku

Memparametrisasi fungsi menurut dtype dan lebar pada kurung siku compile-time.

Daftar isi

Fungsi pada Mojo dapat memuat parameter yang nilainya ditetapkan saat kompilasi di dalam kurung siku, terpisah dari argumen runtime yang berada di dalam kurung biasa. Topik ini membahas deklarasi parameter lebar dan dtype pada fungsi yang bekerja dengan SIMD, bentuk pemanggilan eksplisitnya, serta parameter tipe generik yang melayani banyak tipe data. Setelah topik ini dibaca, satu fungsi dapat ditulis untuk lebar atau tipe mana pun yang dibutuhkan pemanggilnya.

Parameter compile-time pada kurung siku#

Kurung siku pada deklarasi fungsi menampung parameter compile-time, yaitu nama yang nilainya wajib diketahui compiler sebelum program dijalankan. Kurung biasa setelahnya menampung argumen runtime sebagaimana fungsi pada umumnya. Bentuk def jumlahkan[lebar: Int](v: SIMD[DType.float64, lebar]) -> Float64: memuat satu parameter compile-time bernama lebar bertipe Int dan satu argumen runtime bernama v. Parameter lebar dipakai di dalam tipe argumen tersebut, sehingga setiap nilai lebar menghasilkan argumen dengan tipe yang berbeda.

Compiler menyiapkan spesialisasi, yaitu versi fungsi yang dikompilasi untuk satu kombinasi parameter. Pemanggilan jumlahkan[4] menghasilkan versi dengan v bertipe SIMD[DType.float64, 4], sedangkan pemanggilan jumlahkan[8] menghasilkan versi lain dengan lebar 8. Kedua versi dibangun dari satu body yang sama tanpa duplikasi sumber. Tipe SIMD itu sendiri diparametrisasi dengan cara yang sama, sehingga bentuk kurung siku ini sudah dikenal dari penulisan tipe seperti SIMD[DType.float64, 4].

Parameter compile-time tidak terbatas pada lebar vektor. Contoh pada topik ini memakai Int untuk lebar, DType untuk jenis tipe vektor, dan trait sebagai batasan parameter tipe. Nilai parameternya ditulis pada urutan yang sama dengan deklarasi, sehingga fungsi dengan dua parameter menerima nilai dtype lebih dahulu dan lebar kemudian. Kesalahan urutan maupun jenis nilai diperiksa compiler sebelum program dijalankan, karena daftar parameter selalu diselesaikan pada fase kompilasi.

Memparametrisasi lebar dan dtype#

Program berikut memuat dua fungsi berparameter. Fungsi jumlahkan hanya berparameter lebar untuk tipe float64, sedangkan fungsi identitas berparameter dtype dan lebar sekaligus sehingga dapat menerima vektor tipe apa pun.

mojo parameter.mojo
def jumlahkan[lebar: Int](v: SIMD[DType.float64, lebar]) -> Float64:
    return v.reduce_add()

def identitas[dtype: DType, lebar: Int](v: SIMD[dtype, lebar]) -> SIMD[dtype, lebar]:
    return v

def main():
    var v4 = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0)
    var v8 = SIMD[DType.float64, 8](0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5)
    print(jumlahkan[4](v4))
    print(jumlahkan[8](v8))
    var g = identitas[DType.float32, 2](SIMD[DType.float32, 2](1.5, 2.5))
    print(g.reduce_add())
bash
# jalankan program parameter lebar dan dtype
mojo run parameter.mojo
text Keluaran
10.0
32.0
4.0

Pemanggilan jumlahkan[4](v4) dan jumlahkan[8](v8) menetapkan lebar pada kurung siku, dan kedua panggilan itu membangun dua spesialisasi dari fungsi yang sama. Keluaran 10.0 dan 32.0 masing-masing berasal dari penjumlahan seluruh lane kedua vektor. Fungsi identitas menuliskan parameter dtype sebelum lebar, dan urutan inilah yang diikuti saat pemanggilan eksplisitnya, yaitu identitas[DType.float32, 2]. Nilai kembaliannya bertipe SIMD[dtype, lebar], sehingga penjumlahan yang menghasilkan keluaran 4.0 dihitung dalam tipe float32.

Parameter tipe generik#

Parameter kurung siku juga dapat menampung tipe secara langsung, bukan hanya angka. Deklarasi def pertama[T: ImplicitlyCopyable](v: T) -> T: membuat T menjadi parameter tipe, yaitu nama tipe yang ditetapkan pemanggil. Batasan ImplicitlyCopyable menuntut tipe yang mengisi T dapat disalin secara implisit, dan batasan itu wajib karena body fungsi mengembalikan v, sebuah tindakan yang menyalin nilai parameternya. Program berikut memanggil fungsi tersebut pada empat tipe berbeda.

mojo tipe-generik.mojo
def pertama[T: ImplicitlyCopyable](v: T) -> T:
    return v

def main():
    print(pertama(42))
    print(pertama[Int](7))
    print(pertama("teks"))
    print(pertama[Float64](2.5))
bash
# jalankan program parameter tipe generik
mojo run tipe-generik.mojo
text Keluaran
42
7
teks
2.5

Pemanggilan pertama(42) menyimpulkan T sebagai Int langsung dari argumennya, sedangkan pertama[Int](7) menuliskan tipe itu secara eksplisit. Kedua bentuk sah dan menghasilkan keluaran yang sama. Penyimpulan ini berhasil karena T muncul langsung sebagai tipe argumen, sedangkan lebar pada fungsi sebelumnya tersembunyi di dalam SIMD[DType.float64, lebar] sehingga wajib ditulis pada kurung siku. Keluaran teks menunjukkan String juga dapat mengisi T, dan 2.5 menunjukkan hal yang sama untuk Float64.

Setiap pemanggilan dengan tipe yang berbeda juga membangun spesialisasi tersendiri. Fungsi pertama pada program di atas dikompilasi untuk Int, untuk String, dan untuk Float64, dan ketiganya berbagi satu body sumber. Batasan pada parameter tipe menentukan operasi yang sah di dalam body, sehingga body yang menyalin nilai menuntut tipe yang dapat disalin. Pemanggilan dengan tipe yang melanggar batasan tersebut ditolak compiler pada waktu kompilasi.

Kesalahan umum#

  • Lupa kurung siku saat memanggil fungsi berparameter lebar. Pemanggilan jumlahkan(v4) ditolak dengan pesan invalid call to 'jumlahkan': value passed to 'v' cannot be converted from 'SIMD[.float64, 4]' to 'SIMD[.float64, lebar]', it depends on an unresolved parameter 'lebar'. Perbaikannya adalah menuliskan nilai parameternya, yaitu jumlahkan[4](v4).
  • Membatasi parameter tipe dengan AnyType pada fungsi yang mengembalikan nilai parameternya. Definisi def pertama[T: AnyType](v: T) -> T ditolak dengan pesan value of type 'T' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable' karena pengembalian nilai menyalin nilai itu. Perbaikannya adalah batasan ImplicitlyCopyable yang menjamin salinan tersebut sah.
  • Menaruh parameter lebar di dalam kurung biasa. Definisi def jumlahkan(lebar: Int) yang kemudian menuliskan SIMD[DType.float64, lebar] ditolak dengan pesan cannot use a dynamic value in a parameter list karena lebar dianggap nilai runtime. Perbaikannya adalah memindahkan parameternya ke kurung siku pada deklarasi fungsi.

Ringkasan#

  • Kurung siku pada deklarasi fungsi menampung parameter compile-time, sedangkan kurung biasa menampung argumen runtime.
  • Parameter lebar dan dtype dipanggil secara eksplisit, misalnya jumlahkan[4](v4), dan setiap nilai menghasilkan spesialisasi tersendiri.
  • Parameter tipe T dengan batasan trait dapat disimpulkan dari argumen atau ditulis eksplisit pada kurung siku.
  • Batasan ImplicitlyCopyable dibutuhkan ketika body fungsi menyalin nilai parameternya.

Referensi#