Messing around with delimited continuations, fibers, and algebraic effects

Overview

A Simple Virtual Machine with Effects

Each thread of execution in this VM is called a Fiber. A Fiber is unique, can be sent between threads, but can not be copied. It has:

  • A local stack
  • Some bytecode
  • A program counter

Which is pretty neat.

An effect is basically a dynamically-scoped function. System injection is leveraging effects so that the host runtime can interact with the VM.

A simple example

Here's the stack of an example fiber running a program—the [X] denote stack frames

[A] 1 2 [B] 3 4

Let's say we want to print 4 using system injection. First thing we do is execute the print effect. This checks the frames [A] and [B] for handlers. Not surprisingly, there are none.

For this reason, 4 is popped off the stack and wrapped up with the name of the effect; the Fiber itself then returns this wrapped print|4 effect. This is then handled by the runtime, and the Fiber is then resumed by passing in a value. Let's say printing returns zero:

[A] 1 2 [B] 3 4 -- initial stack
[A] 1 2 [B] 3   -- system injection
[A] 1 2 [B] 3 0 -- new stack

Introducing handlers

Beautiful. Now, let's say that we are back to the original stack, and want to print again. This time, however, [A] has registered an effect handler. We've denoted the fact that [A] now has a handler with a *:

[A*] 1 2 [B] 3 4

Wow! A handler is just a function that takes a value, and ultimately either returns another value or resumes with a value.

Let's start with the easier of the two cases, that the effect just resumes:

[A*]:
    print = v -> resume (v + 1)

Let's walk through the call to print again, with this new definition of print in place:

[A*] 1 2 [B] 3 4

Let's say we want to print 4 using effect handling. First thing we do is execute the print effect. This checks the frames [A*] and [B] for handlers. This time, we find a print handler on frame [A*].

For this reason, 4 is popped off the stack. We then create a new fiber, and initialize it with the print handler function:

[A*] 1 2 [B] 3 4 -- initial stack

[A*] 1 2 [B] 3 -- not active
[C] 4          -- active handler stack

We then run this function:

[A*] 1 2 [B] 3 -- not active
[C] 5          -- active after `(v + 1)`

And then invoke resume. When resume is invoked, we pop 5 off the stack, destroy the handling stack, push 5 on to the original stack, and resume execution:

[A*] 1 2 [B] 3 -- not active
[C]            -- resume 5

[A*] 1 2 [B] 3 5 -- active

Resume mirrors the path of a system injection, only the system that is handling the code is not the parent system.

Something a bit more complex

This is the trivial case; let us now discuss the more complex case, when resume is not invoked and a value is returned instead.

Here's the original stack, yet again:

[A*] 1 2 [B] 3 4

And the handler is now:

[A*]:
    print = v -> (v - 1)

Note that the handler does not resume and instead returns the value minus one. With this new handler in mind, let's simulate the system:

Let's say we want to print 4 using effect handling. First thing we do is execute the print effect. This checks the frames [A*] and [B] for handlers. This time, we find a print handler on frame [A*].

For this reason, 4 is popped off the stack. We then create a new fiber, and, like before, initialize it with the print handler function:

[A*] 1 2 [B] 3 4 -- initial stack

[A*] 1 2 [B] 3 -- not active
[C] 4          -- active handler stack

[A*] 1 2 [B] 3 -- not active
[C] 3          -- active after (v - 1)

No surprises so far; we now execute the return instruction, like this were a normal function:

[A*] 1 2 [B] 3 -- not active
3              -- returned

Returning just replaces the topmost stack frame with the value to be returned. This is true in all cases.

So now, the question becomes, what happens next?

When an effect is called, we keep track of the stack frame that caused the effect to be raised. so, for instance:

[A*] 1 2 [B] 3
[C:A*] 3

The stack frame [C] was created because of an effect defined in [A*], hence: [C:A*].

When we return, we check whether the stack frame was created because of an effect. because [C] was obviously created because of an effect, instead of returning from [C], we instead return from [A*]:

