كيف يمكنك أن تفعل أي شيء مفيد دون حالة قابلة للتغيير؟

StackOverflow https://stackoverflow.com/questions/1020653

  •  06-07-2019
  •  | 
  •  

سؤال

لقد قرأت الكثير من الأشياء حول البرمجة الوظيفية مؤخرًا، ويمكنني فهم معظمها، لكن الشيء الوحيد الذي لا أستطيع استيعابه هو الترميز عديم الحالة.يبدو لي أن تبسيط البرمجة عن طريق إزالة الحالة القابلة للتغيير يشبه "تبسيط" السيارة عن طريق إزالة لوحة القيادة:قد يكون المنتج النهائي أبسط، ولكن حظًا سعيدًا في جعله يتفاعل مع المستخدمين النهائيين.

يتضمن كل تطبيق مستخدم تقريبًا الحالة كمفهوم أساسي.إذا كتبت مستندًا (أو منشورًا SO)، تتغير الحالة مع كل إدخال جديد.أو إذا كنت تلعب لعبة فيديو، فهناك الكثير من متغيرات الحالة، بدءًا من مواقع جميع الشخصيات، التي تميل إلى التحرك باستمرار.كيف يمكنك أن تفعل أي شيء مفيد دون تتبع القيم المتغيرة؟

في كل مرة أجد شيئًا يناقش هذه المشكلة، يكون مكتوبًا بلغة وظيفية فنية حقًا تفترض خلفية FP ثقيلة لا أملكها.هل يعرف أي شخص طريقة لشرح ذلك لشخص يتمتع بفهم جيد وقوي للترميز الحتمي ولكن من هو n00b الكامل على الجانب الوظيفي؟

يحرر:يبدو أن مجموعة من الردود حتى الآن تحاول إقناعي بمزايا القيم الثابتة.لقد حصلت على هذا الجزء.فمن المنطقي تماما.ما لا أفهمه هو كيف يمكنك تتبع القيم التي يجب أن تتغير، وتتغير باستمرار، بدون متغيرات قابلة للتغيير.

هل كانت مفيدة؟

المحلول

<اقتباس فقرة>   

وأو إذا كنت تلعب لعبة فيديو، هناك   طن من المتغيرات الدولة، بدأت   مع مواقف جميع   الشخصيات، الذين يميلون إلى التحرك   باستمرار. كيف يمكن لكم ربما تفعل   أي شيء مفيد دون تتبع   تغيير القيم؟

إذا كنت مهتما، من هنا سلسلة من المقالات التي تصف برمجة اللعبة مع إرلانج.

وربما لن تحب هذا الجواب، ولكنك لن <ط> الحصول على برنامج وظيفي حتى استخدامه. يمكنني الرد على عينات رمز ويقول "هنا، أليس كذلك رؤية " - ولكن إذا كنت لا تفهم بناء الجملة والمبادئ الأساسية، ثم عينيك فقط الصقيل انتهت. من وجهة نظرك، يبدو كما لو أنني أفعل نفس الشيء كلغة حتمية، ولكن مجرد وضع جميع أنواع الحدود لجعل هادف برمجة أكثر صعوبة. وجهة نظري، كنت مجرد تعاني من Blub مفارقة .

وكنت متشككا في البداية، لكني قفزت على قطار البرمجة الوظيفية قبل بضع سنوات وسقطت في الحب معها. خدعة مع البرمجة الوظيفية هو القدرة على التعرف على الأنماط، خاصة المهام المتغيرة، ونقل حالة ملحة إلى المكدس. A، على سبيل المثال، يصبح لحلقة العودية:

// 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))

وإصدارات حتمية وظيفية متطابقة، ولكن يستخدم نسخة تعمل بشكل واضح أي دولة قابلة للتغيير. رمز وظيفي وتبقي يقام كل دولة على كومة - الشيء الجميل في هذا النهج هو أنه إذا سارت الامور بشكل سيء، وتصحيح الأخطاء أمر سهل، كل ما تحتاجه هو جهاز تتبع المكدس

وهذه المقاييس تصل إلى أي عدد من الكائنات في اللعبة، لأن جميع الكائنات (أو مجموعة من الأجسام ذات الصلة) يمكن أن تقدم في موضوع بهم.

