Skip to content

Latest commit

 

History

History
1538 lines (1187 loc) · 57.5 KB

programming.rst.txt

File metadata and controls

1538 lines (1187 loc) · 57.5 KB

Writing, running, and debugging applications

Writing applications

For a successful programming in Logtalk, you need a good working knowledge of Prolog and an understanding of the principles of object-oriented programming. Most guidelines for writing good Prolog code apply as well to Logtalk programming. To those guidelines, you should add the basics of good object-oriented design.

One of the advantages of a system like Logtalk is that it enable us to use the currently available object-oriented methodologies, tools, and metrics [Champaux92] in Prolog programming. That said, writing applications in Logtalk is similar to writing applications in Prolog: we define new predicates describing what is true about our domain objects, about our problem solution. We encapsulate our predicate directives and definitions inside new objects, categories and protocols that we create by hand with a text editor or by using the Logtalk built-in predicates. Some of the information collected during the analysis and design phases can be integrated in the objects, categories and protocols that we define by using the available entity and predicate documenting directives.

Source files

Logtalk source files may define any number of entities (objects, categories, or protocols) and Prolog code. If you prefer to define each entity in its own source file, then it is recommended that the source file be named after the entity identifier. For parametric objects, the identifier arity can be appended to the identifier functor. By default, all Logtalk source files use the extension .lgt but this is optional and can be set in the adapter files. Intermediate Prolog source files (generated by the Logtalk compiler) have, by default, a _lgt suffix and a .pl extension. Again, this can be set to match the needs of a particular Prolog compiler in the corresponding adapter file. For example, we may define an object named vehicle and save it in a vehicle.lgt source file that will be compiled to a vehicle_lgt.pl Prolog file. If we have a sort(_) parametric object we can save it on a sort_1.lgt source file that will be compiled to a sort_1_lgt.pl Prolog file. This name scheme helps avoid file name conflicts (remember that all Logtalk entities share the same name space). To further prevent file name conflicts, depending on the backend compiler, the names of the intermediate Prolog files may include a directory hash.

Logtalk source files may contain Prolog code interleaved with Logtalk entity definitions. Plain Prolog code is usually copied as-is to the corresponding Prolog output file (except, of course, if subject to the term-expansion mechanism). Prolog modules are compiled as objects. The following Prolog directives are processed when read (thus affecting the compilation of the source code that follows): ensure_loaded/1, use_module/1-2, op/3, and set_prolog_flag/2. The initialization/1 Prolog directive may be used for defining an initialization goal to be executed when loading a source file. Most calls to Logtalk built-in predicates from file initialization/1 directives are compiled for better performance.

The text encoding used in a source file may be declared using the directives_encoding_1 directive when running Logtalk with back-end Prolog compilers that support multiple encodings (check the encoding_directive <flag_encoding_directive> flag in the adapter file of your Prolog compiler).

Logtalk source files can include the text of other files by using the directives_include_1 directive. Although there's also a standard Prolog include/1 directive, any occurrences of this directive in a Logtalk source file is handled by the Logtalk compiler, not by the backend Prolog compiler.

Portable applications

Logtalk is compatible with almost all modern Prolog compilers. However, this does not necessarily imply that your Logtalk applications will have the same level of portability. If possible, you should only use in your applications Logtalk built-in predicates and ISO Prolog specified built-in predicates and arithmetic functions. If you need to use built-in predicates (or built-in arithmetic functions) that may not be available in other Prolog compilers, you should try to encapsulate the non-portable code in a small number of objects and provide a portable interface for that code through the use of Logtalk protocols. An example will be code that access operating-system specific features. The Logtalk compiler can warn you of the use of non-ISO specified built-in predicates and arithmetic functions by using the portability <flag_portability> compiler flag.

Conditional compilation

Logtalk supports conditional compilation within source files using the directives_if_1, directives_elif_1, directives_else_0, and directives_endif_0 directives. This support is similar to the support found in several Prolog systems such as ECLiPSe, SWI-Prolog, or YAP.

Avoiding common errors

Try to write objects and protocol documentation before writing any other code; if you are having trouble documenting a predicate perhaps we need to go back to the design stage.

Try to avoid lengthy hierarchies. Composition is often a better choice over inheritance for defining new objects (Logtalk supports component-based programming through the use of categories). In addition, prototype-based hierarchies are semantically simpler than class-based hierarchies.

Dynamic predicates or dynamic entities are sometimes needed, but we should always try to minimize the use of non-logical features such as asserts and retracts.

Since each Logtalk entity is independently compiled, if an object inherits a dynamic or a meta-predicate predicate, then the respective directives must be repeated to ensure a correct compilation.

In general, Logtalk does not verify if a user predicate call/return arguments comply with the declared modes. On the other hand, Logtalk built-in predicates, built-in methods, and message sending control structures are fully checked for calling mode errors.

Logtalk error handling strongly depends on the ISO compliance of the chosen Prolog compiler. For instance, the error terms that are generated by some Logtalk built-in predicates assume that the Prolog built-in predicates behave as defined in the ISO standard regarding error conditions. In particular, if your Prolog compiler does not support a read_term/3 built-in predicate compliant with the ISO Prolog Standard definition, then the current version of the Logtalk compiler may not be able to detect misspell variables in your source code.

