TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

What Does "With Continuation" Mean? (2020)

107 点作者 d4nyll超过 1 年前

13 条评论

carterschonwald超过 1 年前
So there’s a funny thing this doesn’t touch on: the semantics of call&#x2F;cc is genuinely confusing to understand! There’s a related construct that’s much more legible and has a much easier to understand: call with delimited continuation!<p>Oleg K wrote a very articulate piece about this some long time ago <a href="https:&#x2F;&#x2F;okmij.org&#x2F;ftp&#x2F;continuations&#x2F;against-callcc.html" rel="nofollow">https:&#x2F;&#x2F;okmij.org&#x2F;ftp&#x2F;continuations&#x2F;against-callcc.html</a>
评论 #39311962 未加载
评论 #39312502 未加载
评论 #39324304 未加载
评论 #39319805 未加载
canjobear超过 1 年前
I took a Scheme programming class where we did tons of stuff with continuations. I got obsessed and wrote all kinds of weird code. I even started using continuation passing style in Python. It was a dark time. Looking back, none of that code makes any sense at all.<p>When I think back to my days of mucking around with call&#x2F;cc my main emotion is relief that I’ve forgotten how it works. It’s a load off my mind
评论 #39310896 未加载
评论 #39311862 未加载
评论 #39312493 未加载
jedberg超过 1 年前
I&#x27;m just happy to see Professor Harvey still participating in the forums. He taught me SICP in Scheme 27 years ago and it is still some of the most important computer science I ever learned.
评论 #39310450 未加载
tmoertel超过 1 年前
Here&#x27;s Oleg&#x27;s great explanation of continuations in terms most programmers already understand: <a href="https:&#x2F;&#x2F;blog.moertel.com&#x2F;posts&#x2F;2008-05-05-olegs-great-way-of-explaining-delimited-continuations.html" rel="nofollow">https:&#x2F;&#x2F;blog.moertel.com&#x2F;posts&#x2F;2008-05-05-olegs-great-way-of...</a>
评论 #39310393 未加载
kccqzy超过 1 年前
I&#x27;m glad that this explanation involves a comparison with goto especially with a discussion of &quot;goto considered harmful&quot;. But IMO excessively using continuations results in the same kind of spaghetti code as code that excessively uses goto. Delimited continuations, on the other hand, essentially places a restriction on where the continuation can return to. The analogy with using goto is that the target of the goto has to be within a well specified block of code. This restriction gets rid of the temptation by programmers to take shortcuts and write overly clever but unmaintainable code.
评论 #39324571 未加载
评论 #39319823 未加载
jillesvangurp超过 1 年前
Continuations are also the backbone of co-routines in Kotlin and has first class support. One nice feature of the coroutines framework is that they made it really easy to adapt existing asynchronous frameworks in Java (there are many) and on other platforms. The list of supported frameworks includes things like reactive Java, vert.x, spring webflux, Java Futures. And that&#x27;s just the JVM. If you are using kotlin-js in the browser or on node.js, promises and async&#x2F;await are covered too. And on IOS, coroutines also do the right thing with e.g. garbage collection and other mechanisms in IOS.<p>Most of this stuff is supported via extension functions. But for asynchronous things that aren&#x27;t supported it&#x27;s really easy to adapt them yourself with the suspendCancellableCoroutine function. This is a suspending function that takes a function with a continuation as the parameter. Inside the function you do whatever needs doing and handle your callbacks, futures, awaits, or whatever and call continuation.resume, continuation.resumeWithException, or continuation.invokeOnCancellation as needed (which sadly is not a feature every async framework or language supports).<p>With a few small extension functions you can pretend most things are nice kotlin suspend functions and not deal with the spaghetti code typically needed otherwise. E.g. Spring Flux is an absolute PITA in Java because of this and essentially effortless in Kotlin. Your code looks like normal kotlin code. Nothing special about it. All the flux madness is shoveled under the carpet.<p>In the same way the new virtual threads in Java are supported trivially because all of that is exposed via the existing Theading and Threadpool APIs. So, you can just dispatch your co-routine asyncs and launches as virtual threads via a dispatcher that you create with a little extension function on Threadpool. Most of this stuff &quot;just works&quot; out of the box. There&#x27;s a lot of confusion on this topic in the Java community. For Kotlin this is just yet another way to do async stuff that joins a long list of existing other ways. It has its pros and cons for different use cases and is there if you need it. No big deal but very nice to have around. I&#x27;ve not found any use for it yet and we&#x27;re on Spring Boot and java 21. We already have everything asynchronous and non blocking.
neilv超过 1 年前
I don&#x27;t know whether it helps, but here&#x27;s another explanation that doesn&#x27;t involve a visual programming language:<p>First, imagine that procedures (or functions) are first-class values, like in some languages are called &quot;anonymous functions&quot;, &quot;closures&quot;, &quot;lambdas&quot;, etc.<p>Now imagine that every procedure call is effectively a goto with arguments... and the familiar part about being able to return from that procedure, to its calling point, is implemented by an implicit additional value created at calling time... which is a &quot;continuation&quot; <i>procedure with argument(s) that you call to return the value(s) from the other procedure</i>.<p>To make it first-class continuations, imagine that &quot;continuation&quot; procedure value you call to return the value(s) from another procedure... can be made accessible to the program as a first-class value. Which means you can store it in a variable or data structure like any other value. And you can call that &quot;continuation&quot; procedure value multiple times -- returning to that dynamic state of the calling context in the program many times.<p>It&#x27;s one of those things that application code almost never uses directly, but that could be super useful and simplifying in the implementation of another programming language, or carefully controlled in particular libraries (e.g., you wanted to make a stateful Web backend library or DSL that made it really easy to express the logic for some kind of interaction tree responding to multiple steps of form input).
评论 #39313098 未加载
joe_the_user超过 1 年前
Required reference &quot;Objects the poor man&#x27;s Closures[continuations]&quot; [1]. Well, the links actually say &quot;Closures And Objects Are Equivalent&quot;, which is the point. But I&#x27;d offer a deeper point - continuations&#x2F;closures are rightly considered one of the hardest things most programmers ever encounter while objects are things most programmers deal with daily. Sure, that means use continuations if you want to look like a bad ass but use objects if you want a project to succeed.<p>[1]<a href="http:&#x2F;&#x2F;wiki.c2.com&#x2F;?ClosuresAndObjectsAreEquivalent" rel="nofollow">http:&#x2F;&#x2F;wiki.c2.com&#x2F;?ClosuresAndObjectsAreEquivalent</a>
评论 #39311851 未加载
cryptonector超过 1 年前
The easiest way to think about continuations is to consider them a generalization of function returns. The continuation of a C function f() is the return address <i>and</i> the saved frame pointer of the calling function -- and that looks a lot like a closure, and that&#x27;s because it is, except that a) you can only pass that closure one argument in C: the return value, and b) you actually can&#x27;t get a value for this closure in C, and c) there&#x27;s no closures as such in C anyways :)<p>[And what is a closure? It&#x27;s a tuple of {function body pointer, data pointer}, and &quot;callbacks&quot; in C often look just like that, but not as a singular value combining the two parts but as two separate values. In better languages a function is indistinguishable from a closure.]<p>Anywhere that you see `call&#x2F;cc` you can think of it as making a first-class function value (a closure) of the current location (which would be the return address of the call to `call&#x2F;cc`) and then passing that closure to the function given as an argument to `call&#x2F;cc`. After you parse that sentence you&#x27;ll realize that&#x27;s a bit crazy: how does one make a function out of... a function&#x27;s return address (and saved frame pointer)? The answer is: reify that {return address, saved frame pointer} as a closure. (&#x27;Reify&#x27; means, roughly, to make a hidden thing not hidden.)<p>Still, it must seem crazy. Yet it&#x27;s not that crazy, certainly not if you make it so you can only call it once, and only if `call&#x2F;cc` hasn&#x27;t returned yet. What&#x27;s crazy is that in Scheme this continuation can be called repeatedly, so how? Here&#x27;s one way to do it: replace the stack with allocating function call frames on the heap!, which in a language with a GC means that all those function call frames remain alive as long as closures (which a continuation <i>is</i>) refer to them. (Another way to do it is with copying stacks.)<p>One can easily (for some value of &quot;easily&quot;) implement a language that has `call&#x2F;cc` in a language that doesn&#x27;t but which has a) closures, b) a powerful macro system &#x2F; AST &#x2F; homoiconicity. All one has to do is take a program, apply continuation passing style (CPS) conversion to it (a fairly straightforward transformation where the result is unreadable for most humans), which automatically causes all function call frames to be captured in closures (thus put on the heap), but also automatically makes continuations explicit values (closures). The `call&#x2F;cc` is a trivial function that passes its now-explicit continuation argument to the function that `call&#x2F;cc` is given to call.<p>Allocating function call frames on the heap is a performance disaster. And that&#x27;s where delimited continuations come in: the goal being to allocate function call frames on mini stacks.<p>`call&#x2F;cc` is really a bit of a parlor trick, and a very nifty one at that, but <i>continuations</i> are a very important concept that comes up all over the place, and one that computer scientists and software engineers ought to be at least conversant with.
评论 #39315766 未加载
评论 #39324548 未加载
kazinator超过 1 年前
yield based on delimited continuations in TXR Lisp, showing that unwind-protect works:<p><pre><code> (defun grandkid () (unwind-protect (yield-from parent &#x27;in-grandkid) (put-line &quot;returning from grandkid&quot;))) (defun kid () (unwind-protect (progn (yield-from parent &#x27;in-kid) (grandkid)) (put-line &quot;returning from kid&quot;))) (defun parent () (unwind-protect (progn (yield-from parent &#x27;in-parent) (kid)) (put-line &quot;returning from parent&quot;))) (let ((fn (obtain (parent)))) (prinl &#x27;a) (prinl (call fn)) (prinl &#x27;b) (prinl (call fn)) (prinl &#x27;c) (prinl (call fn)) (prinl &#x27;d) (prinl (call fn)) (prinl &#x27;e)) </code></pre> Run:<p><pre><code> $ txr cont.tl a in-parent b in-kid c in-grandkid d returning from grandkid returning from kid returning from parent nil e </code></pre> Each yield captures a new delimited continuation up to the parent prompt. Each (call fn) dispatches a new continuation to continue on to the next yield, or termination.<p>So with all this back-and-forth re-entry, why do the unwind-protects just go off once? Because of a mechanism that I call &quot;absconding&quot;: the answer to the dynamic wind problem.<p>Absconding is a way of performing a non-local dynamic control transfer without triggering unwinding. It&#x27;s much like the way, say, the C longjmp is unaware of C++ destructors: the longjmp absconds past them.<p>With absconding we can get out of the scope where we have resumed a continuation without disturbing the scoped resources which that context needs. Then with the continuation we captured, we can go back there. Everything dynamic is intact. Dynamically scoped variables, established exception handlers, you name it.<p>The regular function returns are not absconding so they trigger the unwind-protect in the normal way.<p>absconding is an elephant gun, that should only be used in the implementation of primitives like obtain&#x2F;yield.<p>15 second tutorial:<p>Cleanup yes:<p><pre><code> 1&gt; (block foo (unwind-protect (return-from foo 42) (prinl &#x27;cleanup))) cleanup 42 </code></pre> Cleanup no:<p><pre><code> 2&gt; (block foo (unwind-protect (sys:abscond-from foo 42) (prinl &#x27;cleanup))) 42 </code></pre> That&#x27;s all there is to absconding. yield-from uses it. It captures a new continuation, packages it up and absconds to the prompt, where that is unpacked, the new continuation updated in place of the old so that <i>fn</i> will next dispatch that new one.
ChrisArchitect超过 1 年前
(2020)
TruffleLabs超过 1 年前
Call&#x2F;cc !
pnut超过 1 年前
If I ever start writing statements like &quot;Part the first&quot;, please take me out back and put us all out of that misery.