هل تجد أنك لا تزال بحاجة إلى متغيرات يمكنك تغييرها، وإذا كان الأمر كذلك فلماذا؟

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

سؤال

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

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

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

values.sum

أو (في حالة عدم تقديم المبلغ)

function collection.sum --> inject(zero, function (v,t) --> t+v )

و

x = if a > b then a else b

أو

n = case s 
  /^\d*$/ : s.to_int
  ''      : 0
  '*'     : a.length
  '?'     : a.length.random
  else    fail "I don't know how many you want"

عندما تحتاج إلى ذلك، ويكون لديك فهم القائمة، والخريطة/التجميع، وما إلى ذلك متاحًا.

هل تجد أنك لا تزال تريد/تحتاج إلى متغيرات قابلة للتغيير في مثل هذه البيئة، وإذا كان الأمر كذلك، فلماذا؟

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

الأمثلة المفضلة لدي حتى الآن (وأفضل اعتراض أتوقعه لهم):

  1. بول جونسون خوارزمية فيشر ييتس الإجابة، وهي قوية جدًا عندما تقوم بتضمين القيود الكبيرة.ولكن بعد ذلك، كما يشير catulahoops، لا ترتبط مشكلة Big-O بسؤال SSA، بل بوجود أنواع بيانات قابلة للتغيير، ومع وضع ذلك جانبًا، يمكن كتابة الخوارزمية بشكل واضح في SSA:

     shuffle(Lst) ->
         array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).
     shuffle(Array, 0) -> Array;
     shuffle(Array, N) ->
         K = random:uniform(N) - 1,
         Ek = array:get(K, Array),
         En = array:get(N, Array),
         shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).
    
  2. jpalecek's مساحة المضلع مثال:

    def area(figure : List[Point]) : Float = {
      if(figure.empty) return 0
      val last = figure(0)
      var first= figure(0)
      val ret = 0
      for (pt <- figure) {
        ret+=crossprod(last - first, pt - first)
        last = pt
      }
      ret
    }
    

    والتي ربما لا تزال مكتوبة بشيء مثل:

    def area(figure : List[Point]) : Float = {
        if figure.length < 3
            0
          else
            var a = figure(0)
            var b = figure(1)
            var c = figure(2)
            if figure.length == 3
                magnitude(crossproduct(b-a,c-a))
              else 
                foldLeft((0,a,b))(figure.rest)) { 
                   ((t,a,b),c) => (t+area([a,b,c]),a,c)
                   }
    

    أو بما أن بعض الناس يعترضون على كثافة هذه الصيغة، فمن الممكن إعادة صياغتها:

    def area([])    = 0.0   # An empty figure has no area
    def area([_])   = 0.0   # ...nor does a point
    def area([_,_]) = 0.0   # ...or a line segment
    def area([a,b,c]) =     # The area of a triangle can be found directly
        magnitude(crossproduct(b-a,c-a))
    def area(figure) =      # For larger figures, reduce to triangles and sum
        as_triangles(figure).collect(area).sum
    
    def as_triangles([])      = []  # No triangles without at least three points
    def as_triangles([_])     = []
    def as_triangles([_,_])   = []
    def as_triangles([a,b,c | rest) = [[a,b,c] | as_triangles([a,c | rest])]
    
  3. تعتبر نقطة برينسيس حول صعوبة تنفيذ قوائم الانتظار O(1) ذات الهياكل غير القابلة للتغيير مثيرة للاهتمام (وقد توفر الأساس لمثال مقنع) ولكن كما ذكرنا، فهي تتعلق بشكل أساسي بقابلية تغيير بنية البيانات، وليس بشكل مباشر حول مسألة التعيينات المتعددة .

  4. أنا مفتون بإجابة غربال إراتوستينس، لكني غير مقتنع.لا يبدو أن المولد الكبير المناسب، اسحب أكبر عدد تريده من الأعداد الأولية الموضح في الورقة التي استشهد بها، من السهل تنفيذه بشكل صحيح مع SSA أو بدونه.


حسنا، شكرا للجميع على المحاولة.نظرًا لأن معظم الإجابات تبين أنها إما 1) تعتمد على هياكل بيانات قابلة للتغيير، وليس على مهمة واحدة، و2) إلى الحد الذي كانت فيه حول نموذج مهمة واحدة يمكن مواجهتها بسهولة من قبل الممارسين المهرة في المجال، سأقوم بما يلي: احذف السطر من حديثي و/أو أعد هيكلته (ربما احتفظ به كموضوع للمناقشة في حالة نفاد الكلمات قبل نفاد الوقت).