<اقتباس فقرة>   

وفقط عن كل مستخدم التطبيق I   يمكن ان يخطر ينطوي على الدولة باعتبارها نواة   المفهوم.

في اللغات الوظيفية، بدلا من تحور الدولة من الأشياء، ونحن ببساطة العودة كائن جديد مع التغييرات التي نريدها. على نحو أكثر كفاءة مما يبدو. هياكل البيانات، على سبيل المثال، من السهل جدا لتمثيل هياكل البيانات وغير قابل للتغيير. مداخن، على سبيل المثال، من السهل بمكان أن تنفيذ:

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

ولا قابلة للتغيير اللازمة لإنشاء ومعالجتها القوائم. هياكل البيانات كلها تقريبا يمكن تحويلها بسهولة إلى حكمه الوظيفية. كتبت href="http://en.wikibooks.org/wiki/F_Sharp_Programming/Advanced_Data_Structures" هنا الذي يوفر تطبيقات الثابتة للمداخن، طوابير، أكوام اليسارية، الأحمر- الأشجار السوداء، وقوائم كسول. ليس مقتطف شفرة واحد يحتوي على أية دولة قابلة للتغيير. ل"يتحور" شجرة، وإنشاء العلامة التجارية واحدة جديدة مع عقدة جديدة أريد - وهذا هو فعالة جدا لأنني لست بحاجة إلى عمل نسخة من البريدالعقدة نفسها في شجرة، ويمكنني أن إعادة استخدام القديمة في بلدي شجرة جديدة.

وعن طريق مثال أكثر أهمية، كتبت أيضا هذا المحلل SQL حيث لا جنسية تماما (أو على الأقل <ط> بي متاحة عديمي الجنسية، وأنا لا أعرف ما إذا كانت مكتبة lexing الأساسية هي عديمي الجنسية).

والبرمجة عديمي الجنسية تماما كما هو معبرة وقوية كما البرمجة جليل، فإنه يتطلب فقط القليل من الممارسة لتدريب نفسك لبدء التفكير statelessly. بطبيعة الحال، "البرمجة عديمي الجنسية عندما يكون ذلك ممكنا، والبرمجة جليل عند الضرورة" يبدو أن شعار اللغات الأكثر وظيفية نجس. ليس هناك ضرر في أن يتراجع على mutables عندما النهج الوظيفي فقط ليست نظيفة أو كفاءة كما.

نصائح أخرى

والجواب باختصار: لا يمكنك

وإذن ما هذه الضجة حول ثبات بعد ذلك؟

إذا كنت على دراية جيدة باللغة حتمية، ثم أنت تعلم أن "غلوبالس سيئة". لماذا ا؟ لأنها إدخال (أو لديها القدرة على تقديم) بعض تبعيات يصعب فك جدا في التعليمات البرمجية. وتبعيات ليست جيدة. تريد التعليمات البرمجية ليكون <م> وحدات . أجزاء من البرنامج لا تؤثر على أجزاء أخرى أقل قدر ممكن. وFP تأتيك إلى الكأس المقدسة من نمطية: ليس له آثار جانبية <م> على الإطلاق . لديك فقط و بك (س) = ذ. وضع x في واحصل على Y بها. لا تغييرات على x أو أي شيء آخر. FP يجعلك التوقف عن التفكير في الدولة، والبدء في التفكير من حيث القيم. جميع وظائف تتلقاها ببساطة القيم وإنتاج قيم جديدة.

وهذا له العديد من المزايا.

أولا، أي آثار جانبية تعني برامج أبسط وأسهل لسبب عنه. لا تشعر بالقلق من أن تقديم جزء جديد من البرنامج هو الذهاب الى التدخل وتحطم و، جزءا العمل الحالية.

والثانية، وهذا يجعل البرنامج موازاة مسلي (الموازاة كفاءة هو مسألة أخرى).

وثالثا، هناك بعض مزايا الأداء الممكنة. ويقول لديك وظيفة:

double x = 2 * x

والآن كنت وضعت في قيمة 3 في، وتحصل على قيمة 6 من أصل. كل مره. ولكن يمكنك أن تفعل ذلك في ضرورة أيضا، أليس كذلك؟ نعم. ولكن المشكلة هي أنه في حتمية، يمكنك أن تفعل حتى أكثر . يمكنني القيام به:

