|Did you know ...||Search Documentation:|
|Pack cosmos -- docs/guide - Copy - Copy.md|
--- | --- |
|Types | Data
--- | --- |
--- | --- |
+ - * /
|`< > <= >= = !=`
Syntax: a language's grammar.
Before doing anything, you may check that you've installed the language. By opening the language with the -v flag, you should see installation version number and confirm.
Open a command-line and type,
$ cosmos -v Cosmos 0.16
If the language is installed, you'll see something akin to this.
A good way to try out a new language is by opening the interpreter, if one is available, and making statements in it. This can be done with the -i flag.
We'll assume you have already downloaded the language.
$ cosmos -i > x=1 | x = 1 > x=1 or 2=x | x = 1 | x = 2 > str='hello' | str = 'hello' > l=[1,2,3] | l=[1,2,3]
This way, you should get a better idea of how the language works. Whenever you see a new kind of statement in this guide, you may use the interpreter to try it on.
Make a file
hello.co with the content,
A program that writes "hello world" on the screen is one of the simplest programs you can do. This can be done with a
The file can be loaded with the
$ cosmos -l hello 'hello world'`
A simple way to make a statement in Cosmos is to use equality, aka the
x=1 y=z 2.5=z
These are three kinds of statements you can make.
x=1 print('hello world') io.writeln('hello world')
The first uses the relation of equality.
Then, the built-in
writeln relation is taken from a module
io to write 'hello world'.
Joining statements --
x=1 y=2 and x=1
Statements can be joined together with and.
Statements from separate lines are implictly joined.
... is the same as ...
x=1 and y=2
Data types --
Two basic types of data we've seen so far are Number and String.
Number includes 1, 2 and 2.5.
Unlike numbers, strings refer to a piece of text surrounded by double or single quotation in code.
String includes 'hello world' and "2".
Remember that 2 is a Number but '2' is a String.
The special num (or real) function computes the result of the mathematical operations, passing the result to x.
This is not necessary.
| x=1+2 print(x) //1+2
This is a technically correct program and saying that the value of x is 1+2 is an answer. Though, it may not be the one we want.
A logic program may sometimes,
Different data types, such as strings, may have their own interpretation of addition. We may use addition to "add" two strings together, for example.
print(str(x)) //'hello world'
This is akin to casting in procedural languages. A value like 1+2 has a different type than Number. It's stored as-is before being solved by the language's arithmetic system.
Cosmos is one of the few existing languages to have CLP as the default arithmetic system.
$ cosmos -i > x=1+y | x = 1+y > x=num(1+y) | x = _124 | y = _123 > x>5 | x > 5
As this is a rather unique decision, we should probably explain.
A regular language would have x>5 or y+1 simply fail. In those cases, it doesn't know the value of x or y. It wouldn't then be able to solve those equations.
As a logic language, they're simply taken as true until proven wrong. It's the same for equality, e.g. x=y would also give an error in a regular language. A logic language has constraints that are solved when needed.
This is commonly called Constraint Logic Programming (CLP). Cosmos has CLP for Reals as its default arithmetic. This also means that it uses floating-point numbers.
There are other known systems. However, they would not work well as the default arithmetics. CLP(FD) is limited to integers which would mean users couldn't use floating-point numbers at all.
As the default arithmetics, we wanted a system that would, (1) work under normal circumnstances, at the least, and (2) not be limited to that; if possible, explore and make use of logic programming. If it weren't possible to use numbers with decimals, it would clearly not work under the circumnstances in which procedural arithmetics do, as they do use them, thus breaking (1).
However, one can use other systems by calling the host language, and using a system from them. Furthermore, the current system may be changed by modifying core.pl.
 As far as we know, CLP for Reals goes back to Prolog III. See http://prolog-heritage.org/en/ph30.html.
 It should be rather be asked why a logic language would not use CLP and instead present a classical, procedural system as the default one! Even though Prolog III has it, see . It's practically asking users to make incorrect programs, logically speaking.
Let's codify the intuition that "the double of x is y".
rel double(x,y) y=num(x*2) double(2,z)
We've seen statements that use built-in relations like print and _=_ but this is the first time we define our own relation. Specifically,
A statement like
double(2,z) is, by our definition,
z=2*2, once we've substituted
In other words, the result is that z now equals 2*2.
Though double is not particularly useful, relations in general can single out pieces of code allowing for later use.
As we'll see later, relations can be kept in a module.
Cosmosâ¢ is one of the few remaining logic programming languages. It's no surprise therefore that there's some connection.
Logic itself concerns with statements that may be true or false, such as,
`x equals 2
The double of 2 is 4.
Socrates is a human.
What these statements have in common is that they each have a _relation_. This is more evident when written in logic notation, ```x = 2 double(2, 4) human('socrates') rain().
The relationship in
double(2, 4) is double, which we defined beforehand. Naturally, this statement is true.
Out of those, _=_ is distinguishable enough that it doesn't need to be in logic notation. Generally, arithmetics stay in arithmetic notation.
Consider these two statements. Although the meaning is the same, one of them uses double as a relation and the other uses it as a function.
Any major "structure" such as rel and if are delimited by an increase in whitespace (generally, comprised of four spaces or a single tab character).
The code that comprises the if- and else-parts are evident through this method.
An error is issued if there's any inconsistency. For example, if you chose the first indent to be four spaces but the second to be three.
As long as you make a consistent rule, i.e. four spaces or one tab-only, there should be no issues.
 As suggested by the programming language Pythonâ¢, true statements can be used as placeholders.
In a logic language, these operators are more important than they may first seem. They are not only logical operators, they describe the flow of a logic language.
We've already seen them in use.
> x=1 and 2=y | x = 1 and y = 2 > x=1 or 2=x | x = 1 | x = 2
Logic interpretation A and B: Both A and B are true.
A or B: Either A, B or both are true.
A more complicated example --
This one is a bit more complicated.
`x=1 or x=2` essentially states that x may be 1 or 2. Which is to say, we don't know what the value is!
But what about `(x=1 or x=2) and x!=1`?
rel p(x) x=1 or x=2 rel main() p(x) x!=1 io.writeln(x) //2
Logic and procedural interpretations --
Naturally, we have said that x is 1 or 2. Since we then state that x is not 1, it's only possible for it to be 2. This is the logical interpretation of the program.
The program will then call io.writeln to write 2 on the screen.
Logical Engine (I) --
Cosmos is a language based on logic. Still, it runs on a computer. As such, there has to be a procedural (i.e. a machine-based) explanation on how the program runs.
We might say it's a logic program that's run by a procedural "logical engine".
We may call it the logic-procedural interpretation. This would be the procedural explanation on how our logic program is run.
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.
p(x)is called, first x=1 is evaluated.
p(x), and evaluates x=2.
It's because this is a logic language that we must ensure our program works correctly.
This brings us to the well-known limitations of LP.
rel p(x) p(x) p(x)
The program will call
p(x), which will call
p(x). This is an example of recursion. This is an example of an infinite loop, as the program will run forever.
The problem with this is that even correct logical problems may fall into an infinite loop.
Adding to our repertoire, we have if and not.
Logically speaking, this lets us represents all kinds of statements in the form,
`If X, then Y.
Which are common in logic and show up in practical programming.
`If it's raining, then someone will bring an umbrella.
If someone is a human, they're mortal. (aka All humans are mortals)
It's not raining.
print('condition is true')
print('condition is not true')
We may again open the interpreter to try if-statements.
$ cosmos -i > if(x!=1) x=2; | x=2 > if(x!=1) x=2 or x=1; | x=2 | x=1 > if(x!=1) x=2; | x=2
The body of the if-stm will be evaluated when the condition, e.g. x=1, is true.
if(false) x=2 else x=1;
if(x=1) x=1 else x=0;
It's possible for an if-statement to have an else-clause. If the condition is true, the if-part holds, otherwise the else-part holds. In the latter example, the interpreter tried out both possibilities and gave two answers. _elseif_ --
Naturally, this is just a way to write, _Logical interpretation_ You may think of an if-statement as equivalent to, if(A) B else C; <==> (A and B) or (not A and C) An if-statement without else is, if(A) B; <==> (not A) or B Though this may not be its exact implementation. _not_ --
> not x=2 | x!=2 > not 5<1 | x=1 | x=0 > not x<=2 | x>2
As you may have noted, _not_ simply negates a statement. _Logical interpretation_ The negation of A is simply false when A holds, and true when A doesn't. Logic-Procedural (II) -- Note that we have not set the mechanics for _if_ (and _not_) completely. The only requirements are that, - It's a logically sound conditional. - If it can't do that, an error is given. The reason for this is that we want to allow for different optimizations and ways to implement a logical conditional/negation to be tried. It's possible that a new method is found in the future. For us, this is already an improvement. Prolog famously implemented negation in an unsound way (that also gave no warnings whatsoever). This has highly limited experimentation with logic programming, as two operators were rendered mostly unusable. For relations that require a specific implementation, we use the _when_ operator. The _when_ is simply syntax for and/or, when(A) B else C <==> (A and B) or (C) As you see, this is a rather barebones conditional. It evaluates to and/or, and is therefore pure code. However, the _else_ is redundant, as the condition is not used. A further analysis will show that _when_ will get caught in an infinite loop in many times where _if_ doesn't, although it will work for the first answer. You may use it to implement your own conditional, as long as you write out the negation, when(x=2) true else x!=2 Furthermore, it can be easily switched with other conditionals as they share similar keywords. List of Conditionals (Summary) -- if: guaranteed to be logically sound, gives error if the condition is too complex. when: simple conversion to and/or. Even if it fulfills the condition and goes to if-code, it may still backtrack to else-code. Works for any condition. choose: simply checks if the condition is true, then chooses if or else-code. It'll not backtrack, but then this may not be logically sound. We recommend sticking to _if_ when possible. _if_ works best with simple conditions, i.e. simple inequalities such as != or >=. A Conditional in Formal Logic -- This is just trivia, but it may be of interest to some that the actual conditional _A->B_ in formal logic does not have an _else_. It's simply something adopted from procedural programming where we often specify what to do if a condition is not true. This has often been adopted by LP aswell. `If it's raining, then I will fly.` An odd thing about the formal conditional is that it holds true when the condition is false. As we said before, `if(A) B; <==> (not A) or B`. This is actually rather arbitrary! The implication is that if we confirm that it did not rain, then this odd implication is true. Perhaps we'd rather say: "you would not fly anyway!" That would maybe render the implication false. What about an implication that is always false when the condition is false? This would be arbitrary aswell. Though this may be a moot point, one wonders if the formal implication is not a good fit for a conditional in natural language. There is no justification given for this. It was simply something deemed useful. It's useful in our programs to say, `If the switch is set, our program will beep.` And, if the condition doesn't hold, simply go on with our program. We're going by the assumption that our machinery is correct.  See reif or (->;).  There really isn't. Consult the definition and explanation given for implication in the _Principia Mathematica_ for an example. Whitespace ---- It's about time we explained our use of whitespace and indendation. Any major "structure" such as rel and if are delimited by an increase in whitespace (generally, comprised of four spaces or a single tab character). if(true) false else false The code that comprises the if- and else-parts are evident through this method. An error is issued if there's any inconsistency. For example, if you chose the first indent to be four spaces but the second to be three. As long as you make a consistent rule, i.e. four spaces or one tab-only, there should be no issues. Syntax-wise, whitespace only adds _ands_ to the end of lines (unless a keyword like _else_ or _case_ pops up) and ends any unindent with a _semicolon_. As such, the semantics of whitespace are very easy to understand. Coming from a procedural language, it may seem odd that a semicolon is used to end a structure. Still, this is in line with the syntax of most LP language. ### 2D. Data Functors ---- We start with special relation _functor_.
By using the special relation _functor_, we declare that _F_ is a functor, or rather, it's what will be used to make functors. We can now make functors with the name/label _F_.
F(1, 2) = F(1, a)
Functors are a kind of composite data. They are what some languages call a _tuple_. If 1 or 'hello' is a single value, F(1,'hello') is a value made from combining both. We made two functors. Since they are equal to each other, we can also conclude that its values are. Hence, `a=2`. A Practical Example --- Let's say we want to represent a person in our program. We could just make a functor `Person`.
We can now make our first person.
bob = Person('bob', 23)
We have made the convention that the first two fields of `Person` stand for name and age, though if needed we could have included more fields. This allows us to make programs about one or more persons! This is not the only way we could represent a person, of course. Cosmosâ¢ has _objects_ aswell, which are very similar in that sense! We'll cover them later. Tuple --- If we simply to group things together, Lists --- A list is another kind of composite data. Let's say we want a list of things. This can easily be done with the syntax,
l = [1, 2, 3]
We've made a list with the values 1, 2 and 3. There are operations we can make on lists. We can add, subtract or search for elements within it. These can be found in module list.
l2 = list.
writeln(l) //[1, 2, 3]
writeln(l2) //[1, 2, 3, 55]
If we _push_ value 55 into l, we'll get a new list l2 with elements.
l = [1,2,3]
first(l, head) //head is 1
rest(l, tail) //tail is [2, 3]
A common operation is to get the _first_ element in a list, or the _rest_- the remaining, which itself is a new list. ### 3. A "Prolog" tutorial Functors ---- ### 3A. Paradigms Typically, programming is divided into four main paradigms, - Procedural (imperative) - Functional - Logic -- Logic-procedural (?!) - Object-oriented Although this classification is increasingly dated, it's still used (and somewhat useful) to this day. Logic ---
In the logic paradigm, or logic programming (LP), which we follow to the letter, as our language _is_ in fact a logic programming language, this is simply a set of statements. - The double of 1 is x. - The variable of y equals x. - The value of y is written on the screen. It's easy to see by looking at the program that this applies. We may simply look at the program and see that x equals the result of a function _double_, y is x, and the value is written (which we know the statement _print_ does). We only have to take a moment to conclude that 2 will be written if we run the program, and it is.
If a paradigm is a way to look at a given program, the procedural paradigm looks at it imperatively. A program is a recipe, or list of instructions to be executed by the computer.
This paradigm follows the computer program closely. It's the closest to how the computer actually acts.
The following instructions would be executed.
This is on purpose, the language is made so that it can be reasoned or written in this style to some extent.
Though you may often simply see the program as a series of statements in the logic paradigm, it's sometimes needed to know how the computer executes them.
After all, the program runs in a computer.
We'll call this the "Logic-Procedural" paradigm (though it's a made-up term we invented). The Logic-Procedural interpretation of a program is given by how our procedural logic engine executes the logic program.
Often, the only reason we need to know this is so our program has good performance. An efficient program will run as quickly as it can and not consume many memory slots.
A lot of this may be better off understood as styles.
Most important for our language is the notion of a pure logic program.
Logic programs allow us to effectively write logical statements and have the language work them out correctly.
All you have to do is use pure relations! Then, your relation is also pure.
Because it's the language's main conditional, any number of optimizations may be applied to it as long as it keeps its properties as a conditional (while the operator when is kept for fine-tuning; if you want a minimal conditional to which you can apply your own optimizations).
when is a sound but more barebones conditional. Implementing a pure factorial relation in when makes for a good case study.
when is a pure conditional. Still, since it requires you to manually negate the condition (which is redundant otherwise) and may not provide future optimizations, unless you implement them yourself, it's less preferred and not the default go-to for conditionals.
Remember that relations may backtrack or yield more than one solution. Even if a factorial relation succeeds the first time, an ill-defined program may cause on the second solution.
Let's compare this to,
when(x=0)y=1 else y=x*
... or ...
(x=0 and y=1) or (y=x*fact(num(x-1)))
fact(1,x) would still work, it would for the first solution alone. Remember that relations may backtrack or yield more than one solution.
This would suffice were it a regular imperative or functional definition of factorial, since those are meant to run only once and never backtrack.
However, this is not so for a logical language and different reasoning is needed. If it ever tries to find a second solution, it will procceed to the second clause. The reader is invited to keep this in mind and accompany the execution of
when(x=0)y=1 else x>0 y=x*
As you see, we have,
What then if we simply removed backtracking?
choose is a non-logical conditional that doesn't backtrack.
As such, it's fit for code with side-effects. A simple prompt, for example.
print('type a number')io.
print('You typed 5!')else print('You didn't type 5!?')
Such code is non-logical in the first place- and has no need for backtracking.
The safest option is still to rely on if. It should never be unsafe to use if. For cases it can't be used, you'll invariably be given a warning or error at least. However, choose and when are available if you're aware of the limitations.