For those who might be interested in experimenting with extending Prolog arithmetic to support custom types for functional DSL’s, I’ve written an arithmetic_types
pack as an extension to library(arithmetic)
(https://github.com/ridgeworks/arithmetic_types). It’s basically a clone of arithmetic
, using the same :- arithmetic_function/1
directive, but provides its goal expansion as user:goal_expansion/2
rather than system:goal_expansion/2
. Effectively, this means arithmetic_types
overrides library(arithmetic)
once it’s loaded.
The pack also includes a few “type_modules” as a starter kit; see the pack README
for details. Some examples of usage follow.
Indexing and slicing (Python model) of lists and strings/atoms:
?- T is [a,b,c][0].
T = a.
?- T is [a,b,c][-1].
T = c.
?- L is [a,b,c][1:2].
L = [b].
?- L is [a,b,c][1:E].
L = [b, c],
E = 3.
?- N is [a,2+3,c][1].
N = 5.
?- Ch is "abcd"[0].
C = a.
?- A=abc, S is A[_:string_length(A)].
A = abc,
S = "abc".
?- Ch = "D", Lower is "abcdefghijklmnopqrstuvwxyz"[Ch-"A"].
Ch = "D",
Lower = d.
The last example exploits the fact that "A"
evaluates to a number (character code of A
). In cases where extended arithemtic is ambiguous (e.g., is “A” a string or a number?), the existing semantics overrides the extension. (Also applies to atoms like pi
, e
, inf
, etc.)
Note that the begin/end values of a slice default to 0 and the length of the block input argument respectively.
Expressions in lists are are not evaluated until they are the result of an indexing operation, supporting lazy evaluation. E.g., a “safe” division:
?- between(0,1,Den), between(-1,1,Num),
Q is [[-inf,nan,inf][sign(Num)+1], Num/Den][Den\==0].
Den = 0,
Num = -1,
Q = -1.0Inf ;
Den = Num, Num = 0,
Q = 1.5NaN ;
Den = 0,
Num = 1,
Q = 1.0Inf ;
Den = 1,
Num = Q, Q = -1 ;
Den = 1,
Num = Q, Q = 0 ;
Den = Num, Num = Q, Q = 1.
There are two conditional evaluations done here; the first (Den\==0
which uses atomic comparisions in type_bool
) to determine if the divide by 0 applies, and the second (sign(Num)+1
) to select which IEEE continuation value to use.
The pack also includes an ndarray
type loosely modeled after a subset of the Python class. As is, it can be used to solve systems of linear equations using the inverse matrix formula, e.g., the solution of:
x + y + z + w = 13
2x + 3y − w = −1
−3x + 4y + z + 2w = 10
x + 2y − z + w = 1
Using flag(prefer_rationals)=true (otherwise approximate floating point values may be generated):
?- A is ndarray([[1,1,1,1],[2,3,0,-1],[-3,4,1,2],[1,2,-1,1]]),
B is ndarray([[13],[-1],[10],[1]]),
Vs is ndarray([[X],[Y],[Z],[W]]),
Vs is dot(inverse(A),B).
A = #(#(1, 1, 1, 1), #(2, 3, 0, -1), #(-3, 4, 1, 2), #(1, 2, -1, 1)),
B = #(#(13), #(-1), #(10), #(1)),
Vs = #(#(2), #(0), #(6), #(5)),
X = 2,
Y = 0,
Z = 6,
W = 5.
- function
ndarray
creates an n dimensional array from a nested list (B
and Vs
are column vectors)
- function
inverse
returns its matrix argument inverted
- function
dot
is the dot product of it’s ndarray
arguments
Type ndarray
is also a sequence type, like lists and strings, so indexing and slicing is done the same way.:
?- X is new(ndarray,[3]), X[_:_] is ndarray([1,2,3,4,5])[1:4].
X = #(2, 3, 4).
Indexing and slicing are examples of polymorphism; the same function “template” used with different types, in this case lists, strings and arrays. See the pack README for an expanded explanation.