Planet Scheme

Wednesday, February 25, 2026

spritely.institute

Hoot 0.8.0 released!

We are excited to announce the release of Hoot 0.8.0! Hoot is a Scheme to WebAssembly compiler backend for Guile, as well as a general purpose WebAssembly toolchain. In other words, Scheme in the browser!

This release contains new features and bug fixes and since the 0.7.0 release back in October.

New features

  • New (hoot repl) module. At long last, there is now a built-in read-eval-print loop implementation! Previous releases added a macro expander, a Scheme interpreter, and a runtime module system, but now it’s possible to do live hacking from a Hoot program inside a WebAssembly runtime!

    • To use the REPL, compile your Wasm binary with the necessary debug flag during development: guild compile-wasm -g1. This will include the runtime module system in the resulting binary. Expect compilation time and binary size to increase significantly. The trade-off is that a live hacking workflow will make recompilations fewer and farther between.

  • While not shipping in Hoot directly, initial support for using the Hoot REPL from Emacs has been added in the new geiser-hoot extension. We have submitted geiser-hoot for inclusion in MELPA and Guix so it will be easy to install in the very near future.

  • Enhanced (hoot web-server) module. To support the use of REPLs running within a web browser tab, the most common development use case, the web server doubles as a REPL server, proxying TCP traffic from REPL clients (more about that below) over a WebSocket to the connected browser tab.

    • These enhancements introduce two new, optional depedencies to Hoot: Fibers and guile-websocket. If either of these dependencies are not present at build time, the (hoot web-server) module will not be built.

    • The web server can now be extended with a user-supplied request router. An example of this can be found in our hoot-slides repository.

  • New (hoot web-repl) module. This module can be imported and compiled into the Wasm binary so that it can act as a REPL server. This is complicated by the fact that a browser client cannot act as a server, it is strictly a client. Instead, it connects to the aforementioned (hoot web-server) which acts as a proxy for all connected REPL clients.

  • New hoot command-line tool. This command will be used as a place to collect handy Hoot development tools. So far, there are two subcommands:

    • hoot repl: Open a REPL running in Node. Useful for quickly trying out basic Scheme expressions in Hoot without having to compile a standalone WebAssembly program.

    • hoot server: Conveniently launch the development web server in (hoot web-server).

  • New (web request) and (web response) modules that export a sliver of the API defined in Guile’s modules of the same names.

  • New (web socket) module that provides a input/output interface to WebSocket client connections. Mimicks the module of the same name in guile-websocket.

  • Added customizable module loader interface via new current-module-loader parameter. Two concrete loaders are provided: By default, modules are loaded from the file system by searching a load path. This is useful when running in a non-browser runtime such as NodeJS. When run-web-repl in (web repl) is used, connected REPLs are configured to use an HTTP-based loader. This loader makes HTTP requests to a special endpoint on the development web server to fetch source code.

    • Note that modules loaded at runtime are loaded from source and then interpreted. Unlike Guile, where modules are automatically compiled to bytecode, Hoot cannot compile individual modules to Wasm (which would require compiling the compiler to Wasm which is an interesting future possibility).

Community highlights

Check out this chiptune tracker made with Hoot by Vivianne Langdon!

Additionally, check out Wastrel, a Wasm GC to C compiler developed by Andy Wingo. Wastrel notably uses Hoot’s Wasm toolchain. A Wasm program compiled with Wastrel runs faster than the same program on NodeJS!

Documentation changes

  • Updated Installation chapter to mention new optional dependencies.

  • Added Modules and REPL sections to the Scheme reference chapter.

  • Added Development chapter.

  • Update Status section to remove mention of missing R7RS support that we have now.

  • Removed docs for obsolete --emit-names flag

  • Add documentation for -g flag to guild compile-wasm.

  • Fixed example in the JavaScript reflection section that was using the obsolete load_main signature.

Toolchain changes

  • Split Wasm validation out of (wasm vm) and into new (wasm validation) module.

  • Keep data computed within the validation pass in <validated-wasm> records so that data can be used during instantiation rather than redundantly recomputing it.

  • Added explicit support for representing a “canonicalizationâ€�: a world in which structurally equal types are equal.

  • (wasm vm) types <wasm-func>, <wasm-struct>, <wasm-array> now refer to their types by index into a canonicalized set.

  • Added untagged <wasm-array> backing stores to (wasm vm) for all simple scalar numeric types, including i8 and i16 packed types.

  • Modified (wasm vm) to look up named heap type references in the instance’s canonicalization.

  • Added bytevector->wasm-array, wasm-array->bytevector to (wasm vm).

  • Added support for some of the “noneâ€� bottom types.

  • Packed array data is now stored signed, wrapped from i32 when set, and only unwrapped to unsigned in get_u functions.

  • Added string.from_code_point and string.concat lowerings in (wasm lower-stringrefs).

  • Renamed outdated extern.internalize and extern.externalize to their current names, any.convert_extern and extern.convert_any.

  • Added new has-wasm-header? procedure to (wasm parse).

  • Parse core reference types to <ref-type> records rather than symbol abbreviations in (wasm parse).

Miscelanneous changes

  • Modified schedule-task in (fibers scheduler) (which is implemented using inline Wasm on the target) to be a no-op when called at expansion time on the host i.e. used at the module top-level or from a procedural macro.

  • Added support for vector and call-with-values primitives to (hoot primitives) module so they can be used in interpreted code.

  • truncate is now exported from (guile).

  • Allow exports to clobber each other in module-declare! to support live hacking of modules where define-module forms are often re-evaluated many times.

  • Extracted JS Uint8Array bindings from internals of (fibers streams) to new (hoot typed-arrays) module.

  • Implement subset of Guile’s procedural module API for hackable programs (i.e. programs that are built with runtime module support).

  • Added (hoot config) target-side module for accessing certain build-time constants (currently just the Hoot version string).

  • Extracted (hoot library) module from (hoot library-group) so that the library parser can be used on the target for live hacking purposes.

  • Added define-module implementation to (guile) that simply throws an error if used during compilation. A separate implementation is installed for use by the interpreter in hackable programs.

  • Added #:replace? argument to module-export! to allow replacement of exports for live hacking purposes.

  • Exported module-root from (hoot modules).

  • Added module-imported-modules procedure to (hoot modules).

  • Changed file I/O host functions to return null when a file cannot be opened so a Scheme exception that can be handled by user code rather than a host exception that cannot.

  • Extracted contents of (scheme file) to new (hoot file) module for use in internal code such as the implementation of the file system module loader in (hoot hackable).

  • Moved implementation of string-join, string-concatenate, string-prefix?, and string-prefix-ci? from (guile) to (hoot strings).

  • Moved case-insensitive string procedures from (scheme char) to (hoot strings).

  • Added string-drop to (hoot strings).

  • Added every and fold-right procedures to (hoot lists).

  • Moved implementation of and-map and or-map from (guile) to (hoot lists).

  • Added symbol-append to (hoot symbols).

  • Added less verbose custom printer for <module> record type.

  • Switched from positional to keyword arguments for make-soft-port in (hoot ports).

  • Added list-index to (guile).

