Backtracking in
Time and Space 

Quinn Wilton / @wilton_quinn
Robert Virding / @rvirding

Welcome 👋

  • Hi, I'm Quinn!
    • Applied Researcher @ Fission
      • Building a planetary scale database for local-first applications
    • Programming language anthropologist
    • @wilton_quinn on Twitter​
       
  • ...and I'm Robert!

There will always be things we wish to say in our programs that in all known languages can only be said poorly

- Alan Perlis, Epigrams on Programming (1982)

Old Stockholm Telephone Tower, 1890

5,500 Telephone Lines of Fire Hazard

AXD 301, 1998

240,000 Simultaneous Calls

Ellemtel, 1988

The Computer Science Laboratory

2012           2017          2022

WhatsApp

Discord

Adobe

EEF

10,000+

Elixir

Gleam

LFE

2,000

"Web-Scale"

Open Source

10,000+ (forums, Slack, etc)

(* List of BEAM languages and users not comprehensive!)

The Unique Challenges of Telecom

Strand: New Concepts in Parallel Programming (1990)

1963

Högertrafikomläggningen

Håll dig till höger,
Svensson, håll
dig till höger
Annars slutar det
bara med en smäll

Keep to the right, Svensson

"Otherwise it just ends with a bang"

Stockholm, September 3rd, 1967

The first recorded instance of hot-code swapping

Montreal, 1970

TAUM group, including Alain Colmeraur

Traduction Automatique de l’Université de Montréal

(From "Fifty Years of Prolog and Beyond", 2022)
(From "Metamorphosis Grammars", 1978)
(̵F̵r̴o̸m̷ ̸"̴M̴e̸t̵a̶m̷o̴r̸p̷h̸o̷s̴i̴s̴ ̷G̸r̵a̸m̴m̵a̷r̴s̵"̸,̷ ̶1̵9̵7̶8̸)̵

Prolog was not created as a programming language [...] we wanted to tell a story in French to the computer, then we would ask questions and it was supposed to answer back. [...] Prolog was created as a tool to do that.

- Alain Colmerauer, Profession scientifique (1991)

From the start, Erlang was designed as a practical tool for getting the job done—this job being to program basic telephony services on a small telephone exchange. Programming this exchange drove the development of the language.

- Joe Armstrong, A History of Erlang (2007)

calc(n(X), X).

calc(add(X0, Y0), Z) :-
    calc(X0, X1),
    calc(Y0, Y1),
    Z #= X1 + Y1.

calc(sub(X0, Y0), Z)  :-
    calc(X0, X1), calc(Y0, Y1), Z #= X1 - Y1.

calc(mult(X0, Y0), Z) :-
    calc(X0, X1), calc(Y0, Y1), Z #= X1 * Y1.

add/2

n(2)

n(2)

n(2)

mult/2

?- calc(add(n(5), mult(n(2), n(3))), Z).
Z = 11.

?- calc(mult(n(3), add(n(X), n(Y))), 9),
   X in 0..10,
   Y in 0..10,
   label([X, Y]).
X = 0,
Y = 3 ;
X = 1,
Y = 2 ;
X = 2,
Y = 1 ;
X = 3,
Y = 0.

More algebra than a peasant in the 1400s would get in their whole lifetime

Prolog Meta-Interpreters (MI)

vanilla(true).
vanilla((A,B)) :-
    vanilla(A),
    vanilla(B).
vanilla(g(G)) :-
    clause(G, Body),
    vanilla(Body).

Vanilla MI

(Adapted from "The Power of Prolog")

Prolog Meta-Interpreters (MI)

(Adapted from "The Power of Prolog")
vanilla(true).
vanilla((A,B)) :-
    vanilla(A),
    vanilla(B).
vanilla(g(G)) :-
    clause(G, Body),
    vanilla(Body).

Vanilla MI

"Reversed" MI

reversed(true).
reversed((A,B)) :-
    reversed(B).
    reversed(A),
reversed(g(G)) :-
    clause(G, Body),
    reversed(Body).
(Adapted from "Use of Prolog for developing a new programming language", 1992)
reduce([]).
reduce([g(G) | T]) :-
  clause(G, Body),
  vanilla(Body),
  reduce(T).
reduce([Lhs | More]) :-
  erlang2prolog(Lhs, Rhs),
  append(Rhs, More, More1),
  reduce(More1).

"Suspendable" MI