Coding style guidelines

It is suggested that all code between an entity opening and closing directives be indented by one tab stop. When defining entity code, both directives and predicates, Prolog coding style guidelines may be applied. All Logtalk source files, examples, and standard library entities use tabs (the recommended setting is a tab width equivalent to 4 spaces) for laying out code. Closed related entities can be defined in the same source file. However, for best performance, is often necessary to have an entity per source file. Entities that might be useful in different contexts (such as library entities) are best defined in their own source files.

Running a Logtalk session

We run Logtalk inside a normal Prolog session, after loading the necessary files. Logtalk extends but does not modify your Prolog compiler. We can freely mix Prolog queries with the sending of messages and our applications can be made of both normal Prolog clauses and object definitions.

Starting Logtalk

Depending on your Logtalk installation, you may use a script or a shortcut to start Logtalk with your chosen Prolog compiler. On POSIX operating systems, the scripts should be available from the command-line; scripts are named upon the used Prolog compilers. On Windows, the shortcuts should be available from the Start Menu. If no scripts or shortcuts are available for your installation, operating-system, or Prolog compiler, you can always start a Logtalk session by performing the following steps:

  1. Start your Prolog compiler.
  2. Load the appropriate adapter file for your compiler. Adapter files for most common Prolog compilers can be found in the adapters subdirectory.
  3. Load the library paths file corresponding to your Logtalk installation contained in the paths subdirectory.
  4. Load the Logtalk compiler/runtime files contained in the compiler subdirectory.

Note that the adapter files, compiler/runtime files, and library paths file are Prolog source files. The predicate called to load (and compile) them depends on your Prolog compiler. In case of doubt, consult your Prolog compiler reference manual or take a look at the definition of the predicate '$lgt_load_prolog_code'/3 in the corresponding adapter file.

Most Prolog compilers support automatic loading of an initialization file, which can include the necessary directives to load both the Prolog adapter file and the Logtalk compiler. This feature, when available, allows automatic loading of Logtalk when you start your Prolog compiler.

Compiling and loading your applications

Your applications will be made of source files containing your objects, protocols, and categories. The source files can be compiled to disk by calling the Logtalk built-in predicate predicates_logtalk_compile_1:

| ?- logtalk_compile([source_file1, source_file2, ...]).

This predicate runs the compiler on each file and, if no fatal errors are found, outputs Prolog source files that can then be consulted or compiled in the usual way by your Prolog compiler.

To compile to disk and also load into memory the source files we can use the Logtalk built-in predicate predicates_logtalk_load_1:

| ?- logtalk_load([source_file1, source_file2, ...]).

This predicate works in the same way of the predicate logtalk_compile/1 but also loads the compiled files into memory.

Both predicates expect a source file name or a list of source file names as an argument. The Logtalk source file name extension, as defined in the adapter file (by default, .lgt), can be omitted.

If you have more than a few source files then you may want to use a loader helper file containing the calls to the logtalk_load/1-2 predicates. Consulting or compiling the loader file will then compile and load all your Logtalk entities into memory (see below for details).

With most Prolog back-end compilers, you can use the shorthands {File} for logtalk_load(File) and {File1, File2, ...} for logtalk_load([File1, File2, ...]). The use these shorthands should be restricted to the Logtalk/Prolog top-level interpreter as they are not part of the language specification and may be commented out in case of conflicts with backend Prolog compiler features.

The built-in predicate predicates_logtalk_make_0 can be used to reload all modified source files. Files are also reloaded when the compilation mode changes. For example, assume that you have loaded your application files and found a bug. You can easily recompile the files in debug mode by using the queries:

| ?- set_logtalk_flag(debug, on).
...

| ?- logtalk_make.
...

After debugging and fixing the bugs, you can reload the files in normal (or optimized) mode by turning the debug <flag_debug> flag off and calling the logtalk_make/0 predicate again.