int y = 2;
int double(x){ return x * y; }

ولكن ما يمكن القيام به أيضا

int y = 2;
int double(x){ return x * (y++); }

والمترجم بد لا يعرف ما إذا أنا ذاهب ليكون لها آثار جانبية أم لا، مما يجعل الأمر أكثر صعوبة لتحسين (أي ضعف 2 لا يلزم أن يكون 4 في كل مرة). واحد وظيفية لا يعرف أنني سوف - وبالتالي، فإنه يمكن تحسين كل الوقت الذي يرى "ضعف 2"

والآن، على الرغم من خلق قيم جديدة في كل مرة يبدو الإسراف بشكل لا يصدق لأنواع معقدة من القيم من حيث ذاكرة الكمبيوتر، فإنه ليس من الضروري أن يكون الأمر كذلك. لأنه إذا كان لديك و (خ) = ص، والقيم x و y هي "في الغالب نفس" (على سبيل المثال الأشجار التي تختلف فقط في عدد قليل يورق) ثم x و y يمكن تبادل أجزاء من الذاكرة - لأن أيا منهما لن يتحور .

وحتى إذا كان هذا الشيء unmutable كبيرة جدا، لماذا لم تجيب على ذلك لا يمكنك أن تفعل أي شيء مفيد من دون دولة قابلة للتغيير. حسنا، من دون التحولية، فإن البرنامج بأكمله يكون و العملاقة (س) = وظيفة ذ. والشيء ذاته الذهاب للجميع أجزاء البرنامج: وظائف فقط، وظائف بمعنى "النقي" في ذلك. كما قلت، وهذا يعني و (خ) = ذ <م> كل الوقت. هكذا على سبيل المثال أن طلب & ReadFile ( "على Myfile.txt") في حاجة إلى إعادة قيمة السلسلة نفسها في كل مرة. ليس من المفيد جدا.

لذلك، توفر كل FP <م> بعض يعني من تحور الدولة. "الصرفة" لغات وظيفية (مثل هاسكل) القيام بذلك باستخدام المفاهيم مخيفة إلى حد ما مثل الكائنات الدقيقة الاحاديه الخلية، في حين أن "نجس" منها (على سبيل المثال ML) تسمح بذلك مباشرة.

وبطبيعة الحال، لغات وظيفية تأتي مع مجموعة من غيرها من الأشياء الجيدة التي تجعل برمجة أكثر كفاءة، مثل وظائف من الدرجة الأولى وما إلى ذلك.

لاحظ أن القول بأن البرمجة الوظيفية لا تحتوي على "حالة" أمر مضلل بعض الشيء وقد يكون سبب الارتباك.من المؤكد أنها لا تحتوي على "حالة قابلة للتغيير"، ولكن لا يزال من الممكن أن تحتوي على قيم يمكن التلاعب بها؛لا يمكن تغييرها في مكانها (على سبيل المثال.عليك إنشاء قيم جديدة من القيم القديمة).

يعد هذا تبسيطًا مفرطًا، ولكن تخيل أن لديك لغة OO، حيث يتم تعيين جميع الخصائص الموجودة في الفئات مرة واحدة فقط في المنشئ، وجميع الأساليب هي وظائف ثابتة.لا يزال بإمكانك إجراء أي عملية حسابية تقريبًا عن طريق جعل الأساليب تأخذ كائنات تحتوي على جميع القيم التي تحتاجها لحساباتها ثم تعيد كائنات جديدة بالنتيجة (ربما مثيل جديد لنفس الكائن حتى).

قد يكون من "الصعب" ترجمة التعليمات البرمجية الموجودة إلى هذا النموذج، ولكن ذلك لأنه يتطلب حقًا طريقة مختلفة تمامًا للتفكير في التعليمات البرمجية.كأثر جانبي، على الرغم من أنه في معظم الحالات تحصل على الكثير من الفرص للتوازي مجانًا.

إضافة: (فيما يتعلق بتعديلك لكيفية تتبع القيم التي تحتاج إلى التغيير)
سيتم تخزينها في بنية بيانات غير قابلة للتغيير بالطبع ...

