Veya bir video oyunu oynarsanız, sürekli hareket etme eğilimi gösteren tüm karakterlerin pozisyonlarından başlayarak tonlarca devlet değişkeni vardır. Değişen değerleri takip etmeden faydalı herhangi bir şeyi nasıl yapabilirsiniz?
Eğer ilgileniyorsanız, burada Erlang ile oyun programlama açıklayan bir yazı dizisi.
Muhtemelen bu cevabı beğenmeyeceksiniz, ama siz kullanana kadar fonksiyonel bir program alamayacaksınız . Kod örnekleri gönderebilir ve "İşte, görmüyor musun " diyebilirim - ama sentaksı ve temel prensipleri anlamıyorsan, o zaman gözlerin sırlanır. Sizin bakış açınıza göre, zorunlu bir dil ile aynı şeyi yapıyorum, ancak programlamayı bilerek daha zor hale getirmek için her türlü sınırı ayarlıyorum. Benim açımdan, sadece Blub paradoksunu deneyimliyorsun .
İlk başta şüpheliydim, ama birkaç yıl önce fonksiyonel programlama trenine atladım ve ona aşık oldum. İşlevsel programlama ile ilgili hile, desenleri, belirli değişken atamaları tanıyabilir ve zorunlu durumu yığına taşıyabilir. Örneğin bir for-loop özyineleme olur:
// 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
Çok hoş değil, ama mutasyon olmadan aynı etkiyi elde ettik. Tabii ki, mümkün olan her yerde, tamamen döngüden kaçınmayı ve soyutlamayı seviyoruz:
// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)
Seq.iter yöntemi koleksiyon boyunca numaralandırılır ve her öğe için anonim işlevi çağırır. Çok kullanışlı :)
Biliyorum, sayıları yazdırmak tam olarak etkileyici değil. Bununla birlikte, oyunlarla aynı yaklaşımı kullanabiliriz: yığındaki tüm durumu tutun ve özyinelemeli çağrıdaki değişikliklerle yeni bir nesne oluşturun. Bu şekilde, her kare, oyunun durumsuz bir anlık görüntüsüdür; burada her kare, vatansız nesnelerin güncellenmesi gereken her türlü değişiklikle birlikte yepyeni bir nesne oluşturur. Bunun sahte kodu şöyle olabilir:
// 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))
Zorunlu ve işlevsel sürümler aynıdır, ancak işlevsel sürüm açıkça değiştirilebilir bir durum kullanmaz. İşlevsel kod tüm durumu yığında tutar - bu yaklaşımla ilgili güzel şey, bir şeyler ters giderse, hata ayıklamanın kolay olması, ihtiyacınız olan tek şey bir yığın izlemesidir.
Bu, oyundaki herhangi bir sayıda nesneye kadar ölçeklendirilir, çünkü tüm nesneler (veya ilgili nesnelerin koleksiyonları) kendi iş parçacıklarında oluşturulabilir.
Düşündüğüm hemen hemen her kullanıcı uygulaması, devleti temel bir kavram olarak içeriyor.
İşlevsel dillerde, nesnelerin durumunu değiştirmek yerine, istediğimiz değişikliklerle yeni bir nesne döndürürüz. Göründüğünden daha etkilidir. Örneğin veri yapılarının değişmez veri yapıları olarak temsil edilmesi çok kolaydır. Örneğin, yığınların uygulanması son derece kolaydır:
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);
}
}
}
Yukarıdaki kod iki değişmez liste oluşturur, bunları yeni bir liste yapmak için birleştirir ve sonuçları ekler. Uygulamada hiçbir yerde değiştirilebilir durum kullanılmaz. Biraz hantal görünüyor, ancak bunun nedeni sadece C # 'ın ayrıntılı bir dil olması. İşte F # 'daki eşdeğer program:
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
Listeleri oluşturmak ve değiştirmek için değiştirilebilir. Neredeyse tüm veri yapıları işlevsel eşdeğerlerine kolayca dönüştürülebilir. Burada yığınların, kuyrukların, sol yığınların, kırmızı-siyah ağaçların, tembel listelerin değişmez uygulamalarını sağlayan bir sayfa yazdım . Tek bir kod snippet'i değiştirilemez durum içermiyor. Bir ağacı "mutasyona uğratmak" için, istediğim yeni düğüme sahip yepyeni bir tane oluşturuyorum - bu çok verimli çünkü ağaçtaki her düğümün bir kopyasını yapmam gerekmiyor, eskileri yeni ağacı.
Daha önemli bir örnek kullanarak, ben de tamamen vatansız olan bu SQL ayrıştırıcı yazdı (ya da en azından benim kod vatansız, temel lexing kütüphane vatansız olup olmadığını bilmiyorum).
Vatansız programlama, durumsal programlama kadar etkileyici ve güçlüdür, vatansız düşünmeye başlamak için kendinizi eğitmek için biraz pratik gerektirir. Tabii ki, "mümkünse durum bilgisi olmayan programlama, gerektiğinde durum bilgisi olan programlama" en saf olmayan fonksiyonel dillerin sloganı gibi görünmektedir. İşlevsel yaklaşım o kadar da temiz ya da verimli olmadığında mabula geri düşmenin bir zararı yoktur.