Bagikan melalui


Generalisasi Otomatis

F# menggunakan inferensi jenis untuk mengevaluasi jenis fungsi dan ekspresi. Topik ini menjelaskan bagaimana F# menggeneralisasi argumen dan jenis fungsi secara otomatis sehingga berfungsi dengan banyak jenis ketika hal ini memungkinkan.

Generalisasi Otomatis

Ketika melakukan inferensi jenis pada sebuah fungsi, kompilator F# menentukan apakah parameter yang diberikan dapat bersifat umum. Kompilator memeriksa setiap parameter dan menentukan apakah fungsi memiliki dependensi pada jenis tertentu dari parameter tersebut. Jika tidak, jenis tersebut dianggap generik.

Contoh kode berikut menggambarkan fungsi yang disimpulkan oleh kompilator sebagai generik.

let max a b = if a > b then a else b

Jenis tersebut disimpulkan sebagai 'a -> 'a -> 'a.

Jenis tersebut menunjukkan bahwa ini adalah fungsi yang mengambil dua argumen jenis tak diketahui yang sama dan mengembalikan nilai dari jenis yang sama. Salah satu alasan mengapa fungsi sebelumnya bisa menjadi generik adalah karena operator lebih besar dari (>) itu sendiri adalah generik. Operator lebih besar dari memiliki tanda tangan 'a -> 'a -> bool. Tidak semua operator bersifat generik, dan jika kode dalam suatu fungsi menggunakan jenis parameter bersama dengan fungsi atau operator non-generik, jenis parameter tersebut tidak dapat digeneralisasi.

Karena max bersifat generik, max dapat digunakan dengan jenis seperti int, float, dan sebagainya, seperti yang ditunjukkan dalam contoh berikut.

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

Namun, kedua argumen harus dari jenis yang sama. Tanda tangannya adalah 'a -> 'a -> 'a, bukan 'a -> 'b -> 'a. Oleh karena itu, kode berikut menghasilkan kesalahan karena jenis-jenisnya tidak cocok.

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

Fungsi max juga berfungsi dengan semua jenis yang mendukung operator lebih besar dari. Oleh karena itu, Anda juga bisa menggunakannya pada string, seperti yang ditunjukkan dalam kode berikut.

let testString = max "cab" "cat"

Pembatasan Nilai

Kompilator melakukan generalisasi otomatis hanya pada definisi fungsi lengkap yang memiliki argumen eksplisit dan pada nilai sederhana yang tidak dapat diubah.

Artinya, kompilator akan mengeluarkan kesalahan jika Anda mencoba untuk mengompilasi kode yang tidak cukup dibatasi untuk menjadi jenis tertentu, tetapi juga tidak dapat digeneralisasikan. Pesan kesalahan untuk masalah ini mengacu pada pembatasan ini pada generalisasi otomatis untuk nilai sebagai pembatasan nilai.

Biasanya, kesalahan pembatasan nilai terjadi ketika Anda ingin sebuah konstruksi menjadi generik, tetapi kompilator tidak memiliki informasi yang cukup untuk menggeneralisasinya, atau ketika Anda secara tidak sengaja menghilangkan informasi jenis yang cukup dalam konstruksi nongenerik. Solusi untuk kesalahan pembatasan nilai adalah memberikan informasi yang lebih eksplisit untuk lebih membatasi masalah inferensi jenis, dengan salah satu cara berikut:

  • Batasi jenis menjadi nongenerik dengan menambahkan anotasi jenis eksplisit ke nilai atau parameter.

  • Jika masalahnya adalah menggunakan konstruksi yang tidak dapat diregeneralisasi untuk menentukan fungsi generik, seperti komposisi fungsi atau argumen fungsi curried yang diterapkan secara tidak lengkap, cobalah untuk meregenerasi fungsi tersebut sebagai definisi fungsi biasa.

  • Jika masalahnya adalah ekspresi yang terlalu kompleks untuk digeneralisasi, buatlah menjadi fungsi dengan menambahkan parameter tambahan yang tidak digunakan.

  • Tambahkan parameter jenis generik eksplisit. Opsi ini jarang digunakan.

Contoh kode berikut menggambarkan setiap skenario ini.

Kasus 1: Ekspresi yang terlalu kompleks. Dalam contoh ini, daftar counter dimaksudkan untuk menjadi int option ref, tetapi tidak didefinisikan sebagai nilai sederhana yang tidak dapat diubah.

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

Kasus 2: Menggunakan konstruksi yang tidak dapat digeneralisasi untuk menentukan fungsi generik. Dalam contoh ini, konstruksi tidak dapat digeneralisasi karena melibatkan aplikasi parsial argumen fungsi.

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

Kasus 3: Menambahkan parameter tambahan yang tidak digunakan. Karena ekspresi ini tidak cukup sederhana untuk generalisasi, kompilator mengeluarkan kesalahan pembatasan nilai.

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

Kasus 4: Menambahkan jenis parameter.

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

Dalam kasus terakhir, nilai menjadi fungsi jenis, yang dapat digunakan untuk membuat nilai dari berbagai jenis, misalnya sebagai berikut:

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

Lihat juga