cara mendefinisikan fungsi higher-order di rust

Cara Mendefinisikan Fungsi higher-order di Rust

Pada artikel ini, kita akan menyelidiki bagaimana Rust mendukung fungsi-fungsi higher-order dan bagaimana kita dapat mendefinisikannya.

Higher-order functions (HOF) adalah fungsi yang parameter dan nilai kembalinya adalah fungsi itu sendiri. Dengan kata lain, jika sebuah bahasa mendukung fungsi higher-order, maka kita mengatakan bahwa fungsi-fungsi ini adalah warga negara kelas satu, yaitu nilai.

Fungsi-Fungsi dalam Rust

Kita dapat mendefinisikan fungsi dalam Rust melalui kata kunci fn. Seperti biasa, untuk mendefinisikan sebuah fungsi kita harus menentukan nama, parameter, dan tipe nilai yang dikembalikan:

fn plus_one(n: i32) -> i32 {
    n + 1
}

Kata kunci return bersifat opsional. Jika kita tidak menentukannya, pernyataan terakhir dari fungsi dianggap sebagai pernyataan return.

Seperti yang telah kami katakan sebelumnya, fungsi dalam Rust adalah warga kelas satu. Oleh karena itu, kita dapat menyimpannya dalam sebuah variabel. Setelah disimpan dalam sebuah variabel, kita dapat memanggilnya seperti biasa:

fn main() {
    let add_one = plus_one;

    println!("{}", add_one(1));
}

Fungsi Sebagai Parameter

Pada bagian di atas, kami telah mendemonstrasikan cara mendefinisikan fungsi dan menyimpannya dalam variabel. Sekarang, mari kita lihat cara mengoper fungsi sebagai parameter ke fungsi lain.

Pertama, kita harus membuat definisi fungsi higher-order:

fn binary_operator<F>(n: i32, m: i32, op: F) -> i32 
                     where F: Fn(i32, i32) -> i32 {
    op(n, m)
}

binary_operator memasukkan dua angka, n dan m, dan sebuah fungsi, op. Fungsi ini menerapkan op pada n dan m, dan mengembalikan hasilnya.

Perhatikan tipe dari parameter op. Ini adalah tipe umum F, yang disempurnakan dalam klausa where pada binary_operator. Secara khusus, kami mendefinisikannya sebagai sebuah fungsi (Fn) dengan dua parameter numerik ((i32, i32)), yang mengembalikan sebuah parameter (i32). Fn di sini mewakili sebuah penunjuk fungsi, yaitu alamat memori di mana fungsi tersebut disimpan.

Fungsi yang diberi nama

Cara paling sederhana untuk mengoper fungsi sebagai parameter adalah dengan menggunakan name (named function):

add(n: i32, m: i32) -> i32 {
    n + m
}

fn binary_operator<F>(n: i32, m: i32, op: F) -> i32 
                     where F: Fn(i32, i32) -> i32 {
    op(n, m)
}

fn main() {
    println!("{}", binary_operator(5, 6, add));
}

Pada contoh di atas, pertama-tama kita mendefinisikan sebuah fungsi (add) untuk menambahkan dua angka dan menggunakannya sebagai parameter untuk binary_operator. main mencetak 11, seperti yang diharapkan.

Fungsi Anonim

Terkadang tidak perlu memberi nama pada fungsi. Sebagai contoh, kita mungkin ingin mendefinisikan sebuah fungsi dengan cepat, untuk digunakan hanya di satu tempat. Di sinilah fungsi-fungsi anonim berperan:

fn main() {
    println!("{}", binary_operator(5, 6, |a: i32, b: i32| a - b));
}

Pada contoh di atas, kita mendefinisikan sebuah fungsi anonim secara langsung dalam pemanggilan binary_operator. Kita mendefinisikan daftar parameter di antara pipa (||), diikuti dengan badan fungsi itu sendiri.

