For anyone who’s wished they could do logic programming in Julia (but, like me, found miniKanren difficult to learn), I’m excited to announce Julog.jl, a package for Prolog-style logic programming in Julia! It just got added to the registry, so you can install it with:
] add Julog
It’s called Julog.jl
rather than Prolog.jl
because it doesn’t exactly implement Prolog syntax or semantics, but it’s very close, and also allows for tight integration with standard Julia code. Some neat features include:
- Prolog-like syntax
- Interpolation of expressions
- Evaluation of custom Julia functions
Example
The @julog
macro can be used to create logical terms or Horn clauses using Prolog-like syntax. It can also applied to a list of clauses to create a knowledge base. For example, the traditional Zen lineage chart can be encoded as:
clauses = @julog [
ancestor(sakyamuni, bodhidharma) <<= true,
teacher(bodhidharma, huike) <<= true,
teacher(huike, sengcan) <<= true,
teacher(sengcan, daoxin) <<= true,
teacher(daoxin, hongren) <<= true,
teacher(hongren, huineng) <<= true,
ancestor(A, B) <<= teacher(A, B),
ancestor(A, C) <<= teacher(B, C) & ancestor(A, B),
grandteacher(A, C) <<= teacher(A, B) & teacher(B, C)
]
We can then query the knowledge base via SLD resolution:
Query: Is Sakyamuni the dharma ancestor of Huineng?
julia> sat, subst = resolve(@julog(ancestor(sakyamuni, huineng)), clauses);
julia> sat
true
Query: Who are the grandteachers of whom?
julia> sat, subst = resolve(@julog(grandteacher(X, Y)), clauses);
julia> subst
4-element Array{Any,1}:
{Y => sengcan, X => bodhidharma}
{Y => daoxin, X => huike}
{Y => hongren, X => sengcan}
{Y => huineng, X => daoxin}
If you really like Prolog syntax, you can also use the @prolog
macro on a string that contains a Prolog program, which will then be parsed to a list of Julog clauses. However, not all Prolog syntax is currently supported, though the subset corresponding to Datalog should work fine.
Interpolation
You can interpolate Julia expressions when constructing Julog terms using the @julog
macro. Julog supports two forms of interpolation. The first form is constant interpolation using the $
operator, where ordinary Julia expressions are converted to Const
s:
Interpolating Julia expressions
julia> e = exp(1)
2.718281828459045
julia> term = @julog irrational($e)
irrational(2.718281828459045)
julia> dump(term)
Compound
name: Symbol irrational
args: Array{Term}((1,))
1: Const
name: Float64 2.718281828459045
The second form is term interpolation using the :
operator, where pre-constructed Julog terms are interpolated into a surrounding Julog expression:
Interpolating Julog terms
julia> e = Const(exp(1))
2.718281828459045
julia> term = @julog irrational(:e)
irrational(2.718281828459045)
julia> dump(term)
Compound
name: Symbol irrational
args: Array{Term}((1,))
1: Const
name: Float64 2.718281828459045
Interpolation allows us to easily generate Julog knowledge bases programatically using Julia code:
julia> people = @julog [avery, bailey, casey, darcy];
julia> heights = [@julog(height(:p, cm($(rand(140:200))))) for p in people]
4-element Array{Compound,1}:
height(avery, cm(155))
height(bailey, cm(198))
height(casey, cm(161))
height(darcy, cm(175))
Custom Functions
In addition to standard arithmetic functions, Julog
supports the evaluation of custom functions during proof search, allowing users to leverage the full power of precompiled Julia code. This can be done by providing a dictionary of functions when calling resolve
. This dictionary can also accept constants (allowing one to store, e.g., numeric-valued fluents), and lookup-tables. E.g.:
Example custom functions
funcs = Dict()
funcs[:pi] = pi
funcs[:sin] = sin
funcs[:cos] = cos
funcs[:square] = x -> x * x
funcs[:lookup] = Dict((:foo,) => "hello", (:bar,) => "world")
@assert resolve(@julog(sin(pi / 2) == 1), Clause[], funcs=funcs)[1] == true
@assert resolve(@julog(cos(pi) == -1), Clause[], funcs=funcs)[1] == true
@assert resolve(@julog(lookup(foo) == "hello"), Clause[], funcs=funcs)[1] == true
@assert resolve(@julog(lookup(bar) == "world"), Clause[], funcs=funcs)[1] == true
More details on usage can be found in the README, and more examples are in the test folder. Let me know if you find this package useful, or have ideas for extensions or improvements!
(Performance improvements would be especially welcome. I’ve tried all the standard tricks used in Prolog implementations, as well as the standard suggestions for Julia, but the benchmark tasks still don’t run as fast as in SWI-Prolog, and it’d be great to find out why! Even if the answer is just that an interpreter is always going to run slower.)