Named constants?

Am I right in thinking that Prolog(s) don’t support named constants?

To define:

 icd10hypertension = R03.0

we need to:

icd10hypertension('R03.0').

?

But to use this, we need to

f(A,B,C) :-    icd10hypertension(X), patients(A, B, X).

suffering runtime unification costs and readability? We would have liked to write:

f(A,B,C) :-   patients(A, B,  icd10hypertension).

Have you looked at using Conditional compilation and program transformation?

1 Like

The runtime costs are trivial. And I’m not convinced that it’s a significant hit to readability.

Anyway, you could do something like this (you’d probably want better predicate names):

f(A, B) :- patients_expand(A, B, icd10hypertension).

patients_expand(A, B, X_exp) :-
    call(X_exp, X),
    patients(A, B, X).

icd10hypertension('R03.0').

(I didn’t know what the “C” in your f(A,B,C) was for, so I left it out.)

If this turns out to be a performance problem (be sure to profile first), it’s easy to expand patients_expand/3 at compile-time.

When you think about it all constants are named. The constants themselves don’t exist (ok they have an ideal existence!), we use their names. The constant 42 is not a number, it’s a numeral that is used to name a number.

Now, both icd10hypertensionand 'R02.0' are ‘constants’. It’s just two different names for the same thing. You just write your code to accept either or both.

1 Like