An extended version of this predicate, predicates_logtalk_make_1, accepts all, clean, check, circular, documentation, and caches arguments for, respectively, reloading modified Logtalk source files, deleting any intermediate files generated by the compilation of Logtalk source files, checking for code issues, listing of circular dependencies, generating documentation, and deleting dynamic binding caches. With most Prolog backend compilers, you can use the shorthands {*} for logtalk_make(all), {!} for logtalk_make(clean), {?} for logtalk_make(missing), {@} for logtalk_make(circular), {#} for logtalk_make(documentation), and {$} for logtalk_make(caches). The logtalk_make(clean) goal can be specially useful before switching backend Prolog compilers as the generated intermediate files may not be compatible. The logtalk_make(caches) goal is usually used when benchmarking compiler performance improvements.

Loader utility files

Most examples directories contain a Logtalk utility file that can be used to load all included source files. These loader utility files are usually named loader.lgt or contain the word "loader" in their name. Loader files are ordinary source file and thus compiled and loaded like any source file. For an example loader file named loader.lgt we would type:

| ?- logtalk_load(loader).

Usually these files contain a call to the Logtalk built-in predicates predicates_set_logtalk_flag_2 (e.g. for setting global, project-specific, flag values) and predicates_logtalk_load_1 or predicates_logtalk_load_2 (for loading project files), wrapped inside a Prolog initialization/1 directive. For instance, if your code is split in three Logtalk source files named source1.lgt, source2.lgt, and source3.lgt, then the contents of your loader file could be:

:- initialization((
    % set project-specific global flags
    set_logtalk_flag(events, allow),
    % load the project source files
    logtalk_load([source1, source2, source3])
)).

Another example of directives that are often used in a loader file would be op/3 directives declaring global operators needed by your application. Loader files are also often used for setting source file-specific compiler flags (this is useful even when you only have a single source file if you always load it with using the same set of compiler flags). For example:

:- initialization((
    % set project-specific global flags
    set_logtalk_flag(underscore_variables, dont_care),
    set_logtalk_flag(source_data, off),
    % load the project source files
    logtalk_load(
        [source1, source2, source3],
        % source file-specific flags
        [portability(warning)]),
    logtalk_load(
        [source4, source5],
        % source file-specific flags
        [portability(silent)])
)).

To take the best advantage of loader files, define a clause for the multifile and dynamic logtalk_library_path/2 predicate for the directory containing your source files as explained in the next section.

A common mistake is to try to set compiler flags using logtalk_load/2 with a loader file. For example, by writing:

| ?- logtalk_load(loader, [optimize(on)]).

This will not work as you might expect as the compiler flags will only be used in the compilation of the loader.lgt file itself and will not affect the compilation of files loaded through the initialization/1 directive contained on the loader file.

Libraries of source files

Logtalk defines a library simply as a directory containing source files. Library locations can be specified by defining or asserting clauses for the dynamic and multifile predicate predicates_logtalk_library_path_2. For example:

:- multifile(logtalk_library_path/2).
:- dynamic(logtalk_library_path/2).

logtalk_library_path(shapes, '$LOGTALKUSER/examples/shapes/').

The first argument of the predicate is used as an alias for the path on the second argument. Library aliases may also be used on the second argument. For example:

:- multifile(logtalk_library_path/2).
:- dynamic(logtalk_library_path/2).

logtalk_library_path(lgtuser, '$LOGTALKUSER/').
logtalk_library_path(examples, lgtuser('examples/')).
logtalk_library_path(viewpoints, examples('viewpoints/')).

This allows us to load a library source file without the need to first change the current working directory to the library directory and then back to the original directory. For example, in order to load a loader.lgt file, contained in a library named viewpoints, we just need to type:

| ?- logtalk_load(viewpoints(loader)). 

The best way to take advantage of this feature is to load at startup a source file containing clauses for the logtalk_library_path/2 predicate needed for all available libraries. This allows us to load library source files or entire libraries without worrying about libraries paths, improving code portability. The directory paths on the second argument should always end with the path directory separator character. Most back-end Prolog compilers allows the use of environment variables in the second argument of the logtalk_library_path/2 predicate. Use of POSIX relative paths (e.g. '../' or './') for top-level library directories (e.g. lgtuser in the example above) is not advised as different back-end Prolog compilers may start with different initial working directories, which may result in portability problems of your loader files.

The library notation provides functionality inspired by the file_search_path/2 mechanism introduced by Quintus Prolog and later adopted by some other Prolog compilers.

Compiler linter

The compiler includes a linter that checks for a wide range of possible problems in source files. Notably, the compiler checks for unknown entities, unknown predicates, undefined predicates (i.e. predicates that are declared but not defined), missing directives (including missing dynamic/1 and meta_predicate/1 directives), redefined built-in predicates, calls to non-portable predicates, singleton variables, tautology and falsehood goals (i.e. goals that are can be replaced by true or fail), and trivial fails (i.e. calls to predicates with no match clauses). Some of the linter warnings are controlled by compiler flags. See the next section for details.

Compiler flags

The predicates_logtalk_load_1 and predicates_logtalk_compile_1 always use the current set of default compiler flags as specified in your settings file and the Logtalk adapter files or changed for the current session using the built-in predicate predicates_set_logtalk_flag_2. Although the default flag values cover the usual cases, you may want to use a different set of flag values while compiling or loading some of your Logtalk source files. This can be accomplished by using the predicates_logtalk_load_2 or the predicates_logtalk_compile_2 built-in predicates. These two predicates accept a list of options affecting how a Logtalk source file is compiled and loaded:

| ?- logtalk_compile(Files, Options).

or:

| ?- logtalk_load(Files, Options).

In fact, the logtalk_load/1 and logtalk_compile/1 predicates are just shortcuts to the extended versions called with the default compiler flag values. The options are represented by a compound term where the functor is the flag name and the sole argument is the flag value.

We may also change the default flag values from the ones loaded from the adapter file by using the predicates_set_logtalk_flag_2 built-in predicate. For example:

| ?- set_logtalk_flag(unknown_entities, silent).

The current default flags values can be enumerated using the predicates_current_logtalk_flag_2 built-in predicate:

| ?- current_logtalk_flag(unknown_entities, Value).

Value = silent
yes

Logtalk also implements a directives_set_logtalk_flag_2 directive, which can be used to set flags within a source file or within an entity. For example:

% compile objects in this source file with event support
:- set_logtalk_flag(events, allow).

:- object(foo).

    % compile this object with support
    % for dynamic predicate declarations
    :- set_logtalk_flag(dynamic_declarations, allow).
    ...

:- end_object.

...

Note that the scope of the set_logtalk_flag/2 directive is local to the entity or to the source file containing it.

Version flags

single: version_data flag

version_data(Value)

Read-only flag whose value is the compound term logtalk(Major,Minor,Patch,Status). The first three arguments are integers and the last argument is an atom, possibly empty, representing version status: aN for alpha versions, bN for beta versions, rcN for release candidates (with N being a natural number), and stable for stable versions. The version_data flag is also a de facto standard for Prolog compilers.

Lint flags

single: unknown_entities flag

unknown_entities(Option)

Controls the unknown entity warnings, resulting from loading an entity that references some other entity that is not currently loaded. Possible option values are warning (the usual default) and silent. Note that these warnings are not always avoidable, specially when using reflective designs of class-based hierarchies.

single: unknown_predicates flag

unknown_predicates(Option)

Defines the compiler behavior when calls to unknown predicates (or non-terminals) are found. An unknown predicate is a called predicate that is neither locally declared or defined. Possible option values are error, warning (the usual default), and silent (not recommended).

single: undefined_predicates flag

undefined_predicates(Option)

Defines the compiler behavior when calls to declared but undefined predicates (or non-terminals) are found. Note that calls to declared but undefined predicates (or non-terminals) fail as per closed-world assumption. Possible option values are error, warning (the usual default), and silent (not recommended).

single: portability flag

portability(Option)

Controls the non-ISO specified Prolog built-in predicate and non-ISO specified Prolog built-in arithmetic function calls warnings plus use of non-standard Prolog flags and/or flag values. Possible option values are warning and silent (the usual default).

single: missing_directives flag

missing_directives(Option)

Controls the missing predicate directive warnings. Possible option values are warning (the usual default) and silent (not recommended).

single: duplicated_directives flag

duplicated_directives(Option)

Controls the duplicated predicate directive warnings. Possible option values are warning (the usual default) and silent (not recommended). Note that conflicting directives for the same predicate are handled as errors, not as duplicated directive warnings.

single: trivial_goal_fails flag

trivial_goal_fails(Option)

Controls the printing of warnings warnings for calls to local static predicates with no matching clauses. Possible option values are warning (the usual default) and silent (not recommended).

single: always_true_or_false_goals flag

always_true_or_false_goals(Option)

Controls the printing of warnings for goals that are always true or false. Possible option values are warning (the usual default) and silent (not recommended).

single: lambda_variables flag

lambda_variables(Option)

Controls the printing of lambda variable related warnings. Possible option values are warning (the usual default) and silent (not recommended).

single: suspicious_calls flag

suspicious_calls(Option)

Controls the printing of suspicious call warnings. Possible option values are warning (the usual default) and silent (not recommended).

single: redefined_built_ins flag

redefined_built_ins(Option)

Controls the Logtalk and Prolog built-in predicate redefinition warnings. Possible option values are warning (the usual default) and silent. Warnings about redefined Prolog built-in predicates are often the result of running a Logtalk application on several Prolog compilers as each Prolog compiler defines its set of built-in predicates.

single: singleton_variables flag

singleton_variables(Option)

Controls the singleton variable warnings. Possible option values are warning (the usual default) and silent (not recommended).

single: underscore_variables flag

underscore_variables(Option)

Controls the interpretation of variables that start with an underscore (excluding the anonymous variable) that occur once in a term as either don't care variables or singleton variables. Possible option values are dont_care and singletons (the usual default). Note that, depending on your Prolog compiler, the read_term/3 built-in predicate may report variables that start with an underscore as singleton variables. There is no standard behavior, hence this option.

Optional features compilation flags

single: complements flag

complements(Option)

Allows objects to be compiled with support for complementing categories turned off in order to improve performance and security. Possible option values are allow (allow complementing categories to override local object predicate declarations and definitions), restrict (allow complementing categories to add predicate declarations and definitions to an object but not to override them), and deny (ignore complementing categories; the usual default). This option can be used on a per-object basis. Note that changing this option is of no consequence for objects already compiled and loaded.

single: dynamic_declarations flag

dynamic_declarations(Option)

Allows objects to be compiled with support for dynamic declaration of new predicates turned off in order to improve performance and security. Possible option values are allow and deny (the usual default). This option can be used on a per-object basis. Note that changing this option is of no consequence for objects already compiled and loaded. This option is only checked when sending an asserta/1 or assertz/1 message to an object. Local asserting of new predicates is always allowed.

single: events flag

events(Option)

Allows message sending calls to be compiled with event-driven programming support disable in order to improve performance. Possible option values are allow and deny (the usual default). Objects (and categories) compiled with this option set to deny use optimized code for message-sending calls that does not trigger events. As such, this option can be used on a per-object (or per-category) basis. Note that changing this option is of no consequence for objects already compiled and loaded.

single: context_switching_calls flag

context_switching_calls(Option)

Allows context switching calls (<</2) to be either allowed or denied. Possible option values are allow and deny. The default flag vale is allow. Note that changing this option is of no consequence for objects already compiled and loaded.

Back-end Prolog compiler and loader flags

single: prolog_compiler flag

prolog_compiler(Flags)

List of compiler flags for the generated Prolog files. The valid flags are specific to the used Prolog backend compiler. The usual default is the empty list. These flags are passed to the backend Prolog compiler built-in predicate that is responsible for compiling to disk a Prolog file. For Prolog compilers that don't provide separate predicates for compiling and loading a file, use instead the prolog_loader/1 flag.

single: prolog_loader flag

prolog_loader(Flags)

List of loader flags for the generated Prolog files. The valid flags are specific to the used Prolog backend compiler. The usual default is the empty list. These flags are passed to the backend Prolog compiler built-in predicate that is responsible for loading a (compiled) Prolog file.

Other flags

single: scratch_directory flag

scratch_directory(Directory)

Sets the directory to be used to store the temporary files generated when compiling Logtalk source files. This directory can be specified using an atom or using library notation. The directory must always end with a slash. The default value is a sub-directory of the source files directory, either './lgt_tmp/' or './.lgt_tmp/' (depending on the back-end Prolog compiler and operating-system). Relative directories must always start with './' due to the lack of a portable solution to check if a path is relative or absolute.

single: report flag

report(Option)

Controls the default printing of messages. Possible option values are on (by usual default, print all messages that are not intercepted by the user), warnings (only print warning and error messages that are not intercepted by the user), and off (do not print any messages that are not intercepted by the user).

single: code_prefix flag

code_prefix(Character)

Enables the definition of prefix for all functors of Prolog code generated by the Logtalk compiler. The option value must be a single character atom. Its default value is '$'. Specifying a code prefix provides a way to solve possible conflicts between Logtalk compiled code and other Prolog code. In addition, some Prolog compilers automatically hide predicates whose functor start with a specific prefix such as the character $. Although this is not a read-only flag, it should only be changed at startup time and before loading any source files.

single: optimize flag

optimize(Option)

Controls the compiler optimizations. Possible option values are on (used by default for deployment) and off (used by default for development). Compiler optimizations include the use of static binding whenever possible, the removal of redundant calls to true/0 from predicate clauses, the removal of redundant unifications when compiling grammar rules, and inlining of predicate definitions with a single clause that links to a local predicate, to a plain Prolog built-in (or foreign) predicate, or to a Prolog module predicate with the same arguments. Care should be taken when developing applications with this flag turned on as changing and reloading a file may render static binding optimizations invalid for code defining in other loaded files. Turning on this flag automatically turns off the debug <flag_debug> flag.

single: source_data flag

source_data(Option)

Defines how much information is retained when compiling a source file. Possible option values are on (the usual default for development) and off. With this flag set to on, Logtalk will keep the information represented using documenting directives plus source location data (including source file names and line numbers). This information can be retrieved using reflection and is useful for documenting, debugging, and integration with third-party development tools. This flag can be turned off in order to generate more compact code.

single: debug flag

debug(Option)

Controls the compilation of source files in debug mode (the Logtalk default debugger can only be used with files compiled in this mode). Also controls, by default, printing of debug> and debug(Topic) messages. Possible option values are on and off (the usual default). Turning on this flag automatically turns off the optimize <flag_optimize> flag.

single: reload flag

reload(Option)

Defines the reloading behavior for source files. Possible option values are skip (skip loading of already loaded files; this value can be used to get similar functionality to the Prolog directive ensure_loaded/1 but should be used only with fully debugged code), changed (the usual default; reload files only when they are changed since last loaded provided that the any explicit flags and the compilation mode are the same as before), and always (always reload files).

single: relative_to flag

relative_to(Directory)

Defines a base directory for resolving relative source file paths. The default value is the directory of the source file being compiled.

single: hook flag

hook(Object)

Allows the definition of compiler hooks that are called for each term read form a source file and for each compiled goal. This option specifies an object (which can be the pseudo-object user <apis:user/0>) implementing the expanding <apis:expanding/0> built-in protocol. The hook object must be compiled and loaded when this option is used. It's also possible to specify a Prolog module instead of a Logtalk object but the module must be pre-loaded and its identifier must be different from any object identifier. The object is expected to define clauses for the methods_term_expansion_2 and methods_goal_expansion_2 predicates. In the case of the term_expansion/2 predicate, the first argument is the term read form the source file while the second argument returns a list of terms corresponding to the expansion of the first argument. In the case of the goal_expansion/2 predicate, the second argument should be a goal resulting from the expansion of the goal in the first argument. The predicate goal_expansion/2 is recursively called on the expanded goal until a fixed point is reached. Care must be taken to avoid compilation loops.

single: clean flag

clean(Option)

Controls cleaning of the intermediate Prolog files generated when compiling Logtalk source files. Possible option values are off and on (the usual default). When turned on, this flag also forces recompilation of all source files, disregarding any existing intermediate files. Thus, it is strong advisable to turn on this flag when switching backend Prolog compilers as the intermediate files generated by the compilation of source files may not be portable (due to differences in the implementation of the standard write_canonical/2 predicate).

User-defined flags

Logtalk provides a predicates_create_logtalk_flag_3 predicate that can be used for defining new flags.

Reloading and smart compilation of source files

As a general rule, reloading source files should never occur in production code and should be handled with care in development code. Reloading a Logtalk source file usually requires reloading the intermediate Prolog file that is generated by the Logtalk compiler. The problem is that there is no standard behavior for reloading Prolog files. For static predicates, almost all Prolog compilers replace the old definitions with the new ones. However, for dynamic predicates, the behavior depends on the Prolog compiler. Most compilers replace the old definitions but some of them simply append the new ones, which usually leads to trouble. See the compatibility notes for the back-end Prolog compiler you intend to use for more information. There is an additional potential problem when using multi-threading programming. Reloading a threaded object does not recreate from scratch its old message queue, which may still be in use (e.g. threads may be waiting on it).

When using library entities and stable code, you can avoid reloading the corresponding source files (and, therefore, recompiling them) by setting the compiler flag reload to skip. For code under development, you can turn off the clean flag to avoid recompiling files that have not been modified since last compilation (assuming that back-end Prolog compiler that you are using supports retrieving of file modification dates). You can disable deleting the intermediate files generated when compiling source files by changing the default flag value in your settings file, by using the corresponding compiler flag with the compiling and loading built-in predicates, or, for the remaining of a working session, by using the call:

| ?- set_logtalk_flag(clean, off).

Some caveats that you should be aware. First, some warnings that might be produced when compiling a source file will not show up if the corresponding object file is up-to-date because the source file is not being (re)compiled. Second, if you are using several Prolog compilers with Logtalk, be sure to perform the first compilation of your source files with smart compilation turned off: the intermediate Prolog files generated by the Logtalk compiler may be not compatible across Prolog compilers or even for the same Prolog compiler across operating systems (e.g. due to the use of different character encodings or end-of-line characters).

Using Logtalk for batch processing

If you use Logtalk for batch processing, you probably want to turn off the report <flag_report> flag to suppress all messages of type banner, comment, comment(_), warning, and warning(_) that are normally printed. Note that error messages and messages providing information requested by the user will still be printed.

Optimizing performance

The default compiler flag settings are appropriated for the development but not necessarily for the deployment of applications. To minimize the generated code size, turn the source_data <flag_source_data> flag off. To optimize runtime performance, turn on the optimize <flag_optimize> flag.

Pay special attention to file compilation/loading order. Whenever possible, compile/load your files taking into account file dependencies to enable static binding optimizations. The easiest way to find the dependencies and thus the best compilation/loading order is to use the diagrams tool to generate a file dependency diagram for your application.

Minimize the use of dynamic predicates. Parametric objects can often be used in alternative. When dynamic predicates cannot be avoided, try to make them private. Declaring a dynamic predicate also as a private predicate allows the compiler to optimize local calls to the database methods (e.g. methods_assertz_1 and methods_retract_1) that handle the predicate.

Sending a message to self implies dynamic binding but there are often cases where control_send_to_self_1 is misused to call an imported or inherited predicate that is never going to be redefined in a descendant. In these cases, a super call, control_call_super_1, can be used instead with the benefit of often enabling static binding. Most of the guidelines for writing efficient Prolog code also apply to Logtalk code. In particular, define your predicates to take advantage of first-argument indexing. In the case of recursive predicates, define them as tail-recursive predicates whenever possible.

Debugging Logtalk applications

The Logtalk distribution includes in its tools directory a command-line debugger, implemented as a Logtalk application. It can be loaded by typing:

| ?- logtalk_load(debugger(loader)).

This tool implements debugging features similar to those found on most Prolog systems. There are some differences, however, between the usual implementation of Prolog debuggers and the current implementation of the Logtalk debugger that you should be aware. First, unlike some Prolog debuggers, the Logtalk debugger is not implemented as a meta-interpreter. This translates to a different, although similar, set of debugging features when compared with some of the more sophisticated Prolog debuggers. Second, debugging is only possible for entities compiled in debug mode. When compiling an entity in debug mode, Logtalk decorates clauses with source information to allow tracing of the goal execution. Third, implementation of spy points allows the user to specify the execution context for entering the debugger. This feature is a consequence of the encapsulation of predicates inside objects.

Compiling source files and entities in debug mode

Compilation of source files in debug mode is controlled by the debug <flag_debug> compiler flag. The default value for this flag, usually off, is defined in the adapter files. Its value may be changed at runtime by calling:

| ?- set_logtalk_flag(debug, on).

In alternative, if we want to compile only some source files in debug mode, we may instead write:

| ?- logtalk_load([file1, file2, ...], [debug(on)]).

The clean <flag_clean> compiler flag should be turned on whenever the debug <flag_debug> flag is turned on at runtime. This is necessary because debug code would not be generated for files previously compiled in normal mode if there are no changes to the source files.

After loading the debugger, we may check or enumerate, by backtracking, all loaded entities compiled in debug mode as follows:

| ?- debugger::debugging(Entity).

To compile only a specific entity in debug mode, use the directives_set_logtalk_flag_2 directive inside the entity.

Logtalk Procedure Box model

Logtalk uses a Procedure Box model similar to those found on most Prolog compilers. The traditional Prolog procedure box model uses four ports (call, exit, redo, and fail) for describing control flow when a predicate clause is used during program execution:

call

predicate call

exit

success of a predicate call

redo

backtracking into a predicate

fail

failure of a predicate call

Logtalk, as found on some recent Prolog compilers, adds a port for dealing with exceptions thrown when calling a predicate:

exception

predicate call throws an exception

In addition to the ports described above, Logtalk adds two more ports, fact and rule, which show the result of the unification of a goal with, respectively, a fact and a rule head:

fact

unification success between a goal and a fact

rule

unification success between a goal and a rule head

The user may define for which ports the debugger should pause for user interaction by specifying a list of leashed ports. For example:

| ?- debugger::leash([call, exit, fail]).

Alternatively, the user may use an atom abbreviation for a pre-defined set of ports. For example:

| ?- debugger::leash(loose).

The abbreviations defined in Logtalk are similar to those defined on some Prolog compilers:

none

[]

loose

[fact, rule, call]

half

[fact, rule, call, redo]

tight

[fact, rule, call, redo, fail, exception]

full

[fact, rule, call, exit, redo, fail, exception]

By default, the debugger pauses at every port for user interaction.

Defining spy points

Logtalk spy points can be defined by simply stating which file line numbers or predicates should be spied, as in most Prolog debuggers, or by fully specifying the context for activating a spy point. In the case of line number spy points, the line number must correspond to the first line of an entity clause. To simplify the definition of line number spy points, these are specified using the entity identifier instead of the file name (as all entities share a single namespace, an entity can only be defined in a single file).

Defining line number and predicate spy points

Line number and predicate spy points are specified using the method spy/1. The argument can be either a pair entity identifier - line number (Entity-Line) or a predicate indicator (Name/Arity) or a list of spy points. For example:

| ?- debugger::spy(person-42).

Spy points set.
yes

| ?- debugger::spy(foo/2).

Spy points set.
yes

| ?- debugger::spy([foo/4, bar/1]).

Spy points set.
yes

Line numbers and predicate spy points can be removed by using the method nospy/1. The argument can be a spy point, a list of spy points, or a non-instantiated variable in which case all spy points will be removed. For example:

| ?- debugger::nospy(_).

All matching predicate spy points removed.
yes

Defining context spy points

A context spy point is a term describing a message execution context and a goal:

(Sender, This, Self, Goal)

The debugger is evoked whenever the execution context is true and when the spy point goal unifies with the goal currently being executed. Variable bindings resulting from the unification between the current goal and the goal argument are discarded. The user may establish any number of context spy points as necessary. For example, in order to call the debugger whenever a predicate defined on an object named foo is called we may define the following spy point:

| ?- debugger::spy(_, foo, _, _).

Spy point set.
yes

For example, we can spy all calls to a foo/2 predicate by setting the condition:

| ?- debugger::spy(_, _, _, foo(_, _)).

Spy point set.
yes

The method nospy/4 may be used to remove all matching spy points. For example, the call:

| ?- debugger::nospy(_, _, foo, _).

All matching context spy points removed.
yes

will remove all context spy points where the value of self matches the atom foo.

Removing all spy points

We may remove all line number, predicate, and context spy points by using the nospyall/0 predicate:

| ?- debugger::nospyall.

All line number spy points removed.
All predicate spy points removed.
All context spy points removed.
yes

Tracing program execution

Logtalk allows tracing of execution for all objects compiled in debug mode. To start the debugger in trace mode, write:

| ?- debugger::trace.

yes

Note that, when tracing, spy points will be ignored. While tracing, the debugger will pause for user input at each leashed port, printing an informative message with the port name and the current goal. Before the port number, when a spy point is set for the current clause or goal, the debugger will print a # character for line number spy points, a + character for predicate spy points, and a * character for context spy points. The debugger also provides determinism information by prefixing the exit port with a * character when a call succeeds with choice-points pending. After the port name, the debugger prints the goal invocation number. This invocation number is unique and can be used to correlate the port trace messages.

To stop tracing and turning off the debugger, write:

| ?- debugger::notrace.

yes

Debugging using spy points

Tracing a program execution may generate large amounts of debugging data. Debugging using spy points allows the user to concentrate its attention in specific points of its code. To start a debugging session using spy points, write:

| ?- debugger::debug.

yes

At the beginning of a port description, the debugger will print a #, +, or * character before the current goal if there is, respectively, a line number, a predicate, or a context spy point defined.

To stop the debugger, write:

| ?- debugger::nodebug.

yes

Note that stopping the debugger does not remove any defined spy points.

Debugging commands

The debugger pauses at leashed ports when tracing or when finding a spy point for user interaction. The commands available are as follows:

c — creep

go on; you may use the spacebar, return, or enter keys in alternative

l — leap

continues execution until the next spy point is found

s — skip

skips debugging for the current goal; valid at call, redo, and unification ports

q — quasi-skip

skips debugging until returning to the current goal or reaching a spy point; valid at call and redo ports

r — retry

retries the current goal but side-effects are not undone; valid at the fail port

j — jump

reads invocation number and continues execution until a port is reached for that number

z — zap

reads port name and continues execution until that port is reached reads negated port name and continues execution until a port other than the negated port is reached

i — ignore

ignores goal, assumes that it succeeded; valid at call and redo ports

f — fail

forces backtracking; may also be used to convert an exception into a failure

n — nodebug

turns off debugging

@ — command; ! can be used in alternative

reads and executes a query

b — break

suspends execution and starts new interpreter; type end_of_file to terminate

a — abort

returns to top level interpreter

Q — quit

quits Logtalk

p — print

writes current goal using the print/1 predicate if available

d — display

writes current goal without using operator notation

w — write

writes current goal quoting atoms if necessary

$ — dollar

outputs the compiled form of the current goal (for low-level debugging)

x — context

prints execution context

. — file

prints file, entity, predicate, and line number information at an unification port

e — exception

prints exception term thrown by the current goal

= — debugging

prints debugging information

< — write depth

sets the write term depth (set to 0 to reset)

* — add

adds a context spy point for the current goal

/ — remove

removes a context spy point for the current goal

+ — add

adds a predicate spy point for the current goal

- — remove

removes a predicate spy point for the current goal

# — add

adds a line number spy point for the current clause

| — remove

removes a line number spy point for the current clause

h — condensed help

prints list of command options

? — extended help

prints list of command options

Context-switching calls

Logtalk provides a control construct, control_context_switch_2, which allows the execution of a query within the context of an object. Common debugging uses include checking an object local predicates (e.g. predicates representing internal dynamic state) and sending a message from within an object. This control construct may also be used to write unit tests.

Consider the following toy example:

:- object(broken).

    :- public(a/1).

    a(A) :- b(A, B), c(B).
    b(1, 2). b(2, 4). b(3, 6).
    c(3).

:- end_object.

Something is wrong when we try the object public predicate, a/1:

| ?- broken::a(A).

no

For helping diagnosing the problem, instead of compiling the object in debug mode and doing a trace of the query to check the clauses for the non-public predicates, we can instead simply type:

| ?- broken << c(C).

C = 3
yes

The <</2 control construct works by switching the execution context to the object in the first argument and then compiling and executing the second argument within that context:

| ?- broken << (self(Self), sender(Sender), this(This)).

Self = broken
Sender = broken
This = broken

yes

As exemplified above, the <</2 control construct allows you to call an object local and private predicates. However, it is important to stress that we are not bypassing or defeating an object predicate scope directives. The calls take place within the context of the specified object, not within the context of the object making the <</2 call. Thus, the <</2 control construct implements a form of execution-context switching.

The availability of the <</2 control construct is controlled by the context_switching_calls <flag_context_switching_calls> compiler flag (its default value is defined in the adapter files of the back-end Prolog compilers).

Using compilation hooks and term expansion for debugging

It is possible to use compilation hooks and the term expansion mechanism for conditional compilation of debugging goals. Assume that we chose the predicate debug/1 to represent debug goals. For example:

member(Head, [Head| _]) :-
    debug((write('Base case: '), writeq(member(Head, [Head| _])))).
member(Head, [_| Tail]) :-
    debug((write('Recursive case: '), writeq(member(Head, Tail)))),
    member(Head, Tail).

When debugging, we want to call the argument of the predicate debug/1. This can be easily accomplished by defining a hook object containing the following definition for goal_expansion/2:

goal_expansion(debug(Goal), Goal).

When not debugging, we can use a second hook object to discard the debug/1 calls by defining the predicate goal_expansion/2 as follows:

goal_expansion(debug(_), true).

The Logtalk compiler automatically removes any redundant calls to the built-in predicate true/0 when compiling object predicates.

Debugging messages

Calls to the logtalk::print_message/3 predicate where the message kind is either debug or debug(_) are only printed, by default, when the debug <flag_debug> flag is turned on. Note that using these messages does not require compiling the code in debug mode, only turning on the flag. To avoid having to define methods_message_tokens_2 grammar rules for translating each debug message, Logtalk provides default tokenization for four meta-messages that cover the most common cases:

@Message

By default, the message is printed as passed to the write/1 predicate followed by a newline.

Key-Value

By default, the message is printed as Key: Value followed by a newline. The value is printed as passed to the writeq/1 predicate.

List

By default, the list items are printed indented one per line. The items are preceded by a dash and printed as passed to the writeq/1 predicate.

Title::List

By default, the title is printed followed by a newline and the indented list items, one per line. The items are preceded by a dash and printed as passed to the writeq/1 predicate.

These print messages goals can always be combined with hooks as described in the previous section to remove them in production ready code.

Some simple examples of using these meta-messages:

| ?- logtalk::print_message(debug, core, @'Phase 1 completed').
yes

| ?- set_logtalk_flag(debug, on).
yes

| ?- logtalk::print_message(debug, core, @'Phase 1 completed').
>>> Phase 1 completed
yes

| ?- logtalk::print_message(debug, core, answer-42).
>>> answer: 42
yes

| ?- logtalk::print_message(debug, core, [arthur,ford,marvin]).
>>> - arthur
>>> - ford
>>> - marvin
yes

| ?- logtalk::print_message(debug, core, names::[arthur,ford,marvin]).
>>> names:
>>> - arthur
>>> - ford
>>> - marvin
yes