Pengantar Konsep Pemrograman Fungsional di F#

Pemrograman fungsional adalah gaya pemrograman yang menekankan penggunaan fungsi dan data yang tidak dapat diubah. Pemrograman fungsional yang diketik adalah ketika pemrograman fungsional dikombinasikan dengan jenis statis, seperti dengan F#. Secara umum, konsep berikut ditekankan dalam pemrograman fungsional:

  • Fungsi sebagai konstruksi utama yang Anda gunakan
  • Ekspresi, bukan pernyataan
  • Nilai yang tidak dapat diubah daripada variabel
  • Pemrograman deklaratif daripada pemrograman imperatif

Seluruh seri ini, Anda akan mengeksplorasi konsep dan pola dalam pemrograman fungsional menggunakan F#. Dalam prosesnya, Anda juga akan belajar beberapa F#.

Terminologi

Pemrograman fungsional, seperti paradigma pemrograman lainnya, dilengkapi dengan kosakata yang pada akhirnya perlu Anda pelajari. Berikut adalah beberapa istilah umum yang akan sering Anda temui:

  • Fungsi - Fungsi adalah konstruksi yang akan menghasilkan output ketika diberi input. Secara lebih formal, fungsi ini memetakan item dari satu set ke set lain. Formalisme ini diangkat ke dalam konkret dalam banyak cara, terutama ketika menggunakan fungsi yang beroperasi pada pengumpulan data. Ini adalah konsep yang paling mendasar (dan penting) dalam pemrograman fungsional.
  • Ekspresi - Ekspresi adalah konstruksi dalam kode yang menghasilkan nilai. Di F#, nilai ini harus terikat atau diabaikan secara eksplisit. Ekspresi dapat digantikan dengan panggilan fungsi dengan mudah.
  • Kemurnian - Kemurnian adalah properti dari fungsi sehingga nilai pengembaliannya selalu sama untuk argumen yang sama, dan evaluasinya tidak memiliki efek samping. Fungsi pure sepenuhnya bergantung pada argumennya.
  • Transparansi Referensial - Transparansi Referensial adalah properti dari ekspresi sehingga dapat diganti dengan output nya tanpa memengaruhi perilaku program.
  • Ketetapan - Ketetapan berarti nilai tidak dapat diubah di tempat. Ini kontras dengan variabel, yang dapat berubah di tempat.

Contoh

Contoh berikut menunjukkan inti konsep ini.

Fungsi

Konstruksi yang paling umum dan mendasar dalam pemrograman fungsional adalah fungsi. Berikut adalah fungsi sederhana yang menambahkan 1 ke bilangan bulat:

let addOne x = x + 1

Jenis tanda tangannya adalah sebagai berikut:

val addOne: x:int -> int

Tanda tangan dapat dibaca sebagai, "addOne menerima int bernama x dan akan menghasilkan int". Secara lebih formal, addOnememetakan nilai dari set bilangan bulat ke kumpulan bilangan bulat. Token -> menandakan pemetaan ini. Di F#, Anda biasanya dapat melihat tanda tangan fungsi untuk memahami kegunaannya.

Jadi, mengapa tanda tangan itu penting? Dalam pemrograman fungsional yang diketik, implementasi fungsi sering kali kurang penting daripada tanda tangan jenis aktual! Fakta bahwa addOne menambahkan nilai 1 ke bilangan bulat menarik pada durasi, tetapi ketika Anda membangun program, fakta bahwa hal itu menerima dan mengembalikan int adalah apa yang menginformasikan bagaimana Anda akan benar-benar menggunakan fungsi ini. Selain itu, setelah Anda menggunakan fungsi ini dengan benar (sehubungan dengan jenis tanda tangannya), mendiagnosis masalah apa pun dapat dilakukan hanya di dalam isi fungsi addOne. Ini adalah pendorong di balik pemrograman fungsional yang diketik.

Expressions