هذا ليس "حلًا" مقترحًا، ولكن أسهل طريقة لمعرفة أن هذا سيعمل دائمًا هو أنه يمكنك تخزين هذه القيم غير القابلة للتغيير في خريطة (قاموس/قابل للتجزئة) مثل البنية، مع مفتاح "اسم متغير".

من الواضح أنك ستستخدم نهجًا أكثر عقلانية في الحلول العملية، ولكن هذا يُظهر أسوأ الحالات إذا لم ينجح أي شيء آخر، فيمكنك "محاكاة" الحالة القابلة للتغيير باستخدام مثل هذه الخريطة التي تحملها عبر شجرة الاستدعاء الخاصة بك.

وأعتقد أن هناك سوء تفاهم بسيط. برامج الوظيفية البحتة لها الدولة. والفرق هو كيفية غرار تلك الدولة. في البرمجة الوظيفية البحتة، يتم التلاعب الدولة من خلال الوظائف التي تأخذ بعض الدولة وإعادة الدولة القادمة. التسلسل من خلال الدول ثم يتحقق عن طريق تمرير الدولة من خلال سلسلة من المهام نقية.

ودولة قابلة للتغيير حتى العالمية يمكن أن تكون على غرار بهذه الطريقة. في هاسكل، على سبيل المثال، وهو برنامج هي وظيفة من عالم إلى عالم. وهذا هو، يمكنك تمرير في الكون كله ، وبرنامج بإرجاع الكون الجديد. في الممارسة العملية، رغم ذلك، كنت بحاجة فقط لتمرير في أجزاء من الكون الذي برنامجك مهتم فعلا. وبرامج تعود في الواقع <م> سلسلة من الإجراءات أن تكون بمثابة تعليمات لبيئة العمل التي يعمل البرنامج.

وأنت تريد أن ترى هذا أوضح من حيث البرمجة حتمية. حسنا، دعنا نلقي نظرة على بعض البرمجة حتمية حقا بسيطة في لغة وظيفية.

والنظر في هذا الرمز:

int x = 1;
int y = x + 1;
x = x + y;
return x;

وجميلة مستنقع القياسية كود حتمي. لا تفعل أي شيء للاهتمام، ولكن هذا موافق للتوضيح. أعتقد أنك سوف نتفق على أن هناك دولة المعنية هنا. قيمة التغييرات س متغير مع مرور الوقت. الآن، دعونا تغيير التدوين قليلا عن طريق اختراع بناء جملة جديدة:

let x = 1 in
let y = x + 1 in
let z = x + y in z 

ووضع الأقواس لجعله أكثر وضوحا ما يعنيه هذا:

let x = 1 in (let y = x + 1 in (let z = x + y in (z)))

وهكذا ترون، على غرار الدولة من خلال سلسلة من العبارات النقية التي تربط بين المتغيرات خالية من التعابير التالية.

وسوف تجد أن هذا النمط يمكن نمذجة أي نوع من الدولة، حتى IO.

هنا كيف تكتب التعليمات البرمجية دون حالة قابلة للتغيير:بدلاً من وضع الحالة المتغيرة في متغيرات قابلة للتغيير، يمكنك وضعها في معلمات الوظائف.وبدلاً من كتابة الحلقات، تكتب دوالً متكررة.على سبيل المثال هذا الكود الحتمي:

f_imperative(y) {
  local x;
  x := e;
  while p(x, y) do
    x := g(x, y)
  return h(x, y)
}

يصبح هذا الكود الوظيفي (بناء الجملة الشبيه بالمخطط):

(define (f-functional y) 
  (letrec (
     (f-helper (lambda (x y)
                  (if (p x y) 
                     (f-helper (g x y) y)
                     (h x y)))))
     (f-helper e y)))

أو رمز هاسكليش هذا

f_fun y = h x_final y
   where x_initial = e
         x_final   = loop x_initial
         loop x = if p x y then loop (g x y) else x

بما يخص لماذا يحب المبرمجون الوظيفيون القيام بذلك (وهو ما لم تطلبه)، فكلما زاد عدد أجزاء برنامجك التي أصبحت عديمة الحالة، كلما زادت الطرق المتاحة لتجميع القطع معًا دون أن ينكسر أي شيء.قوة نموذج عديمي الجنسية لا تكمن في انعدام الجنسية (أو النقاء) في حد ذاته, ولكن القدرة التي تمنحك على الكتابة قوية، قابلة لإعادة الاستخدام الوظائف والجمع بينها.