Early Erlang Meta-Interpreter (1988)

(Adapted from "Use of Prolog for developing a new programming language", 1992)
reduce([]).
reduce([g(G) | T]) :-
  clause(G, Body),
  vanilla(Body),
  reduce(T).
reduce([Lhs | More]) :-
  erlang2prolog(Lhs, Rhs),
  append(Rhs, More, More1),
  reduce(More1).

"Suspendable" MI

?- reduce([
  factorial(3, F),
  g(write(result(F))),
]).

result(6)
F = 6

Printing factorial(3)

Early Erlang Meta-Interpreter (1988)

Early Erlang Meta-Interpreter (1988)

(Adapted from "Use of Prolog for developing a new programming language", 1992)
erlang2prolog(factorial(0, 1), [])
erlang2prolog(factorial(N, F), [
  g(N1 is N - 1),
  factorial(N1, F1),
  g(F is N * F1)
])

Definition of factorial/2

A programming language is low level when its programs require attention to the irrelevant.

- Alan Perlis, Epigrams on Programming (1982)

(Adapted from a 1988 implementation of Erlang 🤫)
/*
 * /als/user/joe/tos/slurp.pro
 *
 * Author: Joe Armstrong
 * Creation Date: 1988-03-25
 * Purpose:
 *      slurp in erlang code
 *
 */

Term rewriting!