Ekspresi adalah konstruksi yang mengevaluasi nilai. Berbeda dengan pernyataan, yang melakukan tindakan, ekspresi dapat dianggap melakukan tindakan yang memberikan kembali nilai. Ekspresi hampir selalu digunakan dalam pemrograman fungsional dari pada pernyataan.

Pertimbangkan fungsi sebelumnya, addOne. Isi dariaddOne ekspresi adalah:

// 'x + 1' is an expression!
let addOne x = x + 1

Ini adalah hasil dari ekspresi ini yang menentukan jenis hasil dari fungsiaddOne. Misalnya, ekspresi yang membentuk fungsi ini dapat diubah menjadi jenis yang berbeda, seperti string:

let addOne x = x.ToString() + "1"

Tanda tangan fungsinya sekarang:

val addOne: x:'a -> string

Karena semua jenis dalam F# dapat memiliki ToString() yang dipanggil, jenis x telah dibuat generik (disebut Generalisasi Otomatis), dan jenis yang dihasilkan adalah string.

Ekspresi bukan hanya isi fungsi. Anda dapat memiliki ekspresi yang menghasilkan nilai yang Anda gunakan di tempat lain. Ekspresi yang umum adalah if:

// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0

let addOneIfOdd input =
    let result =
        if isOdd input then
            input + 1
        else
            input

    result

Ekspresi if menghasilkan nilai yang disebut result. Perhatikan bahwa Anda dapat menghilangkan result sepenuhnya, membuat ekspresi if sebagai isi dari fungsi addOneIfOdd. Hal utama yang perlu diingat tentang ekspresi adalah bahwa ekspresi menghasilkan nilai.

Ada jenis khusus, unit, yang digunakan ketika tidak ada yang dikembalikan. Misalnya, pertimbangkan fungsi sederhana ini:

let printString (str: string) =
    printfn $"String is: {str}"

Tanda tangan terlihat seperti ini:

val printString: str:string -> unit

Jenis unit menunjukkan bahwa tidak ada nilai aktual yang dikembalikan. Ini berguna ketika Anda memiliki rutinitas yang harus "melakukan pekerjaan" meskipun tidak memiliki nilai untuk kembali sebagai hasil dari pekerjaan itu.

Ini sangat kontras dengan pemrograman imperatif, di mana konstruksi if yang setara adalah pernyataan, dan menghasilkan nilai sering dilakukan dengan variabel yang bermutasi. Misalnya, dalam C#, kode mungkin ditulis seperti ini:

bool IsOdd(int x) => x % 2 != 0;

int AddOneIfOdd(int input)
{
    var result = input;

    if (IsOdd(input))
    {
        result = input + 1;
    }

    return result;
}

Perlu dicatat bahwa C# dan bahasa pemrogram gaya C lainnya mendukung ekspresi ternary, yang memungkinkan pemrograman kondisional berbasis ekspresi.

Dalam pemrograman fungsional, jarang terjadi memutasi nilai dengan pernyataan. Meskipun beberapa bahasa pemrograman fungsional mendukung pernyataan dan mutasi, tidak umum untuk menggunakan konsep-konsep ini dalam pemrograman fungsional.

Fungsi Pure

Seperti disebutkan sebelumnya, fungsi pure adalah fungsi yang:

  • Selalu evaluasi ke nilai yang sama untuk input yang sama.
  • Tidak memiliki efek samping.

Sangat membantu untuk memikirkan fungsi matematika dalam konteks ini. Dalam matematika, fungsi hanya bergantung pada argumennya dan tidak memiliki efek samping. Dalam fungsi matematika f(x) = x + 1, nilai f(x) hanya bergantung pada nilai x. Fungsi pure dalam pemrograman fungsional adalah cara yang sama.

Saat menulis fungsi pure, fungsi harus hanya bergantung pada argumennya dan tidak melakukan tindakan apa pun yang menghasilkan efek samping.

Berikut adalah contoh fungsi non-pure karena tergantung pada status global yang dapat diubah:

let mutable value = 1

let addOneToValue x = x + value