يمكنك العثور على برنامج تعليمي جيد يحتوي على الكثير من الأمثلة في ورقة جون هيوز لماذا تعتبر البرمجة الوظيفية مهمة؟.

وإنها طرق مختلفة تماما من فعل الشيء نفسه.

والنظر في مثال بسيط مثل إضافة أرقام 3 و 5 و 10. تخيل التفكير في القيام بذلك عن طريق تغيير لأول مرة قيمة 3 بإضافة 5 إلى ذلك، ثم إضافة 10 إلى أن "3"، ثم إخراج التيار قيمة "3" (18). هذا يبدو سخيفا واضح، وإنما هو في جوهره الطريق ويتم ذلك غالبا أن البرمجة حتمية القائم على الدولة. في الواقع، هل يمكن أن يكون العديد من مختلف "3" ليالي التي لديها قيمة 3، ولكن تختلف. كل هذا يبدو غريبا، لأننا قد منغرس مع، الى حد بعيد معقولة جدا، فكرة أن الأرقام غير قابل للتغيير.

والآن نفكر في إضافة 3 و 5 و 10 عندما كنت تأخذ من القيم أن يكون غير قابل للتغيير. يمكنك إضافة 3 و 5 لإنتاج قيمة أخرى، 8، ثم إضافة 10 إلى تلك القيمة لإنتاج بعد قيمة أخرى، 18.

وهذه هي الطرق ما يعادل تفعل الشيء نفسه. جميع المعلومات اللازمة موجود في كلتا الطريقتين، ولكن في أشكال مختلفة. في واحدة جود معلومات عن الدولة وقواعد لتغيير الدولة. في الآخر جود المعلومات في البيانات الثابتة والتعاريف وظيفية.

لقد تأخرت في المناقشة، ولكني أردت إضافة بعض النقاط للأشخاص الذين يعانون من البرمجة الوظيفية.

  1. تحتفظ اللغات الوظيفية بنفس تحديثات الحالة تمامًا مثل اللغات الضرورية ولكن يفعلون ذلك عن طريق تمرير الحالة المحدثة إلى استدعاءات الوظائف اللاحقة.فيما يلي مثال بسيط للغاية على السفر عبر خط الأعداد.ولايتك هي موقعك الحالي.

أولاً الطريقة الحتمية (بالكود الكاذب)

moveTo(dest, cur):
    while (cur != dest):
         if (cur < dest):
             cur += 1
         else:
             cur -= 1
    return cur

الآن الطريقة الوظيفية (بالكود الكاذب).أنا أعتمد بشدة على المشغل الثلاثي لأنني أريد أن يتمكن الأشخاص من الخلفيات الحتمية من قراءة هذا الرمز بالفعل.لذا، إذا كنت لا تستخدم العامل الثلاثي كثيرًا (كنت أتجنبه دائمًا في أيام الأمر الحتمية) فإليك كيفية عمله.

predicate ? if-true-expression : if-false-expression

يمكنك ربط التعبير الثلاثي بوضع تعبير ثلاثي جديد بدلاً من التعبير الخاطئ

predicate1 ? if-true1-expression :
predicate2 ? if-true2-expression :
else-expression

لذلك مع أخذ ذلك في الاعتبار، إليك الإصدار الوظيفي.

moveTo(dest, cur):
    return (
        cur == dest ? return cur :
        cur < dest ? moveTo(dest, cur + 1) : 
        moveTo(dest, cur - 1)
    )

