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)
2012 2017 2022
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!)
(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.
?- 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.
vanilla(true).
vanilla((A,B)) :-
vanilla(A),
vanilla(B).
vanilla(g(G)) :-
clause(G, Body),
vanilla(Body).
(Adapted from "The Power of Prolog")
(Adapted from "The Power of Prolog")
vanilla(true).
vanilla((A,B)) :-
vanilla(A),
vanilla(B).
vanilla(g(G)) :-
clause(G, Body),
vanilla(Body).
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).
(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).
?- reduce([
factorial(3, F),
g(write(result(F))),
]).
result(6)
F = 6
(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)
])
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
*
*/
:- 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 🤫)
(https://github.com/elixir-nx/nx)
defmodule Example do
import Nx.Defn
defn multiply(x, y) do
x * y
end
end
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
(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
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).
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)
sentence --> noun_phrase, verb_phrase.
noun_phrase --> det, noun.
verb_phrase --> verb, noun_phrase.
det --> "the".
det --> "a".
noun --> ...
verb --> ...
(Whitespace stripped for clarity)
?- phrase(sentence, "the computer parsed a sentence").
true.
?- phrase(sentence, "the physicist said, 'Hello, Mike'").
false.
?- phrase(sentence, X).
X = "the viking befriended a ninja" ;
X = "a conductor aggrieved the telephone" ;
X = "the cloud weathered the storm" ;
...
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).
RETRIEVE name, age, occupation WHERE
name = "Mike Williams" AND
occupation = "telecom engineer" AND
NOT age < 29
(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"))
].
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
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
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
unused_line(1).
unused_line(2).
unused_line(3).
ring_phone :- unused_line(X), shell('say ring'), fail.
ring_phone :- shell('say banana phone').
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)
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}],
]>
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)