Fungsi addOneToValue ini jelas tidak pure, karena value dapat diubah kapan saja untuk memiliki nilai yang berbeda dari 1. Pola ketergantungan pada nilai global ini harus dihindari dalam pemrograman fungsional.

Berikut adalah contoh lain dari fungsi yang tidak pure, karena melakukan efek samping:

let addOneToValue x =
    printfn $"x is %d{x}"
    x + 1

Meskipun fungsi ini tidak bergantung pada nilai global, fungsi ini menulis nilai x ke output program. Meskipun tidak ada yang salah secara inheren dengan melakukan ini, ini berarti bahwa fungsinya tidak pure. Jika bagian lain dari program Anda bergantung pada sesuatu di luar program, seperti buffer output, maka memanggil fungsi ini dapat memengaruhi bagian lain itu dari program Anda.

Menghapus pernyataan printfn membuat fungsi pure:

let addOneToValue x = x + 1

Meskipun fungsi ini secara inheren lebih baik daripada versi sebelumnya dengan pernyataan printfn, fungsi ini menjamin bahwa semua yang fungsi ini lakukan mengembalikan nilai. Memanggil fungsi ini beberapa kali menghasilkan hasil yang sama: fungsi ini hanya menghasilkan nilai. Prediktabilitas yang diberikan oleh pure adalah sesuatu yang banyak programmer fungsional usahakan.

Ketetapan

Terakhir, salah satu konsep paling mendasar dari pemrograman fungsional yang diketik adalah ketetapan. Dalam F#, semua nilai tidak dapat diubah secara default. Artinya nilai ini tidak dapat dimutasi di tempat kecuali Anda secara eksplisit menandainya sebagai dapat diubah.

Dalam praktiknya, bekerja dengan nilai yang tidak dapat diubah berarti Anda mengubah pendekatan Anda untuk pemrograman dari, "Saya perlu mengubah sesuatu", menjadi "Saya perlu menghasilkan nilai baru".

Misalnya, menambahkan 1 ke nilai berarti menghasilkan nilai baru, bukan memutasi yang sudah ada:

let value = 1
let secondValue = value + 1

Dalam F#, kode berikut tidak memutasi fungsi value; sebaliknya, kode ini melakukan pemeriksaan kesetaraan:

let value = 1
value = value + 1 // Produces a 'bool' value!

Beberapa bahasa pemrograman fungsional tidak mendukung mutasi sama sekali. Dalam F#, ini didukung, tetapi bukan perilaku default untuk nilai.

Konsep ini meluas lebih jauh ke struktur data. Dalam pemrograman fungsional, struktur data yang tidak dapat diubah seperti set (dan banyak lagi) memiliki implementasi yang berbeda dari yang mungkin Anda harapkan. Secara konseptual, sesuatu seperti menambahkan item ke set tidak mengubah set, item menghasilkan set baru dengan nilai tambahan. Secara rahasia, ini sering dicapai dengan struktur data yang berbeda yang memungkinkan pelacakan nilai secara efisien sehingga representasi data yang sesuai dapat diberikan sebagai hasilnya.

Gaya bekerja dengan nilai dan struktur data ini sangat penting, karena memaksa Anda untuk memperlakukan operasi apa pun yang memodifikasi sesuatu seperti membuat versi batu dari hal itu. Ini memungkinkan hal-hal seperti kesetaraan dan perbandingan menjadi konsisten dalam program Anda.

Langkah berikutnya

Bagian berikutnya akan secara menyeluruh mencakup fungsi, menjelajahi berbagai cara Anda dapat menggunakannya dalam pemrograman fungsional.

Menggunakan fungsi di F# mengeksplorasi fungsi secara mendalam, menunjukkan cara Anda dapat menggunakannya dalam berbagai konteks.

Bacaan lebih lanjut

Seri Berpikir Secara Fungsional adalah sumber daya bagus lainnya untuk mempelajari tentang pemrograman fungsional dengan F#. Ini mencakup dasar-dasar pemrograman fungsional dengan cara pragmatis dan mudah dibaca, menggunakan fitur F# untuk mengilustrasikan konsep.