I’m using dicts to store semi-structured data, and use a discriminator field, such as tag name, or ‘type’ attribute of the data to represent different types of the same message.
However, when writing a handler to handle for instance dicts of type ‘d’ e.g. d{a:1} you cannot use a clause header such as:
handle_message(d{}) :- ...
or
handle_message(msg{ opcode:header } :- ...
because the unification is too strict, I find myself doing.
Nope. Well, you can of course use term_expansion/2 to write it the way you would like and have the compiler turn it into something that can be executed. As is though, head arguments are unified and dict unification unifies the entire dict. I don’t think it is wise to change either of that
(P.S. replying on my perception of the software engineering problem you hint about and not on the specific question of dict unification)
In similar scenarios, I use compound terms to represent the data, define parametric objects whose identifiers match the data functors, and define predicates such as your handle_message/1 predicate as messages to the parametric objects. I.e. instead of calling:
handle_message(Data)
I call:
Data::handle_message
Dealing with a discriminator field can be done by using that field name as the name of the compound term. E.g.
header(HeaderSize, ...)::handle_message
Handing a new data format then just requires define a corresponding parametric object that will nicely encapsulate all predicate definitions relevant to that new data format (instead of scattering them in the middle of clauses for other data formats for all defined predicates; think maintenance). This also notably makes it very simple to use the database for storing/updating/saving the data. Common predicates get declared in protocols implemented by the objects. These often are organized in small hierarchies allowing easy handling of e.g. default values for the data.
This solution is (arguably more) data-centric as instead of having predicates that take data as arguments we have data that accept predicates as messages/requests. Note that the data instances themselves are not individual objects but simply compound terms being passed around or clauses in the database. The parametric objects can be seen as templates for the different data formats holding the details on how to specifically handle them. The parameters (representing the data fields) are logic variables with O(1) access, which translates to logical semantics and good performance. Btw, it’s possible, with some limitations (due to SWI-Prolog eager-evaluation of dict terms) to pass dicts as parameter values.
I had this exact same question and found this topic.
I think dicts are very neat, and coming from other languages like Clojure, it feels very natural to want to destructure arguments. I wanted something like (:<)/2 that works in the head part. So basically I want to dispatch on the dict tag and maybe grab some subset.
In your example you use ::, I’m unable to find that. Logtalk uses that for method calls.