-
Notifications
You must be signed in to change notification settings - Fork 1
Logical Operators
Statements may be said to have a logical and a procedural interpretation. The latter describes how our program is run as it's evaluated by an interpreter, aka the machine-based explanation.
The programmer may determine a statement such as x=1 and x=2
to be false simply because it's mathematically inconsistent; this is the logical/declarative interpretation.
Logic/declarative interpretation
A and B: Both A and B are true.
A or B: Either A, B or both are true.
Procedural interpretation
A and B: A and B are both evaluated, in sequence.
A or B: A is evaluated. If that fails, B is evaluated.
> x=1 and 2=y
| x = 1 and y = 2
> x=1 or 2=x
| x = 1
| x = 2
As said before, and/or
controls the order of evaluation and consequently the flow of the language.
-
and
evaluates statements sequentially. If any statement is found to befalse
, evaluation fails. - On the other hand,
or
will only fail if all statements are found to befalse
.
But more than that, or
introduces non-determinism to the language. A statement such as x=1 or 2=x
may be said to be non-deterministic in that when presented to the interpreter, it may give multiple "answers".
It may be said to be short-circuiting in that if the user doesn't ask for all answers, the second statement may not need to be evaluated at all.
A case statement is simple shorthand to and/or. It can also be written as cond.
//this is equivalent to ...
//(x=1 and y=1) or 2=y
case
x = 1
y = 1
case
y = 2
It's possible there's no answer to be found, in which case the interpreter will give false as the answer.
> x=1 and x=2
| false
The two truth constants are false and true.
> true
| true
> false
| false
Any answer besides false is implicitly a true answer, even if the interpreter doesn't reply with true, i.e. x=1
is seen as true by the interpreter.
Such statements can be used as placeholders.
rel p(x)
true
We've not made the code for our relation p yet; therefore we use the statement otherwise redundant true as a placeholder.
Let's evaluate (x=1 or x=2) and x!=1
.
- x=1 is evaluated.
- x!=1 is then evaluated.
- (From 2) x=1 doesn't hold.
- Therefore, we go back and evaluate x=2. (Backtracking occurs.)
- Both x=2 and x!=1 now hold.
rel p(x)
x=1 or x=2
rel main()
p(x)
x!=1
io.writeln(x) //2
main()
The above code operates on a similar principle. (x=1 or x=2)
is encoded by p(x)
. Since x!=1
holds, the program will print 2.
When examining the program procedurally, we see that a program may go back to an early or-stm to ensure correctness. This is called backtracking and it's said that an or-stm creates a choice-point.
Adding to our repertoire, we have if and not.
These operators are meant to represent statements in the form,
If X, then Y.
If X, then Y, else Z.
not X.
Which are common in logic and show up in practical programming.
It's possible for an if-stm to be accompanied by an else-clause. An else-clause is evaluated when the condition doesn't hold.
These are examples of their uses,
> not 2=2
| false
> not 1=2
| true
> not x=2
| x!=2
> not true
| false
Logical interpretation
The negation of A is false when A holds, and true when A doesn't.
> if(x=1) a=2;
| x=2 and a=2
| true
> if(x=1) a=2 else a=1;
| x=2 and a=2
| a=1
> if(false) x=2 else x=1;
| x=1
if(true)
print('condition is true')
else
print('condition is not true')
Logical interpretation
if(A) B else C; <==> (A and B) or (not A and C)
if(A) B; <==> (not A) or B
A straightforward implementation of if
and not
akin to the one in procedural or functional languages is available through use of modes (see Static-Checks).
Therefore, the discussion below concerns the relational use of such operators.
Some additional attention must be taken to their working, when compared to and/or
.
A relational implementation of them in Cosmos is experimental. It's up to change and we may accept suggestions on the matter.
The reif or reified if is applied when the condition is a simple equality with atomic operands. The optimization avoid unnecessary use of non-determinism.[1] As of now,
- A
i=0
,x=t.y
orl=[x]
condition will be optimized.x<0
orx=p()
may not work with the optimization.[2] - Failing that,
if(A) B else C;
is expanded to (A and B) or (not A and C)
.
if(A) B;
is expanded to (A and B) or (not A)
.
See an explanation on not
below.
[1] See indexing dif/2.
[2] The latter needs to access an element 'y', the former is syntax for p(x)
.
See fact
below.
rel fact(x,y)
if(x=0)
y=1
else
y=x*fact(x-1)
- If the statement is simple inequality, it's reversed, e.g.
not x<=1
becomes x>1 andnot x=1
becomes x!=1. - If not, a compiler error will be given.
Why?
After some testing, we arrived to the following conclusion.
Issuing a compiler error is often beneficial to the user. A lousy implementation of not
may not do what the user intended it to do. In such cases, an error will avoid possible issues from negation-by-delayal[3]. It's likely that, if the user comes from a procedural background, they really intended to use the procedural approach, so once again an error is helpful. Seeing an error, the user then simply looks for the procedural approach, or whatever approach it is they're looking for.
Therefore, an error is issued if relational not
hasn't been fully implemented for a given statement.
[3] The approach used by MU-Prolog. not p(x) is delayed until x is ground. "If a MU-PROLOG program returns some answer, you can be confident that it is correct." - An Introduction to MU-PROLOG.
functions
Typically, in a procedural language, a straightforward implementation of not/if
is possible by not allowing non-ground variables. Once again, this is covered by modes.
function q(x)
...
if(not p(x))
...
q(2)
In summary, the programmer might encase use of not inside a function. A function will use procedural mechanisms for both not and if. The compiler will ideally check if parameters are ground.
manual implementation
It goes without saying but logic programmers have the option of implementing negative statements manually.
rel is_two(x)
x=2
rel non_two(x)
x!=2
A relation non_two is implemented manually as a negation of is_two.
The when
operator provides a simple expansion. when(A) B else C;
is expanded to (A and B) or C
.
when(x=2)
y=2
else
non_two(x)
when
will not attempt any optimizations, and is simply a different way of writing case
, so it can be used whenever you want to manually provide the negation, or none is necessary.