Querying facts database with records

I’m trying to use library(record) to manage and query a fact database but I’m not sure if this is how it should be done. Let’s say I have

:- record user(name, location).

If I now do user_location(U, "Vienna"), the U is unbound, so in user_name(U, Name) Name is still unbound and even user_name(U, "Non existent name") will also succeed. Only by doing call(U) do I get an instantiation. Is this how the library is supposed to be used for querying?

It is not meant for that. It is meant to provide named arguments to compound terms. Similar facilities go by names such as argnames (Ciao), structs (ECLiPSe) and quite likely a lot more.

Since version 7, SWI-Prolog has dicts that provide more or less the same functionality, but using a more pleasant syntax.

So, what are you trying to do? Have predicate arguments by name rather than position?

Exactly… I am storing some 100K-1M facts per file, so I was hoping to save some space by using records instead of dictionaries, so key names don’t have to repeat, think CSV vs JSON :wink:

What about indexing of dictionary-facts? I need to do quick lookups of facts via numeric ids, will this work?

The call() trick is actually kind of working :slight_smile:

All this is not going to be any more efficient than using a predicate with a lot of clauses

fact(Col1, Col2, ...)

Turning that into something like this only costs more memory and slows down indexing (a little).

fact(mystruct(Col1, Col2, ...))

You can indeed use records to create a compound with the arguments in the right position, as in

make_my_struct([name('Bob'), age(Age)], Term),
call(Term).

If you want better performance, write a wrapper for these two and use goal_expansion/2 to turn that into a straight call at compile time.

I’ve done similar tricks using dicts. As these are not meant as goals, you can make a declaration for the argument names of a predicate and then use goal_expansion/2 to turn tag{k1:V1, ...} into tag(..., V1, ....).

Possibly that should be turned into a library. As we speak though, discussions between various Prolog systems are ongoing to synchronize these issues.

My worry is that something like fact(user{name: ..., location: ...}) will not be as good index-wise as using plain user/2 (Some of my facts/terms have arity >10). If I understand it correctly, library(record) just lets me give names to positional arguments so there is no performance overhead.

Now let’s imagine that user has more fields/higher arity - wouldn’t using dictionaries then require the actual query from my first example to be in form of fact(D), user{location: "Vienna", name: Name} :< D because partial unification like fact(user{location: "Vienna", name: Name}) does’t work? And I guess there will be 0 use of indexing in this case?

See mail post above this. Dictionaries are not useful for your case. The way to go is to use a predicate with flat facts and some goal expansion mechanism that rewrites a term with named variables to the proper goal as I explained above. Using dicts for this representation is a good option, so you’re program looks like

...,
user{location:"Vienna", name:Name},
...

and compiles to e.g.,

user(_, _, "Vienna", _, Name, _, _, _),

Note that for indexing, atoms are faster than strings as atoms just look at the handle and strings actually need to access the text.

1 Like

I see, so it is about dictionaries as “natural” interface to records. If I arrive at something useful, would such addition/predicate be a good addition to library(record)?

I’m afraid not. First of all, dicts are an alternative to library(record), so one would either have to build something inside library(record) that does not use dicts (which is possible) or create a new library.

More importantly though, this type of stuff is under discussion between SWI, XSB and Ciao at the moment. I prefer to wait and come with something portable at this stage.

1 Like

I’ve arrived at this (runtime) version:

find_record(Dict) :-
  find_record(Dict, _).

find_record(Dict, R) :-
  is_dict(Dict, Tag),
  atomic_list_concat([Tag,'_',data], Functor),
  bagof(
    P,
    Key^Value^(get_dict(Key, Dict, Value), P =.. [Functor, Key, R, Value]),
    Preds
  ),
  maplist(call, Preds), call(R).

Thank you for the idea, this makes for very nice interface for querying records, it’s pretty cool :slight_smile:

I am not sure how to do the expansion version it without relying on internals of library(record) to check if dictionary tag is defined as a record, I don’t see any public API for this, as well as looking up position of each record field?