Hello, everyone!
492 |
493 | I hope you all have your thinking caps on because this is going require your full attention.
494 |
495 | In this presentation I'm going to show you a brand new paradigm for handling
496 | async flow control in your code.
497 |
498 | So buckle up, because we have a lot of ground to cover, and it's going to be a wild ride.
Let's start by asking a simple question.
499 |
500 | What is flow control?
501 |
502 | Flow **control** is how imperative programs make a choice about what code is to
503 | be run.
504 |
505 | I want to emphasize that control doesn't just mean the visual design of the source
506 | code ... but also how much control **you** the end-developer has over the tools you
507 | can leverage.
508 |
509 | This is a subtle but important distinction that will be demonstrated throughout
510 | this presentation.
Let's continue.
511 |
512 | What is async flow control?
513 |
514 | Pause for effect. Count to 5.
515 |
516 | Async flow control is the same definition but using asynchronous constructs.
517 |
518 | I hope you are now recalling all the async constructs you know about.
519 |
520 | I'll give you a second to think about them in your head.
521 |
522 | Pause for effect. Count to 5.
I'm sure at some point in time we've employed all of these constructs to
523 | express async flow control ... with varying degress of success.
524 |
525 | However, some are considered by the dev community better than others.
526 |
527 | Can you rank-order these async constructs in your head from worst-to-best?
528 |
529 | Pause for effect. Count to 5.
530 |
531 | Callbacks are regarded as less-than-ideal. We've all heard the term "callback
532 | hell" and do everything we can to avoid falling into the trap of callbacks
533 | within callbacks within callbacks.
Why are callbacks within callbacks a bad paradigm?
534 |
535 | Pause for effect. Count to 5.
536 |
537 | What about the image on the right is less-than-ideal?
538 |
539 | Pause for effect. Count to 5.
540 |
541 | The zen of python dictates that "flat is better than nested" ... but why?
542 |
543 | When thinking about the visual design of code, there are a couple of features
544 | we care about:
545 |
546 | - Readability
547 | - Maintainability
548 | - The likelyhood of missing an error
549 |
550 | These are mission critical features of our code that require our thoughtful
551 | consideration. It is **imperative** that we are able to understand the code that
552 | we are writing ... and possibly more importantly, reading.
553 |
554 | Can you think of any other features we should care about?
555 |
556 | Pause for effect. Count to 5.
You'll read countless articles about how great `async`/`await` is and everyone
557 | should use it.
558 |
559 | You probably ranked `async`/`await` pretty high on your list, didn't you?
560 |
561 | Pause for effect. Count to 5.
562 |
563 | However, can we think of any downsides to using `async`/`await`?
564 |
565 | Pause for effect. Count to 5.
Here's a list of downsides I could think of quickly.
566 |
567 | Pause for effect. Count to 5.
568 |
569 | You're probably thinking: wait, I don't understand, some of these "downsides"
570 | are things that have never negatively impacted me.
571 |
572 | Trust your intuition.
But what if I told you there was a better way to express `async` flow control?
573 |
574 | Would you believe me?
575 |
576 | Pause for effect. Count to 5.
577 |
578 | But first, a detour.
Did you know functions have a color?
579 |
580 | Huh?! Excuse me?
581 |
582 | Pause for effect. Count to 3.
583 |
584 | Yes! Not all functions are created equal. They are not always interchangable.
585 |
586 | Functions behave differently depending on their **color**.
There's no problem with `blue` functions calling other `blue` functions: you'll
587 | get a return value synchronously.
588 |
589 | There's also no problem with `red` functions calling `blue` functions: you'll
590 | still get a promise that you can `await`.
591 |
592 | However, what happens when you call a `red` function inside a `blue` one?
593 |
594 | Pause for effect. Count to 3.
595 |
596 | What I'm really trying to demonstrate here is kind of subtle ...
597 | which is async/await **forces** our entire flow control to be async by default.
598 |
599 | You **must** always await the result of a `red` function.
600 |
601 | Don't worry, this is all leading to delimited continuations and why they are so interesting.
It's easy enough to just make every function `red` and then you `await`
602 | it inside the initial script ... right?
603 |
604 | So, what's the problem?
That's all fine and good but it has real implications to how the code functions.
605 |
606 | You are now living in the world where everything is a **promise**.
607 |
608 | Pause for effect.
609 |
610 | Ahhh, this is interesting.
611 |
612 | I want you to think about everything being a promise and with it all the pros and cons.
613 |
614 | Why would everything being a promise, be an issue?
Promises are great, right?
615 |
616 | Yes, they are! I use promises all the time, but there are a lot of questions to
617 | ask yourself when using them.
618 |
619 | Pause for effect. Count to 5.
620 |
621 | Finally, the JS engine resolves a promise via a **microtask**.
622 |
623 | Oh this is interesting.
624 |
625 | We've found a rabbit hole ... but here are some questions to ask yourself:
626 |
627 | - What is a microtask?
628 | - Why do I care that a Promise is a microtask?
629 | - What does that mean for the flow control of my code?
630 |
631 | I'm not going to answer these questions today, but rather ask you all to
632 | investigate further.
633 |
634 | Instead I'll just list some examples of micro- and macro-tasks
Here's the list.
635 |
636 | Pause for effect. Count to 5.
Finally! We made it!
637 |
638 | So what are delimited continuations?
639 |
640 | I'm going to try to explain delimited continuations and fail.
641 |
642 | This is something that I'm still struggling to grok as someone who is actively
643 | using them. I don't have all the answers and I'm **not** the expert.
644 |
645 | But that's okay, this presentation is designed to be an introduction and prompt you
646 | all to learn more.
647 |
648 | Before we go any further, I must make a detour.
Typescript has basic support for generators but not for the type of constructs
649 | we are building.
650 |
651 | This ... is a big problem. If we can't leverage typescript to resolve types
652 | properly we lose a ton of value in using delimited continuations as the
653 | foundation for async flow control.
654 |
655 | However, there's a hack and it's called yield delegates.
Yield delegates (the `yield *` syntax) provides a way to reach into the generator and have it output the proper type for us.
656 |
657 | It's a hack to be sure, but it works very well for our use-case.
Finally, you might be thinking, some code!
658 |
659 | This is where things get weird.
660 |
661 | Continuation is a library built by the team at frontside where most of the
662 | experimentation is happening.
As you can see at the bottom, we are doing nothing fancy. We are simply
663 | combining two strings together using a template function.
664 |
665 | You'll see references to `k` a lot in these examples. That is the continuation
666 | function. When called, it continues the yield from the shift onto the next
667 | yield statement. It is the continuation mechanism. Think of is like a
668 | callback.
Are we having fun yet?
669 |
670 | Laugh manically.
Let's take a step back for a second. How could we implement something similar
671 | using plain `blue` functions?
672 |
673 | We have a function return a function return a function.
674 |
675 | This is a variant of callback hell, it's not ideal, but it works for the example we
676 | demonstrated previously.
Let's try this again but using a coninuation-passing style `blue` function.
677 |
678 | Here we receive the templated string as a callback to our `cps` function.
679 |
680 | This is also a variant of callback hell. Not great and in this example
681 | unnecessary.
682 |
683 | But, you can imagine when you start adding `red` functions here that it will
684 | eventually become necessary.
685 |
686 | Let's continue.
Back to delimited continuations. Except this time, we are going to mix some
687 | `red` functions into our flow control.
Using deliminited continuations, we can compose `shift()` to do some complex
688 | flow control.
689 |
690 | I'd love to focus on this example, however, for the sake of preventing our
691 | collective **minds** from exploding, I'll spare us the details and briefly
692 | summarize.
693 |
694 | - Here we are adding a bunch of numbers together
695 | - Some numbers are resolved syncronously and some are async
696 | - The key idea I want to illustrate is that composition using `shift()` is not
697 | only possible but relatively flat and easy to do
698 |
699 | Please feel free to grab the github repo for this slide deck and run this
700 | example at your leisure.
Finally, I want to demonstrate computation in a bottle which is what `reset()`
701 | is all about.
702 |
703 | Here we are using `reset()` to make the counter reusable.
704 |
705 | Now we can call `useCounter` as many times as we want in order to create new
706 | instances of our counter.
707 |
708 | ```ts
709 | evaluate(function*() {
710 | let x = yield* useCounter();
711 | let y = yield* useCounter();
712 | });
713 | ```
714 |
715 | I'm going to stop there. There is so much more to learn about this paradigm
716 | but unfortunately we have to move on.
It's time to go higher level.
I know what you're thinking.
717 |
718 | This is a lot to handle.
719 |
720 | I'd like to take a second for us all to take a deep breathe because we aren't
721 | done yet.
722 |
723 | Pause for effect. Count to 5.
724 |
725 | Ok, here we go
Building on top of `effection` we now have a set of middle-level primitives that
726 | allow us to build any flow control paradigm we want.
727 |
728 | Using suspend, action, spawn, createChannel, and sleep we have everything we
729 | need to express all forms of async flow control. This is the one paradigm to
730 | rule all paradigms.
731 |
732 | I know it probably hasn't clicked yet, and I'm probably not doing the best job
733 | explaining its full potential.
734 |
735 | But give it time, stay engaged, join the discord where we are actively
736 | developing this technology.
Here is a function that fetches all book chapters in parallel but processes
737 | them in sequence using `for await ... of`.
738 |
739 | Pretty cool.
740 |
741 | Pause for effect. Count to 5.
The key point I want to make here is the end-user needs to be thoughtful about
742 | the promises they activate while at the same time leveraging the JS engine to
743 | handle async flow control for them.
744 |
745 | This is tough because there are no signals -- not even in typescript -- that
746 | aid you in detecting unhandle promise rejections.
747 |
748 | Ouch.
Let's go higher level.
749 |
750 | `starfx` builds off of effection in order to be used inside the browser.
751 |
752 | It is an experimental library that could eventually supersede redux-saga as
753 | well as react-query and more.
Here we use a couple APIs from `starfx` to do the same thing but with a bunch
754 | of safety mechanisms built in.
755 |
756 | `request` is a wrapper around `fetch` that will automatically abort when a task
757 | is `halt()`ed.
758 |
759 | `parallel` is a function to call all functions it receives at the same time and
760 | returns a channel that the user can loop over in sequence -- or even as they become
761 | available. It's dealers choice.
762 |
763 | Further, `parallel` will never throw, it automatically catches errors, and returns
764 | a `Result` for each request.
765 |
766 | `Result` is inspired by rust's `Result` type which is highly regarded.
767 |
768 | To be clear, this is a proposed API, we are stilling working on the initial
769 | implementation.
So what do we have?
770 |
771 | Read slide.
772 |
773 | We are not restricted to the async flow control `Promise` and `async`/`await` provides.
774 |
775 | We are able to create as many different flow control structures as we want, all leveraging delimited continuations.
We are just getting started.
776 |
777 | This is an area of active development and a foundation on which we can build
778 | any async flow control constructs.
779 |
780 | I don't know if it has clicked for anyone else, but it has pretty much consumed
781 | all my free time and energy.
I wanted to give a quick shout out to Aptible, the most successful PaaS you
782 | didn't know existed.
783 |
784 | I've been working there for 4+ years.
785 |
786 | Please feel free to contact me or aptible if you are interested in learning
787 | more.
Also a shout out to the team at Frontside who created most of the paradigms and
788 | libraries I've been talking about today.
--------------------------------------------------------------------------------
/deck.md:
--------------------------------------------------------------------------------
1 | ---
2 | paginate: true
3 | ---
4 |
5 | # Delimited continuations are all you need
6 |
7 | 
8 |
9 |
19 |
20 | ---
21 |
22 | Flow **control** is how imperative programs make a choice about what code is to
23 | be run.[^1]
24 |
25 |
40 |
41 | ---
42 |
43 | Async flow control is the same definition but using asynchronous constructs.
44 |
45 |
60 |
61 | ---
62 |
63 | There a lot of familiar paradigms we can employ in Typescript:
64 |
65 | - callbacks
66 | - promises
67 | - generators
68 | - observables
69 | - `async`/`await`
70 |
71 |
85 |
86 | ---
87 |
88 | # Why are callbacks within callbacks a bad paradigm?
89 |
90 | 
91 |
92 |
118 |
119 | ---
120 |
121 | You'll read countless articles about how great `async`/`await` is and everyone
122 | should use it.
123 |
124 | However, can we think of any downsides to using `async`/`await`?
125 |
126 |
138 |
139 | ---
140 |
141 | # Downsides of `async`/`await`
142 |
143 | - Everything is a promise
144 | - Everything is now async
145 | - The browser controls the flow of execution for `async`/`await`
146 | - Unhandled promise rejections are pernicious
147 |
148 |
158 |
159 | ---
160 |
161 | What if I told you there was a better way to express `async` flow control?
162 |
163 |
172 |
173 | ---
174 |
175 | # Detour: What color is your function?[^2]
176 |
177 | ---
178 |
179 | Did you know functions have a color?
180 |
181 | - `function() {}` -> blue
182 | - `async function() {}` -> red
183 | - `function*() {}` -> green
184 | - `async function*() {}` -> yellow
185 |
186 |
197 |
198 | ---
199 |
200 | - `function() {}` -> `blue`
201 | - `async function() {}` -> `red`
202 |
203 | There's no problem with `blue` functions calling other `blue` functions: you'll
204 | get a return value synchronously.
205 |
206 | There's also no problem with `red` functions calling `blue` functions: you'll
207 | still get a promise that you can `await`.
208 |
209 | However, what happens when you call a `red` function inside a `blue` one?
210 |
211 |
229 |
230 | ---
231 |
232 | It's easy enough to just make every function `red` and then you `await` it
233 | inside the initial script:
234 |
235 | ```ts
236 | async function init() {
237 | // do some flow control
238 | }
239 |
240 | init().then(console.log).catch(console.error);
241 | ```
242 |
243 |
249 |
250 | ---
251 |
252 | That's all fine and good but it has real implications to how the code functions.
253 |
254 | You are now living in the world where everything is a **promise**.
255 |
256 |
269 |
270 | ---
271 |
272 | Promises are great, right?
273 |
274 | Questions to think about:
275 |
276 | - How do I compose promises to express flow control? (e.g. parallel, race, etc.)
277 | - How do I handle promise errors?
278 | - What happens if I forget to `.catch()` every promise?
279 | - How can I cancel a promise?
280 |
281 | Finally, the JS engine resolves a promise via a **microtask**.
282 |
283 |
306 |
307 | ---
308 |
309 | - macrotasks:
310 | - `setTimeout`
311 | - `setInterval`
312 | - `setImmediate`
313 | - `requestAnimationFrame`
314 | - UI rendering
315 | - microtasks:
316 | - `Promise`
317 | - `process.nextTick`
318 | - `queueMicrotask`
319 | - `MutationObserver`
320 |
321 | Read more about microtasks.[^3]
322 |
323 |
328 |
329 | ---
330 |
331 | # What are delimited continuations (DC)?
332 |
333 | - It's all about flow control
334 | - Doesn't matter if it's sync or async, DCs look the same
335 | - DC is the abstraction upon which all flow control paradigms can be built
336 | - **continuation:** The rest of code execution **reified (A)** inside a function
337 | - **delimited:** The **reified (A)** function can return a value -- which allows
338 | for composition
339 | - Two primitives -- `shift()` and `reset()` -- are all you need for complex
340 | async flow control
341 |
342 |
357 |
358 | ---
359 |
360 | # Detour: Yield delegates
361 |
362 | Typescript has basic support for generators but not for the type of constructs
363 | we are building.[^4]
364 |
365 | ```ts
366 | function* fun() {
367 | return number;
368 | }
369 |
370 | function* raw() {
371 | const value = yield fun(); // value = any
372 | }
373 | ```
374 |
375 |
385 |
386 | ---
387 |
388 | Yield delegates `yield *` provide a way to reach into the generator and have it
389 | output the proper type.
390 |
391 | ```ts
392 | function* fun() {
393 | return number;
394 | }
395 |
396 | function* typed() {
397 | const value = yield* fun(); // value = number
398 | }
399 | ```
400 |
401 | Now we can use some clever tricks to get the proper types out of our `yield`
402 | statements.
403 |
404 |
409 |
410 | ---
411 |
412 | # Code examples
413 |
414 | Using `continuation`[^5]
415 |
416 |
424 |
425 | ---
426 |
427 | ## Sync example
428 |
429 | The first example I would like to show is one of simple string concatenation.
430 |
431 | ---
432 |
433 | The entire flow of the `evaluate()` function is synchronous in this example and
434 | as such we can use it like a normal sync function.
435 |
436 | ```ts
437 | import { Computation, shift, evaluate } from './deps.ts';
438 |
439 | function slot(): Computation {
440 | return shift(function* (k) {
441 | return k;
442 | });
443 | }
444 |
445 | function dc() {
446 | return evaluate<(s: string) => (s: string) => string>(function* () {
447 | const greeting = yield* slot();
448 | const thing = yield* slot();
449 | return `${greeting}, ${thing}!`;
450 | });
451 | }
452 |
453 | const tmpl = dc();
454 | const result = tmpl('hello')('world');
455 | console.log(result); // hello, world!
456 | ```
457 |
458 |
467 |
468 | ---
469 |
470 | ### key points
471 |
472 | - `k` is the continuation "callback"
473 | - When you return `k` from a shift, the evaluate function returns that `k`
474 | function as its value
475 | - Inside the evaluate function `greeting` and `thing` are the values passed into
476 | it from outside the `evaluate` body function
477 | - `shift()` can be thought of as slots that eventually get filled by values
478 | passed into `evaluate()`!
479 |
480 | ---
481 |
482 | 
483 |
484 |
489 |
490 | ---
491 |
492 | ```ts
493 | function dc() {
494 | return (left: string) => {
495 | return (right: string) => {
496 | return `${left}, ${right}!`;
497 | };
498 | };
499 | }
500 |
501 | const tmpl = dc();
502 | const result = tmpl('hello')('world');
503 | console.log(result); // hello, world!
504 | ```
505 |
506 |
515 |
516 | ---
517 |
518 | Continuation-passing style[^6]
519 |
520 | ```ts
521 | function cps(cont: (s: string) => void) {
522 | return (left: string) => {
523 | return (right: string) => {
524 | cont(`${left}, ${right}!`);
525 | };
526 | };
527 | }
528 |
529 | const tmpl = cps((result) => {
530 | console.log(result); // hello, world!
531 | });
532 | tmpl('hello')('world');
533 | ```
534 |
535 |
548 |
549 | ---
550 |
551 | ## Async example
552 |
553 | In the next example, I would like to show how we can incorporate async flow
554 | control.
555 |
556 | ```ts
557 | import { evaluate, shift } from './deps.ts';
558 |
559 | const run = evaluate<(n: number) => Promise>(function* () {
560 | const left = yield* shift(function* (k) {
561 | return k;
562 | });
563 |
564 | const right = yield* shift(function* (resolve) {
565 | return Promise.resolve(55).then(resolve);
566 | });
567 |
568 | return left + right;
569 | });
570 |
571 | const result = await run(13);
572 | console.log(result); // 68
573 | ```
574 |
575 |
579 |
580 | ---
581 |
582 | ### key points
583 |
584 | - It doesn't matter if it's sync or async, delimited continuations can handle
585 | that flow control
586 | - The `right` shift returns a promise which is what gets returned when calling
587 | `run(13)`
588 | - Since the return value is a promise, we `await` the answer
589 | - I renamed the `shift` `k` variable to `resolve` to demonstrate how similar it
590 | is to a promise "continuation."
591 | - When you use a promise, it will always be async
592 | - When you use a delimited continuation, it might be async
593 | - It doesn't matter if the flow of code execution is sync or async, delimited
594 | continuations handle them the exact same
595 |
596 | ---
597 |
598 | ## Composition example
599 |
600 | ```ts
601 | import { shift, evaluate, Computation, Continuation } from "./deps.ts";
602 |
603 | type ShiftProp = (
604 | res: Continuation,
605 | rej?: Continuation
606 | ) => Computation;
607 |
608 | function* add(
609 | lhs: ShiftProp,
610 | rhs: ShiftProp
611 | ): Computation {
612 | const left = yield* shift(lhs);
613 | const right = yield* shift(rhs);
614 | return left + right;
615 | }
616 |
617 | const sync = (value: number) =>
618 | function* (k: Continuation) {
619 | return k(value);
620 | };
621 |
622 | const ev = evaluate(function* () {
623 | const first = yield* add(sync(13), function* (k) {
624 | return Promise.resolve(55).then(k);
625 | });
626 |
627 | const second = yield* add(
628 | function* (k) {
629 | setTimeout(() => k(21), 1000);
630 | },
631 | function* (k) {
632 | k(Math.random());
633 | }
634 | );
635 |
636 | const result = yield* add(sync(first), sync(second));
637 | console.log(result);
638 | return result;
639 | });
640 |
641 | console.log(ev);
642 | ```
643 |
644 |
660 |
661 | ---
662 |
663 | # Counter example
664 |
665 | ```ts
666 | interface Count {
667 | value: number;
668 | increment(): Count;
669 | }
670 |
671 | function useCounter() {
672 | return reset(function* () {
673 | for (let i = 0; ; i++) {
674 | yield* shift(function* (k) {
675 | return { value: i, increment: k };
676 | });
677 | }
678 | });
679 | }
680 |
681 | let start = evaluate(useCounter);
682 |
683 | let once = start.increment();
684 | let twice = once.increment();
685 | let thrice = twice.increment();
686 |
687 | console.dir([once.value, twice.value, thrice.value]); // [1, 2, 3]
688 | ```
689 |
690 |
709 |
710 | ---
711 |
712 | 
713 |
714 | # wat
715 |
716 | ---
717 |
718 | 
719 |
720 | # wat
721 |
722 | ---
723 |
724 | I'm still very confused by the coding paradigm. Everytime I look at this code I
725 | get a headache.
726 |
727 | Most end-developers aren't going to be using delimited continuations directly.
728 |
729 | Rather, this tool will allow library developers to build on top of it.
730 |
731 | ---
732 |
733 | ## effection v3[^7]
734 |
735 | `effection` takes `continuation` and builds a task tree. All tasks are cleaned
736 | up automatically via a set of cancellation strategies:
737 |
738 | - tasks spawn other tasks
739 | - tasks can be `halt()`ed
740 | - All descendants are `halt()`ed
741 | - All ancestors are `halt()`ed
742 |
743 |
746 |
747 | ---
748 |
749 | `effection` also has higher level compositions of `shift()` and `reset()` which
750 | grants us a flourish of functionality. For example:
751 |
752 | - `suspend()`: permenantly suspend a generator function at a `yield` point.
753 | - `action()`: which is a wrapper for `shift()` with proper cleanup of the task
754 | - `spawn()`: creates a sub-task
755 | - `createChannel()`: kind of like an event emitter but using DC
756 | - `sleep(n)`: temporarily suspend generator function for for (n) milliseconds
757 |
758 | ---
759 |
760 | 
761 |
762 | # I still don't get it
763 |
764 |
776 |
777 | ---
778 |
779 | Building on top of `effection` we now have a set of middle-level primitives that
780 | allow us to build any flow control paradigm we want.
781 |
782 |
796 |
797 | ---
798 |
799 | Jake Archibald recently wrote an interesting article about unhandled
800 | rejections.[^8]
801 |
802 | In it he talks about the use case of wanting to fetch book chapters in parallel
803 | but process them in sequence:
804 |
805 | ```ts
806 | async function showChapters(chapterURLs) {
807 | const chapterPromises = chapterURLs.map(async (url) => {
808 | const response = await fetch(url);
809 | return response.json();
810 | });
811 |
812 | for await (const chapterData of chapterPromises) {
813 | appendChapter(chapterData);
814 | }
815 | }
816 | ```
817 |
818 |
826 |
827 | ---
828 |
829 | He goes on to describe the the potential for bugs because of
830 | `unhandled rejections`. Below is the "final" solution.
831 |
832 | ```diff
833 | async function showChapters(chapterURLs) {
834 | const chapterPromises = chapterURLs.map(async (url) => {
835 | const response = await fetch(url);
836 | return response.json();
837 | });
838 |
839 | + for (const promise of chapterPromises) promise.catch(() => {});
840 |
841 | for await (const chapterData of chapterPromises) {
842 | appendChapter(chapterData);
843 | }
844 | }
845 | ```
846 |
847 |
857 |
858 | ---
859 |
860 | # `starfx`[^9]
861 |
862 | 
863 |
864 |
872 |
873 | ---
874 |
875 | Using `starfx`[^9] we could do something like this:
876 |
877 | ```ts
878 | import { request, json, parallel, forEach } from 'starfx';
879 |
880 | function* showChapters(chapterURLs: string[]) {
881 | const reqs = chapterURLs.map(function (url) {
882 | return function* () {
883 | const response = yield* request(url);
884 | return yield* json(response);
885 | };
886 | });
887 |
888 | const chapters = yield* parallel(reqs);
889 |
890 | yield* forEach(chapters.sequence, function* (chapter) {
891 | if (chapter.ok) {
892 | appendChapter(chapter.value);
893 | } else {
894 | console.error(chapter.error);
895 | }
896 | });
897 | }
898 | ```
899 |
900 |
919 |
920 | ---
921 |
922 | No uncaught exceptions, code is just as simple to understand.
923 |
924 | Further, we automatically pass an `AbortController.signal` to all http requests
925 | because of the `request` fx we wrote.
926 |
927 | When a task is `halt()`ed or crashes, we trigger the signal to abort the `fetch`
928 | call.
929 |
930 | Automatic cleanup!
931 |
932 |
941 |
942 | ---
943 |
944 | # We are just getting started
945 |
946 | 
947 |
948 | Things we plan on building:
949 |
950 | - Inspector / debugger for `effection`
951 | - Side-effect system for `redux` (ala `redux-saga`)
952 | - Query and cache management (ala `react-query`)
953 | - Web server (ala `express`)
954 | - View library (ala `react`)
955 |
956 |
965 |
966 | ---
967 |
968 | 
969 |
970 |  [^10]
971 |
972 | The most successful PaaS you didn't know existed.
973 |
974 | https://aptible.com
975 |
976 |
985 |
986 | ---
987 |
988 | # The Frontside[^11]
989 |
990 | https://frontside.com
991 |
992 | https://discord.gg/frontside
993 |
994 |
998 |
999 | ---
1000 |
1001 | # fin
1002 |
1003 | [^1]: https://blog.container-solutions.com/is-it-imperative-to-be-declarative
1004 | [^2]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function
1005 | [^3]: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules
1006 | [^4]: https://github.com/microsoft/TypeScript/issues/32523
1007 | [^5]: https://github.com/thefrontside/continuation
1008 | [^6]: https://en.wikipedia.org/wiki/Continuation-passing_style
1009 | [^7]: https://github.com/thefrontside/effection
1010 | [^8]: https://jakearchibald.com/2023/unhandled-rejections/
1011 | [^9]: https://github.com/neurosnap/starfx
1012 | [^10]: https://aptible.com
1013 | [^11]: https://frontside.com
1014 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/deno.json
--------------------------------------------------------------------------------
/deno.lock:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2",
3 | "remote": {
4 | "https://deno.land/x/continuation@0.1.5/mod.ts": "690def2735046367b3e1b4bc6e51b5912f2ed09c41c7df7a55c060f23720ad33"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ex/async.ts:
--------------------------------------------------------------------------------
1 | import { evaluate, shift } from "./deps.ts";
2 |
3 | const run = evaluate<(n: number) => Promise>(function* () {
4 | const left = yield* shift(function* (k) {
5 | return k;
6 | });
7 |
8 | const right = yield* shift(function* (resolve) {
9 | return Promise.resolve(55).then(resolve);
10 | });
11 |
12 | return left + right;
13 | });
14 |
15 | const result = await run(13);
16 | console.log(result); // 68
17 |
--------------------------------------------------------------------------------
/ex/callback.ts:
--------------------------------------------------------------------------------
1 | function plan() {
2 | const left = "hello";
3 | const right = "world";
4 | return `${left}, ${right}!`;
5 | }
6 |
7 | function dc() {
8 | // reify the continuation of this function in a function
9 | return (left: string) => {
10 | // reify the continuation of this function in a function
11 | return (right: string) => {
12 | // return the result
13 | return `${left}, ${right}!`;
14 | };
15 | };
16 | }
17 |
18 | const tmpl = dc();
19 | const result = tmpl("hello")("world");
20 | console.log(result);
21 |
22 | function cps(cont: (s: string) => void) {
23 | return (left: string) => {
24 | return (right: string) => {
25 | cont(`${left}, ${right}!`);
26 | };
27 | };
28 | }
29 |
30 | const tmplCps = cps((result) => {
31 | console.log(result);
32 | });
33 | tmplCps('hello')('world');
34 |
--------------------------------------------------------------------------------
/ex/compose.ts:
--------------------------------------------------------------------------------
1 | import { shift, evaluate, Computation, Continuation } from "./deps.ts";
2 |
3 | type ShiftProp = (
4 | res: Continuation,
5 | rej?: Continuation
6 | ) => Computation;
7 |
8 | function* add(
9 | lhs: ShiftProp,
10 | rhs: ShiftProp
11 | ): Computation {
12 | const left = yield* shift(lhs);
13 | const right = yield* shift(rhs);
14 | return left + right;
15 | }
16 |
17 | const sync = (value: number) =>
18 | function* (k: Continuation) {
19 | return k(value);
20 | };
21 |
22 | function* async(): Computation<{
23 | resolve: (r: T) => void;
24 | reject: (e: Error) => void;
25 | }> {
26 | return yield* shift(function* (k) {
27 | const promise = new Promise((resolve, reject) => {
28 | k({ resolve, reject });
29 | });
30 |
31 | return promise;
32 | });
33 | }
34 |
35 | const ev = evaluate>(function* () {
36 | const { resolve } = yield* async();
37 |
38 | const first = yield* add(sync(13), function* (k) {
39 | return Promise.resolve(55).then(k);
40 | });
41 |
42 | const second = yield* add(
43 | function* (k) {
44 | setTimeout(() => k(21), 1000);
45 | },
46 | function* (k) {
47 | k(Math.random());
48 | }
49 | );
50 |
51 | const result = yield* add(sync(first), sync(second));
52 | resolve(result);
53 | });
54 |
55 | const result = await ev;
56 | console.log(result);
57 |
--------------------------------------------------------------------------------
/ex/counter.ts:
--------------------------------------------------------------------------------
1 | import { reset, shift, evaluate } from "./deps.ts";
2 |
3 | interface Count {
4 | value: number;
5 | increment(): Count;
6 | }
7 |
8 | function useCounter() {
9 | return reset(function* () {
10 | for (let i = 0; ; i++) {
11 | yield* shift(function* (k) {
12 | return { value: i, increment: k };
13 | });
14 | }
15 | });
16 | }
17 |
18 | const start = evaluate(useCounter);
19 |
20 | const once = start.increment();
21 | const twice = once.increment();
22 | const thrice = twice.increment();
23 |
24 | console.dir([once.value, twice.value, thrice.value]); // [1, 2, 3]
25 |
--------------------------------------------------------------------------------
/ex/create-template.ts:
--------------------------------------------------------------------------------
1 | import { Computation, shift, evaluate } from "./deps.ts";
2 |
3 | function slot(): Computation {
4 | return shift(function* (k) {
5 | return k;
6 | });
7 | }
8 |
9 | function dc() {
10 | return evaluate<(s: string) => (s: string) => string>(function* () {
11 | const greeting = yield* slot();
12 | const thing = yield* slot();
13 | return `${greeting}, ${thing}!`;
14 | });
15 | }
16 |
17 | const tmpl = dc();
18 | const result = tmpl("hello")("world");
19 | console.log(result);
20 |
--------------------------------------------------------------------------------
/ex/deps.ts:
--------------------------------------------------------------------------------
1 | export {
2 | shift,
3 | evaluate,
4 | reset,
5 | } from "https://deno.land/x/continuation@0.1.5/mod.ts";
6 |
7 | export type { Computation, Continuation } from "https://deno.land/x/continuation@0.1.5/mod.ts";
8 |
--------------------------------------------------------------------------------
/ex/starfx.ts:
--------------------------------------------------------------------------------
1 | import { request, json, parallel, forEach } from 'starfx';
2 |
3 | function* showChapters(chapterURLs: string[]) {
4 | const reqs = chapterURLs.map(function (url) {
5 | return function* () {
6 | const response = yield* request(url);
7 | return yield* json(response);
8 | };
9 | });
10 |
11 | const chapters = yield* parallel(reqs);
12 |
13 | yield* forEach(chapters.sequence, function* (chapter) {
14 | if (chapter.ok) {
15 | appendChapter(chapter.value);
16 | } else {
17 | console.error(chapter.error);
18 | }
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/ex/yield-delegate.ts:
--------------------------------------------------------------------------------
1 | import { evaluate } from "./deps.ts";
2 |
3 | function* fun() {
4 | return 1;
5 | }
6 |
7 | function* raw(): Generator {
8 | const val = yield fun();
9 | return val;
10 | }
11 |
12 | function* typed() {
13 | const val = yield* fun();
14 | return val;
15 | }
16 |
17 | console.log(evaluate(typed));
18 |
--------------------------------------------------------------------------------
/img/aptible.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/img/arrow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/arrow.jpg
--------------------------------------------------------------------------------
/img/calc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/calc.png
--------------------------------------------------------------------------------
/img/callback-hell.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/callback-hell.jpg
--------------------------------------------------------------------------------
/img/confused.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/confused.jpg
--------------------------------------------------------------------------------
/img/db_vader.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/db_vader.jpg
--------------------------------------------------------------------------------
/img/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/qrcode.png
--------------------------------------------------------------------------------
/img/starfx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/starfx.png
--------------------------------------------------------------------------------
/img/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neurosnap/deck-continuations/fbbb912092adc557ad4e61f3b8c15d0956037cf1/img/tree.png
--------------------------------------------------------------------------------
/img/yellow-to-lavender.svg:
--------------------------------------------------------------------------------
1 |
43 |
--------------------------------------------------------------------------------