[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)