Re: Some thoughts on dicts in SWIPL 7

Jan Wielemaker <[email protected]>
Newsgroups gmane.comp.ai.prolog.swi
Message-ID <[email protected]>
Hi Bob,

Thanks for sharing.  I've been using dicts during the last couple of days
for real programming too.  To be precise, for implementing the completely
imperative description of processing RDFa documents.  The good news is that
it allows for an almost literal translation of the text.  It looks quite
ok, but from a Prolog point it is a bit awkward :-(  Still, having an
implementation that stays close to the spec is probably better than
trying a declarative redesign of the spec and implementing that ...

On 02/17/2014 10:39 PM, Bob Minors wrote:
> As a learning exercise, I've been converting an existing program to use
> dicts in place of some rather clumsy ordered list structures. It's going
> quite well - I like dicts - and it's given rise to some thoughts that
> I'd like to share. Two little things and a broad question about
> programming style.
> 
> The original data structure was an ordered list of 27 elements (some of
> which were themselves ordered lists) and, when calling predicates on it,
> I would pick out the ones I wanted using nth0. Something like this:
> 
>     foo(X,Y) :- nth0(12,X,Apples), nth0(23,X,Bananas),
> foo1(Apples,Bananas,Y).
> 
> This worked fine, but it's always been ugly and tricky to maintain. Now
> I've replaced the whole X structure with a dict and I can do something
> like this:
> 
>     foo(X,Y) :- foo(X.apples,X.bananas,Y).
> 
> which is simpler, more readable and more maintainable. That's the
> motivation.

That was the idea.  Note that a fixed length of 27 elements was a rather
poor design.  Using a compound and arg/3 or library(record) would have
been a better choice.  But still, with dicts it is barely slower than
arg/3 and more readable.  Good :-)  One of the aims of dicts was also
to make the choice easier.  That seems to have worked too in your case.

> One thing I needed to do in several places, was to create empty dicts
> with given tags and given lists of keys but with all the values
> uninstantiated, ready for filling in later. There's no built-in function
> that does quite what I wanted, so this was how I coded it:
> 
>     free_pair(X,X:_).
> 
>     blank_dict(Tag,Keylist,Dict) :-
>         maplist(free_pair,Keylist,Pairs),
>         dict_create(Dict,Tag,Pairs).
> 
>     12 ?- blank_dict(foo,[a,b,c],Dict).
>     Dict = foo{a:_G1029, b:_G1035, c:_G1041}.
> 
> It's not a huge thing, it's used in just seven places in my program but
> it saves typing ":_" over and over again (in one case, 27 times). Is
> this something that would often be done with dicts? Would it be worth
> having a built-in function for it? Just a suggestion.

I don't know.  Is there so much advantage in

   	?- blank_dict(foo,[a,b,c],Dict).
vs.	?- Dict = foo{a:_,b:_,c:_}.

The latter is definitely faster (unless we do partial evaluation).  The
still open future route is to have psi-terms, so you can extend the term
dynamically using unification.  Why don't you start with a small dict and
use .put(Name,Value)?  Of course, this creates a new dict, so you need some
form of threaded state.

> Next thing: one of the values in the top-level dict is a list of smaller
> dicts, representing a FIFO queue of elements waiting to be processed,
> and there is a predicate that needs to check one of the key values in
> the dict at the head of the list to see if it's ready for processing.
> I've defined a function that plucks out head of the list:
> 
>     X.head(Tag) := H :- X.Tag=[H|_].
> 
> so I can write something like this:
> 
>     process_queue(X) :- X.head(queue).is_ready(), process_queue1(X.queue).
> 
> where the is_ready() is defined elsewhere as a function on a queue
> element dict. This works and is easy to read (for me, at least) but it's
> looking more like functional programming than conventional Prolog and
> I'm starting to feel the ground shifting under my feet. Is this a bad
> thing? Probably not.

All in all, this is still mapped to traditional Prolog, i.e., it is
only syntactic sugar.  Variables are still logical variables and code
can still be non-deterministic.  We'll have to learn good practice
(thanks for sharing).  This seems fine to me.

> Now a stylistic question. When writing predicates that call smaller
> predicates, there is a choice between of pulling out the relevant fields
> from the dict at the top level and passing them down as variables to the
> lower-level predicates, or simply passing down the whole dict and
> letting the lower-level predicates take what they want. One the one
> hand, something like
> 
>     foobar(X,FooBar) :- foo(X.a, X.b, Foo), bar(X.c, X.d, X.e, Foo, Foobar).
> 
> or this:
> 
>     foobar(X,Y) :- foo(X,Foo), bar(X,Foo,FooBar).
> 
> The first scheme keeps all the structural detail of X at the top level
> and the lower level predicates (foo and bar) don't need to know about
> it, so if I change the top-level structure they should be unaffected and
> that seems good from a module point of view; but the second scheme might
> be more maintainable if, say, foo is later improved and needs to
> reference X.d after all - this can be done without changing the
> top-level calling predicates, which makes the top level more robust.
> Having thought about it, I've come to the conclusion that it depends on
> which is expected to be the more stable and which the more volatile
> during program development - top-level dict or lower-level modules.
> Top-down design vs bottom-up coding. No right answer.

I think that your observation is correct.  There is no uniform answer.
I have similar issues with using option lists.  The tradeof appears in
any language having record-like data types.

	Cheers --- Jan
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.