Compatibility with Teyjus, Prolog and legacy Elpi
Elpi is a dialect of λProlog and runs most programs written for Teyjus. Collected here are the places where it deviates from Teyjus or from standard Prolog, and the older Elpi syntax that newer spellings have superseded but that a program may still be written in.
Lexical differences
Elpi’s tokens are close to those of Teyjus. Both share:
the identifier character set: letters, digits and the sign characters
+ - * / ^ < > = ` ' ? @ # $ & ! _ ~;the rule for telling a variable from a constant: a leading
_or upper-case letter marks a variable;the integer and real shapes:
.5yes,3.no;the comments:
%to end of line, nesting/* */.
They differ in:
Name start. A Teyjus name may start with a sign character (
=abcis one name). An Elpi name must start with a letter,_,@or.; a leading sign character begins an operator.Dot.
.is punctuation in Teyjus. In Elpi.before a letter continues a qualified name.Negative numbers.
-2(no space) is an integer literal in Elpi; Teyjus has no negative numeric literal.Strings. An Elpi string may contain a literal newline; a Teyjus string may not. Teyjus recognises many more escapes (
\a \v \f \e \d \^c \ddd \xhh…); Elpi recognises\n \b \t \r \\ \"and the doubled"".Operators. Teyjus programs declare operators with
infix/prefix/ … ; Elpi’s operator set is fixed (see Lexical conventions).
See Lexical conventions for the full lexical rules.
Semantic differences
In Teyjus and standard λProlog, ; is a special, built-in control
construct, not an ordinary predicate: the cut is transparent through it, so
a ! written inside a disjunct is treated as if it were written in
the enclosing rule: it cuts that rule’s own untried sibling rules too, not
just the other disjunct.
In Elpi, ; genuinely is an ordinary (higher-order) predicate, actually
called as such through the two rules it is defined by, (A ; _) :- A. and
(_ ; B) :- B. (Control and cut), so it is a real call, one
more stack frame. A ! written inside a disjunct only cuts back to that
call: it discards ;’s own untried alternative (the other disjunct) but,
unlike Teyjus, does not reach further out to cut the enclosing rule’s own
sibling rules:
code/cut-in-disjunct.elpi:
1% ; is a regular (higher-order) predicate in Elpi, defined by exactly two
2% rules -- so a ! written inside one of its disjuncts only cuts back to
3% that rule selection, one stack frame up: it discards ;'s own untried
4% alternative, but does not reach further out to cut p's own second rule.
5% In Teyjus and standard lambda-Prolog, where ; is a built-in control
6% construct and the cut is transparent through it, p's second rule would
7% be cut away too.
8
9pred p.
10p :- (!, fail ; true).
11p :- print "p's second rule fired".
12
13main :- p, print "after p".
p's second rule fired
after p
p’s second rule still fires here: the ! inside the first rule’s
disjunct only prevented falling back to true, not the whole first rule
from failing over to the second one.
Unification outside the pattern fragment (Unification and variables)
aborts by default; Teyjus instead delays it as a constraint. Passing the
deprecated -delay-problems-outside-pattern-fragment flag restores that
behaviour.
Superseded Elpi keywords
Newer spellings have replaced several older Elpi keywords. The old ones still parse and mean the same thing (Type declarations):
kind name type -> … -> typeis nowdata name A … Z, one placeholder per parameter instead of onetype ->:data i32. kind i32 type. data tree A. kind tree type -> type. data map A B. kind map type -> type -> type.
kindalso takes a comma-separated list of names (kind day, month type.);datatakes one name.type name <type>for a data constructor is nowsymb name <type>;symbolis a slightly-less-old spelling ofsymb. All three ofsymb leaf tree A. symbol leaf tree A. % or symbol leaf : tree A. type leaf tree A.
declare the same constant, and all three take a comma-separated list of names (
type apple, pear item.).symbol, likesymb, also takes an optional:before the type;typedoes not.externalbefore a declaration is nowbuiltin.A
symb(ortype) whose type ends inpropis really a predicate: write it as apredorfuncsignature instead, sosymb p int -> prop.becomespred p int.(orfunc p int.when it is deterministic).The type
prop, and its λProlog spellingo, is now(pred), with(func)for the deterministic case.fprop, a former keyword for the latter, has been removed entirely: write(func).variadic T Rin a type is the old spelling of a trailing..on the last argument (Type declarations):type f variadic int prop.is nowpred f int.. .(funcif deterministic).
Older signature forms
type name … -> prop gives a predicate a type with no mode information at
all: every argument is unified, none matched, since there is nothing marking
one as input. Older Elpi paired such a declaration with a separate
mode (name i o). directive to add that information; this was never a
Teyjus or standard λProlog feature, only Elpi’s own now-legacy syntax.
Current Elpi does not parse ``mode`` as a directive at all. It is not a
keyword, so mode (name i o). is read as an ordinary fact about a
predicate called mode, and fails to type-check.
The i: / o: markers survive, but only inline, one before each
argument type, in place of the single -> a modern signature uses
(Type declarations): pred name i:int, o:string. is the old
spelling of pred name int -> string.. Unlike -> they can also
interleave inputs and outputs (:functional pred name o:A, i:A.), the
one thing -> cannot express, but that is discouraged: keep the outputs
last.
:functional before a pred marks it deterministic, like the func
keyword, which is preferred. It is still needed for a signature with
interleaved arguments as above, where func (which implies ->) cannot
be used.
The occur check
Elpi performs the occur check by default: unifying a variable with a term
that already contains it fails, rather than building a cyclic term. SWI-Prolog
and ISO Prolog default the other way: occurs_check is false, ordinary
=/2 builds the cyclic term, and a program that wants the check asks for it
per call with unify_with_occurs_check/2. Elpi is the reverse: checked
everywhere, with the :nooc attribute opting one predicate out. The
attribute, the unsound_unif builtin and ground_term are described in
Unification and variables.
Modules
A .sig file’s signature (which names a module exports) is ignored; Elpi
has no separate notion of a module’s public interface. import and
accum_sig/use_sig are parsed, looking for a compiled .mod /
.sig unit the way Teyjus would produce one, but Elpi has no such compiled
unit format to begin with, so in practice they never find anything to load;
accumulate (File structure and attributes) is the only way
to pull in another file’s code. Elpi accumulates each file once, even if
several accumulated files each accumulate it too; Teyjus accumulates every
time, which duplicates rules and is rarely what one wants. Relative
accumulate paths, and -I, are
File structure and attributes; the elpi command line tool
additionally reads the colon-separated
TJPATH environment variable as a fallback search path, for compatibility
with Teyjus scripts that set it.