หรือถ้าคุณเล่นวิดีโอเกมมีตัวแปรสถานะเป็นจำนวนมากเริ่มต้นจากตำแหน่งของตัวละครทั้งหมดที่มีแนวโน้มที่จะเคลื่อนไหวอย่างต่อเนื่อง คุณจะทำอะไรที่มีประโยชน์โดยไม่ติดตามการเปลี่ยนแปลงค่าได้อย่างไร
หากคุณสนใจนี่คือบทความเกี่ยวกับการเขียนโปรแกรมเกมกับ Erlang
คุณอาจจะไม่ชอบคำตอบนี้ แต่คุณจะไม่ได้รับโปรแกรมการทำงานจนกว่าคุณจะใช้มัน ฉันสามารถโพสต์ตัวอย่างโค้ดและพูดว่า "ที่นี่คุณไม่เห็น " - แต่ถ้าคุณไม่เข้าใจไวยากรณ์และหลักการพื้นฐานแล้วดวงตาของคุณก็จ้องมอง จากมุมมองของคุณดูเหมือนว่าฉันกำลังทำสิ่งเดียวกันกับภาษาที่จำเป็น แต่แค่ตั้งค่าขอบเขตทุกประเภทเพื่อทำให้การเขียนโปรแกรมยากขึ้น มุมมองของฉันคุณเพิ่งจะได้สัมผัสกับความขัดแย้งของ Blubความขัดแย้งร้องไห้สะอึกสะอื้น
ตอนแรกฉันสงสัย แต่ฉันก็กระโดดขึ้นรถไฟฝึกฟังก์ชั่นเมื่อไม่กี่ปีก่อนและตกหลุมรักมัน เคล็ดลับที่มีฟังก์ชั่นการโปรแกรมสามารถจดจำรูปแบบการกำหนดตัวแปรเฉพาะและย้ายสถานะที่จำเป็นไปยังสแต็ก ยกตัวอย่างเช่นการวนซ้ำ:
// 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 # เป็นภาษา verbose นี่คือโปรแกรมที่เทียบเท่าใน 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 นี้ซึ่งไร้สัญชาติโดยสิ้นเชิง (หรืออย่างน้อยโค้ดของฉันคือไร้สัญชาติฉันไม่รู้ว่าไลบรารี่พื้นฐานเป็นไร้สัญชาติ)
การเขียนโปรแกรมไร้สัญชาติเป็นเพียงการแสดงออกและมีประสิทธิภาพเช่นเดียวกับการเขียนโปรแกรม stateful มันก็ต้องมีการฝึกฝนเล็กน้อยเพื่อฝึกฝนตัวเองให้เริ่มคิดไร้สัญชาติ แน่นอน "การเขียนโปรแกรมไร้สัญชาติเมื่อเป็นไปได้การเขียนโปรแกรมไร้สัญชาติหากจำเป็น" ดูเหมือนจะเป็นคำขวัญของภาษาที่ใช้งานไม่ได้ส่วนใหญ่ ไม่มีอันตรายใด ๆ หากย้อนกลับไปสู่ความไม่แน่นอนเมื่อแนวทางการทำงานนั้นไม่สะอาดหรือมีประสิทธิภาพ