या अगर आप एक वीडियो गेम खेलते हैं, तो सभी पात्रों के पदों के साथ शुरू होने वाले, कई राज्य चर हैं, जो लगातार घूमते रहते हैं। बदलते मूल्यों पर नज़र रखे बिना आप संभवतः कैसे उपयोगी कुछ कर सकते हैं?
यदि आप रुचि रखते हैं, तो यहां उन लेखों की एक श्रृंखला है जो Erlang के साथ गेम प्रोग्रामिंग का वर्णन करते हैं।
आप शायद इस उत्तर को पसंद नहीं करेंगे, लेकिन जब तक आप इसका उपयोग नहीं करते हैं तब तक आपको कार्यात्मक कार्यक्रम नहीं मिलेगा । मैं कोड नमूने पोस्ट कर सकता हूं और कह सकता हूं कि "यहां, आप नहीं देख रहे हैं " - लेकिन अगर आप वाक्यविन्यास और अंतर्निहित सिद्धांतों को नहीं समझते हैं, तो आपकी आंखें बस चमकती हैं। आपके दृष्टिकोण से, ऐसा लगता है जैसे मैं एक अनिवार्य भाषा के रूप में एक ही काम कर रहा हूं, लेकिन सिर्फ प्रोग्रामिंग को और अधिक कठिन बनाने के लिए सभी प्रकार की सीमाओं को स्थापित करना। मेरी बात, आप सिर्फ ब्लड विरोधाभास का अनुभव कर रहे हैं ।
मुझे पहली बार में संदेह हुआ, लेकिन मैं कुछ साल पहले कार्यात्मक प्रोग्रामिंग ट्रेन में कूद गया और इसके साथ प्यार हो गया। कार्यात्मक प्रोग्रामिंग के साथ चाल पैटर्न को पहचानने में सक्षम हो रही है, विशेष रूप से चर असाइनमेंट, और आवश्यक स्थिति को स्टैक पर ले जाने के लिए। उदाहरण के लिए, लूप, पुनरावर्तन बन जाता है:
// 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
यह बहुत सुंदर नहीं है, लेकिन हमें बिना किसी उत्परिवर्तन के समान प्रभाव मिला। बेशक, जहां भी संभव हो, हम पूरी तरह से लूपिंग से बचना पसंद करते हैं और बस इसे दूर करते हैं:
// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)
Seq.iter विधि संग्रह के माध्यम से गणना करेगा और प्रत्येक आइटम के लिए अनाम फ़ंक्शन को आमंत्रित करेगा। बेहद सुविधाजनक :)
मुझे पता है, मुद्रण संख्या बिल्कुल प्रभावशाली नहीं है। हालांकि, हम गेम के साथ एक ही दृष्टिकोण का उपयोग कर सकते हैं: स्टैक में सभी स्थिति को पकड़ो और पुनरावर्ती कॉल में हमारे परिवर्तनों के साथ एक नई वस्तु बनाएं। इस तरह, प्रत्येक फ्रेम गेम का एक स्टेटलेस स्नैपशॉट है, जहां प्रत्येक फ्रेम बस एक नई वस्तु बनाता है, जिसे स्टेटलेस ऑब्जेक्ट्स को अपडेट करने की आवश्यकता होती है। इसके लिए छद्म कोड हो सकता है:
// 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))
अनिवार्य और कार्यात्मक संस्करण समान हैं, लेकिन कार्यात्मक संस्करण स्पष्ट रूप से कोई भी परिवर्तनशील स्थिति का उपयोग नहीं करता है। कार्यात्मक कोड सभी स्थिति को स्टैक पर रखता है - इस दृष्टिकोण के बारे में अच्छी बात यह है कि, अगर कुछ गलत होता है, डिबगिंग आसान है, तो आपको केवल एक स्टैक ट्रेस की आवश्यकता है।
यह खेल में किसी भी संख्या तक वस्तुओं को मापता है, क्योंकि सभी वस्तुओं (या संबंधित वस्तुओं के संग्रह) को अपने स्वयं के धागे में प्रस्तुत किया जा सकता है।
हर उपयोगकर्ता एप्लिकेशन के बारे में मैं सोच सकता हूं कि इसमें एक मुख्य अवधारणा के रूप में राज्य शामिल है।
कार्यात्मक भाषाओं में, वस्तुओं की स्थिति को बदलने के बजाय, हम बस एक नई वस्तु वापस करते हैं जो हम चाहते हैं। इसकी तुलना में यह अधिक कुशल है। डेटा संरचनाएं, उदाहरण के लिए, अपरिवर्तनीय डेटा संरचनाओं के रूप में प्रतिनिधित्व करना बहुत आसान है। उदाहरण के लिए ढेर, लागू करने के लिए बेहद आसान हैं:
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);
}
}
}
उपरोक्त कोड दो अपरिवर्तनीय सूचियों का निर्माण करता है, उन्हें एक नई सूची बनाने के लिए एक साथ जोड़ता है, और परिणामों को जोड़ता है। आवेदन में कहीं भी कोई परिवर्तनशील स्थिति का उपयोग नहीं किया गया है। यह थोड़ा भारी लगता है, लेकिन यह केवल इसलिए है क्योंकि C # एक मौखिक भाषा है। यहाँ 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
सूचियों को बनाने और हेरफेर करने के लिए कोई भी परिवर्तनशील आवश्यक नहीं है। लगभग सभी डेटा संरचनाओं को उनके कार्यात्मक समकक्षों में आसानी से परिवर्तित किया जा सकता है। मैंने यहां एक पृष्ठ लिखा है जो ढेर, कतार, वामपंथी ढेर, लाल-काले पेड़, आलसी सूची के अपरिवर्तनीय कार्यान्वयन प्रदान करता है। कोड के एक भी स्निपेट में कोई भी परिवर्तनशील स्थिति नहीं होती है। एक पेड़ को "म्यूट" करने के लिए, मैं नया नोड चाहता हूं, जिसके साथ मैं चाहता हूं - यह बहुत ही कुशल है क्योंकि मुझे पेड़ में हर नोड की एक प्रति बनाने की आवश्यकता नहीं है, मैं अपने नए में पुराने का पुन: उपयोग कर सकता हूं पेड़।
अधिक महत्वपूर्ण उदाहरण का उपयोग करते हुए, मैंने यह SQL पार्सर भी लिखा है जो पूरी तरह से स्टेटलेस है (या कम से कम मेरा कोड स्टेटलेस है, मुझे नहीं पता कि अंतर्निहित लेक्सिंग लाइब्रेरी स्टेटलेस है)।
स्टेटलेस प्रोग्रामिंग, स्टेटफुल प्रोग्रामिंग की तरह ही एक्सप्रेसिव और पावरफुल है, बस अपने आप को स्टैटिस्टिकल रूप से सोचने के लिए खुद को प्रशिक्षित करने के लिए थोड़े अभ्यास की आवश्यकता होती है। बेशक, "जब संभव हो स्टेटलेस प्रोग्रामिंग, स्टेटफुल प्रोग्रामिंग जहां आवश्यक हो" सबसे अशुद्ध कार्यात्मक भाषाओं का आदर्श वाक्य लगता है। कार्यात्मक दृष्टिकोण बस के रूप में स्वच्छ या कुशल नहीं है, तो म्यूटैबल्स पर वापस गिरने में कोई नुकसान नहीं है।