شكرًا لك مرة أخرى.

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

المحلول

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

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

نصائح أخرى

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

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

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

-- | Implementation of the random swap algorithm for shuffling.  Reads a list
-- into a mutable ST array, shuffles it in place, and reads out the result
-- as a list.

module Data.Shuffle (shuffle) where


import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.STRef
import System.Random

-- | Shuffle a value based on a random seed.
shuffle :: (RandomGen g) => g -> [a] -> [a]
shuffle _ [] = []
shuffle g xs = 
    runST $ do
      sg <- newSTRef g
      let n = length xs
      v <- newListArray (1, n) xs
      mapM_ (shuffle1 sg v) [1..n]
      getElems v

-- Internal function to swap element i with a random element at or above it.
shuffle1 :: (RandomGen g) => STRef s g -> STArray s Int a -> Int -> ST s ()
shuffle1 sg v i = do
  (_, n) <- getBounds v
  r <- getRnd sg $ randomR (i, n)
  when (r /= i) $ do
    vi <- readArray v i
    vr <- readArray v r
    writeArray v i vr
    writeArray v r vi


-- Internal function for using random numbers
getRnd :: (RandomGen g) => STRef s g -> (g -> (a, g)) -> ST s a
getRnd sg f = do
  g1 <- readSTRef sg
  let (v, g2) = f g1
  writeSTRef sg g2
  return 

الخامس

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

في الوقت نفسه، هناك أسباب لعدم قيامنا جميعًا بكتابة التعليمات البرمجية في نموذج SSA للبدء بها:

  1. عادةً ما يستغرق الأمر المزيد من العبارات (أو المزيد من أسطر التعليمات البرمجية) لكتابة التعليمات البرمجية بهذه الطريقة.الإيجاز له قيمة.
  2. إنها دائمًا أقل كفاءة.نعم، أعلم أنك تتحدث عن لغات أعلى - نطاق عادل - ولكن حتى في عالم Java وC#، بعيدًا عن التجميع، فإن السرعة مهمة.هناك عدد قليل من التطبيقات التي تكون فيها السرعة غير ذات صلة.
  3. ليس من السهل أن نفهم.على الرغم من أن SSA "أبسط" بالمعنى الرياضي، إلا أنها أكثر تجريدًا من المنطق السليم، وهو ما يهم في برمجة العالم الحقيقي.إذا كان عليك أن تكون ذكيًا حقًا لتتمكن من ذلك، فلا مكان له في البرمجة بشكل عام.

حتى في الأمثلة المذكورة أعلاه، من السهل إحداث ثقوب.خذ لك case إفادة.ماذا لو كان هناك خيار إداري يحدد ما إذا كان '*' مسموح به، وواحد منفصل عما إذا كان '?' مسموح؟أيضًا، لا يُسمح بالصفر في حالة الأعداد الصحيحة، إلا إذا كان لدى المستخدم إذن نظام يسمح بذلك.

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

أعتقد أنك ستجد أن اللغات الأكثر إنتاجية تسمح لك بالمزج بين الأساليب الوظيفية والضرورية، مثل OCaml وF#.

