Dict unification

I’ve been playing around with using dicts materialized from json recently and keep getting caught out with how best to extract values from them.

The partial unification syntax lets us do something like:

?- _{name:N} :< _{name:bob, age:21}.
N = bob.

However this partial unification breaks down in child dicts. e.g.

?- _{ value:_{name:N} } :< _{ value:_{name:bob, age:21} }.
false.

% Needs full unification
?- _{ value:_{name:N, age:A} } :< _{ value:_{name:bob, age:21} }.
N = Bob,
A = 21.

So if the JSON API has optional fields or in future is extended with fields and one has converted the response into a dict, its unsafe to unify against data within a child dicts.

Which means: to be sure of extracting the data you want, you need to break the unification over multiple steps by extracting subdicts, and their subdicts etc and unify against those.

?- _{ value:Person } :< _{ value:_{name:bob, age:21} },  _{ name:Name } :< Person.
Person = _7896{age:21, name:bob},
Name = bob.

Shouldn’t the :< operator also be applied at subdict levels also, in which case the failed examples would succeed?

Maybe I am missing the big picture here. If you want to get individual values and you know the shape of your JSON, you can directly access it instead of using partial unification. Like this:

?- Person = _{ value:_{name:bob, age:21} },
   Name = Person.value.name.
Person = _{value:_{age:21, name:bob}},
Name = bob.

I usually avoid doing any “extraction” step and directly type the thing I need. Here is a silly example:

?- Person = _{ value:_{name:bob, age:21} },
   format("~w is ~d years old", [Person.value.name, Person.value.age]).
bob is 21 years old
Person = _{value:_{age:21, name:bob}}.

Sure it slowly starts to look less like Prolog and more like any of the popular “object” languages (C++, Python, Java, JavaScript etc) but maybe that’s exactly the point.

You would need the partial unification when you want to extract a subset of the key-value pairs from the full dictionary.

?- _{a:X, c:Y} :< _{a:1, b:foo, c:[1,2,3]}.
X = 1,
Y = [1, 2, 3].

If for some reason the JSON shape itself is somehow unfriendly I would be fixing my objects and not how I access the data from Prolog.

The next step I guess is to have for dicts something like xpath for XML? I am not sure if that already exists somehow? I might be wrong but it seems that this library already works on the Prolog representation of the parsed XML so supposedly a working solution is not too far away?

Thanks, as I said I’m not sure of the best approach :slightly_smiling_face:. I didn’t know about using the full keypath, however as you said, you would have to use the full path for each value we want to extract, and in most cases we are trying to unify more than one value (think extracting scalar values from a 3rd party JSON API response).

From a user point of view, ‘:<’ being an operator for matching against a dict for the extraction of multiple values but only ignoring unmatched keys at the root level and not the branches seems like a ‘gotcha’.

I’ve written an operator on swish located here to demonstrate what I expected to happen (though swish doesn’t allow defining new operators, so the example isnt ‘xfx’).

To be perfectly honest if it did work recursively I would have been massively suprised :slight_smile: but now that you mention, I don’t know of any reason to think the one is any more “intuitive” than the other. If you check out the comment under the docs for select_dict/3 you will see that at least one other person (@kilian) thought that it should obviously work recursively.

1 Like

I have been reading through all the currently available predicates on dicts. There is the built-ins and also library(dicts). There is not a single predicate that works recursively. I wonder why this is.

Sure you can nest dicts; once nested, you can chain the .. But making the recursion the default assumes that dicts will be nested. Isn’t this going too far (from a usability point of view)?

I was also trying to see how your example works. I tried the following and it somehow throws:

?- :<<(_{a:A, b:B}, _{a:1, b:_{aa:2, bb:3}}).

I am just trying to understand how this should work.

I just cooked up that code in 5 minutes as a proof-of-concept :smiley: I’ve updated it to deal with your example.

The manual refers to dicts as 'structures with named arguments". So originally they were not designed primarily as a tree representation structure and an assumption that dicts will usually not be nested could be argued to be the norm.

However, once the http/json library was updated in v7 with representing json data as dicts, then almost all dicts created via this method will have nested dicts.

Since I have a solution, I’m not too bothered that partial unification isn’t recursive,