Bug fixes

  • Fixed format-exception not writing all of its output to the current error port.

  • Fix eof-object export in (ice-9 binary-ports).

  • Fixed off-by-one error for procedures with rest args in (hoot eval).

  • Fixed min/max to only accept real numbers, handle NaNs, and normalize exact zeroes.

  • Fixed continuation composition leaving an unwind continuation on the stack.

  • Fixed prompt unwinding in certain join continuation situations.

  • Fixed compilation of unwind primcalls at join points.

  • Fixed runtime module system ignoring replacement bindings in Guile modules.

Browser compatibility

  • Compatible with Safari 26 or later.

  • Compatible with Firefox 121 or later.

  • Compatible with Chrome 119 or later.

Get Hoot

Hoot is available in GNU Guix:

$ guix pull
$ guix install guile guile-hoot

Also, Hoot is now available in Debian, though it will take awhile for this release to make it there.

Otherwise, Hoot can be built from source via our release tarball. See the Hoot homepage for a download link and GPG signature.

Documentation for Hoot 0.8.0, including build instructions, can be found here.

Get in touch

For bug reports, pull requests, or just to follow along with development, check out the Hoot project on Codeberg.

If you build something cool with Hoot, let us know on our community forum!

Thanks to our supporters

Your support makes our work possible! If you like what we do, please consider becoming a Spritely supporter today!

Diamond tier

  • Aeva Palecek
  • David Anderson
  • Holmes Wilson
  • Lassi Kiuru

Gold tier

  • Alex Sassmannshausen
  • Juan Lizarraga Cubillos

Silver tier

  • Austin Robinson
  • Brit Butler
  • Charlie McMackin
  • Dan Connolly
  • Danny OBrien
  • Deb Nicholson
  • Eric Bavier
  • Eric Schultz
  • Evangelo Stavro Prodromou
  • Evgeni Ku
  • Glenn Thompson
  • James Luke
  • Jonathan Frederickson
  • Jonathan Wright
  • Joshua Simmons
  • Justin Sheehy
  • Matt Panhans
  • Michel Lind
  • Mike Ledoux
  • Nathan TeBlunthuis
  • Nia Bickford
  • Noah Beasley
  • Steve Sprang
  • Travis Smith
  • Travis Vachon

Bronze tier

  • Alan Zimmerman
  • Aria Stewart
  • BJ Bolender
  • Ben Hamill
  • Benjamin Grimm-Lebsanft
  • Brooke Vibber
  • Brooklyn Zelenka
  • Carl A
  • Crazypedia No
  • François Joulaud
  • Gerome Bochmann
  • Grant Gould
  • Gregory Buhtz
  • Ivan Sagalaev
  • James Smith
  • Jason Wodicka
  • Jeff Forcier
  • Marty McGuire
  • Mason DeVries
  • Michael Orbinpost
  • Neil Brudnak
  • Nelson Pavlosky
  • Philipp Nassua
  • Robin Heggelund Hansen
  • Rodion Goritskov
  • Ron Welch
  • Stefan Magdalinski
  • Stephen Herrick
  • Steven De Herdt
  • Tamara Schmitz
  • Thomas Talbot
  • William Murphy
  • a b
  • r g
  • terra tauri

Until next time, happy hooting! 🦉

by Dave Thompson (contact@spritely.institute) at Wednesday, February 25, 2026

Tuesday, February 24, 2026

The Racket Blog

Racket v9.1

posted by Stephen De Gabrielle and John Clements


We are pleased to announce Racket v9.1 is now available from https://download.racket-lang.org/.

As of this release:

  • Documentation organization and navigation can be specialized by language family, to allow users to interact with documentation in a way that is tailored to that language family. This is currently used by Rhombus.
  • The for form and its variants accept an #:on-length-mismatch specifier. 3.18 Iterations and Comprehensions: for, for/list, …
  • DrRacket improves the GUI for choosing color schemes.
  • DrRacket has curved syntax arrows. The degree of curvature indicates the relative left- or right-displacement of the arrow’s target.
  • DrRacket’s “Insert Large Letters” uses characters that match the comment syntax of the buffer’s language, making it useful (and fun!) in Rhombus.
  • The exn-classify-errno maps network and filesystem error numbers on various platforms to posix-standard symbols, to enable more portable code. 10.2 Exceptions
  • The behavior of Racket BC on certain character operations (most notably eq?) is changed to match that of Racket CS, with a small performance penalty for these operations for BC programs. 19 Performance 1.5 Implementations
  • The make-struct-type procedure can inherit the current inspector using a 'current flag. This is the default behavior, but there are situations in which it’s not possible to refer to the current inspector. 5.2 Creating Structure Types
  • Bundle configurations can better control the conventions for locating shared object files with the --enable-sofind=<conv> flags.
  • The system-type function can report on platform and shared-object-library conventions with new flags. 15.8 Environment and Runtime Information
  • The openssl/legacy library makes it possible to access OpenSSL’s built-in “legacy” provider, to get access to insecure and outdated algorithms. OpenSSL: Secure Communication
  • Typed Racket improves expected type propagation for keyword argument functions.
  • There are many other repairs and documentation improvements!

Don’t forget to run raco pkg migrate 9.0

Thank you

The following people contributed to this release:

Alexander Shopov, beast-hacker, Bob Burger, Brad Lucier, Cadence Ember, David Van Horn, evan, François-René Rideau, Gustavo Massaccesi, Jacqueline Firth, Jade Sailor, Jason Hemann, Jens Axel Søgaard, John Clements, Jonas Rinke, Matthew Flatt, Matthias Felleisen, Mike Sperber, Noah Ma, Pavel Panchekha, Rob Durst, Robby Findler, Ryan Culpepper, Sam Tobin-Hochstadt, Stephen De Gabrielle, and Wing Hei Chan.

Racket is a community developed open source project and we welcome new contributors. See racket/README.md to learn how you can be a part of this amazing project.

Feedback Welcome

Questions and discussion welcome at the Racket community on Discourse or Discord.

Please share

If you can - please help get the word out to users and platform specific repo packagers

Racket - the Language-Oriented Programming Language - version 9.1 is now available from https://download.racket-lang.org

See https://blog.racket-lang.org/2026/02/racket-v9-1.html for the release announcement and highlights.

by John Clements, Stephen De Gabrielle at Tuesday, February 24, 2026

Wednesday, February 18, 2026

Retropikzel's blog

Andy Wingo

two mechanisms for dynamic type checks

Today, a very quick note on dynamic instance type checks in virtual machines with single inheritance.

The problem is that given an object o whose type is t, you want to check if o actually is of some more specific type u. To my knowledge, there are two sensible ways to implement these type checks.

if the set of types is fixed: dfs numbering

Consider a set of types T := {t, u, ...} and a set of edges S := {<t|ε, u>, ...} indicating that t is the direct supertype of u, or ε if u is a top type. S should not contain cycles and is thus a direct acyclic graph rooted at ε.

First, compute a pre-order and post-order numbering for each t in the graph by doing a depth-first search over S from ε. Something like this:

def visit(t, counter):
    t.pre_order = counter
    counter = counter + 1
    for u in S[t]:
        counter = visit(u, counter)
    t.post_order = counter
    return counter

