I just wrote a blog post about what happens when you make URLs immutable: mapping from a hash to a NixOS configuration of that hash. Turns out “nice” deployments (fast, zero-downtime, atomic) become much easier, without the complexity of something like Kubernetes. Blog post here, feedback appreciated!
That’s really cool!
I was having similar crazy thoughts:
If file paths are pointers, so are urls.
Why not push nix to start taming them!
PATH variable = dns suffix list (avoid)
file path = dns A record
inode number = ip address
symlink = dns CNAME record
signature = cert/pub key
centralized version of zooko triangle:
(half-baked thoughts, please be lenient)
- human friendly name (dns records)
friendly name CNAME record to hash of service A record - centralized number (ip addresses)
with ipv6, there should be enough bits to truncate hash of service to an iid…
(got to be aware of birthday problem though…) - secure identifier (public keys)
still have to trust public keys, but signature games ease the burden.
I can’t wait to read about how persistence gets handled!
This discussion went even further at least as early as 2021: Announcing Nomia, a general resource manager inspired by Nix
To summarise: What if any operating system resource was immutably addressable?
I very much subscribe to this view, and would go even further: What if any computation state was immutably addressable?
Racket – one of the most alive and relevant Lisps – pretty much goes in this direction with its first-class treatment of continuations.
There’s also https://www.unison-lang.org/ which takes this idea to the expression level. Everything is content addressed. Everything is distributed. It’s very neat
i couldn’t figure out nomia,
but found a paper and tried to write a CESK* machine to think about
immutably-addressable first-class undelimited continuations:
attempt
#!/usr/bin/env -S nix shell nixpkgs#rlwrap nixpkgs#swiProlog --command rlwrap swipl
:- use_module(library(crypto)).
:- use_module(library(http/json)).
:- initialization(main, main).
% this stuff is too advanced for me
% https://arxiv.org/pdf/1007.4446
% butchered from Figure 3 and Figure 12
% (Figure 8 is fun to try to make content-addressable)
% REPL
main :- write("call-by-value lambda-calculus with call/cc (and nats):\n"),
repeat, repl.
repl :- write("λ> "), read_line_to_codes(user_input, Input),
parser(Input,[Exp]), eval(Exp, Out),
writef("\e[32mANSWER=>\e[0m %w\n\n", [Out]), !, fail.
% Parser
parser(String, Exps) :- phrase(exp(Exps), String).
exp([E]) --> wso, sexp(E), wso.
exp([]) --> [].
sexp(E) --> slist(E).
sexp(E) --> satom(E).
slist(E) --> "(", wso, elm(E), wso, ")".
elm([E|Es]) --> sexp(E), wsr, !, elm(Es).
elm([E]) --> sexp(E).
elm([]) --> [].
satom(A) --> symbol(Cs), { atom_codes(A,Cs)}.
satom(A) --> number(Cs), { number_codes(A,Cs)}.
symbol([C|Cs]) --> symbol_first(C), symbol_rest(Cs).
symbol_first(C) --> [C], { code_type(C, alpha) }.
symbol_rest([C|Cs]) --> [C], { code_type(C, alnum); string_codes([C],"/") }, symbol_rest(Cs).
symbol_rest([]) --> [].
number([C|Cs]) --> digit(C), number(Cs).
number([C]) --> digit(C).
digit(C) --> [C], { code_type(C, digit) }.
wsr --> ws, wsr; ws.
wso --> ws, wso; [].
ws --> [W], { code_type(W, white); code_type(W, space) }.
% Control String (code)
term(Exp) :-
variable(Exp);
abstraction(Exp,_,_);
application(Exp,_,_);
number(Exp).
value(Exp) :-
closure(Exp, _, _, _);
number(Exp);
continuation(Exp).
variable(Exp) :-
atom(Exp), \+ address(Exp).
abstraction(Exp, Var, Body) :-
Exp = [lambda, [Var], Body],
variable(Var), term(Body).
application(Exp, E1, E2) :-
Exp = [E1, E2],
term(E1), term(E2).
procedure(Exp) :-
closure(Exp,_,_,_);
callcc(Exp);
continuation(Exp).
closure(Exp, Var, Body, Env) :-
Exp = [closure, [Var], Body, Env],
variable(Var), term(Body), environment(Env).
callcc('call/cc').
continuation(Exp) :-
address(Exp).
address(Addr) :-
atom(Addr), atom_concat('a-', A, Addr),
catch(hex_bytes(A,_),_, false),
string_length(A,64).
% Environment (Variable to Address)
environment(T) :- is_dict(T,env).
lookup(Var, Env, Address) :-
Address = Env.get(Var),
variable(Var), environment(Env), address(Address).
extendEnv(Var, Address, Env, Out) :-
Out = Env.put(Var, Address),
variable(Var), address(Address), environment(Env).
% Store (Address to Storable)
store(T) :- is_dict(T,store).
storable(S) :-
value(S);
kont(S).
alloc(Term, Address) :-
term_string(Term, Str),
crypto_data_hash(Str, Hash, [algorithm(sha256)]),
atom_string(Addr, Hash),
atom_concat('a-', Addr, Address).
dereference(Address, Store, Storable) :-
Storable = Store.get(Address),
address(Address), store(Store), storable(Storable).
extendStore(Address, Storable, Store, Out) :-
Out = Store.put(Address, Storable),
address(Address), storable(Storable), store(Store).
% Kontinuations (just think stack)
k_mt(Frame) :-
Frame = [k_mt].
k_arg(Frame, Exp, Env, Kont_Address) :-
Frame = [k_arg, Exp, Env, Kont_Address],
term(Exp), environment(Env), address(Kont_Address).
k_fun(Frame, Fun, Kont_Address) :-
Frame = [k_fun, Fun, Kont_Address],
address(Kont_Address), procedure(Fun).
kont(Frame) :-
k_mt(Frame);
k_arg(Frame, _, _, _);
k_fun(Frame, _, _).
% Transitions
step(Exp, Env, Store, Kont, Next) :-
variable(Exp),
lookup(Exp, Env, Address),
dereference(Address, Store, Val),
Next = [Val, Env, Store, Kont],
printState("Variable Lookup", Next).
step(Exp, Env, Store, Kont, Next) :-
abstraction(Exp, Var, Body),
closure(Closure, Var, Body, Env),
Next = [Closure, Env, Store, Kont],
printState("Abstraction", Next).
step(Exp, Env, Store, Kont, Next) :-
application(Exp, E1, E2),
k_arg(Frame, E2, Env, Kont),
alloc(Frame, KontA),
extendStore(KontA, Frame, Store, StoreE),
Next = [E1, Env, StoreE, KontA],
printState("Application", Next).
step(Exp, _, Store, Kont, Next) :-
procedure(Exp),
dereference(Kont, Store, Frame),
k_arg(Frame, Arg, EnvA, KontA),
k_fun(FrameF, Exp, KontA),
alloc(FrameF, KontF),
extendStore(KontF, FrameF, Store, StoreE),
Next = [Arg, EnvA, StoreE, KontF],
printState("Continue to Argument", Next).
step(Exp, _, Store, Kont, Next) :-
value(Exp),
dereference(Kont, Store, Frame),
k_fun(Frame, Closure, KontF),
closure(Closure, Var, Body, EnvC),
alloc(Exp, Address),
extendStore(Address, Exp, Store, StoreE),
extendEnv(Var, Address, EnvC, EnvE),
Next = [Body, EnvE, StoreE, KontF],
printState("Continue into Function", Next).
step(Exp, _, Store, Kont, Next) :-
closure(Exp, Var, Body, EnvC),
dereference(Kont, Store, Frame),
k_fun(Frame, Fun, KontC),
callcc(Fun),
alloc(KontC, Address),
extendStore(Address, KontC, Store, StoreE),
extendEnv(Var, Address, EnvC, EnvE),
Next = [Body, EnvE, StoreE, KontC],
printState("call/cc Closure", Next).
step(Exp, Env, Store, Kont, Next) :-
continuation(Exp),
dereference(Kont, Store, Frame),
k_fun(Frame, Fun, _),
callcc(Fun),
dif(Exp, Kont), % if same, then it inf loops...
Next = [Kont, Env, Store, Exp],
printState("call/cc Continuation", Next).
step(Exp, Env, Store, Kont, Next) :-
continuation(Exp),
dereference(Kont, Store, Frame),
k_fun(Frame, Fun, N),
callcc(Fun),
Exp = Kont,
Next = [Kont, Env, Store, N],
% to get (call/cc (call/cc (lambda (k) k))) to work...
% im not sure if this is correct or if i made a mistake somewhere...
printState("call/cc (same) Continuation", Next).
step(Exp, Env, Store, Kont, Next) :-
value(Exp),
dereference(Kont, Store, Frame),
k_fun(Frame, C, _),
address(C),
Next = [Exp, Env, Store, C],
printState("Apply Continuation", Next).
% json for cheap dict pretty printing
printState(Name, [Exp, Env, Store, Kont]) :-
writef("\e[31m%w====>\e[0m\n", [Name]),
writef("\e[33m{C}:\e[0m\n%w\n", [Exp]),
writef("\e[33m{E}:\e[0m\n", []),
json_write(current_output,Env), write("\n"),
writef("\e[33m{S}:\e[0m\n", []),
json_write(current_output,Store), write("\n"),
writef("\e[33m{K*}:\e[0m\n%w\n\n\n", [Kont]).
% machine stops when C has a value and K* points to k_mt (empty).
answer(State) :-
State = [Exp, _, Store, Kont],
value(Exp),
dereference(Kont, Store, Frame),
k_mt(Frame).
run(State, Out) :-
answer(State),
State = [Out, _, _, _];
\+ answer(State),
State = [Exp, Env, Store, Kont],
step(Exp, Env, Store, Kont, Next),
run(Next, Out).
inject(Exp, State) :-
k_mt(Frame),
alloc(Frame, KontA),
extendStore(KontA, Frame, store{}, Store),
State = [Exp, env{}, Store, KontA].
eval(Exp, Out) :-
inject(Exp, State),
printState("Init", State),
run(State, Out).
% Escape before getting sucked into an infinite loop:
% (call/cc (lambda (esc) ((lambda (x) ((lambda (i) (i i)) (lambda (i) (i i)))) (esc 10))))
sorry if off-topic,
what would immutably-addressable first-class environments mean?
I think I missed the point a little;
is this pushing down the resolution further into the script by replacing the paths in the Nix files?
You get the hash right now via Git → Flake but this would be a standalone variant?
(I think also the same with Flake on it’s own but you have no way to reference it by content which is what Git is for)