سؤال

أحاول فهم مفهوم الاستمرارية ووجدت العديد من الأمثلة التعليمية الصغيرة مثل هذا من مقالة ويكيبيديا:

(define the-continuation #f)

(define (test)
  (let ((i 0))
    ; call/cc calls its first function argument, passing 
    ; a continuation variable representing this point in
    ; the program as the argument to that function. 
    ;
    ; In this case, the function argument assigns that
    ; continuation to the variable the-continuation. 
    ;
    (call/cc (lambda (k) (set! the-continuation k)))
    ;
    ; The next time the-continuation is called, we start here.
    (set! i (+ i 1))
    i))

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

لذلك أنا أبحث عن نماذج تعليمات برمجية مفيدة بشكل أكثر وضوحًا لما يمكن أن تقدمه لي الاستمرارات كمبرمج.

هتافات!

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

المحلول

في Algo & Data II استخدمنا هذه الأشياء في جميع الأوقات "للخروج" أو "العودة" من دالة (طويلة)

على سبيل المثال، تم تنفيذ خوارزمية BFS لاجتياز الأشجار على النحو التالي:

(define (BFS graph root-discovered node-discovered edge-discovered edge-bumped . nodes)
  (define visited (make-vector (graph.order graph) #f))
  (define q (queue.new))
  (define exit ())
  (define (BFS-tree node)
    (if (node-discovered node)
      (exit node))
    (graph.map-edges
     graph
     node
     (lambda (node2)
       (cond ((not (vector-ref visited node2))
              (when (edge-discovered node node2)
                (vector-set! visited node2 #t)
                (queue.enqueue! q node2)))
             (else
              (edge-bumped node node2)))))
    (if (not (queue.empty? q))
      (BFS-tree (queue.serve! q))))

  (call-with-current-continuation
   (lambda (my-future)
     (set! exit my-future)
     (cond ((null? nodes)
            (graph.map-nodes
             graph
             (lambda (node)
               (when (not (vector-ref visited node))
                 (vector-set! visited node #t)
                 (root-discovered node)
                 (BFS-tree node)))))
           (else
            (let loop-nodes
              ((node-list (car nodes)))
              (vector-set! visited (car node-list) #t)
              (root-discovered (car node-list))
              (BFS-tree (car node-list))
              (if (not (null? (cdr node-list)))
                (loop-nodes (cdr node-list)))))))))

كما ترون، سيتم الخروج من الخوارزمية عندما تعود الدالة المكتشفة للعقدة إلى القيمة الصحيحة:

    (if (node-discovered node)
      (exit node))

ستعطي الوظيفة أيضًا "قيمة الإرجاع":'العقدة'

سبب خروج الدالة هو هذا البيان:

(call-with-current-continuation
       (lambda (my-future)
         (set! exit my-future)

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

لذلك، يتم استخدام call-cc (هنا) للانتقال من دالة العودية، بدلاً من انتظار انتهاء العودية بأكملها من تلقاء نفسها (وهو ما قد يكون مكلفًا للغاية عند القيام بالكثير من الأعمال الحسابية)

مثال أصغر آخر يفعل الشيء نفسه مع call-cc:

(define (connected? g node1 node2)
  (define visited (make-vector (graph.order g) #f))
  (define return ())
  (define (connected-rec x y)
    (if (eq? x y)
      (return #t))
    (vector-set! visited x #t)
    (graph.map-edges g
                     x
                     (lambda (t)
                       (if (not (vector-ref visited t))
                         (connected-rec t y)))))
  (call-with-current-continuation
   (lambda (future)
     (set! return future)
     (connected-rec node1 node2)
     (return #f))))

نصائح أخرى

@تربيتة

شاطئ البحر

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

WAComponent >> call: aComponent
    "Pass control from the receiver to aComponent. The receiver will be
    temporarily replaced with aComponent. Code can return from here later
    on by sending #answer: to aComponent."

    ^ AnswerContinuation currentDo: [ :cc |
        self show: aComponent onAnswer: cc.
        WARenderNotification raiseSignal ]

لطيف جدا!

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

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

يتم استخدام الاستمرارية بواسطة بعض خوادم الويب وأطر الويب لتخزين معلومات الجلسة.يتم إنشاء كائن استمرار لكل جلسة ثم يتم استخدامه بواسطة كل طلب داخل الجلسة.

هناك مقال حول هذا النهج هنا .

لقد صادفت تنفيذًا لـ amb عامل في هذا المشنور من http://www.randomhacks.net, باستخدام الاستمرارية.

وهنا ما amb يقوم المشغل بما يلي:

# amb will (appear to) choose values
# for x and y that prevent future
# trouble.
x = amb 1, 2, 3
y = amb 4, 5, 6

# Ooops! If x*y isn't 8, amb would
# get angry.  You wouldn't like
# amb when it's angry.
amb if x*y != 8

# Sure enough, x is 2 and y is 4.
puts x, y 

وهنا تنفيذ المنشور:

# A list of places we can "rewind" to
# if we encounter amb with no
# arguments.
$backtrack_points = []

# Rewind to our most recent backtrack
# point.
def backtrack
  if $backtrack_points.empty?
    raise "Can't backtrack"
  else
    $backtrack_points.pop.call
  end
end

# Recursive implementation of the
# amb operator.
def amb *choices
  # Fail if we have no arguments.
  backtrack if choices.empty?
  callcc {|cc|
    # cc contains the "current
    # continuation".  When called,
    # it will make the program
    # rewind to the end of this block.
    $backtrack_points.push cc

    # Return our first argument.
    return choices[0]
  }

  # We only get here if we backtrack
  # using the stored value of cc,
  # above.  We call amb recursively
  # with the arguments we didn't use.
  amb *choices[1...choices.length]
end

# Backtracking beyond a call to cut
# is strictly forbidden.
def cut
  $backtrack_points = []
end

انا يعجبني amb!

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

تعتبر الاستمرارات بديلاً جيدًا لسلسلة المحادثات لكل طلب في برمجة الخادم (بما في ذلك الواجهات الأمامية لتطبيقات الويب.

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

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

يعد عامل التشغيل amb مثالًا جيدًا يسمح بالبرمجة التعريفية المشابهة للبروغ.

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

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

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

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

هذا مثال يظهر حالة بسيطة جدا.

map.getZoomAsync(function(zoom) {
    alert("Current zoom level is " + zoom); // this is the continuation
});  
alert("This might happen before or after you see the zoom level message");

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

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

يمكن استخدام الاستمرارات لتنفيذ الاستثناءات، ومصحح الأخطاء.

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