You can then define a translation table, for example (although it might make more sense to split out the ‘icd10’ part into a 3rd column, giving the code (icd10, snomed, cpt4, etc.); or have multiple code tables.

code(icd10_hypertension, 'R02.0').
code(icd10_prehypertension, 'R03.0'). 
code(icd10_essential_hypertension, 'I10').
3 Likes

Thanks. Is helpful.

1 Like

Many thanks.

Hmmm… If I have 30 or 40 of:

icd10hypertension = R03.0
icd10heart_attack = ...

can I export them all from a module without listing each one?

You mean like this?

:- module(constants, [code/2]).

code(icd10hypertension, 'R03.0').
code(icd10heart_attack, '...').

The individual lines are “facts” that define the predicate code/2, so you’re exporting a single predicate with a number of facts.

Of perhaps I misunderstood your question.

1 Like

No, the purpose of named constants (generally) is:

  • to have a single place in your code to bind a name to value
  • allow the single name to be referenced in many places
  • allow the name to be used in place of the value (providing the reader the meaning to the value), and,
  • (most importantly), complain if the name does not exist (e.g. mispelled).

If I place this name in a single exported predicate, prolog will fail silently on misspellings or missing codes:

  code(icd10hypertension, 'R03.0').
  % f(X)--->false, but should be an error. icd10hypertons is not a missing predicate
  f(X)  :- code(icd10hypertons, C), f(icd10hypertension, 'R03.0').

right? So,

icd10hypertension('R03.0').
% icd10hypertons is a missing predicate
f(X)  :- icd10hypertons(C), f(icd10hypertension, 'R03.0').
...

is preferred. However, now if I create ‘code.pl’ to define and share these named constants, I need to export each of 50, right? (A PITA, and is easy to miss some).

I don’t define all 20,000 icd10 codes this way – I would get them from a database – just this small number have special meaning in the prolog program and I don’t want to paste in meaningless-to-the-reader icd10 values at those places, or have the program continue without error due to silly spelling mistakes.

You could also use term_expansion/2 to generate the definitions. Untested, but something like

term_expansion(constant(Code, Val), [CodeVal, :- export(Code/1)]) :-
   CodeVal =.. [Code, Val].

% define like
constant(icd10hypertension, 'R03.0').

% in other module
:- use_module(constants, [icd10hypertension/1]).

f(X) :- icd10hypertension(C), g(C).

EDIT: Oops, that should be term_expansion/2; updated code to work.

1 Like

Hmmm… interesting.
if I queried the database for the list of predicates, would it find icd10hypertension, or does this method magic it into existence during the query?
If the latter. Perhaps would be better to query constant during the module’s initialization and assert those names?

If your lookup is one-way, you can do this:

code(icd10hypertension, 'R03.0') :- !.
code(icd10heart_attack, '...') :- !.
code(Unknown, _) :- throw(error(unknown_code(Unknown), _)),
1 Like

@peter.ludemann Thank you. This would thwart any static error analysis. I’m not sure this makes a difference in Prolog-land though, since it seems to be without many compile-time types. ::frowning_face:

It also means more visual noise (therefore less readable) than one predicate per code.

We must:

 code(icd10hypertension, C), g(C).

instead of

 icd10hypertension(C), g(C).

I was kinda hoping it would be possible to reduce it to:

g(icd10hypertension.value).

where predicate.value is a macro that rewrites outer(predicate.value) as predicate(X), outer_predicate(predicate) , or somesuch. (ie. or some similar syntax). Does prolog have the notion of unaries-- names bound to exactly one value (in which case the perhaps the compiler could be coerced into simply re-writing the name with its single value)?

Yes, that predicate would be defined. term_expansion/2 is essentially macros for Prolog; loading the file would re-write the constants(whatever, 'R0.3). line to be as if you had written whatever('R0.3'). :- export(whatever/1).

1 Like

Nifty. So its evaluated while the file is compiled? Like a directive? (The docs says "When defined by the user all terms read during consulting are given to this predicate…, ‘consulting’==on load_file?)

Does this do what you want?

:- module(codes, [code/2, code_/2]).

% Lookup by Name or Icd10; throw domain_error if not found
% (Does not backtrack of results - for that, use code_/2).
code(Name, Icd10) :-
    (  code_(Name, Icd10)
    -> true
    ;  domain_error(valid_name_or_code, code(Name, Icd10))
    ).

user:term_expansion(codes(Codes), Preds) :-
    maplist(code_expand, Codes, Preds).

code_expand(Name=Icd10, code_(Name, Icd10)).

% code_(Name, Icd9) facts (backtrackable)
codes([
       icd10_hypertension = 'R02.0',
       icd10_prehypertension = 'R03.0',
       icd10_essential_hypertension = 'I10'
      ]).
?- forall(code_(Name, Icd10), 
          writeln(Name = Icd10)).
icd10_hypertension=R02.0
icd10_prehypertension=R03.0
icd10_essential_hypertension=I10

?- forall(code_(Name, Icd10), format('~q~n', [Name = Icd10])).
icd10_hypertension='R02.0'
icd10_prehypertension='R03.0'
icd10_essential_hypertension='I10'

?- code(icd10_hypertension, Icd10).
Icd10 = 'R02.0'.

?- code_(Name, 'R03.0').
Name = icd10_prehypertension.

?- code(nonsense_name, A).
ERROR: Domain error: `valid_name_or_code' expected, found `code(nonsense_name,_1922)'
ERROR: In:
ERROR:   [12] throw(error(domain_error(valid_name_or_code,...),_1972))
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
2 Likes

If i understand this correctly, all approaches discussed here suggest having a named constant that is retrieved during runtime – in one way or another – via predicate lookup.

I guess this is always slower than a named constant in compiled languages where the symbol gets replaced during compile time by the declared value.

So, to have “real” named constant in prolog would require some kind of extension – perhaps an extension of the macro facility.

Dan

1 Like

Not so much extension. Just use the macro mechanism for replacing some term. The only gotcha is that you somehow have to recognise the thing you want to expand from anything else, so you can give an error if the constant does not exist. You could for example expand c.name. The ffi package does something like that to get access to C #define macros

1 Like

We shape our tools, thereafter our tools shape us.” — either W. Churchill or M. McCluhan, or somebody else.

I think you’re fighting the language. You’re either going to:

  1. read something similar to Kowalski’s Logic for Problem Solving ( http://www.doc.ic.ac.uk/~rak/papers/LogicForProblemSolving.pdf ), then come back to Prolog, i.e., learn the culturally enforced thought patterns,
  2. seek another language that is more acceptable for you,
  3. fall back and use something you already have fluency in, or
  4. continue as you are suffering much frustration.

I was there with Prolog in the early 1990s (eventually did no. 1). I’m there with another language right now (doing no. 4 I’m afraid, but economics will force me into no. 1).

4 posts were split to a new topic: C - How is it possible to ensure that code doesn’t just crash due to a uncaught type error and pointers pointing randomly to memory