Bagikan melalui


Pengantar Konsep Pemrograman Fungsional di F#

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

  • Berfungsi sebagai konstruksi utama yang Anda gunakan
  • Ekspresi alih-alih pernyataan
  • Nilai yang tidak dapat diubah atas variabel
  • Pemrograman deklaratif melalui pemrograman imperatif

Sepanjang seri ini, Anda akan mengeksplorasi konsep dan pola dalam pemrograman fungsi menggunakan F#. Sepanjang perjalanan, Anda juga akan belajar sedikit tentang bahasa pemrograman F#.

Terminologi

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

  • Fungsi - Fungsi adalah konstruksi yang akan menghasilkan output ketika diberikan input. Secara lebih formal, ini memetakan item dari satu himpunan ke himpunan lain. Formalisme ini diwujudkan secara konkret dalam banyak cara, terutama ketika menggunakan fungsi yang beroperasi pada kumpulan data. Ini adalah konsep yang paling mendasar (dan penting) dalam pemrograman fungsi.
  • Ekspresi - Ekspresi adalah konstruksi dalam kode yang menghasilkan nilai. Di F#, nilai ini harus terikat atau diabaikan secara eksplisit. Ekspresi dapat diganti dengan panggilan fungsi secara sepele.
  • Purity - Purity adalah properti dari fungsi sehingga nilai pengembaliannya selalu sama untuk argumen yang sama, dan bahwa evaluasinya tidak memiliki efek samping. Fungsi murni sepenuhnya bergantung pada argumennya.
  • Transparansi Referensial - Transparansi Referensial adalah properti ekspresi sehingga dapat diganti dengan outputnya tanpa memengaruhi perilaku program.
  • Kekekalan - Kekekalan berarti bahwa nilai tidak dapat diubah di tempat. Ini berbeda dengan variabel, yang dapat berubah nilainya di lokasi yang sama.

Contoh

Contoh berikut menunjukkan konsep inti ini.

Fungsi

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

let addOne x = x + 1

Tanda tangan tipenya adalah sebagai berikut:

val addOne: x:int -> int

Tanda tangan dapat dibaca sebagai, "addOne menerima sebuah int bernama x dan akan menghasilkan sebuah int". Secara lebih resmi, addOneadalah memetakan nilai dari kumpulan bilangan bulat ke kumpulan bilangan bulat. Token -> menandakan pemetaan ini. Di F#, Anda biasanya dapat melihat tanda tangan fungsi untuk memahami apa yang dilakukannya.

Jadi, mengapa tanda tangan itu penting? Dalam pemrograman fungsional tertipekan, implementasi fungsi sering kali kurang penting daripada tanda tangan tipe yang sebenarnya! Fakta bahwa addOne menambahkan nilai 1 ke bilangan bulat menarik pada waktu proses, tetapi ketika Anda membuat program, fakta bahwa ia menerima dan mengembalikan int adalah apa yang menginformasikan bagaimana Anda akan benar-benar menggunakan fungsi ini. Selain itu, setelah Anda menggunakan fungsi ini dengan tepat sesuai tanda tangan jenisnya, setiap masalah hanya dapat didiagnosis dalam tubuh fungsi addOne itu sendiri. Ini adalah impetus di balik pemrograman fungsi yang diketik.

Ekspresi

Ekspresi adalah konstruksi yang mengevaluasi ke nilai. Berbeda dengan pernyataan, yang melakukan tindakan, ekspresi dapat dianggap melakukan tindakan yang mengembalikan nilai. Ekspresi hampir selalu digunakan dalam pemrograman fungsional alih-alih pernyataan.

Pertimbangkan fungsi sebelumnya, addOne. Badan dari addOne adalah ekspresi:

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

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

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

Signatur fungsi sekarang:

val addOne: x:'a -> string

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

Ekspresi bukan hanya bagian dari fungsi. Anda dapat memiliki ekspresi yang menghasilkan nilai yang Anda gunakan di tempat lain. 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

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

Ada jenis khusus, unit, yang digunakan ketika tidak ada yang perlu 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 menghasilkan nilai apa pun sebagai hasil dari aktivitas tersebut.

Ini sangat kontras dengan pemrograman imperatif, di mana konstruksi yang setara if adalah pernyataan, dan menghasilkan nilai sering dilakukan dengan variabel 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 gaya C lainnya mendukung ekspresi terner, yang memungkinkan pemrograman bersyarat berbasis ekspresi.

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

Fungsi murni

Seperti yang disebutkan sebelumnya, fungsi murni 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 f(x) = x + 1matematika , nilai f(x) hanya bergantung pada nilai x. Fungsi murni dalam pemrograman fungsional berfungsi dengan cara yang sama.

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

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

let mutable value = 1

let addOneToValue x = x + value

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

Berikut adalah contoh lain dari fungsi non-murni, 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, itu berarti bahwa fungsinya tidak murni. Jika bagian lain dari program Anda bergantung pada sesuatu di luar program, seperti buffer output, maka memanggil fungsi ini dapat memengaruhi bagian lain dari program Anda.

Menghapus printfn pernyataan membuat fungsi murni:

let addOneToValue x = x + 1

Walaupun fungsi ini tidak lebih baik daripada versi sebelumnya dengan pernyataan , fungsi ini menjamin bahwa yang dilakukan fungsi ini hanyalah mengembalikan nilai. Memanggil fungsi ini beberapa kali menghasilkan hasil yang sama: fungsi ini hanya menghasilkan nilai. Prediktabilitas yang diberikan oleh kemurnian adalah sesuatu yang dicari banyak programmer fungsi.

Kekekalan

Akhirnya, salah satu konsep paling mendasar dari pemrograman fungsi yang diketik adalah kekekalan. Di F#, semua nilai tidak dapat diubah secara default. Itu berarti mereka tidak dapat dimutasi langsung kecuali Anda secara eksplisit menandainya sebagai mutable.

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

Misalnya, menambahkan 1 ke nilai berarti menghasilkan nilai baru, tidak bermutasi yang sudah ada:

let value = 1
let secondValue = value + 1

Di F#, kode berikut tidak mengubah value fungsi; 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. Di 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 Anda harapkan pada awalnya. Secara konseptual, sesuatu seperti menambahkan item ke set tidak mengubah set, item menghasilkan set baru dengan nilai tambah. Di bawah sampul, ini sering dicapai oleh struktur data 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 seolah-olah membuat versi baru dari hal itu. Ini memungkinkan hal-hal seperti kesetaraan dan perbandingan agar konsisten dalam program Anda.

Langkah selanjutnya

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

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

Bacaan lebih lanjut

Seri Thinking Functionally adalah sumber daya hebat lainnya untuk mempelajari tentang pemrograman fungsional dengan F#. Ini mencakup dasar-dasar pemrograman fungsional dengan cara yang pragmatis dan mudah dibaca, menggunakan fitur F# untuk menggambarkan konsep.