Then at run-time, when making an object of type t, you arrange to store the type’s pre-order number (its tag) in the object itself. To test if the object is of type u, you extract the tag from the object and check if tagu.pre_order mod 2n < u.post_order–u.pre_order.

Two notes, probably obvious but anyway: one, you know the numbering for u at compile-time and so can embed those variables as immediates. Also, if the type has no subtypes, it can be a simple equality check.

Note that this approach applies only if the set of types T is fixed. This is the case when statically compiling a WebAssembly module in a system that doesn’t allow modules to be instantiated at run-time, like Wastrel. Interestingly, it can also be the case in JIT compilers, when modeling types inside the optimizer.

if the set of types is unbounded: the display hack

If types may be added to a system at run-time, maintaining a sorted set of type tags may be too much to ask. In that case, the standard solution is something I learned of as the display hack, but whose name is apparently ungooglable. It is described in a 4-page technical note by Norman H. Cohen, from 1991: Type-Extension Type Tests Can Be Performed In Constant Time.

The basic idea is that each type t should have an associated sorted array of supertypes, starting with its top type and ending with t itself. Each t also has a depth, indicating the number of edges between it and its top type. A type u is a subtype of t if u[t.depth]=t, if u.depth <= t.depth.

There are some tricks one can do to optimize out the depth check, but it’s probably a wash given the check performs a memory access or two on the way. But the essence of the whole thing is in Cohen’s paper; go take a look!

Jan Vitek notes in a followup paper (Efficient Type Inclusion Tests) that Christian Queinnec discovered the technique around the same time. Vitek also mentions the DFS technique, but as prior art, apparently already deployed in DEC Modula-3 systems. The term “display” was bouncing around in the 80s to describe some uses of arrays; I learned it from Dybvig’s implementation of flat closures, who learned it from Cardelli. I don’t know though where “display hack” comes from.

That’s it! If you know of any other standard techniques for type checks with single-inheritance subtyping, do let me know in the comments. Until next time, happy hacking!

Addendum: Thanks to kind readers, I have some new references! Michael Schinz refers to Yoav Zibin’s PhD thesis as a good overview. Alex Bradbury points to a survey article by Roland Ducournau as describing the DFS technique as “Schubert numbering”. CF Bolz-Tereick unearthed the 1983 Schubert paper, and it is a weird one. Still, I can’t but think that the DFS technique was known earlier; I have a 1979 graph theory book by Shimon Even that describes a test for “separation vertices” that is precisely the same, though it does not mention the application to type tests. Many thanks also to fellow traveller Max Bernstein for related discussions.

by Andy Wingo at Wednesday, February 18, 2026

Saturday, February 7, 2026

crumbles.blog

HOWTO: Set up an IPsec VPN on NixOS and connect to it with Mac OS and iOS

There are two options for IPsec VPNs on NixOS: Libreswan and Strongswan. Since Strongswan has much better NixOS configuration, we’ll use that.

Note! In the best tradition of howto guides on blogs, I’m not an expert on IPsec, Strongswan, VPN configuration, nor even really NixOS. However, these are the settings that worked for me, derived mostly from the DigitalOcean guide to setting up Strongswan on Ubuntu and adapted for Nix. Please try every other support forum you can think of before asking me personally for help, because I probably have no idea :-)

Generating the needed certificates, etc.

For its own very special reasons, Strongswan’s command-line tools for generating keys and certificates don’t work without an /etc/strongswan.conf file present, even though they don’t need it. Fortunately, an empty one is okay, so let’s create one:

$ sudo touch /etc/strongswan.conf

Now use the Strongswan command line tools in a Nix shell to generate the necessary credentials. (Use nix-shell -p strongswan if you don’t have flakes and nix-command enabled!)

$ nix shell nixpkgs#strongswan

Create directory to hold our keys and certificates and set some permissions for safety:

$ mkdir -p ~/pki/{cacerts,certs,private}
$ chmod 700 ~/pki
$ pki --gen --type rsa --size 4096 --outform pem > ~/pki/private/ca-key.pem
$ pki --self --ca --lifetime 3650 --in ~/pki/private/ca-key.pem --type rsa --dn "CN=VPN root CA" --outform pem > ~/pki/cacerts/ca-cert.pem
$ pki --gen --type rsa --size 4096 --outform pem > ~/pki/private/server-key.pem

For a private VPN, I suggest connecting only through its IP address rather than messing about with DNS. In the following, replace 12.34.56.78 with the IP of your own server. If you really want to connect through a domain name, you can delete the last --san @12.34.56.78 below and just use the other two with the IP address replaced by your domain name.

$ pki --pub --in ~/pki/private/server-key.pem --type rsa \
    | pki --issue --lifetime 1825 \
          --cacert ~/pki/cacerts/ca-cert.pem \
          --cakey ~/pki/private/ca-key.pem \
          --dn "CN=12.34.56.78" --san 12.34.56.78 --san @12.34.56.78 \
          --flag serverAuth --flag ikeIntermediate --outform pem \
        > ~/pki/certs/server-cert.pem

Now you can exit the Nix shell and copy these new keys and certificates to the right place:

