Skip to content
cosmos-lang edited this page Jul 31, 2023 · 10 revisions

1. Statements

Logical and procedural interpretation

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.

2. Operators

and/or

> 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 be false, evaluation fails.
  • On the other hand, or will only fail if all statements are found to be false.

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.

Case statements

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

Truth constants

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.

A more complicated example

Let's evaluate (x=1 or x=2) and x!=1.

  1. x=1 is evaluated.
  2. x!=1 is then evaluated.
  3. (From 2) x=1 doesn't hold.
  4. Therefore, we go back and evaluate x=2. (Backtracking occurs.)
  5. 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.

3. Additional Operators

Operators

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

> 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-stm

> 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

Mechanism (Procedural Interpretation)

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.

Conditional

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 or l=[x] condition will be optimized. x<0 or x=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).

Optimizations

See fact below.

rel fact(x,y)
	if(x=0)
		y=1
	else
		y=x*fact(x-1)

Negation

  1. If the statement is simple inequality, it's reversed, e.g. not x<=1 becomes x>1 and not x=1 becomes x!=1.
  2. 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.

Other

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.

Clone this wiki locally