هذا مثال تافه.إذا كان هذا ينقل الأشخاص في عالم اللعبة، فسيتعين عليك تقديم تأثيرات جانبية مثل رسم الموضع الحالي للكائن على الشاشة وإدخال القليل من التأخير في كل مكالمة بناءً على مدى سرعة تحرك الكائن.لكنك مازلت لن تحتاج إلى حالة قابلة للتغيير.

  1. الدرس المستفاد هو أن اللغات الوظيفية "تتحور" عن طريق استدعاء الوظيفة بمعلمات مختلفة.من الواضح أن هذا لا يؤدي إلى تحوير أي متغيرات، ولكن هذه هي الطريقة التي تحصل بها على تأثير مماثل.هذا يعني أنه سيتعين عليك التعود على التفكير بشكل متكرر إذا كنت تريد القيام بالبرمجة الوظيفية.

  2. إن تعلم التفكير بشكل متكرر ليس بالأمر الصعب، لكنه يتطلب الممارسة ومجموعة الأدوات.هذا القسم الصغير في كتاب "تعلم Java" حيث استخدموا التكرار لحساب المضروب لا يقطعه.أنت بحاجة إلى مجموعة أدوات من المهارات مثل إنشاء عمليات تكرارية من التكرار (وهذا هو السبب في أن التكرار الخلفي ضروري للغة الوظيفية)، والاستمرارية، والثوابت، وما إلى ذلك.لن تقوم ببرمجة OO دون التعرف على معدّلات الوصول والواجهات وما إلى ذلك.نفس الشيء بالنسبة للبرمجة الوظيفية.

توصيتي هي القيام بالمخطط الصغير (لاحظ أنني أقول "افعل" وليس "اقرأ") ثم قم بإجراء جميع التمارين في SICP.عندما تنتهي، سيكون لديك عقل مختلف عما كنت عليه عندما بدأت.

والبرمجة الوظيفية <م> يتجنب الدولة و<م> تؤكد وظائف. هناك أبدا أي شيء مثل أي دولة، على الرغم من أن الدولة قد يكون في الواقع شيء أن يكون غير قابل للتغيير أو خبز في الهندسة المعمارية من ما كنت تعمل مع. النظر في الفرق بين خادم الويب ثابت الذي يقوم بتحميل للتو الملفات من نظام الملفات مقابل البرنامج الذي ينفذ مكعب روبيك. السابق سوف يتم تنفيذها من حيث وظائفها تهدف إلى تحويل الطلب إلى طلب مسار الملف إلى رد من محتويات هذا الملف. عمليا لا حاجة لدولة ما وراء القليل من التكوين ( "الدولة" نظام الملفات هو في الحقيقة خارج نطاق البرنامج. البرنامج يعمل بنفس الطريقة بغض النظر عن ما اذكر الملفات في). في الأخير رغم ذلك، كنت بحاجة إلى نموذج المكعب وتنفيذ البرنامج لكيفية العمليات على هذا المكعب تتغير حالته.

وبل هو في الواقع من السهل جدا أن يكون الشيء الذي يبدو وكأنه دولة قابلة للتغيير حتى في اللغات من دون دولة قابلة للتغيير.

والنظر في وظيفة مع نوع s -> (a, s). ترجمة من جملة هاسكل، وهو ما يعني وظيفة التي تأخذ معلمة واحدة من نوع "s" ويعود زوج من القيم، من أنواع "a" و "s". إذا s هو نوع من دولتنا، هذه الدالة تأخذ دولة واحدة وإرجاع دولة جديدة، وربما قيمة (يمكنك دائما العودة "وحدة" الملقب ()، وهو نوع من تعادل "void" في C / C ++، كما و"a" نوع). إذا كنت سلسلة لعدة مكالمات هاتفية من وظائف مع أنواع مثل هذا (الحصول عادت الدولة من وظيفة واحدة وتمريرها إلى أخرى)، لديك دولة "قابلة للتغيير" (في الواقع كنت في كل وظيفة خلق دولة جديدة والتخلي عن القديم ).

قد يكون أسهل للفهم إذا كنت تتخيل دولة قابلة للتغيير باسم "الفضاء" حيث البرنامج يتم تنفيذ، ومن ثم التفكير في البعد الزمني. في لحظة T1، و "الفضاء" هو في حالة معينة (ويقول على سبيل المثال بعض مواقع الذاكرة له قيمة 5). في لحظة T2 لاحق، أنه في حالة مختلفة (على سبيل المثال أن موقع الذاكرة لديها الآن قيمة 10). كل من هذه "شرائح" الوقت هو الدولة، وأنه غير قابل للتغيير (لا يمكنك العودة في الوقت المناسب لتغييرها). لذلك، من وجهة النظر هذه، ذهبت من الزمكان كامل مع الوقت السهم (دولة قابلة للتغيير الخاص بك) لمجموعة من شرائح من الزمكان (عدة ولايات غير قابل للتغيير)، والبرنامج هو مجرد علاج كل شريحة كقيمة وحساب كل منهم كما تطبق وظيفة لسابقتها.

