Atau jika Anda bermain video game, ada banyak variabel keadaan, dimulai dengan posisi semua karakter, yang cenderung bergerak terus-menerus. Bagaimana Anda bisa melakukan sesuatu yang bermanfaat tanpa melacak perubahan nilai?
Jika Anda tertarik, inilah serangkaian artikel yang menjelaskan pemrograman game dengan Erlang.
Anda mungkin tidak akan menyukai jawaban ini, tetapi Anda tidak akan mendapatkan program fungsional sampai Anda menggunakannya. Saya dapat memposting sampel kode dan mengatakan "Di sini, tidakkah Anda melihat " - tetapi jika Anda tidak memahami sintaksis dan prinsip-prinsip yang mendasarinya, maka mata Anda hanya berkaca-kaca. Dari sudut pandang Anda, sepertinya saya melakukan hal yang sama dengan bahasa imperatif, tetapi hanya mengatur semua jenis batasan untuk sengaja membuat pemrograman lebih sulit. Menurut saya, Anda baru saja mengalami paradoks Blub .
Awalnya saya skeptis, tetapi saya melompat ke kereta pemrograman fungsional beberapa tahun yang lalu dan jatuh cinta padanya. Trik dengan pemrograman fungsional adalah mampu mengenali pola, penugasan variabel tertentu, dan memindahkan status imperatif ke stack. Misalnya untuk-loop, menjadi rekursi:
// Imperative
let printTo x =
for a in 1 .. x do
printfn "%i" a
// Recursive
let printTo x =
let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
loop 1
Tidak terlalu cantik, tapi kami mendapat efek yang sama tanpa mutasi. Tentu saja, jika memungkinkan, kami ingin menghindari pengulangan dan abaikan saja:
// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)
Metode Seq.iter akan menghitung melalui koleksi dan memanggil fungsi anonim untuk setiap item. Sangat berguna :)
Saya tahu, mencetak angka tidak terlalu mengesankan. Namun, kami dapat menggunakan pendekatan yang sama dengan game: tahan semua status di tumpukan dan buat objek baru dengan perubahan dalam panggilan rekursif. Dengan cara ini, setiap frame adalah snapshot stateless dari game, di mana setiap frame hanya menciptakan objek baru dengan perubahan yang diinginkan dari objek stateless apa pun yang perlu diperbarui. Kodesemu untuk ini mungkin:
// imperative version
pacman = new pacman(0, 0)
while true
if key = UP then pacman.y++
elif key = DOWN then pacman.y--
elif key = LEFT then pacman.x--
elif key = UP then pacman.x++
render(pacman)
// functional version
let rec loop pacman =
render(pacman)
let x, y = switch(key)
case LEFT: pacman.x - 1, pacman.y
case RIGHT: pacman.x + 1, pacman.y
case UP: pacman.x, pacman.y - 1
case DOWN: pacman.x, pacman.y + 1
loop(new pacman(x, y))
Versi imperatif dan fungsional identik, tetapi versi fungsional jelas tidak menggunakan keadaan bisa berubah. Kode fungsional membuat semua status disimpan di stack - hal yang menyenangkan tentang pendekatan ini adalah bahwa, jika terjadi kesalahan, debugging mudah, yang Anda butuhkan hanyalah jejak stack.
Ini menskala hingga sejumlah objek dalam game, karena semua objek (atau koleksi objek terkait) dapat dirender dalam utasnya sendiri.
Hampir setiap aplikasi pengguna yang saya anggap melibatkan status sebagai konsep inti.
Dalam bahasa fungsional, alih-alih mengubah keadaan objek, kami cukup mengembalikan objek baru dengan perubahan yang kita inginkan. Ini lebih efisien daripada kedengarannya. Struktur data, misalnya, sangat mudah direpresentasikan sebagai struktur data yang tidak berubah. Tumpukan, misalnya, terkenal mudah diimplementasikan:
using System;
namespace ConsoleApplication1
{
static class Stack
{
public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
{
return x == null ? y : Cons(x.Head, Append(x.Tail, y));
}
public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
}
class Stack<T>
{
public readonly T Head;
public readonly Stack<T> Tail;
public Stack(T hd, Stack<T> tl)
{
this.Head = hd;
this.Tail = tl;
}
}
class Program
{
static void Main(string[] args)
{
Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
Stack<int> z = Stack.Append(x, y);
Stack.Iter(z, a => Console.WriteLine(a));
Console.ReadKey(true);
}
}
}
Kode di atas membuat dua daftar yang tidak dapat diubah, menambahkannya bersama untuk membuat daftar baru, dan menambahkan hasilnya. Tidak ada keadaan yang bisa berubah digunakan di mana saja dalam aplikasi. Itu terlihat agak besar, tapi itu hanya karena C # adalah bahasa yang verbose. Berikut program yang setara di F #:
type 'a stack =
| Cons of 'a * 'a stack
| Nil
let rec append x y =
match x with
| Cons(hd, tl) -> Cons(hd, append tl y)
| Nil -> y
let rec iter f = function
| Cons(hd, tl) -> f(hd); iter f tl
| Nil -> ()
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
let z = append x y
iter (fun a -> printfn "%i" a) z
Tidak perlu diubah untuk membuat dan memanipulasi daftar. Hampir semua struktur data dapat dengan mudah dikonversi menjadi padanan fungsionalnya. Saya menulis sebuah halaman di sini yang menyediakan implementasi tumpukan, antrian, tumpukan kiri, pohon merah-hitam, daftar malas. Tidak satu pun cuplikan kode yang berisi status apa pun yang dapat berubah. Untuk "bermutasi" pohon, saya membuat yang baru dengan simpul baru yang saya inginkan - ini sangat efisien karena saya tidak perlu membuat salinan dari setiap simpul di pohon, saya dapat menggunakan kembali yang lama di baru saya pohon.
Menggunakan contoh yang lebih signifikan, saya juga menulis parser SQL ini yang benar-benar stateless (atau setidaknya kode saya stateless, saya tidak tahu apakah lexing library yang mendasari adalah stateless).
Pemrograman stateless sama ekspresif dan kuatnya dengan pemrograman stateful, hanya perlu sedikit latihan untuk melatih diri Anda untuk mulai berpikir tanpa kewarganegaraan. Tentu saja, "pemrograman stateless bila memungkinkan, pemrograman stateful jika diperlukan" tampaknya menjadi moto dari sebagian besar bahasa fungsional yang tidak murni. Tidak ada salahnya kembali pada perumpamaan ketika pendekatan fungsional tidak sebersih atau seefisien itu.