[A*] 1 2 [B] 3 -- first stack
[C:A*] 3       -- before return

3              -- first stack after return

Alright, so the clever among you may have noticed something: The second stack for handling the effect runs once, then is destroyed; the stack below it is never modified.

For this reason, the combination of effects and handlers, with the resume operation, create delimited continuations, which can be used to implement control flow.

Fibers

You might also like...
In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.
In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.

Learn Rust What is this? In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang. This is usef

A tool and library to losslessly join multiple .mp4 files shot with same camera and settings

mp4-merge A tool and library to losslessly join multiple .mp4 files shot with same camera and settings. This is useful to merge multiple files that ar

A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models you subscribe to on Onlyfans.

OF-notifier A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models

A simpler and 5x faster alternative to HashMap in Rust, which doesn't use hashing and doesn't use heap

At least 5x faster alternative of HashMap, for very small maps. It is also faster than FxHashMap, hashbrown, ArrayMap, and nohash-hasher. The smaller

A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32 architectures going step-by-step into the world of reverse engineering Rust from scratch.
A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32 architectures going step-by-step into the world of reverse engineering Rust from scratch.

FREE Reverse Engineering Self-Study Course HERE Hacking Rust A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32

Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

:crab: Small exercises to get you used to reading and writing Rust code!
:crab: Small exercises to get you used to reading and writing Rust code!

rustlings 🦀 ❤️ Greetings and welcome to rustlings. This project contains small exercises to get you used to reading and writing Rust code. This inclu

A catalogue of Rust design patterns, anti-patterns and idioms

Rust Design Patterns An open source book about design patterns and idioms in the Rust programming language that you can read here. Contributing You ar

Simple and performant hot-reloading for Rust

reloady Simple, performant hot-reloading for Rust. Requires Rust nightly and only works on Linux for now. installing CLI To install the CLI helper car

Owner
Isaac Clayton
Student and Software Architect, builder of tools. Enjoys Language Design, Graphics Programming, and Machine Learning.
Isaac Clayton
Algebraic structures, higher-kinded types and other category theory bad ideas

Algar Algebric structures, higher-kinded types and other category theory bad ideas. Yes, you'll have generalized functors, applicatives, monads, trave

Stefano Candori 3 Jan 31, 2023
A simplistic functional programming language based around Lisp syntax.

Orchid A simplistic functional programming language based around Lisp syntax. Short taste # function to return the larger list (fn larger-list (as bs)

rem 3 May 7, 2022
A wrapper around Rust futures that stores the future in space provided by the caller.

StackFuture This crate defines a StackFuture wrapper around futures that stores the wrapped future in space provided by the caller. This can be used t

Microsoft 278 Dec 29, 2022
c-library wrapper around the rust pdb crate

pdbcrust: pdbcrust is a c-library wrapper around the rust pdb crate. The API only exports a minimum subset of the pdb crate functionality. The project

Ulf Frisk 7 Feb 23, 2023
An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous and blocking clients respectively.

eithers_rust An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous a

null 2 Oct 24, 2021
Safe, efficient, and ergonomic bindings to Wolfram LibraryLink and the Wolfram Language

wolfram-library-link Bindings to the Wolfram LibraryLink interface, making it possible to call Rust code from the Wolfram Language. This library is us

Wolfram Research, Inc. 28 Dec 6, 2022
This blog provides detailed status updates and useful information about Theseus OS and its development

The Theseus OS Blog This blog provides detailed status updates and useful information about Theseus OS and its development. Attribution This blog was

Theseus OS 1 Apr 14, 2022
Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously

Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously. It is intended to suit one's every requirement regarding chat on omegle.

null 1 May 25, 2022
Fast and simple datetime, date, time and duration parsing for rust.

speedate Fast and simple datetime, date, time and duration parsing for rust. speedate is a lax† RFC 3339 date and time parser, in other words, it pars

Samuel Colvin 43 Nov 25, 2022