حسنا، ربما لم يكن أسهل للفهم: -)

وقد يبدو inneficient لتمثيل صراحة الدولة بأكملها برنامج كقيمة، والتي لابد من خلق إلا أن تجاهل حظة المقبلة (بعد إنشاء واحدة جديدة). بالنسبة لبعض الخوارزميات قد يكون من الطبيعي، ولكن عندما لا يكون، هناك حيلة أخرى. بدلا من دولة حقيقية، يمكنك استخدام دولة وهمية والتي ليست أكثر من علامة (دعونا ندعو نوع من هذه State# دولة وهمية). وجود هذه الدولة وهمية من وجهة نظر للغة، ويتم تمرير مثل أي قيمة أخرى، ولكن المترجم يغفل تماما عندما توليد رمز الجهاز. أنها تخدم فقط للاحتفال تسلسل التنفيذ.

وعلى سبيل المثال، لنفترض المترجم يعطينا الوظائف التالية:

readRef :: Ref a -> State# -> (a, State#)
writeRef :: Ref a -> a -> State# -> (a, State#)

وترجمة من هذه التصريحات هاسكل مثل يستقبل readRef شيء الذي يشبه مؤشر أو مؤشر إلى قيمة من نوع "a"، والدولة وهمية، وإرجاع قيمة من نوع "a" المشار إليه بواسطة المعلمة الأولى ودولة وهمية جديدة. writeRef مماثل، ولكن التغييرات أشارت إلى قيمة بدلا من ذلك.

إذا كنت استدعاء readRef ثم يسلمها للدولة وهمية إرجاعها بواسطة writeRef (ربما مع دعوات أخرى إلى وظائف لا علاقة لها في الوسط، هذه القيم دولة تخلق "سلسلة" من المكالمات وظيفة)، فإنه سيعود قيمة مكتوب. يمكنك الاتصال writeRef مرة أخرى مع نفس المؤشر / مقبض، وسوف الكتابة إلى موقع ذاكرة نفسه - ولكن، منذ المفهوم إنها تعود ل(وهمية) الدولة الجديدة، و(وهمية) الدولة ما زالت imutable (كانت واحدة جديدة " خلقت"). فإن المترجم استدعاء وظائف في النظام فإنه سيتعين عليها أن ندعو لهم اذا كان هناك متغير حالة الحقيقي الذي كان لا بد من حسابها، ولكن الدولة الوحيدة التي يوجد فيها غير كاملة (قابلة للتغيير) حالة الأجهزة الحقيقية.

و(أولئك الذين يعرفون سوف هاسكل تلاحظ I تبسيط الأمور كثيرا وommited عدة تفاصيل هامة بالنسبة لأولئك الذين يرغبون في معرفة المزيد من التفاصيل، نلقي نظرة على Control.Monad.State من mtl، وفي ST s وIO (الملقب ST RealWorld) الكائنات الدقيقة الاحاديه الخلية .)

وقد نتساءل لماذا دوينانوغرام في مثل هذه طريقة غير مباشرة (بدلا من مجرد وجود دولة قابلة للتغيير في اللغة). الميزة الحقيقية هي أن لديك يتجسد الدولة برنامجك. ما قبل كان ضمنيا (كانت الدولة برنامجك عالمية، والسماح لأشياء مثل العمل على مسافة ) هو الآن صريحة. الوظائف التي لا تتلقى وعودة الدولة لا يمكن تعديله أو تتأثر به؛ انهم "النقي". حتى أفضل، هل يمكن أن يكون المواضيع دولة مستقلة، مع وجود القليل من نوع السحر، ويمكن استخدامها لتضمين حساب الضروري ضمن واحدة النقي، دون أن يجعل من نجس (في الكائن الدقيق الاحادي الخلية ST في هاسكل هو واحد تستخدم عادة لهذه الخدعة ؛ وState# التي ذكرتها أعلاه هو في الحقيقة State# s GHC، وتستخدم من قبل تنفيذه للST وIO الكائنات الدقيقة الاحاديه الخلية)

وبالإضافة إلى الإجابات كبيرة والبعض الآخر يعطي، والتفكير في Integer الطبقات وString في جاوة. مثيلات هذه الفئات هي ثابتة، ولكن هذا لا يجعل الطبقات غير مجدية لمجرد حالات التي لا يمكن تغييرها. ثبات يمنحك بعض السلامة. يمكنك معرفة ما إذا كنت تستخدم سلسلة أو مثيل صحيح كمفتاح لMap، لا يمكن تغيير المفتاح. قارن هذا إلى فئة Date في جاوة:

Date date = new Date();
mymap.put(date, date.toString());
// Some time later:
date.setTime(new Date().getTime());

ولقد قمت بتغيير بصمت مفتاح في الخريطة الخاصة بك! العمل مع كائنات ثابتة، كما هو الحال في البرمجة الوظيفية، والكثير نظافة. انه من الاسهل لسبب ما تحدث آثار جانبية - لا شيء! وهذا يعني أنه من الأسهل للمبرمج، وأيضا من الأسهل بالنسبة للمحسن.

وهذه هي الطريقة التي سوف FORTRAN العمل بدون كتل المشتركة: كنت إرسال الأساليب التي كان القيم التي مرت في والمتغيرات المحلية. هذا كل شيء.

والبرمجة الموجهة للكائنات جلبت لنا دولة والسلوك معا، ولكنها كانت فكرة جديدة للمرة الاولى عندما واجهت ذلك من C ++ 1994.

والجيز، كنت مبرمج وظيفية عندما كنت مهندس ميكانيكي ولم أكن أعرف ذلك!

ونضع في اعتبارنا: اللغات الوظيفية وتورينج كاملة. ولذلك، فإن أي مهمة المفيدة التي من شأنها أن تؤدي في لغة imperitive يمكن القيام به في لغة وظيفية. في نهاية اليوم على الرغم من أنني أعتقد أن هناك ما يمكن أن يقال نهج هجين. لغات مثل F # وكلوجر (وأنا متأكد من الآخرين) تشجيع تصميم عديمي الجنسية، ولكن يسمح للالتحولية عند الضرورة.

وأنت لا يمكن أن يكون لغة وظيفية نقية يمكن أن يكون مفيدا. سيكون هناك دائما مستوى من التحولية أن لديك للتعامل معها، IO هو مثال واحد.

وفكر في اللغات الوظيفية على أنها مجرد أداة أخرى التي تستخدمها. جيدة من أجل أشياء معينة دون غيرها. على سبيل المثال لعبة ما قدمتموه قد لا يكون أفضل طريقة لاستخدام لغة وظيفية، ما لا يقل عن الشاشة سوف يكون لها دولة قابلة للتغيير الذي لا يمكنك أن تفعل أي شيء حول مع FP. الطريقة التي يفكر في المشكلة وسوف النوع من المشاكل التي حل مع FP تكون مختلفة عن تلك التي كنت تستخدم لمع البرمجة حتمية.

وباستخدام الكثير من العودية.

تيك تاك تو في F # (A لغة وظيفية.)

هذا بسيط جدا.يمكنك استخدام أي عدد تريده من المتغيرات في البرمجة الوظيفية...ولكن فقط إذا كانت كذلك محلي المتغيرات (الواردة داخل الوظائف).لذا، ما عليك سوى لف التعليمات البرمجية الخاصة بك في وظائف، وتمرير القيم ذهابًا وإيابًا بين تلك الوظائف (كمعلمات تم تمريرها وقيم تم إرجاعها)... وهذا كل ما في الأمر!

هنا مثال:

function ReadDataFromKeyboard() {
    $input_values = $_POST[];
    return $input_values;
}
function ProcessInformation($input_values) {
    if ($input_values['a'] > 10)
        return ($input_values['a'] + $input_values['b'] + 3);
    else if ($input_values['a'] > 5)
        return ($input_values['b'] * 3);
    else
        return ($input_values['b'] - $input_values['a'] - 7);
}
function DisplayToPage($data) {
    print "Based your input, the answer is: ";
    print $data;
    print "\n";
}

/* begin: */
DisplayToPage (
    ProcessInformation (
        GetDataFromKeyboard()
    )
);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top