في معظم الحالات، يمكنني كتابة تعليمات برمجية تكون ببساطة عبارة عن سطر طويل من "map x to y، تقليل y إلى z".في 95% من الحالات، تعمل البرمجة الوظيفية على تبسيط التعليمات البرمجية الخاصة بي، ولكن هناك منطقة واحدة تظهر فيها الثبات أنيابها:

التباين الكبير بين سهولة التنفيذ والمكدس غير القابل للتغيير وقائمة الانتظار غير القابلة للتغيير.

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

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

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

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


فيما يتعلق بمساحة المضلع، من السهل تحويله إلى شكل وظيفي.لنفترض أن لدينا وحدة Vector مثل هذا:

module Vector =
    type point =
        { x : float; y : float}
        with
            static member ( + ) ((p1 : point), (p2 : point)) =
                { x = p1.x + p2.x;
                  y = p1.y + p2.y;}

            static member ( * ) ((p : point), (scalar : float)) =
                { x = p.x * scalar;
                  y = p.y * scalar;}

            static member ( - ) ((p1 : point), (p2 : point)) = 
                { x = p1.x - p2.x;
                  y = p1.y - p2.y;}

    let empty = { x = 0.; y = 0.;}
    let to_tuple2 (p : point) = (p.x, p.y)
    let from_tuple2 (x, y) = { x = x; y = y;}
    let crossproduct (p1 : point) (p2 : point) =
        { x = p1.x * p2.y; y = -p1.y * p2.x }

يمكننا تحديد دالة المساحة الخاصة بنا باستخدام القليل من سحر الصف:

let area (figure : point list) =
    figure
    |> Seq.map to_tuple2
    |> Seq.fold
        (fun (sum, (a, b)) (c, d) -> (sum + a*d - b*c, (c, d) ) )
        (0., to_tuple2 (List.hd figure))
    |> fun (sum, _) -> abs(sum) / 2.0

أو يمكننا استخدام حاصل الضرب الاتجاهي بدلًا من ذلك

let area2 (figure : point list) =
    figure
    |> Seq.fold
        (fun (acc, prev) cur -> (acc + (crossproduct prev cur), cur))
        (empty, List.hd figure)
    |> fun (acc, _) -> abs(acc.x + acc.y) / 2.0

لا أجد أي وظيفة غير قابلة للقراءة.

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

 shuffle(Lst) ->
     array:to_list(shuffle(array:from_list(Lst), erlang:length(Lst) - 1)).

 shuffle(Array, 0) -> Array;
 shuffle(Array, N) ->
     K = random:uniform(N) - 1,
     Ek = array:get(K, Array),
     En = array:get(N, Array),
     shuffle(array:set(K, En, array:set(N, Ek, Array)), N-1).

إذا كانت كفاءة عمليات المصفوفة هذه مصدر قلق، فهذا سؤال حول هياكل البيانات القابلة للتغيير وليس له علاقة بمهمة واحدة.

لن تحصل على إجابة لهذا السؤال لعدم وجود أمثلة.إنها مجرد مسألة الإلمام بهذا الأسلوب.

رداً على جايسون..

function forbidden_input?(s)
    (s = '?' and not administration.qmark_ok) ||
    (s = '*' and not administration.stat_ok)  ||
    (s = '0' and not 'root node visible' in system.permissions_for(current_user))

n = if forbidden_input?(s)
    fail "'" + s + "' is not allowed."
  else
    case s
      /^\d*$/ : s.to_int
      ''      : 0
      '*'     : a.length
      '?'     : a.length.random
      else    fail "I don't know how many you want"

سأفتقد المهام بلغة غير وظيفية بحتة.في الغالب لأنها تعيق فائدة الحلقات.أمثلة (سكالا):

def quant[A](x : List[A], q : A) = {
  var tmp : A=0
  for (el <- x) { tmp+= el; if(tmp > q) return el; }
  // throw exception here, there is no prefix of the list with sum > q
}

