Naming is a non-trivial problem in programming. A good name conveys meaning, contributing to code readability. But there’s always a natural contention between descriptive names and concise names that help avoid excess verbosity. What’s a good name may also depend on its usage context. Moreover, if every library developer always use the best names, conflicts are bound to occur. The usual solution to these issues is to support renaming of object, module, and predicate names where they are used.
directive can be used to declare object aliases when the programmer prefers
explicit message sending but also wants to use shorter names to minimize
verbosity. For example, Logtalk provides a
random library with multiple
implementations of the same protocol,
randomp. One of those implementations
backend_random as it’s based on the backend Prolog compiler random
number generator. It also provides two portable implementations,
random. We can use an object alias to both shorten the name and simplify
experimenting with the different implementations:
:- object(foo). :- uses([ backend_random as rnd ]). bar :- ..., % the same as backend_random::permutation(L, P) rnd::permutation(List, Permutation), ...
To experiment with a different implementation of the
we just need to change the
rnd object alias definition and recompile.
Object aliases are specially useful when using parametric objects. For example,
assume a library
time/1 parametric object where the parameter is the time
:- object(foo). :- uses([ time('UTC−01:00') as time ]). bar :- ..., % the same as time('UTC−01:00')::now(Now) time::now(Now), ...
In this case, we bind the parameter in a single place in the code, ensuring consistency while simplifying usage and maintenance. This also works nicely for runtime bound parameters. For example, we can also write:
:- object(foo(_HeapOrder_, _OptionsObject_)). :- uses([ heap(_HeapOrder_) as heap, _OptionsObject_ as options ]). bar :- ..., % the same as heap(_HeapOrder_)::as_heap(List, Heap) heap::as_heap(List, Heap), % the same as _OptionsObject_::get(ratio, Ratio) options::get(ratio, Ratio), ...
Predicate aliases provide similar benefits to object aliases but also allow
solving name clashes. Unlike in current Prolog modules practice where a
predicate name (e.g.
member/2) is usually the exclusive of a specific
lists) with other modules forced to use different names
random_member/2), library object predicates always use the best names.
directive can be used to declare predicate aliases for implicit message sending
while also solving name conflicts.
:- uses(btrees, [new/1 as new_btree/1]). :- uses(queues, [new/1 as new_queue/1]). btree_to_queue :- ..., % the same as btrees::new(Tree) new_btree(Tree), % the same as queues::new(Queue) new_queue(Queue), ...
Name clashes can also occur when using multiple inheritance. For example, assume
engineer, both declaring a
predicate, and a third object,
moms_basement_ghost, inheriting from the first
two objects. In this case, we can use the
directive to provide access to both inherited predicates by giving them
:- object(moms_basement_ghost, extends((rpg_player, engineer))). :- alias(rpg_player, [rank/1 as rpg_rank/1]). :- alias(engineer, [rank/1 as engineer_rank/1]). rpg_rank(wizard). engineer_rank(senior). :- end_object.
These aliases directives make possible the queries:
| ?- moms_basement_ghost::rpg_rank(Rank). Rank = wizard yes | ?- moms_basement_ghost::engineer_rank(Rank). Rank = senior yes
As a side note, we can still use
rank/1 as message. In this case, the default
multiple inheritance conflict solver would use the definition, if any, inherited
rpg_player object as it’s listed first in the
object opening directive.
Another common use of predicate aliases is to define an alternative predicate
name simply to make it more clear in its usage context. For example, assume a
sentences object exporting a
% define an alternative name for a non-terminal: :- alias(sentences, [transpose//0 as flip//0]).
Logtalk 3.34.0 added support for defining predicate shorthands where one or more arguments can be omitted by binding the arguments in the original predicate call template. For example:
:- uses(logtalk, [ print_message(debug, my_app, Message) as dbg(Message) ]). bar :- ..., % same as logtalk::print_message(debug, my_app, @oh_no) dbg(@oh_no), ...
Logtalk version of the
directive also provides similar support for defining predicate aliases and
shorthands. For example:
:- object(foo(_OptionsModule_)). :- use_module(_OptionsModule_, [ set/2, get/2, reset/0 ]) :- use_module(pairs, [ map_list_to_pairs(length, Lists, Pairs) as length_pairs(Lists, Pairs) ]). ...
Object and predicate aliases and shorthands can improve code readability, consistency, and maintenance. They also make experimenting with alternatives implementations easier by providing a single point of change. But they should also be used with some care as any reading and understanding of source code, specially long object definitions, must always take into account any defined aliases and shorthands.