Fungsi anonim adalah alat yang sangat kuat, lebih-lebih karena, dalam Rust, fungsi ini dapat “menangkap” lingkungan yang melingkupinya. Dalam hal ini, fungsi-fungsi tersebut juga disebut sebagai penutup.

Cuplikan di atas mengkompilasi dan mencetak -1, seperti yang diharapkan.

Fungsi Sebagai Nilai yang Dikembalikan

Seperti yang telah kami sebutkan sebelumnya, dalam Rust, sebuah fungsi juga dapat mengembalikan fungsi lain. Hal ini sedikit lebih rumit karena konsekuensi dari manajemen memori di Rust, seperti yang akan kita lihat sebentar lagi.

Pada contoh berikut, kita akan memodifikasi binary_operator, yang didefinisikan di atas, bukan untuk menerapkan operator, melainkan untuk mengembalikan sebuah fungsi yang merepresentasikan operator yang tidak diterapkan:

fn unapplied_binary_operator<'a, F>(n:& 'a i32, m:& 'a i32, op:& 'a F) 
                                   -> Box<dyn Fn() -> i32 + 'a>
                                   where F: Fn(i32, i32) -> i32 {
    Box::new(move || op(*n, *m))
}

Definisi unapplied_binary_operator sekarang terlihat jauh lebih kompleks.

Masalah utama dalam mengembalikan sebuah fungsi adalah mendefinisikan panjangnya lifetime dari fungsi tersebut. Lifetime dalam Rust adalah sebuah konstruksi yang digunakan oleh pemeriksa peminjaman untuk memastikan semua peminjaman valid.

Pada contoh di atas, kita mendefinisikan lifetime (‘a) bersama dengan tipe F biasa (mewakili operator biner). Kemudian, kita mengasosiasikan ‘a ke tiga parameter dan juga ke nilai yang dikembalikan dari fungsi tersebut.

Pada dasarnya, kita memberi tahu pemeriksa peminjaman untuk mempertimbangkan lifetime dari fungsi yang dikembalikan oleh unapplied_binary_operator selama ketiga parameter (n, m, dan op) ada.

Selain itu, lifetime dalam Rust hanya bisa ada dengan referensi. Oleh karena itu, kita harus mengubah parameter dan nilai yang dikembalikan menjadi referensi dengan & dan Box.

Secara umum, Box<dyn Fn()> merepresentasikan sebuah nilai yang dikotak-kotakkan yang mengimplementasikan sifat Fn.

Referensi membuat badan fungsi menjadi lebih kompleks dari sebelumnya, karena kita sekarang harus melakukan dereferensi terhadap referensi ke n dan m, dan secara eksplisit membuat fungsi yang dihasilkan dengan menggunakan Box::new().

Hal lain yang menarik dalam implementasi di atas adalah penggunaan move. Kata kunci ini menandakan bahwa semua pengambilan (yaitu, semua referensi ke lingkungan yang melingkupi) terjadi berdasarkan nilai. Jika tidak, semua tangkapan dengan referensi akan dibuang segera setelah fungsi anonim yang dikembalikan ada, meninggalkan penutup dengan referensi yang tidak valid. Dengan kata lain, dengan move, closure mengambil alih kepemilikan variabel yang digunakannya.

unapplied_binary_operator mengembalikan sebuah fungsi, tanpa argumen, mengembalikan hasil dari penerapan op ke n dan m. Dengan ketentuan bahwa kita sekarang menggunakan referensi, kita harus menggunakan peminjaman untuk memanggilnya:

fn main() {
    let n = 5;
    let m = 6;
    println!("{}", unapplied_binary_operator(&n, &m, &add)());
}

Perhatikan bagaimana kita meminjam n, m, dan menambahkan (didefinisikan di atas) dengan menggunakan &. Terakhir, karena unapplied_binary_operator mengembalikan sebuah fungsi tanpa parameter, kita dapat memanggilnya menggunakan tanda kurung kosong. Cuplikan di atas akan mencetak 11, seperti yang diharapkan.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top