يجب أن يحسب هذا الكمية من القائمة، لاحظ المجمع tmp الذي تم تعيينه لعدة مرات.

مثال مماثل سيكون:

def area(figure : List[Point]) : Float = {
  if(figure.empty) return 0
  val last = figure(0)
  var first= figure(0)
  val ret = 0
  for (pt <- figure) {
    ret+=crossprod(last - first, pt - first)
    last = pt
  }
  ret
}

لاحظ في الغالب last عامل.

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

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

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

إذا كان الأمر صعبًا، فقد لا يكون كذلك.إذن، لن يحالفك الحظ من حيث الأداء والذاكرة، إلا إذا كان بإمكانك التكرار واستخدام متغير قابل للتغيير.

بفضل أطروحة تشيرش-تورينج، نعلم أن أي شيء يمكن كتابته بلغة تورينج الكاملة يمكن كتابته بلغة تورينج. أي لغة تورينج كاملة.لذلك، عندما تتعمق في الأمر، لا يوجد شيء لا يمكنك فعله في Lisp ولا يمكنك فعله في C#، إذا حاولت بجد بما فيه الكفاية، أو العكس.(الأهم من ذلك، سيتم تجميع أي منهما وصولاً إلى لغة الآلة x86 في معظم الحالات على أي حال.)

لذا فإن الجواب على سؤالك هو:لا توجد مثل هذه الحالات.كل ذلك هناك حالات يسهل على البشر فهمها في نموذج/لغة أو أخرى - وترتبط سهولة الفهم هنا بالتدريب والخبرة.

ربما تكون المشكلة الرئيسية هنا هي أسلوب التكرار في اللغة.في اللغات التي نستخدم فيها العودية، أي قيم تتغير على مدار الحلقة يتم إعادة ربطها عند استدعاء الدالة مرة أخرى.اللغات التي تستخدم التكرارات في الكتل (على سبيل المثال، Smalltalk's وRuby's inject الطريقة) تميل إلى أن تكون متشابهة، على الرغم من أن العديد من الأشخاص في روبي ما زالوا يستخدمونها each ومتغير قابل للتغيير inject.

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

يعد العمل في هاسكل طريقة جيدة حقًا للتحقق من ضرورة المتغيرات القابلة للتغيير، نظرًا لأن الافتراضي غير قابل للتغيير ولكن المتغيرات القابلة للتغيير متاحة (مثل IORefs, MVars, ، وما إلى ذلك وهلم جرا).لقد قمت مؤخرًا "بالتحقيق" بهذه الطريقة بنفسي، وتوصلت إلى الاستنتاجات التالية.

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

  2. بالنسبة للاتصالات بين الخيوط، تعد المتغيرات القابلة للتغيير ضرورية، لأسباب واضحة إلى حد ما.(وهذا خاص بهاسكل؛أنظمة وقت التشغيل التي تستخدم تمرير الرسائل عند أدنى مستوى لا تحتاج إليها بالطبع.) ومع ذلك، فإن هذا الاستخدام نادر بدرجة كافية لدرجة أنه يتعين عليك استخدام الوظائف لقراءتها وكتابتها (readIORef fooRef val الخ) لا يشكل عبئا كبيرا.

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

لذا، فقد وقفت هذه الأيام بثبات إلى جانب المتغيرات غير القابلة للتغيير.

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

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

لم أفكر كثيرًا في هذا الأمر إلا الآن بعد أن أشرت إليه.

في الواقع أحاول عدم استخدام مهام متعددة دون وعي.

إليك مثال على ما أتحدث عنه، في بيثون

start = self.offset%n
if start:
    start = n-start

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

لن أفوّت مهمة متعددة على الإطلاق.

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

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

الكثير من هذه الأسئلة تتأثر بذوق المبرمج وما يستخدمه في البرمجة.لذلك سأركز على أحد الجوانب التي عادة ما تكون محور معظم الحجج المؤيدة للثبات - التوازي:

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

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

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

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top