[HN Gopher] Beautiful ideas in programming: generators and conti...
       ___________________________________________________________________
        
       Beautiful ideas in programming: generators and continuations
        
       Author : lukastyrychtr
       Score  : 128 points
       Date   : 2021-08-02 09:12 UTC (1 days ago)
        
 (HTM) web link (www.hhyu.org)
 (TXT) w3m dump (www.hhyu.org)
        
       | intrepidhero wrote:
       | > But very briefly, yield in Python is often presented as a
       | simplified method for writing generators, but what it does,
       | suspending the execution of a function with the ability to resume
       | it, is a much general concept than iteration. It enable a routine
       | to do a small amount of work, and then pass the control to
       | another routine, thus allowing them to run concurrently.
       | 
       | I don't think that is what _concurrently_ means. Generators allow
       | interleaved execution or lazy evaluation. In fact the author
       | makes this point further down so I think the above sentence was
       | just a slip of the pen. But I point it out because that very
       | misunderstanding is one that tripped me up for a time.
       | 
       | > More accurately, yield in a function creates a generator, which
       | suspends its execution at the point where yield appears in the
       | definition.
       | 
       | That's better.
        
         | butterisgood wrote:
         | I would say concurrency is more of a way of organizing code
         | into independent parts.
         | 
         | If you have multiple contexts, logically separable, and they
         | can execute independently you end up with some form of
         | concurrent design.
         | 
         | The reason people often think of coroutines (like generators
         | resuming) vs routines (functions) as concurrent vs not
         | concurrent has everything to do with contexts that can be
         | thought of as executing in a non-deterministic order. Certainly
         | there might be external dependencies that "force" an ordering
         | to execution, but at the end of the day, it's the independence
         | of an operation/computation that makes it concurrent with
         | another.
         | 
         | Concurrency can give rise to opportunities for things to
         | execute in parallel, but it does not require them. Parallelism
         | is therefore a condition of execution/evaluation.
         | 
         | On a uniprocessor unix kernel, processes execute concurrently.
         | On a multiproccessor unix kernel, you hope for parallel
         | execution of processes (though they may be totally unrelated in
         | context or goal), but you always maintain the concurrency. The
         | scheduler pauses and switches contexts.
         | 
         | The ability to switch contexts seems fairly equivalent to
         | concurrency. And I don't think much else is needed.
         | 
         | I'm not sure that's 100% correct, but it's how I like to think
         | of this situation.
        
         | jtsiskin wrote:
         | You are thinking of "parallel". This is a fine usage of
         | concurrent.
        
         | chowells wrote:
         | Seems like the standard definition of concurrent to me.
         | Concurrency is a programming model, not an execution model. It
         | means writing multiple pieces of code in a simple linear model
         | that can have their execution interleaved with execution of
         | other code. The primary contrast is managing a complex state
         | machine by hand. _cough_ Node _cough_
         | 
         | Given that concurrency is often implemented with a state
         | machine, the difference is not what happens at run time. The
         | difference is the programming model used.
         | 
         | Parallel execution is a fully separate matter.
        
           | intrepidhero wrote:
           | That's really interesting. Perhaps the gap in my knowledge is
           | the difference between programming model and execution model.
           | 
           | But let me ask another question: What's the difference
           | between yielding from a function and returning? Why is
           | yielding considered concurrent programming and returning
           | sequential?
           | 
           | I guess I had associated concurrent programming with threads
           | but maybe multi-threaded programming is better called
           | parallel execution?
        
             | simiones wrote:
             | Concurrent usually refers to any kind of control flow in
             | which separate (logical) threads of execution can be
             | interleaved, and where the particular interleaving can
             | affect the final result. For example, event systems are
             | inherently concurrent, even if they are single threaded:
             | the order of events affects normally affects the final
             | state of the system (for example, if you receive a request
             | to SET X=0 and a request to INCREMENT X, the final result
             | may be 0 or 1 depending on the order, even if you have a
             | single thread of execution actually handling the events).
             | 
             | In contrast, parallel programming usually refers to program
             | flows where multiple instruction streams can be interleaved
             | arbitrarily without changing the final result. For example,
             | when using mapping a pure function on a list, the execution
             | of the function for each element of the list can be
             | executed in parallel without any concurrency.
             | 
             | Alternatively, parallel execution can refer to any case
             | where multiple instruction streams are actually executed in
             | parallel, regardless of their order.
             | 
             | Concurrency often has pitfalls even with single-threaded
             | execution models. For example, if two generators share some
             | common state, even if they are executed in a single thread,
             | the final result depends on how they are used and may be
             | unexpected.
        
             | Jtsummers wrote:
             | Return terminates the function or thread of execution.
             | Yield (may) provides a value but leaves the thread of
             | execution alone otherwise, only pausing it. It's resumable
             | where a return is not (without a lot of extra bookkeeping).
             | 
             | https://blog.golang.org/waza-talk
             | 
             | A good talk that expresses the current sentiment and helped
             | establish it. Concurrency may be parallel, but doesn't need
             | to be. This does go against the common (outside
             | programming) notion of the meaning of concurrent which
             | means it provides a strong potential for confusion.
        
               | gpderetta wrote:
               | Equivalently, both yield and return invoke the calling
               | function continuation, but while return discards the
               | current one, yield saves it somewhere for resumption.
        
             | nybble41 wrote:
             | I don't think there is a gap in your knowledge so much as a
             | clash in terminology between different (programming)
             | communities. What the Go community here is referring to as
             | "concurrency" sounds to me like the traditional definition
             | of "multithreading", while "parallel execution" meets the
             | definition I've seen before for "concurrency". This was
             | probably done to distinguish operating-system threads
             | (preemptive multitasking, possibly involving multiple CPU
             | cores) from asynchronous application code (user-mode
             | cooperative multitasking on a single core), but to me a
             | form of "concurrency" without _concurrent execution_ , i.e.
             | operations from different threads executing in parallel at
             | the same time, seems like a contradiction.
        
         | vanderZwan wrote:
         | So what _do_ you think concurrently means?
         | 
         | For the record, the quoted explanation you disagree with fits
         | Esterel and other data-flow languages perfectly, and nobody
         | will deny that they are (synchronous) concurrent languages.
        
           | intrepidhero wrote:
           | I thought it meant that two streams of instructions could be
           | executed simultaneously. I thought it meant I needed to start
           | worrying about shared/private memory and locks and things.
           | Python's generators are handy for in that kind of program but
           | not necessary.
           | 
           | But it sounds like from the replies I was wrong.
        
       | narag wrote:
       | This article sounds very familiar. I'm sure to have read an
       | article about the very same two languages' constructs a long time
       | ago. It's dated this year, but I wonder if the author could be
       | recycling an old text.
        
       | math-dev wrote:
       | It's just a goto with state...
        
       | BiteCode_dev wrote:
       | Generators are one of the concepts that devs from other languages
       | under use in Python. It's a natural solution to a ton of
       | problems, since it lazily evaluates, and only stores one result
       | out of the entire sequence at a time, and its interface is
       | compatible with the iterator protocol. It's a mini state machine
       | with it's own little memory world as well.
       | 
       | If you are used to call backs, promises, tick() calls or
       | closures, it will be natural to reach for them a lot, and be
       | disappointed at their state in the Python ecosystem.
       | 
       | But while I do miss better anonymous functions support sometimes
       | in Python, it is rarely a problem because it comes with other
       | tools to arrive to the same result.
       | 
       | Generators are one of them. Of course, you'll have to realize
       | that generators are way more that a lazy way of generating data.
       | You can use them for control flow, hold state, hide complexity or
       | delegate behavior using a common interface. The send() method is
       | very often completely ignored by most devs, and the yield from
       | underused.
       | 
       | But beyond generators are a lot more things that, in the same
       | vein, will bring you to python enlightenment once you start
       | thinking with them instead of trying to make a Python fits into a
       | square hole:
       | 
       | - the functools module
       | 
       | - the itertools module
       | 
       | - context managers
       | 
       | - any/all/zip/iter/next/enumerate
       | 
       | - decorators (it can do so much more than you imagine)
       | 
       | - type hints (take a look at fast api for awesomeness)
        
         | andrewstuart wrote:
         | >> If you are used to call backs, promises, tick() calls or
         | closures, it will be natural to reach for them a lot, and be
         | disappointed at their state in the Python ecosystem.
         | 
         | It's a real disappointment that Python has no answer for
         | JavaScript fat arrow.
         | 
         | The fat arrow is the one construct that completely changes the
         | structure of all the programs I write - it makes things
         | extremely terse and I use it in nearly every function/method in
         | some way.
         | 
         | I'm a big fan of Python's whitespace structure but it seems to
         | be an obstacle to creating single line functions that are both
         | powerful and get multiple things done, which sometimes makes a
         | program easier to read and more understandable. Sure there's
         | lambda functions but I find them clumsy and hard to work with
         | compared to the beauty of the fat arrow.
        
         | anonymousDan wrote:
         | One thing that really annoys me about PySpark is Python's lack
         | of tuple unpacking in lambdas. It turns what should be
         | relatively readable pipelines of transformations into an
         | unreadable mess.
        
         | drcongo wrote:
         | I don't know many languages, do decorators show up in any
         | others? I _love_ them in Python.
        
           | b3morales wrote:
           | Emacs Lisp* has a system called "advice" that's maybe a bit
           | clumsier than Python's, but is also more powerful. The
           | relationship between decorator and decorated [is
           | configurable][0]. For example, the decorated function could
           | be called conditionally on the result of applying the
           | decorator to the arguments, something like:
           | def _combined(args):          return decorator(args) and
           | original(args)
           | 
           | It can also be applied retroactively, not just at the
           | decorated function's definition. This is extremely handy in
           | the context of configuring your text editor, but I admit I
           | wouldn't like it in a program I was working on with other
           | people. (Basically a form of monkeypatching.)
           | 
           | * Granted not really used for general-purpose programming
           | like Python.
           | 
           | [0]:https://www.gnu.org/software/emacs/manual/html_node/elisp
           | /Ad...
        
             | creamytaco wrote:
             | Emacs Lisp has macros which are a lot more elegant (not to
             | mention powerful) than Python decorators which
             | comparatively, are nothing more than a kludge.
        
           | useerup wrote:
           | In C#:                   static IEnumerable<int> Fibonacci()
           | {               int first = 0;               int second = 1;
           | yield return first;               yield return second;
           | while (true)               {                   (first,
           | second) = (second, second + first);                   yield
           | return second;               }           }
        
             | whoisthemachine wrote:
             | Interesting use of tuples I had never considered before, a
             | lot of state is packed in that `(first, second)...` block!
             | Pretty fun though.
        
           | BiteCode_dev wrote:
           | Yes. In fact, E.G: C# and JS have them, although they don't
           | have exactly the same features.
           | 
           | edit, sorry I've meant generators. Incidentally, it's also
           | true for js decorators.
        
             | k__ wrote:
             | How do Python generators differ from JavaScript generators?
        
               | BiteCode_dev wrote:
               | Yield from comes to mind.
        
               | k__ wrote:
               | Isn't JS yield* the same as yield from in Python
        
               | BiteCode_dev wrote:
               | Unless I'm mistaken, it propagate pulling, but not
               | pushing with send(). I haven't checked though.
        
             | goatlover wrote:
             | Decorators are still a pending proposal in ECMAScript, so
             | they would only be available to transpilers. That makes
             | them non-standard until the proposal is approved, where the
             | implementation could change.
        
         | ajuc wrote:
         | Generators are such a game-changer for clean separation of data
         | traversal from data processing. It's my favorite trick in
         | python and I miss it dearly in other languages.
         | 
         | In languages without generators it's easy to write code that
         | traverses some data structure in hard-coded way and does
         | something configurable on every node, but it's hard to write
         | code that traverses a data structure in configurable way and
         | does some hardcoded thing on every element.
         | 
         | In Python it's trivial both ways and even both at once.
        
           | dragonwriter wrote:
           | > It's my favorite trick in python and I miss it dearly in
           | other languages.
           | 
           | Which other languages? Generator support is common:
           | 
           | https://en.m.wikipedia.org/wiki/Generator_(computer_programm.
           | ..
        
             | cogman10 wrote:
             | This list really sort of stretches the imagination on what
             | language level generator support looks like.
             | 
             | Certainly in Java you can use the `Iterator` interface to
             | get generator like behavior. But that's both a whole lot
             | more complex and more verbose. Primarily because you have
             | to coordinate the `hasNext` method with the `next` method.
             | 
             | With python and yield syntax, that naturally falls out
             | without a bunch of extra code juggling.
        
               | dragonwriter wrote:
               | Sure, some of those (C, Java, C++ templates) are pretty
               | weak, but C#, Ruby, ES6, and lots of the other language
               | or library implementations are not. Its far from a unique
               | Python feature.
        
             | tromp wrote:
             | "In Haskell, with its lazy evaluation model, everything is
             | a generator"
        
           | wwweston wrote:
           | How absent is generator support in languages in general? As
           | far as I know, even PHP has had yield for something like a
           | decade, and JS for longer (at least as far back as 2006, when
           | Yield Prolog got discussed on LTU[0]). Or is there something
           | about the Python approach to generators that's distinctive?
           | 
           | [0] http://lambda-the-ultimate.org/node/1732
        
             | tobr wrote:
             | The Ecmascript 4 proposal included the `yield` keyword, but
             | the proposal as a whole was abandoned in 2008. That's why
             | the version names jump from ES3 to ES5. Some of the
             | features then returned in later versions, including
             | generators with ES6 in 2015.
        
             | BiteCode_dev wrote:
             | Language tends to converge on good features, luckily.
             | 
             | Although some generator implementations are not as complete
             | as python ones, and the ecosystem generally donc have much
             | support for it
             | 
             | E.g : Very few languages have an equivalent to itertools.
        
           | muxator wrote:
           | The C++20 coroutines has the potential of being this and much
           | more. As with many things C++, its adoption will be very slow
           | (they are supported by latest compiler versions, but there is
           | no support in the standard libraries yet).
        
           | systemvoltage wrote:
           | This sounds interesting. An example would really be helpful,
           | I have a vague idea of what you're talking about but have a
           | hard time concretizing it.
        
           | idle_zealot wrote:
           | > it's hard to write code that traverses a data structure in
           | configurable way and does some hardcoded thing on every
           | element
           | 
           | Wouldn't this just be a function that operates on an
           | iterator? I suppose generators make the creation of lazy
           | iterators easier, but generally the solution for languages
           | without generators is to have your traversal build a list. So
           | you lose the laziness but the code remains simple. Then map
           | your per-node processing to the list.
        
             | ajuc wrote:
             | > generally the solution for languages without generators
             | is to have your traversal build a list
             | 
             | Yes, but then you move from O(1) to O(n) in memory usage.
             | And if you want to optimize it you have to restructure your
             | code, when in python lists and generators are drop-in
             | replacements.
        
               | justinpombrio wrote:
               | Also, producing a list doesn't work very well as a
               | substitute for infinite generators.
        
       | prashnts wrote:
       | Article briefly mentions:
       | 
       | > Using yield in a function definition is not the only way in
       | which generators can be constructed in Python
       | 
       | Anyone can point out what do they mean by that? Googling didn't
       | help much.
        
         | sherjilozair wrote:
         | Look up iterator, __next__, __iter__.
        
           | prashnts wrote:
           | Right, thanks!
        
       | mikewarot wrote:
       | I did continuations back in 1987, in Turbo Pascal, but I didn't
       | know it until today. I wrote a cooperative multi-tasking library
       | to support some things I needed to have happen at the same time
       | in MS-DOS.
       | 
       | The first time my procedure "fork" returned twice, it took me a
       | while to wrap my head around it. I don't see why you couldn't
       | implement continuations in modern Free Pascal/Lazarus, all you
       | really have to do is make a copy of the stack(using the heap),
       | then adjust BP and SP, everything else on the stack is relative
       | to those two registers.
        
         | touisteur wrote:
         | Posix has getcontext / setcontext and you can probably use/bind
         | pcl [0] to wrap that properly. Boost has boost::context [1].
         | For an example of a binding to pcl you can look up ada-
         | generators [2] which I use heavily (but am not the author).
         | 
         | I've hard a hard time using continuations in languages with
         | not-total capture. Ada has package variables (similar to
         | singleton without the headache) that are not captured by
         | call/cc libraries... There's also the secondary stack (specific
         | to GNAT, not sure) for functions that return objects of unknown
         | size at call site, which also are a pain to capture/restore...
         | Good luck with those.
         | 
         | I think continuations, to be really footgun-less, need to be
         | integrated in the language & runtime, so that when you need to
         | serialize the current state you can 'capture all the things'. A
         | bit like python with its generator/automaton approach.
         | 
         | [0] http://www.xmailserver.org/libpcl.html
         | 
         | [1]
         | http://www.devdoc.net/c/boost-1.65.1/libs/context/doc/html/c...
         | 
         | [2] https://github.com/pmderodat/ada-generators
        
       | shadowgovt wrote:
       | Related to this space is algebraic effects [https://gist.github.c
       | om/yelouafi/57825fdd223e5337ba0cd2b6ed7...], which is the
       | construct I'm most excited about these days. Very few languages I
       | know offer direct support for them, but they inspire the pattern
       | of Hooks in React functional components.
        
         | ducaale wrote:
         | For those who don't know, @yelouafi is the original author of
         | redux-saga.
        
       | agumonkey wrote:
       | Do people use conts in day jobs ?
       | 
       | It seemed to me that beside niche fp or implementation of async,
       | kanren,etc people rarely used them.
       | 
       | Not criticizing the concept just trying to assess the real world
       | usage.
        
         | gpderetta wrote:
         | Go coroutines (and any implementation of stackfull coroutines)
         | are equivalent to one-shot continuations.
        
           | anonymoushn wrote:
           | Go doesn't have coroutines-the-flow-control-construct. It has
           | goroutines, which are lightweight preemptively scheduled
           | threads. You can, on top of this thing, arrange for a bunch
           | of goroutines only one of which is not blocked at a time,
           | which is a bit like having coroutines, but probably involves
           | more synchronization code than a typical implementation of
           | coroutines-the-flow-control-construct.
        
         | useerup wrote:
         | Exception handlers are continuations.
        
           | formerly_proven wrote:
           | I don't think they are, unless you are talking about the few
           | implementations of resumable exceptions. The most obvious
           | counter-example I can think of is C++, where the stack from
           | where the exception was thrown is unwound to the handler,
           | i.e. it's destroyed (doubly so if the exception handler puts
           | anything on the stack). That's also why you can't get a
           | stacktrace from an exception in C++ unless you specifically
           | arranged for the thrower to include that information in the
           | exception object.
        
             | simiones wrote:
             | Even resumable exceptions are/can be much more bounded than
             | continuations. Exceptions always respect the scope
             | structure of the code (even though they can jump between
             | scope levels), whereas continuations can break this
             | arbitrarily.
             | 
             | In particular, in Common Lisp, restarts are only valid in
             | the dynamic environment where they were created by restart-
             | bind. In contrast, call/cc captures this dynamic
             | environment and makes it available later. You can't store
             | restarts and invoke them later the way you can with
             | call/cc.
        
             | PaulDavisThe1st wrote:
             | You can get the stack trace by breaking in "throw". Maybe
             | you meant something else.
        
               | formerly_proven wrote:
               | Breakpoints on throw are triggered before unwinding (you
               | are effectively setting a breakpoint on the function
               | called to unwind).
               | 
               | You simply cannot create a stack trace _from within_ a
               | C++ exception handler because that state is gone. Hence
               | my argument that exceptions are not a continuation, there
               | is nothing to continue.
        
               | PaulDavisThe1st wrote:
               | All true. But also a bit of a diversion, because the
               | information you wanted "from within" the exception
               | handler _is_ available at the throw site. Ergo, if you
               | 're trying to get it from within the handler, you're just
               | trying to get it from the wrong place.
        
             | toolslive wrote:
             | In continuation passing style, a compiler technique,
             | exceptions show up quite naturally as a second
             | continuation. The whole thing is called "Double barrelled
             | continuation passing style".
        
             | miloignis wrote:
             | I believe exceptions are equivalent to a limited form of
             | continuations called escape continuations where control
             | flow can only go upwards (which is the stack unwinding).
             | Note that this is also true of break and continue,
             | actually, both of which can be implemented with either
             | exceptions or escape continuations.
        
           | simiones wrote:
           | I'm guessing you're trying to equate                 try{
           | foo()       }catch(Exception e){         bar(e)       }
           | void foo() {         throw new exception();       }
           | 
           | with                 (bar (call/cc foo))            (defun
           | foo (cont)         (cont (make-exception))
           | 
           | However, this ignores most details of the problem. In fact,
           | (re-entrant) continuations and exceptions can't be mixed in
           | the same language: Common Lisp has not adopted `call/cc` for
           | one because it's impossible or at least much harder to
           | implement `unwind-protect` (`finally`) in the presence of
           | `call/cc` [0].
           | 
           | The major difference is that continuations allow you to do
           | this:                 (defparam *global*)       (defparam
           | *shadow-list* (list))       (defun foo (cont)         (setf
           | *global* cont))       (with-open-file (f "~/file.log")
           | (loop           for line = (read-line stream nil 'eof)
           | until (eq line 'eof)           collect (cons line (call/cc
           | foo)))            (*global* *shadow-list*)       (*global*
           | *shadow-list*) ;; what is the state of the file here? what if
           | there was an exception reading from the file?
           | 
           | [0] https://groups.google.com/g/comp.lang.lisp/c/1Ggl_BUi3Yg/
           | m/V...
        
         | shadowgovt wrote:
         | They show up often in writing interpreters. If you find
         | yourself writing a parser / interpreter for a domain specific
         | language, a good way to approach it is often to build the chain
         | of commands to evaluate as a hierarchy of continuations.
         | 
         | Otherwise, you may find yourself having to implement break,
         | continue, and return using the exception system in your
         | implementing language, and that can end up being either
         | aesthetically unpleasing or downright messy (I painted myself
         | into a corner once writing an evaluation engine in Go and, to
         | meet a deadline, implemented break using panic and recover!).
        
         | bordercases wrote:
         | If lower levels of the stack used them then more people would
         | use them.
         | 
         | https://www.cs.cornell.edu/people/egs/papers/trickles-tocs.p...
         | https://thelackthereof.org/docs/library/cs/continuations.pdf
        
         | _peeley wrote:
         | From a high level, iterators are basically just generators
         | (which themselves are just simple continuations). Rust is a
         | good example since pretty much all looping is done via
         | iterators, and most implementations of the Iterator[0] trait
         | look similar to the examples in the article. If you squint
         | hard, calling `iter.next()` in Rust is just like the Scheme
         | example, where you're continuously calling the generator
         | function to get the next value. So, the people who use Rust at
         | their day job definitely use (a form of) continuations at their
         | day jobs.
         | 
         | [0]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
        
       | dmux wrote:
       | Can anyone explain in a little more detail what's going on in the
       | definition of the Scheme version of sum-of-squares? The previous
       | example where the continuation of 2 + 1 + N is captured makes
       | sense to me, but I'm having a hard time wrapping my head around
       | the resulting continuation when a lambda is involved.
       | 
       | To make it a little cleaner, I've replaced the "receiver"
       | definition with a function reference instead. What's the
       | continuation here?                   (define sum-of-squares
       | (lambda (bound)                                       (call/cc
       | the-receiver)))
        
         | simiones wrote:
         | First of all, (define foo (lambda (args) body)) is just the
         | Scheme idiom for defining functions. It's normally written
         | `(define (foo args) body)` or Python `def foo(args): body`.
         | 
         | In a little more detail, let's take a function that looks like
         | this; I'll use the shorter syntax to avoid line noise:
         | (define (sum-of-squares bound)          0)       (print (sum-
         | of-squares 100)) ;; prints "0"
         | 
         | Now let's see what happens with a continuation:
         | (define (sum-of-squares bound)         (call/cc the-receiver))
         | ;; will return whatever is passed to current continuation
         | ;; for now, it will jump to the receiver, passing the whole
         | call stack to it            (define (the-receiver cont)
         | (cont 18))            (sum-of-squares 100) ;; will start
         | executing sum-of-squares, then jump to the-receiver, passing it
         | the call stack                            ;; then, the-receiver
         | will jump back into sum-of-squares, replacing the value of the
         | whole `call/cc` form with the value 18
         | ;; which will be returned to the top level
         | 
         | This use is similar to the following Python code:
         | def sum_of_squares(bound):         try:
         | the_receiver()         except MyContinuation as e:
         | return e.Value            def the_receiver():         raise
         | MyContinuation(18)
         | 
         | To show some more power of what this can do, let's save the
         | continuation to a global var and call it a few times:
         | (define some-continuation '())       (define (sum-of-squares
         | bound)         (call/cc the-receiver)) ;; will return whatever
         | is passed to current continuation
         | ;; for now, it will jump to the receiver, passing the whole
         | call stack to it            (define (the-receiver cont)
         | (set! some-continuation cont)) ;; the receiver stores the value
         | of cont into some-continuation and returns control to the top
         | level.                                        ;; at this point,
         | sum-of-squares and anything that was calling it has NOT
         | finished executing:                                         ;;
         | it's frozen in the continuation              (display (sum-of-
         | squares 100)) ;;sets some-continuation to the continuation,
         | doesn't print anything            (some-continuation 18)  ;;
         | prints 18       (some-continuation 200) ;; prints 200
         | (display (+ 100 (sum-of-squares 100))) ;;sets some-continuation
         | to a new vale, doesn't print anything            (some-
         | continuation 18)  ;; prints 118       (some-continuation 200)
         | ;; prints 300
        
         | valbaca wrote:
         | The sum-of-squares isn't a continuation, rather it just loops
         | through the squared-ints generator up to the bound.
         | 
         | call/cc is used to allow some form control and allow the loop
         | to exit
         | 
         | > Before the sum-of-square function does anything, I use
         | call/cc to capture the continuation, and assign it to the
         | variable break. Notice that in the syntax of Scheme, this is
         | the last step of the function. Therefore, calling break will
         | immediately exist the function with a returned value, no matter
         | where it is called.
        
           | dmux wrote:
           | I've reread the quote you posted and think I may understand,
           | although I'm probably misusing some terminology: since the
           | call to call/cc is the only thing in the lambda definition,
           | the continuation captured at that point is kind of the
           | termination/return point of the function. If there was
           | something after the call/cc, e.g. a
           | (display "Done with looping")
           | 
           | then invoking the continuation would break out of the loop
           | and display "Done with looping."
        
       | Zababa wrote:
       | Interesting article. I knew the name "continuations", and
       | "call/cc", but not exactly what they were, so thanks for
       | clarifying that. Continuations seem pretty close to what I
       | understand from algebraic effects. Are there big differences
       | between the two? Are they the same thing? Is one a subset of the
       | other?
        
         | anchpop wrote:
         | They are related. You can specify effects with monads
         | (http://goto.ucsd.edu/~nvazou/koka/icfp15.pdf for example), and
         | continuations can be expressed with monads (and therefore as an
         | effect). I believe monads and effects are more general than
         | continuations though. I'm not an expert so don't quote me haha
        
           | compressedgas wrote:
           | I think that they are each expressible with the other.
           | 
           | Monadic reflection can be implemented with continuations.
           | Effects, specifically effect handlers, can be implemented
           | with delimited continuations which weren't mentioned in this
           | article. Delimited continuations and undelimited
           | continuations can implement each other. Both undelimited
           | continuations and delimited continuations can be implemented
           | as a monad.
           | 
           | The main issue with all of this and why effects exist and why
           | monads and delimited continuations aren't enough has to do
           | with the fact that delimited continuations can't be easily
           | type checked and monads aren't open for extension.
           | 
           | I did not like that undelimited continuations were not
           | mentioned. They are so much easier to understand than call/cc
           | continuations as they are the rectification of some segment
           | of the return stack as a function which when called returns
           | through that segment and then to the caller like a regular
           | function.
        
             | hencq wrote:
             | > I did not like that undelimited continuations were not
             | mentioned.
             | 
             | call/cc continuations are undelimited continuations right?
             | It sounds like you're talking about call/ec, escape
             | continuations or one-shot continuations. They're
             | essentially longjmp in C.
        
             | ashton314 wrote:
             | Can you recommend some papers on this?
        
               | manu3000 wrote:
               | Oleg has written a lot about this:
               | http://okmij.org/ftp/continuations/
               | 
               | you can also google "oliver danvy delimited
               | continuations" or "felleisen delimited continuations"
        
       | adamnemecek wrote:
       | I have been thinking about continuations recently in the context
       | of GUIs. Has the idea been explored much? I feel like e.g.
       | dragging things in a list should be a nice match for this but I
       | couldn't quite figure out how to do it myself and I couldn't find
       | any literature.
        
       | fjfaase wrote:
       | In C# it is very simple to implement a generator using the 'yield
       | return' and 'yield break' statements. Also LINQ is based on
       | generators, allowing you to write rather complex queries with
       | relatively compact code.
        
       | [deleted]
        
       ___________________________________________________________________
       (page generated 2021-08-03 23:01 UTC)