Data conversion in janus
Contents
Bi-translation between Prolog Terms and Python Data Structures
janus
takes advantage of a C-level
bi-translation of a large portion of Prolog terms and
Python data structures: i.e., Python lists, tuples,
dictionaries, sets and other data types are translated to
their Prolog term forms, and Prolog terms of restricted
but large syntax are translated to lists, tuples,
dictionaries, sets and so on. Bi-translation is recursive
in that any of these data structures can be nested in any
other data structures (subject to limitations on the
occurrence of mutables in Python data structures).
Due to syntactic similarities between Prolog terms and Python data structures, the Prolog term forms are easy to translate and sometimes appear syntactically identical.
As terminology, when a Python data structure D is translated into a Prolog term T, T is called a (Janus) D term e.g., a dictionary term or a set term. The type representing any Python structure that can be translated to Prolog is called jns_struct while jns_term is the pseudo-type representing all Prolog terms that can be translated into a Python data structure.
We first describe the bi-translation in detail, and summarize it in a table at the end of this section.
Bi-translation Specification of Python Types
Bi-translation between Prolog and Python can be described from the viewpoint of Python types as follows:
- Numeric Types: Python integers and floats are
bi-translated to Prolog integers and floats. For Prologs
that have a finite
min_integer
ormax_integer
a representation error should be thrown if the number returned from Python is out of bounds.
v - Fractional numbers (created by
fractions.Fraction()
) are translateed into
Prolog rationals in Prologs like SWI that support
rationals.
Boolean Numbers in Python are translated to the special Prolog structures
@(true)
and@(false)
.Complex numbers are not translated to Prolog; rather their translation to Prolog returns a Python Object Reference, whose real and imaginary parts can be accessed as attributes.
String Types: Python string types are bi-translated to Prolog atoms by default. There are two reasons for this. First, not all Prologs support an efficient representation of strings, so translating as atoms improves compatibility. A pragmatic second reason is that Python uses strings both for identifiers and arbitrary text, and it is not generally possible to determine the role played by a specific Python string in a given case.
- The Prolog representation #(Term) allows passing any
Prolog term as a Python string. If
Term
is an atom or string, this is the same as passing the atom or string. Any other Prolog term is written canonically to a Prolog atom (or string) that is then passed to Python.
For Prolog systems that offer an efficient
representation of strings, Prolog strings can
automatically be translated to Python strings, while Janus
function calls provide the option
py_string_as(string)
to translate Python
strings to Prolog strings.
Note that a Python string can be enclosed in either
double quotes (''
) or single quotes
('
). In translating from Python to Prolog,
the outer enclosure is ignored, so Python
"’Hello’"
is translated to the Prolog
’\’Hello\’’
, while the Python
’"Goodbye"’
is translated to the Prolog
’"Goodbye"’
.
Sequence Types:
Python lists are bi-translated as Prolog lists and the two forms are syntactically identical. An exception is for Janus calls that use the
py_object(true)
option. This option returns a list as an iterable sequence object that can be backtracked through usingpy_iter()
or examined in other ways.A Python tuple of arity
N >= 0
is bi-translated to a compound Prolog term-/N
(i.e., the functor is a hyphen).Note that as a specia case a Python empty tuple
()
bitranslates to-()
.For those Prologs with a maximum arity Maxa for compound terms, passing a Python term with arity greater than Maxa should throw a representation error.
Mapping Types: The translation of Python dictionaries takes advantage of the syntax of braces, which is supported by all Prologs that support DCGs. The term form of a dictionary is;
{ DictList}
where
DictList
is a comma list of’:’/2
terms that use infix notation. I.e.,{k1:v1, k2:v2}
is syntactic sugar for{}(','(:(k1,v1), :(k2,v2)))
Key:Value
Key
andValue
are the translations of any Python data structures that are both allowable as a dictionary key or value, and supported byjanus
. For instance,Value
can be (the term form of) a list, a set, a tuple or another dictionary as with{’K1’:[1,2,3], ’k2’:(4,5,6)]}
which has a nearly identical term form as
{’K1’:[1,2,3], k2: -(4,5,6)]}
For compatibility with Prologs that support dicts or that do not support DCGs, systems should allow the representation of a dictionary term
Term
as above aspy(Term)
.We note that if
py/1
is defined as prefix operator, (I.e, via the declarationop(600,fx,py)
,py{k1:v1, k2:v2}
is a ISO Prolog compliant term and is also readabls as a named dict.Also note that
{}
translates to a Python string, whilepy({})
translates into an empty Python dict.
Set Types: A Python set S is translated to the term form
py_set(SetList)
where SetList is the list containing exactly the translated elements of S. Due to Python’s implementation of sets, there is no guarantee that the order of elements will be the same in S and SetList.
None Types. The Python keyword
None
is translated to the Prolog term@(none)
.Binary Types: are not yet supported.
Any Python object
Obj
of a type that is not translated to a Prolog term as indicated above, and that does not have an associated iterator is translated to the Python object reference, which can be passed back to Python for an object call or other purposes.The representation of a Python object reference is system-dependent, and should be tested via
py_is_object/1
.The translaton of arbitrary Prolog terms to Python is system-dependent. See e.g., the SWI or XSB manuals for different ways in which this is done.
Tabular Summary
The bi-directional conversion between Prolog and Python terms is summarized in the table below.
Prolog | Python | Notes | |
Variable | ⟶ |
- | (instantiation error) |
Integer | ⟺ |
int | Supports big integers |
Rational | ⟺ |
fractions.Fraction() | |
Float | ⟺ |
float | |
@(none) | ⟺ |
None | |
@(true) | ⟺ |
True | |
@(false) | ⟺ |
False | |
Atom | ⟷ |
String | |
String | ⟶ |
String | Via py_string_as(string) option
(Optional) |
#(Term) | ⟶ |
String | stringify using write_canonical/1 if not atomic |
List | ⟶ |
List | |
List | ⟵ |
Sequence | |
List | ⟵ |
Iterator | Note that a Python Generator is an Iterator |
py_set(List) | ⟺ |
Set | |
-() | ⟺ |
() | Python empty Tuple |
-(a,b, … ) | ⟺ |
(a,b, … ) | Python Tuples. Note that a Prolog pair
A-B maps to a Python (binary) tuple. |
{k:v, …} | ⟹ |
Dict | |
py({k:v, …}) | ⟹ |
Dict | |
py({}) | ⟹ |
{} | Python empty dictionary |
Python Object Reference | ⟺ |
Object | Used for any Python object not above |
Compound | ⟶ |
Domain Error | For any term not designated above. |