Syntax basics
System.put_env("AL_MNESIA_DISTRIBUTED", "false")
System.put_env(
"AL_MNESIA_DIR",
Path.join(System.tmp_dir!(), "al_livebook_#{:erlang.unique_integer([:positive])}")
)
Mix.install(
[{:al, github: "anoma/AL-Ex"}],
config: [al: [packages: [AL.Package.Bootstrap], natives: []]]
)
Setup
This notebook is self-contained. The cell above installs AL directly, no separate node, no cookie, nothing to attach to. The first run compiles AL and its dependencies from scratch, which takes a minute or two. Reopening this notebook later is fast, since Mix.install caches by the exact dependency and config combination. State here is throwaway. It lives only for this notebook's session and disappears when the session ends.
If you already have a real AL node running and want the persistent, shared store instead (useful for actual development, or for the command log's multi-session material), see the intro for how to attach to it instead.
Mix.install has to finish running before AL's macros (run, defclass, and the rest) can be brought into scope, so that happens in its own cell here.
use AL
AL.Branch.head()
Goals
An AL program is a sequence of goals, run together as one transaction with run do ... end. A goal either succeeds, maybe binding some variables along the way, or it fails.
run do
unify(x, 1)
end
Lowercase names like x are variables. You never declare them, you just use them, and AL fills them in as it runs.
A goal that fails aborts the whole transaction.
run do
unify(x, 1)
unify(x, 2)
end
x can't be both 1 and 2, so this one fails.
WAM Arithmetic
vm_is evaluates an arithmetic expression and unifies the result against the first argument.
run do
vm_is(x, 2 + 3 * 4)
end
Comparisons are written directly.
run do
vm_is(x, 10)
x > 5
x < 20
end
This section does not cover constraint propagation.
Lists
Lists look like Elixir's, including cons.
run do
unify([h | t], [1, 2, 3])
end
The same pattern shows up directly in method and clause heads later on, not just in unify.
Printing values
vm_format writes straight to output. ~a prints a value as-is if it's already a string, or its plain representation otherwise. ~d prints an integer. ~% is a newline.
run do
vm_format("the answer is ~d~%", [42])
end
There's also a ~o directive for printing objects specifically, covered in working with objects.
Collecting multiple solutions
Some goals can succeed more than one way. member checks whether something is in a list, and if the second argument is still open, it enumerates every element instead of just checking one.
run do
findall(x, [member([1, 2, 3], x)], results)
end
findall(template, goals, result) runs goals, collects every way they can succeed, and gathers template's binding from each into a list.
Running a goal once per solution
forall is findall's side-effecting sibling. It doesn't collect anything, it just runs its body once for every way the condition can succeed.
run do
forall([member([1, 2, 3], x)]) do
vm_format("~d~%", [x])
end
end
Control flow
implies picks a branch based on which condition succeeds first, top to bottom, with an optional :else.
run do
vm_is(x, 5)
implies do
[x > 3] -> unify(result, :big)
:else -> unify(result, :small)
end
end
alternative is a plain backtracking choice between two goals. Both sides stay live for backtracking.
run do
findall(x, [alternative([unify(x, :a)], [unify(x, :b)])], results)
end
cut commits to whatever's been decided so far in the current call and discards any remaining alternatives.
run do
findall(x, [alternative([unify(x, :a), cut], [unify(x, :b)])], results)
end
fail always fails. pass always succeeds without doing anything, useful as an empty branch.
run do
unify(x, 1);
pass
end
Inspecting and building terms
vm_functor is Prolog's functor/3. Given a ground tuple, it splits off the first element as a name and the rest as args. Given a name and args instead, it builds the tuple.
run do
vm_functor({:point, 1, 2}, name, args)
end
var succeeds if its argument is still open. vm_ground succeeds if a term has no open variables left in it anywhere.
run do
var(x)
end
The closest thing to a lambda
AL has no first-class lambda syntax. call(head, body, args) is the closest thing: it unifies args against head, then runs body, with no class or selector needed.
run do
call([a, b], [vm_is(b, a * 2)], [5, result])
end