:- op(1200,xfy,(--->)).
:- op(500,yfx,'=>').
:- op(1198,xfy,(#)).
:- op(800,xfy,(:=)).

convert((N # Lhs ---> Rhs), (Lhs :- erlang(N,L1,X))) :-
  l2c(L,Rhs),
  append(L,X,L1).
1 # d1(H,Tm) --->
    link(H),
    Self := self,
    link_hw(H,Self),
    send(H,activate_elu5(8)),
    receive([
        msg(H,X) =>
            write(msg(H,X))
    ]),
    reset(H),
    link(Tm),
    put(tone, none),
    d1_loop(H,Tm).
1 # d1 --->
    init_hw,
    simulate(off),
    Tm := spawn(
        switch_manager,
        10
    ),
    Id1 := spawn(
        d1(dts(66),Tm),
        10
    ),
    Id2 := spawn(
        d1(dts(67),Tm),
        10
    ),
    exit(normal).
(Adapted from a 1988 implementation of Erlang 🤫)

Meta-Interpreters in Elixir!

(https://github.com/elixir-nx/nx)
defmodule Example do
  import Nx.Defn

  defn multiply(x, y) do
    x * y
  end
end

Nx's defn macro

Scalar Multiplication

iex(1)> Example.multiply(
  2,
  ~V[1 2 3]
)
#Nx.Tensor<
  s64[3]
  [2, 4, 6]
>
(https://github.com/elixir-nx/nx)
defmodule Nx.Defn do
  defmacro defn(call, do: block) do
    define(call, block)
  end

  defp define(call, block) do
    quote do
      def unquote(call) do
        use Nx.Defn.Kernel
        unquote(block)
      end
    end
  end
end

(Simplified) Nx.Defn.defn/2

(https://github.com/elixir-nx/nx)
defmodule Nx.Defn.Kernel do
  defmacro __using__(_opts) do
    quote do
      import Kernel, only: []
      import Nx.Defn.Kernel
      alias Nx.Defn.Kernel, as: Kernel
    end
  end
  
  def left * right when is_number(left) and is_number(right) do
    Kernel.*(left, right)
  end

  def left * right, do: Nx.multiply(left, right)
end

(Simplified) Nx.Defn.Kernel

The disadvantage of this syntax is that error messages, run time diagnostics, etc. are in terms of Prolog data structures — not Erlang.

- Use of Prolog in developing a new programming language (1992)

1 # toggle_tone(dts(H), none, Tone, Tm) --->
    send(Tm, start_tone(H, Tone)),
    put(tone, Tone).
2 # toggle_tone(dts(H), X, Y, Tm) --->
    send(Tm, stop_tone(H)),
    put(tone, none).

Ellemtel, 1993

Robert and his trains

lock(Train, OldSpeed) ->
	receive  {
	    _ ? block_locked(_, Speed, SensorNo, NewTime) =>
		driver ! set_speed(OldSpeed, Train),
		^{Speed, NewTime, SensorNo};
	    
	    _ ? stop =>
		driver ! set_speed(0, Train),
		throw(normal);
	    
	    _ ? halt =>
		driver ! set_speed(0, Train),
		lock(Train, OldSpeed);
	}.
(Taken from "Erlang och realtidskontronllerad järnväg", 1990)

A PIQUE at Definite Clause Grammars

sentence    --> noun_phrase, verb_phrase.
noun_phrase --> det, noun.
verb_phrase --> verb, noun_phrase.
det         --> "the".
det         --> "a".
noun        --> ...
verb        --> ...

A DCG for simple sentences

(Whitespace stripped for clarity)
?- phrase(sentence, "the computer parsed a sentence").
true.

?- phrase(sentence, "the physicist said, 'Hello, Mike'").
false.

Parsing with the DCG

?- phrase(sentence, X).
X = "the viking befriended a ninja" ;
X = "a conductor aggrieved the telephone" ;
X = "the cloud weathered the storm" ;
...

Generating text with the DCG

sentence -->
    noun_phrase,
    verb_phrase.

noun_phrase -->
    det,
    noun.

verb_phrase -->
    verb,
    noun_phrase.

det --> "the".
det --> "a".
sentence(A,Z) :-
    noun_phrase(A,B),
    verb_phrase(B,Z).

noun_phrase(A,Z) :-
    det(A,B),
    noun(B,Z).

verb_phrase(A,Z) :-
    verb(A,B),
    noun_phrase(B,Z).

det(["the"|X], X).
det(["a"|X], X).

Definite Clause Grammar

Definite Clauses

RETRIEVE name, age, occupation WHERE
    name = "Mike Williams" AND
    occupation = "telecom engineer" AND
    NOT age < 29

The PIQUE query language

(Described in "PIQUE: A relational query language without relations", from 1987)
?- phrase_from_file(query(As, Cs), "query.pique").
As = [name, age, occupation],
Cs = [
    eq(name, "Mike Williams"),
    eq(occupation, "telecom engineer"),
    not(lt(age, "29"))
].

Invoking the parser

query(Attrs, Conditions) -->
    "RETRIEVE",
    attr_list(Attrs),
    "WHERE",
    condition_list(Conditions).

attr_list([A|As]) -->
    attr(A), attr_list_tail(As).

attr_list_tail([])     --> [].
attr_list_tail([A|As]) -->
    ",", attr(A), attr_list_tail(As).

...
defmodule Parser do
  def sentence(a) do
    b = noun_phrase(a)
    z = verb_phrase(c)
  end

  def noun_phrase(a) do
    b = det(a)
    z = noun(c)
  end

  def verb_phrase(a) do
    b = verb(a)
    z = noun_phrase(c)
  end

  def det("the" <> x), do: x
  def det("a" <> x), do: x
  def noun("witch" <> x), do: x
  def noun("demon" <> x), do: x
  def verb("summoned" <> x), do: x
end
sentence -->
    noun_phrase,
    verb_phrase.

noun_phrase -->
    det,
    noun.

verb_phrase -->
    verb,
    noun_phrase.

det --> "the".
det --> "a".
noun --> ...
verb --> ...
iex(1)> sentence("the witch summoned the demon")
""
iex(2)> sentence("the witch creeped past slowly")
** (FunctionClauseError) no function clause matching in Parser.det/1
    
    The following arguments were given to Parser.det/1:
    
        # 1
        "past slowly"

Prolog had DCGs, Erlang didn't, Elixir has the pipe operator.

- Joe Armstrong, A week with Elixir (2013)

defmodule Parser do
  def sentence(a) do
    a |> noun_phrase() |> verb_phrase()
  end

  def noun_phrase(a) do
    a |> det() |> noun()
  end

  def verb_phrase(a) do
    a |> verb() |> noun_phrase()
  end
 end

Parser rewritten to use monads pipes

defmodule Parser do
  import DCG.Defdcg
  
  defdcg sentence, do:
    noun_phrase ~> verb_phrase

  defdcg noun_phrase, do:
    det ~> noun

  defdcg verb_phrase, do:
    verb ~> noun_phrase
  
  defdcg det,  do: "the"
  defdcg det,  do: "a"
  defdcg noun, do: "witch" or "demon" or ...
  defdcg verb, do: "summoned" or ...
end

A tiny DSL for DCGs

Often the semantics of an Erlang expression were not the result of a conscious design decision but were the accidental result of the implementation in Prolog - we call this phenomena semantic embedding - the semantics of Prolog become accidentally embedded in Erlang.

- Use of Prolog for developing a new programming language (1992)

(From "Strand: New Concepts in Parallel Programming", from 1990)

The phones wouldn't stop ringing.

- Robert Virding

ring_phone

unused_line(1).
unused_line(2).
unused_line(3).

ring_phone :- unused_line(X), shell('say ring'), fail.
ring_phone :- shell('say banana phone').

unused_line(X)

X=1

💍

X=2

💍

X=3

💍

🍌☎️

The bottom line is that the more powerful a language, the harder it is to understand systems constructed in it.

- Ben Moseley and Peter Marks, Out of the Tar Pit (2006)

Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.

- Edsger W. Dijkstra, On the Nature of Computing Science (1984)

link(a, b).
link(b, c).
link(c, d).
link(d, a).
link(e, f).
link(f, g).
link(f, h).
link(g, c).

reachable(S, D) :- link(S, D).
reachable(S, D) :-
    link(S, Z),
    reachable(Z, D).

in_cycle(N) :- reachable(N, N).
?- reachable(f, N).
N = a;
N = b;
N = c;
N = d;
N = g;
N = h.
reachable(S, D) :- link(S, D).
reachable(S, D) :-
    link(S, Z),
    reachable(Z, D).
?- in_cycle(N).
N = a;
N = b;
N = c;
N = d;
reachable(S, D) :- link(S, D).
reachable(S, D) :-
    link(S, Z),
    reachable(Z, D).

in_cycle(N) :- reachable(N, N).
(From https://github.com/rust-lang/polonius)

Excerpt from a model of Rust's borrow checker

Neither a Borrower Nor a Lender Be

defmodule Analysis do
  use Croline.DSL

  defdatalog :analysis do
    input inst_name(inst, name)
    input inst_arg(inst, i, arg)
    input fn_label(f, label)
    
    fact ...
    rule ...
  end
end
defmodule Example do
  def add(x, y)
      when is_integer(x) do
    x + y
  end
end
iex(1)> Decompiler.decompile(Example)
{Example, ..., [],
 [
   ...
   {:function, :add, 2, 9,
    [
      {:label, 8},
      {:func_info, {:atom, Example}, {:atom, :add}, 2},
      {:label, 9},
      {:test, :is_integer, {:f, 8}, [x: 0]},
      {:gc_bif, :+, {:f, 0}, 2, [x: 0, x: 1], {:x, 0}},
      :return
    ]},
   ...
 ], 14}
[
  {:fn_label, [{Example, :add, 2, 9}]},
  {:inst_name, [21, :label]},
  {:inst_arg, [21, 0, 8]},
  {:inst_name, [22, :line]},
  {:inst_name, [23, :func_info]},
  {:inst_name, [24, :label]},
  {:inst_arg, [24, 0, 9]},
  {:inst_name, [25, :test]},
  {:inst_arg, [25, 0, :is_integer]},
  {:inst_arg, [25, 1, {:f, 8}]},
  {:inst_arg, [25, 2, {:x, 0}]},
  {:inst_name, [28, :gc_bif]},
  {:inst_arg, [28, 0, :+]},
  {:inst_arg, [28, 1, {:f, 0}]},
  {:inst_arg, [28, 2, 2]},
  {:inst_arg, [28, 3, [x: 0, x: 1]]},
  {:inst_arg, [28, 4, {:x, 0}]},
  {:inst_name, [29, :return]}
]
iex(1)> Decompiler.decompile(Example)
{Example, ..., [],
 [
   ...
   {:function, :add, 2, 9,
    [
      {:label, 8},
      {:func_info, {:atom, Example}, {:atom, :add}, 2},
      {:label, 9},
      {:test, :is_integer, {:f, 8}, [x: 0]},
      {:gc_bif, :+, {:f, 0}, 2, [x: 0, x: 1], {:x, 0}},
      :return
    ]},
   ...
 ], 14}
{:function, :add, 2, 9,
    [
      {:label, 8},
      {:func_info, {:atom, Example}, {:atom, :add}, 2},
      {:label, 9},
      {:test, :is_integer, {:f, 8}, [x: 0]},
      {:gc_bif, :+, {:f, 0}, 2, [x: 0, x: 1], {:x, 0}},
      :return
    ]}
fact inst_exits(:return)
fact inst_exits(:func_info)
 
rule link(src, dest), do:
  inst_name(src, name) and
  !inst_exits(name) and
  dest = src + 1

rule flow(src, dest), do: link(src, dest)
rule flow(src, dest), do:
  flow(src, hop) and
  link(hop, dest)
fact inst_exits(:return)
fact inst_exits(:func_info)
 
rule link(src, dest), do:
  inst_name(src, name) and
  !inst_exits(name) and
  dest = src + 1

rule flow(src, dest), do: link(src, dest)
rule flow(src, dest), do:
  flow(src, hop) and
  link(hop, dest)
{:function, :add, 2, 9,
    [
      {:label, 8},
      {:func_info, {:atom, Example}, {:atom, :add}, 2},
      {:label, 9},
      {:test, :is_integer, {:f, 8}, [x: 0]},
      {:gc_bif, :+, {:f, 0}, 2, [x: 0, x: 1], {:x, 0}},
      :return
    ]}
rule block_head(label, inst), do:
  inst_name(inst, :label) and
  inst_arg(inst, 0, label)
      
rule block_tail(label, inst), do:
  block_head(label, head) and
  flow(head, inst)
defmodule Example do
  def foo(x) do
    bar(x)
    add(x, x)
  end

  def bar(x) do
    identity(x)
  end

  def identity(x) do
    x
  end

  def add(x, y)
      when is_integer(x) do
    x + y
  end
end
rule fn_reachable(src, dest), do:
  fn_call(src, dest)

rule fn_reachable(src, dest), do:
  fn_call(src, hop) and
  fn_reachable(hop, dest)
iex(1)> Analysis.run(Example,
  query:
    "?- fn_reachable({_, :foo, 1}, D)."
)
#MapSet<[
  [D: {Example, :bar, 1}],
  [D: {Example, :identity, 1}],
  [D: {Example, :add, 2}]
]>
(From "Making reliable distributed systems in the presence of software errors", 2003)
fact dangerous({:erlang, :link, 1})
fact dangerous({:erlang, :unlink, 1})

fact dangerous({:erlang, :spawn_link, 1})
fact dangerous({:erlang, :spawn_link, 2})
fact dangerous({:erlang, :spawn_link, 3})
fact dangerous({:erlang, :spawn_link, 4})

fact dangerous({:erlang, :spawn, 1})
fact dangerous({:erlang, :spawn, 2})
fact dangerous({:erlang, :spawn, 3})
fact dangerous({:erlang, :spawn, 4})
rule fn_dangerous(Src), do:
  dangerous(Src)

rule fn_dangerous(Src), do:
  fn_reachable(Src, Dest) and
  fn_dangerous(Dest)
defmodule Example do
  def pure_a(x) do
    pure_b(x)
  end

  def pure_b(x) do
    x
  end

  def impure_a(x) do
    impure_b(x)
  end

  def impure_b(x) do
    spawn(fn -> x end)
  end
end
iex(1)> Analysis.run(Example,
  query: "?- fn_dangerous(F).")
)
#MapSet<[
  [F: {Example, :impure_a, 1}],
  [F: {Example, :impure_b, 1}],
]>

Lua

  • “Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description.”
  • Simple, rather neat little imperative language
  • Dynamic language
  • Lexically scoped
  • Mutable variables/environments/global data
  • Common scripting language in games

Luerl

  • Implements Lua 5.3

  • All of it!

  • Shared, mutable, global data

  • Lua handling of code

  • ...

-record(luerl, {tabs,                           %Table table
                envs,                           %Environment table
                usds,                           %Userdata table
                fncs,                           %Function table
                g,                              %Global table
                %%
                stk=[],                         %Current stack
                cs=[],                          %Current call stack
                %%
                meta=[],                        %Data type metatables
                rand,                           %Random state
                tag,                            %Unique tag
                trace_func=none,                %Trace function
                trace_data                      %Trace data
               }).

%% Table structure.
-record(tstruct, {data,                         %Data table/array
                  free,                         %Index free list
                  next                          %Next index
                 }).

If seriously pressed, good programmers can make an interpreter or compiler for any language they please in any language they are stuck with. This is not very hard, but it is probably the most powerful move a programmer can make.

- Gerald Sussman, Software Design for Flexibility (2021)

Backtracking in Time and Space

By quinnwilton

Backtracking in Time and Space

  • 587