Support for variadic datastructures in heads?

Is there anything like variadic parameters in datastructures in rule heads. For example, suppose we have

     transform(tok(noun ,_ ,_ ,_ ,_ ,_), B) :- 
        f(B).   % << boring

could it be written:

transform(tok(noun, ...), B) :- 
        f(B).   % << boring

for some atom ‘…’?

Its a very painful and error prone process to revisit each occurrence when tok needs to change from arity 6 to arity 7 in many rules.

Alternatively, how might I validate each tok has arity 7 and produce a sensible error with a source file reference?

You can always use a list as an argument I guess? You can also use a dict as an argument, then you can name your arguments? This is especially useful for “option lists”.

Oh- thanks. The code formatting cut the first line. Have repaired the original question.

Without additional information, this is impossible to answer. To me this looks like a code smell:

tok(noun, _, _, _, _, _)

just like it does to you. But what would be better, I can’t tell without some context.

Yes. I have a database base of rules that I’d like to index and match. Some are automatically generated, some are hand written. There rules have datastructure heads. I’d like to deep index the on the heads. While its possible to use lists, we’d end with situations like:

[[_,_,[noun,_,_,_,_,_,_] | _, ], B ] :- 
    f(B)

… now, if asked to manually add a ‘_’ to the inner list for 60 same-same but different rule head – seems like a PITA, as it is for datastructures without wildcards.

For dicts, I don’t think they can be indexed in the head that way

f(B) :- B=1.

transform(tok{pos:noun},B) :-
  f(B).  % boring

Now we add some new attribute text that our rules should ignore:

?- transform(tok{pos:noun, text:'abc'},B). % false

So we still have to edit each rule’s head and add text:_ . Womp, womp.

OK, let’s see if I understood anything :slight_smile:

These are the same in meaning, but different:

foo(bar(a, b, c, d), X).
foo(bar(a, baz(b, c, d)), X).
foo(a, bar(b, c, d), X).

(sorry for the stupid names)

So in this code we have names like foo, bar, baz, a, b, c…

The real question is: which one of those is important for the logic of your program?

For example, is the a above important? Does the value in that spot make for a good index? If so, by all means, write: foo(a, bar(b, c, d), X).

Does that help at all?

Dicts are a sensible solution to this which we possibly should support by default. It connects to ECLiPSe handling of tag{key:value, …}. In ECLiPSe such a term is mapped at read time to tag(A1, A2, …), where a declaration controls the arity and argument order for tag(…). If we create such a mapping and expand it in e.g., term_expansion/2, you can write e.g.

transform{pos:noun} :-

As with any language it is typically a good idea to avoid many arguments. In many cases you can get away using just a few and using (e.g.,) a dict for additional (mostly) read-only arguments.

1 Like

I think this is worth adding, one of the reasons I often use dicts is to allow for “future-proof” data structures that won’t break when we want to add a new field. This is the main use case I have for dicts.

However, it is always a pain that unification requires all keys to be present. Some way of partial unification in the head would be very useful.

2 Likes

Mostly for my own reference, ECLiPSe calls these a struct. See http://eclipseclp.org/doc/tutorial.pdf, section 4.1 Structure Notation. The declaration is e.g.,

:- local struct(book(author, title, year, publisher)).

It is unclear how we should handle export/import of struct declarations. That probably needs to be part of general export/import of macro expansion. The idea would be that any term (dict) book{...} translates to a book/4 term. Any key other than the named four keys results in an error.

I think this could be a useful library that also improves the compatibility with ECLiPSe.

It seems to me that a better syntax would be:

:- struct book(author, title, year, publisher), person(firstname, lastname), ...

which can easily be converted in to the eclipse syntax for compatibility reasons. I am assuming all the dict predicates would work without a problem with the struct’s; the only difference is that partial unification would be allowed.

Export/import

What is the main disadvantage that you see from just declaring it in the module/2 directive? e.g. like:

:- module(mymodule, 
                 [  mypred/2,
                    mystruct/3,
                    ...
                 ].

Thinking about it, perhaps the disadvantage is that you can’t have a predicate with the same name/arity as the struct. Perhaps this would do it:

:- module(mymodule, 
                 [  mypred/2,
                    mystruct/3 as struct,
                    ...
                 ].

Then structs can have their own ‘namespace’ and you could have predicates with the same name/arity as the struct.

EDIT: this last construct also has the advantage of clearly showing, at a glance, which export is a data structure and which is a predicate.

Thanks! I’ve been experimenting with dicts as indexes here: https://swish.swi-prolog.org/p/time_profile_deep_nested.swinb

I think the only downsides I see are:

  • a 50% slowdown for pattern matching, which seems reasonable.
  • it doesn’t seem possible to use dicts as heads (and so its not straightforward to map back and forth between compound terms and dicts):
tree(tok{a:1}, []).   % Fine
tok{a:1}.             % Type error: `callable' expected, found `tok{a:_2052,b:_2056}' (a dict)

One nice benefit of dicts over compound terms that we can write:

tok{x:1,y:2}=F{x:1,y:2}.

whereas for compound terms, this kind of functor unification is an error, and we need to write:

f(1,2)=..[F,1,2]

(I.e. we cannot directly use unification here and so some automated translations may need extra work). Is this intentional - can we rely upon this dict functor name unpacking in the future? I remember reading somewhere there dict as compound terms under the hood, but we shouldn’t rely upon that.

As you have the same set of keys (only one) deep indexing may kick in. That won’t work in general, but will do the job on some scenarios.

In the end a predicate is defined by the name and arity of the callable head term. As is, dicts are compounds (not guaranteed for the future), but the name is not the tag name but the hidden C’dict’ atom and the arity is 2*|Keys|+1. So, that doesn’t lead to anything useful.

Yes, that is part of how dicts are defined. Its syntax, unification and dict predicates are stable. Just the fact that they are compounds (and thus you can use the compound predicates on them) is not guaranteed.

What ECLiPSe does though is a read-time rewrite of dict syntax to a compound term based on the struct/1 declaration. That way, the dicts only exists as a syntactical shorthand for a compound with named arguments. That comes with limitations (you can not dynamically add keys to a dict), but allows using them for dealing with named arguments. We can provide ECLiPSe behavior as a library, providing both scenarios.

Those structs sound very similar to record/1 from library(record). How are they different?

1 Like

The two could be joined. The dict syntax merely provides a convenient syntax for creating records. It also provides a convenient way to access fields as in (e.g.)

 Struct = book{author:Author, title:Title}
1 Like