$ sudo cp -r ~/pki/* /etc/ipsec.d/

Finally, delete the temporary strongswan.conf file we created; NixOS will manage all further Strongswan configuration.

$ sudo rm /etc/strongswan.conf

Configuring Strongswan in configuration.nix

services.strongswan = {
  enable = true;
  # Where your user authentication information will be stored:
  secrets = [ "/etc/ipsec.d/ipsec.secrets" ];

  setup = {
    # Log daemon statuses
    charondebug = "ike 1, knl 1, cfg 0";
    # Allow multiple connections
    uniqueids = "no";
  };

  connections = {
    # You can change the name of the connection from `vpn` if you
    # like; this is only used internally
    vpn = {
      auto = "add";
      compress = "no";
      type = "tunnel";
      keyexchange = "ikev2";
      fragmentation = "yes";
      forceencaps = "yes";
      # Detect and clear any hung connections
      dpdaction = "clear";
      dpddelay = "300s";
      send_cert = "always";
      rekey = "no";
      # Accept connections on any local network interface
      left = "%any";
      # Set this to your domain name (prefixed with @) or your IP address
      leftid = "12.34.56.78";
      leftcert = "server-cert.pem";
      leftsendcert = "always";
      # Tell clients to use this VPN connection for connections to
      # all other IP addresses
      leftsubnet = "0.0.0.0/0";
      # Accept connection from any remote client
      right = "%any";
      # Accept connection from any remote client ID
      rightid = "%any";
      # Authentication method `eap-mschap-v2` works on Mac OS, iOS,
      # and allegedly on Android and Windows too
      rightauth = "eap-mschapv2";
      # Give clients local IP addresses in the 10.0.0.0/1 subnet
      rightsourceip = "10.0.0.0/24";
      # Set this to your preferred DNS server
      rightdns = "1.1.1.1";
      # Clients do not need to send certificates
      rightsendcert = "never";
      # Ask clients for identification when they connect
      eap_identity="%identity";
      # Recommended ciphersuite settings for iOS and Mac; you may need
      # different ones on other platforms
      esp = "aes256-sha256-modp2048";
      ike = "aes256-sha256-modp2048-modpnone";
    };
  };
};

We also need to configure the kernel to allow IP forwarding and do some related hardening by setting the appropriate sysctls:

boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
boot.kernel.sysctl."net.ipv6.all.forwarding" = 1;
boot.kernel.sysctl."net.ipv4.ip_no_pmtu_disc" = 1;
boot.kernel.sysctl."net.ipv4.conf.all.accept_redirects" = 0;
boot.kernel.sysctl."net.ipv4.conf.all.send_redirects" = 0;
boot.kernel.sysctl."net.ipv6.conf.all.accept_redirects" = 0;
boot.kernel.sysctl."net.ipv6.conf.all.send_redirects" = 0;

Finally, we need to configure the NixOS firewall to allow connections on the IPsec ports, and also to route connections through the VPN properly. (Thanks to Erik Dombi on the Strongswan issue tracker for the information on how to set this up.)

You will need to know the name of your network interface. If you don’t use a declarative, static configuration of your IP address (which for a VPN server you probably should, unless you are using Dynamic DNS or something) you may not know it. Find it with ip route; the network interface name is the word that appears after dev. (In my case, it says default via 98.76.54.32 dev ens3 proto static, so my interface is ens3.) Here I’m using ens3. Replace ens3 everywhere in the extraCommands configuration with the name of your own interface if it’s different for you.

# UDP ports 500 and 4500 are used for IPsec connections
networking.firewall.allowedUDPPorts = [ 500 4500 ];
networking.firewall.extraCommands =
  ''
    iptables -P INPUT ACCEPT
    iptables -P FORWARD ACCEPT
    iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    iptables -A FORWARD --match policy --pol ipsec --dir in  --proto esp -s 10.0.0.0/24 -j ACCEPT
    iptables -A FORWARD --match policy --pol ipsec --dir out --proto esp -d 10.0.0.0/24 -j ACCEPT
    iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o ens3 -m policy --pol ipsec --dir out -j ACCEPT
    iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o ens3 -j MASQUERADE
    iptables -t mangle -A FORWARD --match policy --pol ipsec --dir in -s 10.0.0.0/24 -o ens3 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
    iptables -A INPUT -j DROP
    iptables -A FORWARD -j DROP
  '';

Setting up users

Above in configuration.nix we told the Strongswan NixOS module that our secrets will come from the file /etc/ipsec.d/ipsec.secrets, so we need to create it, tell it where to find our server private key, and add some users:

: RSA "server-key.pem"
username : EAP "password"

Replace username and password by your chosen credentials. You can add more lines of this type if you want more users.

Run it!

nixos-rebuild will start the Strongswan service, reconfigure the firewall, and you should have a working VPN.

For some reason after a system reboot, the VPN seems to take a minute or two to start; nixos-rebuild manages to bring it up immediately.

Connecting on Mac OS and iOS

The advantage of an IPsec VPN is that it’s supported natively by Mac OS and iOS. The process is pretty similar on both:

  1. Download the ca-cert.pem file from /etc/ipsec.d/cacerts

  2. On Mac, open it in Keychain Access and add it to the System keychain. You then need to open the System keychain, right click on the certificate and choose ‘Get Info’, unfold the ‘Trust’ disclosure triangle, and select at least ‘Always Trust’ for IPsec.

    On an iOS device, after downloading the file, you will be offered to install the new ‘profile’ by going to the Settings app. Once you install it, you still need to mark the certificate as trusted. Go to General → About → Certificate Trust Settings (right at the bottom) and enable full trust for that root certificate. (There is no fine-grained trust setting on iOS, at least for manual configuration, as far as I know.)

  3. Add a VPN in the VPN settings pane, choosing IKEv2 as the type. Set both the ‘Server address’ and ‘Remote ID’ to the IP address or domain name of your VPN server. Under ‘Authentication’, select ‘User authentication’ method ‘Username’ and enter the username and password you put in the /etc/ipsec.d/ipsec.secrets file.

  4. Create the VPN and turn it on. Hopefully it will work!

Improvements I want to make in a future version of this guide

  • Use Agenix to store secrets instead of just dumping them in /etc
  • Understand more about what those iptables incantations do and maybe pare them down

Saturday, February 7, 2026

Joe Marshall

Vibe Coded Scheme Interpreter

Mark Friedman just released his Scheme-JS interpreter which is a Scheme with transparent JavaScript interoperability. See his blog post at furious ideas.

This interpreter apparently uses the techniques of lightweight stack inspection — Mark consulted me a bit about that hack works. I'm looking forward to seeing the vibe coded architecture.

by Joe Marshall (noreply@blogger.com) at Saturday, February 7, 2026

Wednesday, January 21, 2026

Peter Bex

FOSS for digital sovereignty in the EU

The European Commission has posted a "call for evidence" on open source for digital sovereignty. This seeks feedback from the public on how to reduce its dependency on software from non-EU companies through Free and Open Source Software (FOSS).

This is my response, with proper formatting (the web form replies all seem to have gotten their spaces collapsed) and for future reference.

The added value of FOSS

In times where international relations are tense, it is wise to invest in digital sovereignty. For example, recently there was a controversy surrounding the International Criminal Court losing access to e-mail hosted by Microsoft, a US company, for political reasons.

A year earlier, a faulty CrowdStrike update caused the largest IT outage in history. This was an accident, but it was a good reminder of the power that rests in foreign hands. We have to consider the possibility of a foreign government pressuring a company to issue a malicious update on purpose. This update could target only specific countries.

Bringing essential infrastructure into EU hands makes sense. But why does this have to be FOSS? For instance, the CrowdStrike incident could also have happened with FOSS.

With FOSS, one does not have to trust a single company to maintain high code quality and security. Independent security researchers and programmers will be looking at this code with a fresh perspective. It is also an industry truism that FOSS code tends to be of higher quality, simply because releasing bad code is too embarrassing.

FOSS also reduces vendor lock-in. One can switch vendors and keep using the same product when for example the vendor:

  • goes bankrupt,
  • drops support for the product,
  • drastically increases prices,
  • decides on a different direction for the product than the user wants,
  • or gets acquired by a foreign company.

Therefore, FOSS brings sovereignty by not being at the mercy of a single vendor.

Public sector and consultancies

The EU can set a good example by starting in the public sector: government EU organisations and those of the member states, as well as semi-government organisations like universities and libraries. Closed source software still reigns supreme there. Only "established" companies may apply to tenders. These often employ professionals certified in proprietary tech. This encourages vendor lock-in. The existing dependency ensures lock-in for future projects, as compatibility is often a key requirement.

These same vendors are ruthless and have repeatedly sabotaged FOSS migrations. Microsoft was involved in multiple bribery scandals in The Netherlands, Romania, Italy and Hungary, for example. There have also been allegations of illegal deals that were never investigated, such as with the LiMux project in Munich.

How the EU can help:

  • Fully commit to FOSS. Set a date by which all software used by the public sector must be FOSS and running on hardware within the EU, at fully EU-owned companies. No compromises, no excuses and no easy outs - those were the bane of previous efforts.
  • Map out missing requirements and pay EU consultancy firms to improve FOSS where it is lacking. This will also make said software also more attractive for large private organisations that provide essential services in the EU.

Concrete examples:

  • Many EU and member state institutes rely on American services for hosting or securing their e-mail. E-mail software is a complete commodity, for which there are good European alternatives, based on FOSS. It should be easy to switch.
  • Workstations for public servants typically run on Windows and use Microsoft Office. Switch these to a proven open operating system like Linux and office suite like LibreOffice.

Education and mind share

In schools, informatics is typically taught using proprietary software. This is often cloud software. Schools do not have the expertise or funds to run their own servers. Therefore, they use the easy option that teachers are familiar with: "free" online offerings from US Big Tech. Network effects ensure deeper entrenchment. Big Tech offers steep discounts for educational licenses for these exact reasons.

Vocational schools focus on proprietary tech most used in industry. This goes beyond IT studies. For example, statistics and psychology courses use SPSS over PSPP or R. Mathematics and physics courses use MATLAB over GNU Octave. Engineering courses use AutoCAD instead of FreeCAD or LibreCAD.

A focus on the impact of tech choices in education could change the situation from the ground up. In high school, there could be a place (e.g. in civic education class) to focus on the impact of tech choices on society. This goes beyond domestic versus foreign "cloud" hosting and open versus proprietary code. For example, studies show that social media can have harmful effects on mental well-being, societal cohesion and even democracy.

How the EU can help:

  • Provide funding for course material, and/or create a certification programme for suitable course material to wean schools off of Big Tech software.
  • Start an education campaign aimed at the broader public in order to explain why closed software and the non-EU cloud are harmful. For example, it could focus on concrete issues that affect anyone like data protection, privacy and resistance against "enshittification" such as unwanted ads, price hikes and feature removal.
  • For the existing work force, the EU can fund training in open alternatives so that people feel confident with these alternatives. Such training should include a theoretical component to discuss the benefits of using open alternatives to ensure people are fully on board.

Existing FOSS companies and economic situation

The EU has plenty of FOSS businesses already. A handful of examples: SUSE was one of the first companies to provide FOSS server and desktop operating systems for the enterprise. Tuta and Proton Mail provide innovative secure e-mail solutions. Nextcloud offers cloud-based content collaboration tools. GitLab and Codeberg offer code hosting platforms.

These companies are innovative and profitable, but small in the global market place. Competitors from the US benefit from economies of scale. The initial US market is a large country with a single language and minimal legislation. This allows for quick domestic growth followed by global expansion. The EU market is more fragmented so it is harder to gain a foothold, requiring more up front investment to e.g. support the languages spoken in the EU.

Venture capital is also less likely to invest in the EU because of stricter legislation. Because FOSS solutions give competing companies a chance to offer the product, the returns on investment are lower than with proprietary software where a single company has a monopoly on the software.

Some EU companies have realised that this legislation is an asset: it allows for differentiation from US-based offerings. EU software can compete in the global market place on its own merits.

How the EU can help:

  • Promote tech sovereignty to countries across the world. Start with countries who are not formally allied to the US. This could help EU companies to expand into the global market.
  • Help EU companies become more well-known by organising trade shows exhibiting only FOSS EU companies.
  • Provide funding to organisations like the FSF Europe to run awareness campaigns about FOSS alternatives.
  • Perhaps controversial: heavily tax proprietary, non-EU software or provide tax breaks for FOSS EU software to level the playing field.
  • Even more controversially: prevent foreign-owned companies from operating data centers in the EU. Make it as hard as possible for them to offer high-speed cloud software here. These data centers are already unpopular, as they use precious water and land, and they only make foreign companies more powerful.

Conclusion

The reasons for dependency on foreign proprietary solutions are systemic. The causes are various: from inertia and ignorance to market effects and bribery. The solutions must be equally systemic: from education to policy and funding, all points must be attacked in order to succeed. This is the only way we can get rid of our dependency on non-EU software.

by Peter Bex at Wednesday, January 21, 2026

Saturday, January 17, 2026

Scheme Requests for Implementation

SRFI 267: Raw String Syntax

SRFI 267 is now in draft status.

Raw string syntax are lexical syntax for strings that do not interpret escapes inside of them. They are useful in cases where the string data has a lot of characters like \ or " that would otherwise have to be escaped. The raw string syntax in this document is derived from C++11's raw string literals.

by Peter McGoron at Saturday, January 17, 2026

Friday, January 16, 2026

Scheme Requests for Implementation

SRFI 266: The expr syntax

SRFI 266 is now in draft status.

The syntax expr allows to write arithmetic expressions using a syntax near to mathematical notations, potentialy improving readability of scheme programs.

by José Bollo at Friday, January 16, 2026

Tuesday, January 13, 2026

Gauche Devlog

Extension package registry

Extension package registry

We just renewed the Gauche homepage. It's mostly cosmetics, but one notable change is the Extension Packages page

It's been in our todo list for very long time to create some system to track Gauche extension packages. It is trivial to create a site where users can put the info. What's not trivial is how to keep the info updated.

It's a burden to the user if we ask them to keep updating such info whenever they update their extension package.

If a user puts their website/email for the package, but then moves away from Gauche development, and eventually the site/email become inactive and go away, we don't know what to do with the entry; it'd be also difficult if somebody wants to take over the project.

Should anybody be able to update the package's info? Limiting it to the original authors becomes an issue if they go inactive and out of touch. Allowing it may cause a security issue if someone replaces the distribution URL to malicious one.

To vet the users entering info, we need some mechanism of user registration and authentication, which adds another database to maintain.

These implications kept us from implementing the official mechanism to provide the extension package registry.


Things has changed in the last decade or so.

First, distributed VCS and their hosting services have become a norm. Instead of having personal websites to serve extension package tarballs and documents, developers can put their repository on one of those services and make it public.

Recent Gauche provides a standard framework of building extensions. One important aspect of it is package.scm in the source tree to keep meta information about the package, including version number, authors, "official" repository url, dependencies, etc.

So, once we know the package's repository URL, we can get its meta information!

The author updates package.scm as the development proceeds, because it is a part of the source. No need to update information on the registry separately.

Anybody can create account on those services, but the service gives certain identity to each one and the place to interact with each other. Sure, people move away eventually, but it's rarely that they bother to remove the repositories; and it's easy to inherit the abandoned project.

We already have a official way to state such transfer of control in package.scm (superseded-by slot). If the successor can contact the original author/maitainer/committer, the package.scm in the original repository can have superseded-by field pointing to the new repository. It is not mandatory, but it can make it clear where is the "official" successor.

In other words, we can use the existing public repositories as the metadata database, and merely maintain pointers to them by ourselves.


So, how to manage those pointers? We don't have thousands of extension packages updated daily, so we don't need a sophisticated database server for it.

We decided to piggyback on the public DVCS service again. Gauche package repository index github repo maintains the list of package urls under its packages directory. If you want your packages to be listed, just fork it, add your package, and send a pull request. (If you don't want to use GitHub, just send a patch via email.)

Which repository is added when, by whose request, is recorded in the commits of that repo.

Currenly, pulling metadata and reflecting it on the webpage is done in occasional batch process. We'll adjust the frequency as it goes. If we ever get very popular and receiving tons of new package registration requests, we might need to upgrade the system, but until then, this will be the least-maintenance-cost solution.


To be in the registry, your extension package needs package.scm. I scanned through the existing list on wiki (WiLiKi:Gauche:Packages) and added packages that I could find the public repository with package.scm.

If your extension is old enough not to have package.scm, a convenient way is to run gauche-package populate in the top directory of the source tree. It gives you a template package.scm with some fields filled by whatever information it can find.

Tag: Extensions

Tuesday, January 13, 2026

Tuesday, January 6, 2026

spritely.institute

Mandy: ActivityPub on Goblins

Mandy character artwork

ActivityPub is the protocol that powers the Fediverse. Not only does it allow different instances of the same app to federate with one another, it also allows different apps to federate. For example, a post on a video hosting app could federate to a microblogging app. ActivityPub does a good job of this and is a leap forward from what came before it.

For those unfamiliar, ActivityPub is a decentralized social networking protocol standardized under the W3C. Both Spritely’s Executive Director, Christine Lemmer-Webber and myself (Jessica Tallon) worked on standardizing ActivityPub. The ActivityPub specification left holes in for identity, distributed storage, and more. Since then Spritely has been a continuation of this work, researching and developing the next generation of social web infrastructure.

But where does this leave ActivityPub? Has it been abandoned by Spritely as a stepping stone? No! We’ve long had a project (codenamed Mandy) on our roadmap to implement ActivityPub on top of Goblins. If you open up the ActivityPub specification you’ll actually see mention of actors. The protocol itself is designed with the actor model in mind. Since Goblins is an implementation of the actor model, they should be a natural fit.

The source code for the prototype this blog post is based off can be found here.

Goblins actors over HTTP

ActivityPub is a protocol on top of HTTP, but Goblins doesn’t use HTTP. So, the first step was to make Goblins actors available over HTTP. Fortunately, hooking this up was quite easy. There are many different ways we could do this, but for this prototype I took a fairly simple approach.

Guile has a built in web server. not only that but fibers (the concurrency system Goblins uses) has a backend for this. It means we can pretty quickly start handling requests.

The webserver can be started using the run-server procedure. It takes in a symbol which specifies an implementation ('http would be the built in HTTP server, 'fibers is the one provided by Fibers):

(run-server handler 'fibers)

The handler is a procedure which takes a request and a body and expects the HTTP response as the return value. When writing a typical HTTP server in Fibers we’d suspend the fiber until the response is ready. However, Goblins code is built around sending messages and waiting for promises to resolve. To bridge the API between these two different philosophies, we use a channel.

Channels allow two fibers to send a message to one another. Reading or writing to a channel causes the fiber to suspend until a message is available. We can then send a message to one of our actors and use on to listen to the response, once we have the response we can write our response to our channel.

Goblins vats are event loops which run on a fiber. These event loops manage a queue of messages sent to actors spawned within that vat and are processed sequentially. If we were to just write to the channel, we would suspend underlying fiber for the vat. When the vat’s fiber suspends, it stops it from processing other messages within the queue. To ensure that we’re not blocking the vat by suspending it, we’ll use the helper procedure syscaller-free-fiber which gives us a new fiber outside the vat which can be safely suspended.

(define vat (spawn-vat))
(define (^web-server bcom router)
  (define (handler . args)
    (define response-ch (make-channel))
    (with-vat vat
      (on (apply <- router args)
          (lambda (response)
            (syscaller-free-fiber
             (lambda ()
               (put-message response-ch (vector 'ok response))))
            *unspecified*)
          #:catch
          (lambda (err)
            (syscaller-free-fiber
             (lambda ()
               (put-message response-ch (vector 'err err))))
            *unspecified*)))
    (match (get-message response-ch)
      [#(ok (content-type response))
       (values `((content-type . (,content-type))) response)]
      [#(ok (content-type response) headers)
       (values `((content-type . ,content-type) ,@headers) response)]
      [#(ok response)
       (values '((content-type . (text/plain))) response)]
      [#(err err) (error "Oh no!")]))
  (syscaller-free-fiber
   (lambda ()
     (run-server handler 'fibers `(#:addr ,(inet-pton AF_INET "0.0.0.0")))))
  (lambda () 'running))

(define web-server (with-vat vat (spawn ^web-server (spawn ^router))))

This is a slightly simpler version than the one used in the prototype, but it shows how we’re making asynchronous actors which can return promises accessible to HTTP requests. From the code above, we’ve already bridged into our Goblins actors. This is a pretty flexible bridge as this ^router actor just takes in a request and provides a response, we could dispatch this request in any number of ways. For our prototype, this is the approach we took:

(define-values (registry locator)
  (call-with-vat vat spawn-nonce-registry-and-locator))

(define (^router bcom)
  (lambda (request body)
    (define request-url (request-uri request))
    (match (string-split (uri-path (request-uri request)) #\/)
      [("" "static" filename)
       (static-file (string-append "mandy/web/static/" filename))]
      [("" "object" id)
       (let ((object (<- registry 'fetch (base32-decode id))))
         (<- object 'request))])))

Most web frameworks have a special-purpose routing language that uses strings, which is an inexpressive anti-pattern. We’re fortunate in Scheme to have a powerful pattern matcher that we can use instead. In this case we’re matching a filename for our static files and we’re also making some objects available at /object/<base32-encoded-id>.

We can see above the router we’re spawning something called the nonce registry. This is an actor which provides a mechanism to register any number of actors against some identifier and look them up. The actor handles salting and hashing the IDs and even persistence. This works great for registering ActivityPub objects. Each one gets a unique ID which can be used for lookup later.

These IDs are bytevectors, so we base32 encode them to convert them into a textual form that can be included in a URI. We then just need to add a route to our match clause to look them up. You may notice we’re using <- to send messages which means we get a promise in return. This isn’t a problem for our web server though as the on handler will wait until it’s resolved.

The prototype has more routes and handles slightly more situations than the snippet of code shown above, but the principles introduced are the same.

How does ActivityPub work?

Let’s take a step back and look at ActivityPub itself both because how it works will be useful to keep in mind the rest of the post, and to see if we can see the actor model within the specification.

ActivityPub is actually not too tricky. It has concepts like inboxes and outboxes that you’re probably familiar with from email. It also has activities which describe something the user is “doing”. Activities are the building block of the protocol. Finally, it has objects which are things like Notes, Images, Videos, etc.

ActivityPub is actually two protocols in one. There’s the client-to-server protocol and then the federated server-to-server protocol. These protocols are actually very similar and for the most part mirror one another, but unfortunately the client-to-server protocol gets little love from ActivityPub implementations. Even so, let’s take a look at how I might go about posting a Note (think toot/tweet/microblog text of choice) to my good friend Christine:

POST /outbox/ HTTP/1.1
Host: tsyesika.se
Authorization: Bearer XXXXXXXXXXX
Content-Type: application/ld+json; profile="https://www.w3.org/ns/activitystreams"

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "type": "Create",
    "to": ["https://dustycloud.org/"],
    "object": {
        "type": "Note",
        "content": "Ohai Christine!"
    }
}

The JSON object above represents a Create activity which basically is posting something. Other activities might be Like, Share, Delete, etc. Most activities are transitive (the activity has an object) and our Create activity is no exception. The object inside is a Note with some content.

The activity itself is posted to my outbox as an HTTP POST. The server will assign IDs to both objects and assign me (Jessica) as the author of the note. If you were to do a GET on the outbox, you’d see something like this:

GET /outbox/ HTTP/1.1
Host: tsyesika.se
Authorization: Bearer XXXXXXXXXXX
Content-Type: application/ld+json; profile="https://www.w3.org/ns/activitystreams"

{
    "@context" : "https://www.w3.org/ns/activitystreams",
    "id" : "https://tsyesika.se/outbox",
    "type" : "OrderedCollection",
    "name": "Jessica's Outbox"
    "totalItems" : 1,
    "items": {
        {
            "@context": "https://www.w3.org/ns/activitystreams",
            "type": "Create",
            "id": "https://tsyesika.se/objects/79402654-a9e5-4356-a50d-5109fedbaacc"
            "actor": "https://tsyesika.se"
            "to": ["https://dustycloud.org/"],
            "object": {
                "type": "Note",
                "attributedTo": "https://tsyesika.se"
                "id": "https://tsyesika.se/objects/2f614e93-1fe7-4a8a-ba39-f9e4468ed77f"
                "content": "Ohai Christine!"
            }
        }
    }
}

Reading from a user’s outbox gives you all the things they’ve posted (often it’s paginated, but that was left unimplemented in the prototype). You can see that the note we posted is the only item and the server has assigned IDs the activity, note, and author.

The server also should deliver this activity and note to Christine by looking up her inbox and doing an HTTP POST to it with the activity. I’m not going to go any further into federation but it’s very similar to the client-to-server API.

ActivityPub meets Goblins

If you’ve worked with Goblins or other actor frameworks you might be thinking “these activities look an awful lot like messages between different objects” and you’d be right.

That Create activity we posted above could be written something like this:

(define note (spawn ^note #:content "Ohai Christine!"))
(<- tsyesika-outbox         ; The outbox
    'create                 ; A method called create
    #:object note           ; Create an object
    #:to (list christine))  ; Send it to Christine.

The outbox can then implement whatever things it needs to for its Create activity (assigning an ID), federating the post out, etc. Just like any other Goblins actor would implement a method.

The prototype I’ve built does a fairly simple transformation from the unmarshalled JSON data. The JSON data is accepted and parsed to an association list. Then there is then an unmarshalling step where any nested objects get converted into their corresponding Goblins actors. The result is an activity actor which looks like this:

(define-actor (^as2:activity bcom parent #:key actor object target result origin
                             instrument)
  (extend-methods parent
    ((actor) actor)
    ((object) object)
    ((target) target)
    ((origin) origin)
    ((to-method)
     (string->symbol (string-downcase (assoc-ref ($ parent 'to-data) "type"))))
    ((to-data)
     (append (filter-data `(("actor" . ,actor)
                            ("object" . ,object)
                            ("target" . ,target)
                            ("result" . ,result)
                            ("origin" . ,origin)
                            ("instrument" . ,instrument)))
             ($ parent 'to-data)))))

ActivityPub is based on Activity Streams 2.0 which has a hierarchical structure. The Activity type extends from a base type of Object. This is represented in the Mandy prototype as parent.

We can then use this very simple procedure which takes an activity and converts it to a message:

(define* (activity->message activity #:key send-to)
  (define method-name ($ activity 'to-method))
  (define object ($ activity 'object))
  (define to
    (if send-to
        send-to
        ($ activity 'object)))

  (list to
        method-name
        #:object ($ activity 'object)
        #:actor ($ activity 'actor)
        #:target ($ activity 'target)
        #:self activity))

This produces our message as a list with its method name and a bunch of keyword arguments. The methods can then be defined as normal Goblins methods. If all the keywords aren’t needed for that method behavior, it can include #:allow-other-keys so that everything else can be ignored.

As an example of this let’s see the Collection type which is basically a list of objects. Here’s the implementation in the prototype:

(define* (^as2:collection bcom parent #:optional [items (make-gset)])
  (extend-methods parent
    ((add #:key object #:allow-other-keys)
     (bcom (^as2:collection bcom parent (gset-add items object))))
    ((remove #:key object #:allow-other-keys)
     (bcom (^as2:collection bcom parent (gset-remove items object))))
    ((move #:key object target #:allow-other-keys)
     (define new-items (gset-remove items object))
     ($ target 'add #:object object)
     (bcom (^as2:collection bcom parent new-items)))))

We can see it supports add, remove and move methods. The specification defines the behavior of add as:

Indicates that the actor has added the object to the target. If the target property is not explicitly specified, the target would need to be determined implicitly by context. The origin can be used to identify the context from which the object originated.

In this case, there might be two things which would be important to this method, the first being the #:object keyword and the second being the #:target. Since the collection is being sent the add message, it’s being assumed in the above code that the sender has figured the collection out. The add method then just needs to care about the object, in which case it specifies object as the only key and ignores anything else. Finally, the behavior is straightforward it adds the object to the collection.

Hopefully the above shows how we can take these ActivityPub activities and transform them into Goblins messages. This gives us the desired Goblins ergonomics while implementing ActivityPub objects.

Going further

The prototype that I implemented was a demo trying to explore some of both the Goblins actor HTTP interface and how ActivityPub might be implemented in an actor framework like Goblins. Having helped co-author ActivityPub and then develop Goblins, I’ve had musings of how this implementation might look, but it’s been very exciting to see that they work in practice.

This demo has explored both a HTTP interface with Goblins actors and ActivityPub. I think each one has a lot of potential for future work and I’d love to see Goblins applied in building websites. Goblins could be used both to build the backend of websites by handling the HTTP requests themselves, and in the browser by using Hoot.

There’s a many aspects of an ActivityPub implementation left to explore, for instance Goblins’ persistence system would be well suited to be our database. We could explore adding federation (Goblins being a distributed framework would be well suited to implement). Hopefully in the future we’ll be able to build on this experiment more.

If you found this blog post interesting, both myself and Christine Lemmer-Webber will be giving a FOSDEM 2026 talk on this. We’d love to see you there if you’re attending, but if not the video will be posted shortly after the event.

Thanks to our supporters

Your support makes our work possible! If you like what we do, please consider becoming a Spritely supporter today!

Diamond tier

  • Aeva Palecek
  • David Anderson
  • Holmes Wilson
  • Lassi Kiuru

Gold tier

  • Alex Sassmannshausen
  • Juan Lizarraga Cubillos

Silver tier

  • Brian Neltner
  • Brit Butler
  • Charlie McMackin
  • Dan Connolly
  • Danny OBrien
  • Deb Nicholson
  • Eric Bavier
  • Eric Schultz
  • Evangelo Stavro Prodromou
  • Evgeni Ku
  • Glenn Thompson
  • James Luke
  • Jonathan Frederickson
  • Jonathan Wright
  • Joshua Simmons
  • Justin Sheehy
  • Michel Lind
  • Mike Ledoux
  • Nathan TeBlunthuis
  • Nia Bickford
  • Noah Beasley
  • Steve Sprang
  • Travis Smith
  • Travis Vachon

Bronze tier

  • Alan Zimmerman
  • Aria Stewart
  • BJ Bolender
  • Ben Hamill
  • Benjamin Grimm-Lebsanft
  • Brooke Vibber
  • Brooklyn Zelenka
  • Carl A
  • Crazypedia No
  • François Joulaud
  • Grant Gould
  • Gregory Buhtz
  • Ivan Sagalaev
  • James Smith
  • Jamie Baross
  • Jason Wodicka
  • Jeff Forcier
  • Marty McGuire
  • Mason DeVries
  • Michael Orbinpost
  • Nelson Pavlosky
  • Philipp Nassua
  • Robin Heggelund Hansen
  • Rodion Goritskov
  • Ron Welch
  • Stefan Magdalinski
  • Stephen Herrick
  • Steven De Herdt
  • Thomas Talbot
  • William Murphy
  • a b
  • chee rabbits
  • r g
  • terra tauri

by Jessica Tallon (contact@spritely.institute) at Tuesday, January 6, 2026

Tuesday, December 23, 2025

Retropikzel's blog

Monday, December 22, 2025

Scheme Requests for Implementation

SRFI 257: Simple extendable pattern matcher with backtracking

SRFI 257 is now in final status.

Pattern matching extends Scheme's repertoire of conditional constructs, allowing decomposition of compound data structures and binding their parts to variables. This SRFI proposes one such construct, match, which offers all the core functionality specified in SRFI 200, while extending it with support for non-linear patterns and backtracking. The proposed construct is modular and supports easy extension through the define-match-pattern mechanism. It can be implemented portably in R⁷RS-Small.

by Sergei Egorov at Monday, December 22, 2025

Wednesday, December 17, 2025

Andy Wingo

in which our protagonist dreams of laurels

I had a dream the other evening, in which I was at a large event full of hackers—funny, that this is the extent of my dreams at the moment; as a parent of three young kids, I don’t get out much—and, there, I was to receive an award and give a speech. (I know, I am a ridiculous man, even when sleeping.) The award was something about free software; it had the trappings of victory, but the vibe among attendees was numbness and bitter loss. Palantir had a booth; they use free software, and isn’t that just great?

My talk was to be about Guile, I think: something technical, something interesting, but, I suspected, something inadequate: in its place and time it would be a delight to go deep on mechanism but the moment seemed to call for something else.

These days are funny. We won, objectively, in the sense of the goals we set in the beginning; most software is available to its users under a free license: Firefox, Chromium, Android, Linux, all the programming languages, you know the list. So why aren’t we happy?

When I reflect back on what inspired me about free software 25 years ago, it was much more political than technical. The idea that we should be able to modify our own means of production and share those modifications was a part of a political project of mutual care: we should be empowered to affect the systems that surround us, to the extent that they affect us.

To give you an idea of the milieu, picture me in 1999. I left my home to study abroad on another continent. When I would go to internet cafés I would do my email and read slashdot and freshmeat as one did back then, but also I would often read Z magazine, Noam Chomsky and Michael Albert and Michael Parenti and Arundhati Roy and Zapatistas and all. I remember reading El País the day after “we” shut down the World Trade Organization meeting in Seattle, seeing front-page pictures of pink-haired kids being beat up by the cops and wishing I were there with them. For me, free software fit with all of this: the notion that a better world was possible, and we could build it together.

I won’t lie and say that the ideals were everything. I think much of my motivation to program is selfish: I like to learn, to find out, to do. But back then I felt the social component more strongly. Among my cohort, though, I think we now do free software because we did free software; the motive sedimented into mechanism. These are the spoils of victory: free is the default. But defaults lack a sense of urgency, of the political.

Nowadays the commons that we built is the feedlot of large language models, and increasingly also its waste pond. The software we make is free, but the system in which it is made is not; Linux Magazine 1, Z magazine 0.

All of this makes me think that free software as a cause has run its course. We were the vanguard, and we won. Our dreams of 25 years ago are today’s table stakes. Specifically for my copyleft comrades, it seems that the role of copyright as a societal lever has much less purchase; taken to its conclusion, we might find ourselves siding with Disney and OpenAI against Google.

If I had to choose an idea from the 90s to keep, I would take “another world is possible” over the four freedoms. For me, software freedom is a strategy within a broader humanist project of liberation. It was clever, in that it could motivate people from a variety of backgrounds in a way that was on the whole positive for the humanist project. It inspired me as a meaningful way in which I could work towards a world of people caring for each other. In that spirit, I would like to invite my comrades to reflect on their own hierarchy of principles; too often I see people arguing the fine points of “is this software free” according to a specific definition without appreciating the ends to which the software freedom definition is a means.

Anyway, it turns out that I did win something, the Award for the Advancement of Free Software, for my work on Guile over the years. My work on Guile has waxed and waned, and in these last few years of parenthood it has been rather the latter, but I am proud of some of the technical hacks; and it has been with a heart-warming, wondrous delight that I have been a spectator to the rise of Guix, a complete operating system built on Guile. Apart from its quite compelling technical contributions, I just love that Guix is a community of people working together to build a shared project. I am going to the Guix days in a month or so and in past years it has been such a pleasure to see so many people there, working to make possible another world.

In my dream, instead of talking about Guile, I gave a rousing and compelling impromptu invective against Palantir and their ilk. I thought it quite articulate; I was asleep. In these waking hours, some days later, I don’t know what I did say, but I think I know what I would like to have said: that if we take the means of free software to be the ends, then we will find ourselves arguing our enemies are our friends. Saying that it’s OK if some software we build on is made by people who facilitate ICE raids. People who build spy software for controlling domestic populations. People who work for empire.

What I would like to say is that free software is a strategy. As a community of people that share some kind of liberatory principles of which free software has been a part, let use free software as best we can, among many other strategies. If it fits, great. If you find yourself on the same side of an argument as Palantir, it’s time to back up and try something else.

by Andy Wingo at Wednesday, December 17, 2025

Thursday, December 11, 2025

Arthur A. Gleckler

Schmeep: Scheme on Android

Ever since I worked on the Android project nearly twenty years ago, I've wanted to write mobile apps in Scheme. But the early Android API made it hard to run a good Scheme implementation, and the Android team strongly discouraged the use of languages other than Java. Those limitations are long gone. Still, whenever I got the energy to dive into Android Studio, the NDK, and the latest APIs, build problems and other random complexity would frustrate me, and I would give up and switch to another project.

But now we have LLMs. They are exactly what I needed to get past the slog and into the interesting parts of the project. I'm so glad I tried again.

My new project, Schmeep (Github), is an Android app built around Chibi Scheme, Alex Shinn's R7RS-Small-compatible Scheme implementation. It uses RAX, a simple framework inspired by HTMX, to make HTML-based user interfaces in Scheme. No web server is required. Instead, events are delivered to your Scheme code, which returns new HTML fragments to update the UI.

Schmeep includes a simple Linux program that opens a REPL on the phone over Bluetooth. The whole project builds quickly using a simple Makefile. This means that you rarely wait more than a few seconds to see the effects of a change.

Schmeep has just come to life, but I'm eager to build a real app with it. My plan is to experiment with writing local-first software in Scheme. I'll wrote more here when I do.

by Arthur A. Gleckler at Thursday, December 11, 2025