سؤال
أحاول فهم دلالات الاتصال/CC في المخطط ، وتظهر صفحة ويكيبيديا على الاستمرارية لغز Yin-Yang كمثال:
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))) )
(yin yang))
يجب أن يخرج @*@**@***@****@...
, ، لكني لا أفهم لماذا ؛ كنت أتوقع إخراجها @*@*********
...
هل يمكن لأي شخص أن يشرح بالتفصيل لماذا يعمل لغز Yin-Yang بالطريقة التي يعمل بها؟
المحلول
لا أعتقد أنني أفهم هذا الأمر تمامًا ، لكن لا يمكنني التفكير إلا في واحد (الى ابعد حد توضيح يدوي) شرح لهذا:
- يتم طباعة أول @ و *
yin
وyang
يتم ربطها لأول مرة فيlet*
.(yin yang)
يتم تطبيقه ، ويعود إلى الأعلى ، مباشرة بعد الانتهاء من المكالمة الأولى/CC. - تتم طباعة @ و * التالي ، ثم يتم طباعة آخر * لأنه هذه المرة ،
yin
يتم إعادة ربطها بقيمة المكالمة الثانية/CC. (yin yang)
يتم تطبيقه مرة أخرى ، ولكن هذه المرة إنها تنفذ في الأصلyang
بيئة, ، أينyin
مرتبط بالمكالمة الأولى/CC ، لذلك يعود التحكم إلى طباعة @آخر. الyang
تحتوي الحجة على الاستمرار الذي تم إعادة تكوينه في الممر الثاني ، والذي سيؤدي ذلك كما رأينا بالفعل ، إلى الطباعة**
. لذلك في هذا الممر الثالث ،@*
سيتم طباعتها ، ثم يتم استمرار استمرار طباعة النجوم المزدوجة هذا ، لذلك ينتهي به الأمر 3 نجوم ، ثم يتم إعادة تكوين استمرار النجوم الثلاثي ، ...
نصائح أخرى
فهم مخطط
أعتقد أن نصف المشكلة على الأقل في فهم هذا اللغز هو بناء جملة المخطط ، والذي لا يعرفه معظمهم.
بادئ ذي بدء ، أجد شخصيا call/cc x
أن تكون أكثر صعوبة في الفهم من البديل المكافئ ، x get/cc
. لا يزال المكالمات x ، اجتيازها الاستمرار الحالي, ، لكن بطريقة ما يكون أكثر قابلية للتمثيل في دوائر عقلي.
مع وضع ذلك في الاعتبار ، البناء (call-with-current-continuation (lambda (c) c))
يصبح ببساطة get-cc
. نحن الآن وصولنا إلى هذا:
(let* ((yin
((lambda (cc) (display #\@) cc) get-cc))
(yang
((lambda (cc) (display #\*) cc) get-cc)) )
(yin yang))
والخطوة التالية هي جسم Lambda الداخلي. (display #\@) cc
, ، في بناء الجملة الأكثر دراية (بالنسبة لي ، على أي حال) print @; return cc;
. بينما نحن في ذلك ، دعنا نعيد كتابة أيضًا lambda (cc) body
كما function (arg) { body }
, ، قم بإزالة مجموعة من الأقواس ، وتغيير مكالمات الوظائف إلى بناء جملة C-like ، للحصول على هذا:
(let* yin =
(function(arg) { print @; return arg; })(get-cc)
yang =
(function(arg) { print *; return arg; })(get-cc)
yin(yang))
لقد بدأ الأمر أكثر منطقية الآن. إنها الآن خطوة صغيرة لإعادة كتابة هذا تمامًا في بناء جملة تشبه C (أو يشبه JavaScript ، إذا كنت تفضل) ، للحصول على هذا:
var yin, yang;
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);
انتهى الجزء الأصعب الآن ، لقد قمنا بفك تشفير هذا من المخطط! أنا فقط أمزح؛ كان الأمر صعبًا لأنه لم يكن لدي أي خبرة سابقة في المخطط. لذلك ، دعنا نتعرف على كيفية عمل هذا بالفعل.
تمهيدي على الاستمرارية
راقب جوهر Yin و Yang بشكل غريب: يحدد وظيفة ثم يدعوها على الفور. يبدو مثل (function(a,b) { return a+b; })(2, 3)
, والتي يمكن تبسيطها 5
. لكن تبسيط المكالمات داخل Yin/Yang سيكون خطأً ، لأننا لا نمررها قيمة عادية. نحن نمر الوظيفة أ استمرار.
الاستمرار هو وحش غريب من النظرة الأولى. النظر في البرنامج أبسط بكثير:
var x = get-cc;
print x;
x(5);
بدءًا x
تم تعيينه على كائن الاستمرار الحالي (دب معي) ، و print x
يتم تنفيذها ، طباعة شيء مثل <ContinuationObject>
. حتى الان جيدة جدا.
لكن الاستمرار يشبه الوظيفة ؛ يمكن استدعاؤه بحجة واحدة. ما تفعله هو: خذ الحجة ، ثم القفز إلى أينما تم إنشاء هذا الاستمرار ، واستعادة كل السياق ، وجعله كذلك get-cc
يعيد هذه الحجة.
في مثالنا ، الحجة هي 5
, ، لذلك نحن نقفز بشكل أساسي إلى منتصف ذلك var x = get-cc
بيان ، فقط هذه المرة get-cc
عائدات 5
. لذا x
يصبح 5
, ويمضي البيان التالي للطباعة 5. بعد ذلك نحاول الاتصال 5(5)
, ، وهو خطأ في النوع ، ويتعطل البرنامج.
لاحظ أن استدعاء الاستمرار هو أ القفز, ، وليس مكالمة. لا يعود أبدًا إلى حيث تم استمرار الاستمرار. هذا مهم.
كيف يعمل البرنامج
إذا اتبعت ذلك ، فلا ترفع آمالك: هذا الجزء هو الأصعب. إليك برنامجنا مرة أخرى ، حيث أسقط الإعلانات المتغيرة لأن هذا هو رمز زائف على أي حال:
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);
يتم ضرب السطر الأول 1 و 2 ، فهي بسيطة الآن: احصل على الاستمرار ، واتصل بالوظيفة (ARG) ، والطباعة @
, ، عودة ، تخزين هذا الاستمرار في yin
. نفس الشيء مع yang
. لقد طبعنا الآن @*
.
بعد ذلك ، نسمي الاستمرار في yin
, ، تمريره yang
. هذا يجعلنا نقفز إلى السطر 1 ، داخل هذا GET-CC ، وجعله يعود yang
في حين أن. قيمة ال yang
يتم تمريره الآن إلى الوظيفة التي تطبع @
, ثم يعيد قيمة yang
. حاليا yin
تم تعيين ذلك الاستمرار yang
لديها. بعد ذلك ، ننتقل فقط إلى السطر 2: الحصول على C/C ، طباعة *
, ، تخزين C/C في yang
. لدينا الآن @*@*
. وأخيرا ، نذهب إلى السطر 3.
تذكر ذلك yin
الآن لديه استمرار عندما تم تنفيذ السطر 2 لأول مرة. لذلك نقفز إلى السطر 2 ، طباعة ثانية *
والتحديث yang
. لدينا الآن @*@**
. أخيرًا ، اتصل yin
استمرار مرة أخرى ، والتي ستقفز إلى السطر 1 ، طباعة أ @
. وهلم جرا. بصراحة ، في هذه المرحلة ، يرمي عقلي استثناءًا خارجًا ويفقد كل شيء. لكن على الأقل وصلنا إلى @*@**
!
من الصعب اتباع هذا ، بشكل أكثر صعوبة ، من الواضح. الطريقة المثالية للقيام بذلك هي أن تتجول في أحد الأخطاء التي يمكن أن تمثل التواصل ، لكن للأسف ، لا أعرف أيًا منها. أتمنى أن تكون قد استمتعت بهذا ؛ أنا بالتأكيد.
تأملات أولاً ، الإجابة المحتملة في النهاية.
أعتقد أن الكود يمكن إعادة كتابته مثل هذا:
; call (yin yang)
(define (yy yin yang) (yin yang))
; run (call-yy) to set it off
(define (call-yy)
(yy
( (lambda (cc) (display #\@) cc) (call/cc (lambda (c) c)) )
( (lambda (cc) (display #\*) cc) (call/cc (lambda (c) c)) )
)
)
أو مع بعض عبارات العرض الإضافية للمساعدة في معرفة ما يحدث:
; create current continuation and tell us when you do
(define (ccc)
(display "call/cc=")
(call-with-current-continuation (lambda (c) (display c) (newline) c))
)
; call (yin yang)
(define (yy yin yang) (yin yang))
; run (call-yy) to set it off
(define (call-yy)
(yy
( (lambda (cc) (display "yin : ") (display #\@) (display cc) (newline) cc)
(ccc) )
( (lambda (cc) (display "yang : ") (display #\*) (display cc) (newline) cc)
(ccc) )
)
)
او مثل هذا:
(define (ccc2) (call/cc (lambda (c) c)) )
(define (call-yy2)
(
( (lambda (cc) (display #\@) cc) (ccc2) )
( (lambda (cc) (display #\*) cc) (ccc2) )
)
)
الإجابة المحتملة
قد لا يكون هذا صحيحًا ، لكنني سأذهب.
أعتقد أن النقطة الأساسية هي أن استمرار "يسمى" يعيد المكدس إلى بعض الحالة السابقة - كما لو لم يحدث شيء آخر. بالطبع لا يعلم أننا نراقبها من خلال العرض @
و *
الشخصيات.
نحدد في البداية yin
لتكون استمرارا A
هذا سيفعل هذا:
1. restore the stack to some previous point
2. display @
3. assign a continuation to yin
4. compute a continuation X, display * and assign X to yang
5. evaluate yin with the continuation value of yang - (yin yang)
ولكن إذا اتصلنا yang
استمرار ، هذا يحدث:
1. restore the stack to some point where yin was defined
2. display *
3. assign a continuation to yang
4. evaluate yin with the continuation value of yang - (yin yang)
نبدأ هنا.
أول مرة من خلالك تحصل عليها yin=A
و yang=B
كما yin
و yang
يتم تهيئة.
The output is @*
(كلاهما A
و B
يتم حساب الاستمرارية.)
حاليا (yin yang)
يتم تقييمه على النحو (A B)
لأول مرة.
نحن نعرف ماذا A
يفعل. يفعل هذا:
1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B, we don't compute it.
4. compute another continuation B', display * and assign B' to yang
The output is now @*@*
5. evaluate yin (B) with the continuation value of yang (B')
حاليا (yin yang)
يتم تقييمه على النحو (B B')
.
نحن نعرف ماذا B
يفعل. يفعل هذا:
1. restore the stack - back to the point where yin was already initialised.
2. display *
3. assign a continuation to yang - this time, it is B'
The output is now @*@**
4. evaluate yin with the continuation value of yang (B')
منذ استعادة المكدس إلى حد yin=A
, (yin yang)
يتم تقييمه على النحو (A B')
.
نحن نعرف ماذا A
يفعل. يفعل هذا:
1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B', we don't compute it.
4. compute another continuation B", display * and assign B" to yang
The output is now @*@**@*
5. evaluate yin (B') with the continuation value of yang (B")
نحن نعرف ماذا B'
يفعل. يفعل هذا:
1. restore the stack - back to the point where yin=B.
2. display *
3. assign a continuation to yang - this time, it is B"
The output is now @*@**@**
4. evaluate yin (B) with the continuation value of yang (B")
حاليا (yin yang)
يتم تقييمه على النحو (B B")
.
نحن نعرف ماذا B
يفعل. يفعل هذا:
1. restore the stack - back to the point where yin=A and yang were being initialised.
2. display *
3. assign a continuation to yang - this time, it is B'"
The output is now @*@**@***
4. evaluate yin with the continuation value of yang (B'")
منذ استعادة المكدس إلى حد yin=A
, (yin yang)
يتم تقييمه على النحو (A B'")
.
.......
أعتقد أن لدينا نمط الآن.
في كل مرة ندعو (yin yang)
نحن نحلق من خلال كومة من B
استمرارية حتى نعود إلى متى yin=A
ونحن نعرض @
. نحن حلقة من خلال كومة B
الاستمرارية كتابة أ *
كل مرة.
(سأكون سعيدًا حقًا إذا كان هذا صحيحًا تقريبًا!)
شكرا على السؤال.
Yinyang اللغز مكتوبة في مخطط. أفترض أنك تعرف بناء الجملة الأساسي للمخطط.
لكني أفترض أنك لا تعرف let*
أو call-with-current-continuation
, ، سأشرح هاتين الكلمة الرئيسية.
يشرح let*
إذا كنت تعرف ذلك بالفعل ، فيمكنك تخطيه Explain call-with-current-continuation
let*
, الذي يبدو let
, ، أعمال مثل let
, ، ولكن سيقوم بتقييم متغيراتها المحددة ( (yin ...)
و (yang ...)
) واحد تلو الآخر وبشغف. هذا يعني أنه سيتم تقييمه أولاً yin
, ، و بعد yang
.
يمكنك قراءة المزيد هنا:باستخدام مخطط LET IN
يشرح call-with-current-continuation
إذا كنت تعرف ذلك بالفعل ، فيمكنك تخطيه Yin-Yang puzzle
.
من الصعب بعض الشيء شرح call-with-current-continuation
. لذلك سأستخدم استعارة لشرح ذلك.
صورة معالج كان يعرف تعويذة ، والتي كانت call-with-current-continuation
. بمجرد أن يلقي التعويذة ، فإنه سيخلق عالمًا جديدًا ، ويرسله إلى نفسه. لكنه يستطيع لا تفعل شيئا في الكون الجديد ولكن في انتظار شخص ما يتصل باسمه. ذات مرة تم استدعاؤه, ، سيعود المعالج إلى الكون الأصلي ، وامتلاك الرجل المسكين - "شخص ما" - في متناول اليد ، ويذهب في حياته المعالج. إذا لم يتم استدعاؤه ، عندما انتهى الكون الجديد ، عاد المعالج أيضًا إلى الكون الأصلي.
حسنًا ، لنكن أكثر تقنية.
call-with-current-continuation
هي وظيفة تقبل وظيفة كمعلمة. بمجرد الاتصال call-with-current-continuation
مع وظيفة F
, ، سوف يحزم بيئة الجري الحالية ، والتي تسمى current-continuation
, كمعلمة C
, ، وأرسلها للعمل F
, وتنفيذ F
. لذلك يصبح البرنامج بأكمله (F C)
. أو أن تكون أكثر جافا سكريبت: F(C);
. C
يتصرف مثل وظيفة. إذا C
لا يسمى في F
, ، ثم هو برنامج عادي ، متى F
عائدات، call-with-current-continuation
له قيمة F
قيمة الإرجاع. لكن اذا C
يسمى مع معلمة V
, ، سوف يغير البرنامج بأكمله مرة أخرى. يتغير البرنامج إلى أ حالة متى call-with-current-continuation
تم استدعاؤه. لكن الآن call-with-current-continuation
تعطي قيمة ، وهي V
. ويستمر البرنامج.
لنأخذ مثالاً.
(define (f return)
(return 2)
3)
(display (f whatever)) ;; 3
(display (call-with-current-continuation f)) ;; 2
(display (call-with-current-continuation (lambda (x) 4))) ;; 4
الأول display
انتاج 3
, من السبب.
لكن الثانية display
انتاج 2
. لماذا ا؟
دعنا نغطس فيه.
عند التقييم (display (call-with-current-continuation f))
, ، سيتم تقييمه أولاً (call-with-current-continuation f)
. نحن نعلم أنه سيغير البرنامج بأكمله
(f C)
النظر في تعريف f
, لديها (return 2)
. يجب علينا تقييم (C 2)
. هذا عندما continuation
يطلق عليها. لذلك يغير البرنامج بأكمله مرة أخرى
(display (call-with-current-continuation f))
لكن الآن، call-with-current-continuation
له قيمة 2
. لذلك يصبح البرنامج:
(display 2)
اللغز يين يانغ
دعونا نلقي نظرة على اللغز.
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))
(yin yang))
لنجعلها أكثر قابلية للقراءة.
(define (id c) c)
(define (f cc) (display #\@) cc)
(define (g cc) (display #\*) cc)
(let* ((yin
(f (call-with-current-continuation id)))
(yang
(g (call-with-current-continuation id))))
(yin yang))
دعنا ندير البرنامج في دماغنا.
الجولة 0
let*
اجعلنا نتقييم yin
أول. yin
هو
(f (call-with-current-continuation id))
لذلك نحن نقيم (call-with-current-continuation id)
أول. يحزم البيئة الحالية التي نسميها C_0
للتمييز مع الاستمرار الآخر في الخط الزمني ، ويدخل في عالم جديد تمامًا: id
. ولكن id
فقط يعود C_0
.
يجب أن نتذكر ماذا C_0
هو. C_0
هو برنامج مثل هذا:
(let* ((yin
(f ###))
(yang
(g (call-with-current-continuation id))))
(yin yang))
###
هو عنصر نائب ، والذي سيتم ملؤه في المستقبل بالقيمة C_0
استعادة.
ولكن id
فقط يعود C_0
. لا يتصل C_0
. إذا استدعاء ، فسوف ندخل C_0
عالم. لكن هذا لم يفعل ذلك ، لذلك نواصل التقييم yin
.
(f C_0) ;; yields C_0
f
هي وظيفة مثل id
, ، ولكن له تأثير جانبي - الإخراج @
.
لذا فإن إخراج البرنامج @
ودع yin
أن تكون C_0
. الآن يصبح البرنامج
(let* ((yin C_0)
(yang
(g (call-with-current-continuation id))))
(yin yang))
بعد، بعدما yin
تقييم ، نبدأ في التقييم yang
. yang
هو
(g (call-with-current-continuation id))
call-with-current-continuation
هنا إنشاء استمرار آخر ، اسمه C_1
. C_1
هو:
(let* ((yin C_0)
(yang
(g ###)))
(yin yang))
###
هو عنصر نائب. لاحظ أنه في هذا الاستمرار ، yin
يتم تحديد قيمة S (هذا ما let*
فعل). نحن على يقين من ذلك yin
قيمة C_0
هنا.
حيث (id C_1)
هو C_1
, ، لذا yang
قيمة
(g C_1)
g
له تأثير جانبي - الإخراج *
. لذلك البرنامج يفعل.
yang
قيمة s الآن C_1
.
الآن ، عرضنا @*
لذلك يصبح الآن:
(let* ((yin C_0)
(yang C_1))
(yin yang))
كلاهما yin
و yang
يتم حلها ، يجب علينا التقييم (yin yang)
. إنه
(C_0 C_1)
المقدسة ش*ر!
ولكن أخيرًا ، C_0
يسمى. لذلك نطير إلى C_0
الكون وننسى كل شيء عن هذه sh*ts. لن نعود أبدًا إلى هذا الكون مرة أخرى.
الجولة 1
C_0
خذ مع C_1
الى الخلف. يصبح البرنامج الآن (إذا نسيت ماذا C_0
تعود إلى ، عد لرؤيتها):
(let* ((yin
(f C_1))
(yang
(g (call-with-current-continuation id))))
(yin yang))
آه ، نجد ذلك yin
لا يتم تحديد قيمة S بعد. لذلك نحن نقيمها. في عملية التقييم yin
, ، نخرج أ @
كما f
تأثير جانبي. ونحن نعرف yin
هو C_1
حاليا.
نبدأ في التقييم yang
, ، جئنا عبر call-with-current-continuation
تكرارا. نحن نمارس. نخلق استمرار C_2
و التي تعني:
(let* ((yin C_1)
(yang
(g ###)))
(yin yang))
ونحن نعرض أ *
كما g
التنفيذ. ونحن نأتي إلى هنا
(let* ((yin C_1)
(yang C_2))
(yin yang))
لذلك حصلنا على:
(C_1 C_2)
أنت تعرف إلى أين نحن ذاهبون. نحن ذاهبون إلى C_1
عالم. نتذكر ذلك من الذاكرة (أو نسخ ولصق من صفحة الويب). هو الآن:
(let* ((yin C_0)
(yang
(g C_2)))
(yin yang))
نحن نعرف في C_1
عالم ، yin
تم تحديد قيمة. لذلك نبدأ في التقييم yang
. كما نمارسنا ، سأخبرك مباشرة أنه يعرض *
ويصبح:
(C_0 C_2)
الآن لقد طبعنا @*@**
, ، ونحن ذاهبون إلى C_0
الكون يأخذ مع C_2
.
الجولة 2
كما نمارسنا ، سأخبرك أننا نعرض "@" ، yin
هو C_2
, ، ونخلق استمرارًا جديدًا C_3
, ، و التي تعني:
(let* ((yin C_2)
(yang
(g ###)))
(yin yang))
ونحن نعرض *
, yang
هو C_3
, ، ويصبح
(C_2 C_3)
ويمكننا الاستمرار. لكنني سأتوقف هنا ، لقد أوضحت لك ما هي أول مخرجات Yin-Yang Puzzle.
لماذا عدد *
يزيد؟
الآن رأسك مليء بالتفاصيل. سأجعل ملخصًا لك.
سأستخدم بناء الجملة مثل Haskell لتبسيط. و cc
هو اختصار ل call-with-current-continuation
.
متي #C_i#
هو قوسين من قبل #
, ، هذا يعني أن الاستمرار هو إنشاء هنا. ;
يعني الإخراج
yin = f cc id
yang = g cc id
yin yang
---
yin = f #C_0# ; @
yang = g cc id
yin yang
---
yin = C_0
yang = g #C_1# ; *
yin yang
---
C_0 C_1
---
yin = f C_1 ; @
yang = g #C_2# ; *
yin yang
---
C_1 C_2
---
yin = C_0
yang = g C_2 ; *
yin yang
---
C_0 C_2
---
yin = f C_2 ; @
yang = g #C_3#; *
yin yang
---
C_2 C_3
---
yin = C_1
yang = g C_3 ; *
yin yang
---
C_1 C_3
---
yin = C_0
yang = g C_3 ; *
yin yang
---
C_0 C_3
إذا لاحظت بعناية ، فسيكون ذلك واضحًا لك ذلك
- هناك الكثير من الأكوان (في الواقع لا حصر لها) ، ولكن
C_0
هو الكون الوحيد الذي بدأهf
. بدأ آخرون من قبلg
. C_0 C_n
يقوم دائمًا باستمرار جديدC_max
. انه بسببC_0
هو الكون الأول الذيg cc id
لديها ليس تم تنفيذها.C_0 C_n
عرض أيضا@
.C_n C_m
الذي لن يعرضه 0*
.- الوقت حسب الوقت يتم استنتاج البرنامج
C_0 C_n
, ، وسأثبت ذلكC_0 C_n
يتم فصل المزيد والمزيد من التعبير الآخر ، مما يؤدي إلى@*@**@***...
القليل من الرياضيات
يفترض (n! = 0) هو أكبر ترقيم في جميع الاستمرارية ، ثم C_0 C_n
يسمى.
الافتراض: متى C_0 C_n
يسمى، C_n
هو الحد الأقصى الحالي للاستمرار المرقم.
حاليا يتم إنشاؤه بواسطة C_0 C_n
مثله:
yin = f C_n ; @
yang = g #C_{n+1}#
yin yang
لذلك نستنتج أن:
نظرية I. إذا C_0 C_n
يسمى ، سوف ينتج استمرار , ، بحيث yin
هو C_n
.
ثم الخطوة التالية هي C_n C_{n+1}
.
yin = C_{n-1}
yang = g C_{n+1} ; *
yin yang
السبب yin
هو C_{n-1}
هل هذا عندما C_n
يجري خلقها طاعة نظرية أنا.
وثم C_{n-1} C_{n+1}
يسمى ، ونحن نعرف متى C_{n-1}
تم إنشاؤه ، كما أنه يطيع نظرية أنا. اذا لدينا C_{n-2} C_{n+1}
.
C_{n+1}
هو invariation. لذلك لدينا النظرية الثانية:
نظرية II. إذا C_n C_m
أيّ n < m
و n > 0
يسمى ، سوف يصبح C_{n-1} C_m
.
وقد فحصنا يدويًا C_0
C_1
C_2
C_3
. يطيعون الافتراض وجميع النظريات. ونحن نعرف كيف أولا @
و *
تم إنشاؤه.
حتى نتمكن من كتابة أنماط أدناه.
C_0 C_1 ; @ *
C_[1-0] C_2 ; @ * *
C_[2-0] C_3 ; @ * * *
...
هذا ليس صارمًا ، لكنني أود أن أكتب:
Qed
كما قال إجابة أخرى ، نقوم أولاً بتبسيط (call-with-current-continuation (lambda (c) c))
مع get-cc
.
(let* ((yin
((lambda (cc) (display #\@) cc) get-cc))
(yang
((lambda (cc) (display #\*) cc) get-cc)) )
(yin yang))
الآن ، اثنين من lambda هي مجرد وظيفة متطابقة مرتبطة مع الآثار الجانبية. دعنا نسمي هذه الوظائف f
(إلى عن على display #\@
) و g
(إلى عن على display #\*
).
(let* ((yin (f get-cc))
(yang (g get-cc)))
(yin yang))
بعد ذلك ، نحتاج إلى تحديد أمر التقييم. لكي أكون واضحًا ، سأقدم "تعبيرًا خطوة" يجعل كل خطوة تقييم واضحة. أولاً ، دعونا نسأل: ما هي الوظيفة أعلاه تتطلب؟
يتطلب تعريفات f
و g
. في تعبير الخطوة ، نكتب
s0 f g =>
الخطوة الأولى هي حساب yin
, ، ولكن يتطلب تقييم (f get-cc)
, وتتطلب لاحقا get-cc
.
تحدث تقريبا، get-cc
يمنحك قيمة تمثل "الاستمرار الحالي". دعنا نقول هذا s1
لأن هذه هي الخطوة التالية. لذلك نكتب
s0 f g => s1 f g ?
s1 f g cc =>
لاحظ أن المعلمات لا تنطلق ، مما يعني f
و g
في s0
و s1
ليست ضرورية كما هي فقط لاستخدامها في الخطوة الحالية. هذا يجعل معلومات السياق صريحة. الآن ، ما هي القيمة cc
؟ نظرًا لأنه "استمرار الحالي" ، فهو نوع من الشيء نفسه s1
مع f
و g
ملزمة بنفس القيمة.
s0 f g => s1 f g (s1 f g)
s1 f g cc =>
مرة واحدة لدينا cc
, ، يمكننا التقييم f get-cc
. أيضا ، منذ ذلك الحين f
لا يستخدم في الكود التالي ، ليس علينا نقل هذه القيمة.
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin =>
التالي هو مشابه ل yang
. ولكن الآن لدينا قيمة أخرى لنقلها: yin
.
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin => s3 g yin (s3 g yin)
s3 g yin cc => s4 yin (g cc)
s4 yin yang =>
أخيرًا ، الخطوة الأخيرة هي التقديم yang
إلى yin
.
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin => s3 g yin (s3 g yin)
s3 g yin cc => s4 yin (g cc)
s4 yin yang => yin yang
هذا انتهى من بناء تعبير الخطوة. ترجمته مرة أخرى إلى المخطط بسيط:
(let* ([s4 (lambda (yin yang) (yin yang))]
[s3 (lambda (yin cc) (s4 yin (g cc))]
[s2 (lambda (yin) (s3 yin ((lambda (cc) (s3 yin cc))))]
[s1 (lambda (cc) (s2 (f cc)))])
(s1 s1))
أمر التقييم التفصيلي (هنا Lambda داخل جسم s2
تم التعبير عنه ببساطة كتقييم جزئي s3 yin
عوضا عن (lambda (cc) (s3 yin cc))
):
(s1 s1)
=> (s2 (f s1))
=> @|(s2 s1)
=> @|(s3 s1 (s3 s1))
=> @|(s4 s1 (g (s3 s1)))
=> @*|(s4 s1 (s3 s1))
=> @*|(s1 (s3 s1))
=> @*|(s2 (f (s3 s1)))
=> @*@|(s2 (s3 s1))
=> @*@|(s2 (s3 s1))
=> @*@|(s3 (s3 s1) (s3 (s3 s1)))
=> @*@|(s4 (s3 s1) (g (s3 (s3 s1))))
=> @*@*|(s4 (s3 s1) (s3 (s3 s1)))
=> @*@*|(s3 s1 (s3 (s3 s1)))
=> @*@*|(s4 s1 (g (s3 (s3 s1))))
=> @*@**|(s4 s1 (s3 (s3 s1)))
=> @*@**|(s1 (s3 (s3 s1)))
=> ...
(تذكر ، عند التقييم s2
أو s4
, ، سيتم تقييم المعلمة أولاً
هذا لغز قديم من سيد التورط ديفيد مادور ، الذي أنشأ Unlambda. تمت مناقشة اللغز comp.lang.scheme عدة مرات.
حل جميل من تايلور كامبل:https://groups.google.com/d/msg/comp.lang.scheme/puedvrkyy5w/uijtc_t1loej
المنشور الأصلي من ديفيد مادور (1999):https://groups.google.com/d/msg/comp.lang.scheme/fysq_wplxsw/awxez_uxw20j