A Treatise on Cosmos — the New Programming Language

McAlias
McAlias
Nov 17, 2014 · 13 min read

Alright, guys. Today I want to talk about programming. It is interesting to note that I have solved it. Yes, I solved it. Here are my credentials.

  • I read a lot of programming flamewars and I got aware of the many arguments on why someone’s favorite language is the best language.
  • I made a game in roughly each language. Something simple like Snake (with time-travel) or Chess (with time-travel).

Yes, this does mean I read a lot of Lisp flamewars. It was painful but I read through the flamewars out of my own free will. I wasn’t even forced or paid to do it and I still did. Yes, this does also mean making games in Prolog. It was after doing all this that I had a revelation and designed the ultimate programming language: Cosmos. You can follow the link to see a rough prototype of Cosmos and yes, the prototype is written in Cosmos.

So let’s address the important question that you must be asking yourself: why is Cosmos the best programming language?

Typed x Untyped

65% of programming flamewars concern themselves with the dichotomy between typed-ness and untyped-ness. One of the arguments against types is that they make the language less flexible and more verbose; this makes untypedness great for prototyping and exploratory programming. Many scripting languages have variables that can freely receive any type of value, which adds to the flexibleness.

A second argument against types is that you can achieve the same as typed languages by being very disciplined. This argument is fishy though. If I used a typed language then could I skip 80% of the discipline and get the same result? What if I used the same discipline in a typed language, would the result be two times better? Can’t I use that argument to defend Cobol? If anything, the argument shows why typed languages are good: types are useful in creating scalable code.

That’s why our language adopts a superior third path: optional types.

Many language designers may be familiar with the concept of optional types. It’s a very intuitive notion. Prefer untyped languages? Sure, don’t write the types. Prefer typed languages? Okay. Prefer going untyped at first and adding the types later? Alright.

Note how type errors are very clean and informative in Cosmos. The only non-intuitive thing is the use of “Relation” instead of “Function” (more on this later). The first parameter of math.inc is an Integer and it returns an Integer, thus it is a “Relation Integer Integer”.

Minimalism x Huge Mess

There is this idea that being simple is bad. After all, simplifying something means dumbing it down. Simplicity and power are opposed, which is why every programming language should seek to be a huge mess; that way it will end up powerful. Right?

I want to make clear that I’m not in favor of dumbing things down. I’m not for making things needlessly complicated either. I’ll draw a diagram to explain this.

Naturally, “complex and non-expressive“ is the worst spot to be. In an ideal world, we want to be in the top-right spot with the red circle. Is it possible to be there?

It is. In fact, programming languages should strive to be more simple. Let’s have a look at the Lua programming language as an example.

It has tables. They are similar to objects in JavaScript: basically, they are hashmaps. The thing is, you learn once about tables then you can do anything. They have uses as hashes, arrays, modules and more. (The #1 trick of minimalist design is to have a few general features that can do a lot of things instead of many features that do one thing. If you only have a single feature then you only need to learn one thing and it can only go wrong one way.)

Furthermore, the syntax of Lua can be learned in a day: there aren’t dozens of syntax rules that shorten code in specific situations that rarely ever occur and it works fine. (This is an extension to the #1 trick.)

Note that our priorities are different. The creators of Lua wanted to be pragmatic and not delve into functional programming too much. Since Cosmos delves into functional logic programming it includes another data structure (namely, functors).

These are the concepts which are deemed fundamental to Cosmos:

  • Types.
  • Tables.
  • Functors.
  • Relations.

The #2 trick to minimalism is: “everything should be as simple as it can, but not less.” Each of these features adds a lot to the language. The language would lose a lot if you were to remove any of them. In addition, each feature is used as the base for other features (that is the #1 trick again). While useful by itself, functors are used to make lists. Tables (being similar to JavaScript objects and Lua tables) can be used as the base for modules and objects. Relations — as you will see later — can do the job of functions.

Functional Programming x Imperative Programming

There’s a lot of discussion over whether to use imperative or functional programming. Functional programming is less mainstream but it’s quickly becoming “the new hype” — imperative languages are getting functional features and functional languages are getting more popular. Languages are getting more and more functional over time.

If I could sum it up, and I will, there are two sides to this:

  • Functional programming sounds fine in theory but in practice it’s easier to get things done writing imperatively. It also has weird syntax and is hard to understand. People who focus on creating content and don’t care about language wars shouldn’t bother with this. It sucks!
  • Many features from functional languages do help on programming and creating content! In fact, I have used functional programming in practice. They ensure that the code stays compositional which avoids bugs and makes your code scalable in the long-term, specially if you have optional types. Optional types are the best.

Once again, we take a third path. We want to explore declarative (i.e. functional) languages. We are also interested in making declarative languages more accessible to the average programmer. How good is functional programming, then? And how is our language more accessible?

One reason is, functional programming is more expressive. It can do more neat stuff. For example, you can use list.map to double a list in a single line.

l = list.map([1,2,3], math.double) //l is [2, 4, 6]

This code makes use of higher-order functions (functions can be passed as parameters since they are just data like integers or lists).

Second, it has a feature known as immutability. You can never modify a variable; whenever you would modify a variable in an imperative language, you create a new one here.

Table t = {}
t2 = table.set(t, ‘x’, 1) //t = {} and t2 = { x = 1 }

Consider this code, where we assign the value 1 to a field x in the variable t. Doing this creates a new table t2 and t stays unchanged.

An imperative programmer might get befuddled over why we did this. Well, that’s because by being a bit more disciplined and doing this thing our code becomes 200% more reusable and compositional. A function that does this is called a pure function. Because imperative programming rarely does this, it is full of rigid frameworks and code that “only works once”.

Want a practical example? Here is the main loop for some application (such as a game):

rel main(w1)
w2 = update(w1)
display(w2)
main(w2)

Since it is declarative code, update returns a new world w2 instead of merely modifying w1. The funny thing is, just by doing this our code becomes 200% better. For example, you can now modify the code to store all world states in a list!

rel main(w1, l1)
l2 = list.push(l1, w1)
w2 = update(w1)
display(w1)
main(w2, l2)

Maybe I wanted to quickly prototype some sort of log that records all moves in my Chess game. Maybe I wanted to add a Prince of Persia-like time travel (all that is left for this is to pop the list). It’s ridiculously easy to do that now.

Maybe I didn’t want to do these very specific things… normally main is the outtermost function so you tend to leave it like that. The point is that every function in the code, not only main, is as flexible and compositional as this (as long as it follows functional principles)! Every function can be used as the base for a new function and you can be sure that it won’t break something in another part of the code. We really can do anything. The sky is not the limit in Cosmos. We can go beyond into outer space.

rel main()
w2 = update(w1)
w3 = update(w1)

This code would crash badly in an imperative language. Not so here, we go and split the program into two. Yes, we just split the world into two like it was nothing. Because we can.

This is not false marketing guys. You really can do anything with Cosmos ALPHA.

Imperative Programming

rel main()
x = io.read()
io.writeln("hello, "+x+'!')

Once more, we have taken a third path that combines the upsides of each path. The language is semantically declarative… but it looks imperative! Syntactically, it is a non-verbose scripting imperative language.

Sometimes it’s just easier to think in imperative terms. That’s the idea here, we want to allow imperative thinking and still have the code come out declarative.

Or rather, a problem with most functional languages is that they require one to learn a whole new exquisite syntax along with the new paradigm. This makes it hard to see that imperative and functional programming is, shockingly, not that far apart. All it requires is, again, that we modify a variable instead of creating a new one.

//this code is declarative, although you might 
//have seen similar code in imperative languages
rel double(Integer x, Integer y)
y = x*2

(Syntax makes it hard to see that logic programming and functional programming are not that far apart either, as we’ll get into later.)

It would be quite shallow to say our language isn’t “truly” declarative because of the syntax. It is quite clearly declarative and not only because it has list.map. By contrast, many languages get described as “functional” but you can’t really do functional programming in them since they lack immutability, the most important part of functional programming.

Many people are alright without this; there is a following for Haskell, which could care less that its syntax is optimized for Ph.D. research rather than for the common programmer. For a few people, that syntax simply “makes sense” to their mind and they can effortlessly program in there. It works for them. However, this is not the case for everybody (or most people, for that matter), which is why it makes sense to have a functional language that can be picked up by everybody. Of course, being an easy-to-use functional language is not the only innovation that Cosmos brings.

Logic Programming

I have mentioned Prolog, right? Prolog is famous for introducing a whole way of thinking about programming that is different from the functional and imperative way. Plenty of novel features were introduced in Prolog, although programming at large mostly forgot about it.

Functional programming was once regarded as “dead” and yet it’s getting increasingly more space, isn’t it? It makes one wonder, when functional programming gets widely accepted will logic programming start gaining space? Will the pattern repeat again? Is Prolog the future of programming?

The answer is yes. Lucky for you, you don’t have to wait until the year 2045 of the future. We have already anticipated the future of programming and brought it to you in the form of Cosmos ALPHA! (I was lying when I called Cosmos a functional language; it is a logic-functional language.)

So… what is the deal with logic programming?

First off, it deals with relations rather than functions.

One characteristic of relations is that input and output are interchangeable.

//note that the there is no ‘return’ in the definition
//instead, the parameter y is explicit
rel double(x, y)
y = x*2
rel main()
double(3, y) //y is 6
double(x, 6) //x is 3
io.writeln(‘the double of ‘+math.integerToString(x)+’ is ‘+math.integerToString(y)) //the double of 3 is 6

As seen in the code, any parameter can be used as the output. It’s even possible to have multiple outputs.

rel p(x, y)
x = 1
y = 2

rel main()
p(x, y)
io.writeln(x, y) //1, 2

Both x and y are output parameters in this example.

rel p(x)
x=1 or x=2

rel main()
p(x)
x!=1
io.writeln(x) //2

Another important feature of logic programming is nondeterminism. Ever seen one of those documentaries and youtube videos on quantum physics where a particle can be in two places at the same time until you look at it? It’s basically the same thing except with code. As we see in the code, the x in p(x) is equal to 1 and 2… at the same time! Well, until we wrote x != 1 and it becomes 2.

As you see, relations may give many answers when called. p(x) gave us two answers. A relation may even give zero answers.

//this relation is "false", as it gives zero answers
rel p(x)
x=1 and x!=1

A few imperative languages have tried to imitate some of this by adding features such as generators and tuples, albeit those tend to be less elegant than simply having relations. Note how we can activate nondeterminism with a single or keyword.

What does Cosmos add to Logic Programming languages?

Rather than simply assume that “logic programming is dead”, it’s interesting to think of why it isn’t widely used and how this could be changed. Cosmos is what a logic programming language with modern sensibilities would be.

Prolog is a great language on its own — it has a following and gets frequent updates. The core of Prolog is incredibly minimalist. The current version of Cosmos compiles to SWI-Prolog. However, if you look on Prolog papers or experiences of beginners with Prolog, you’ll see that there are pitfalls. Also, Prolog is old and as such has accumulated a lot of baggage over time.

Help UIs are neat though!

X = 1+2 does not work, it has to be X is 1+2 (a few imperative languages commit a similar sin by having both == and === when only one symbol would suffice). not is deprecated to \+. Most of the utilities you’d expect to have by default must be programmed manually: where is list.map or string.split? These are issues from an usability standpoint and there are plenty of these.

A problem often cited in Prolog books is that Prolog is not logical. A beginner might expect negation to act as it would in logic (specially since the syntax of the language is inspired by predicate logic) and get confused. The cut is a feature that is particularly illogical: that one is completely removed from our language since soft-cut does it better.

One of the goals is to turn Cosmos into what Prolog was expected to be: a very high-level language where you literally program in logic. For instance, the following will work thanks to Constraint Logic Programming (CLP):

rel main()
Integer y
Integer x = y+2
x = 3
io.writeln(y) //1

Some illogical features (e.g. soft-cut) or ways to turn off logical features (e.g. using RawInteger to disable CLP) may be allowed but the language should act logically by default.

Of course, everything fits into the “looks like an imperative language” shtick. As an example, logic languages don’t have a “IF-statement” per se. Well, we do but IF is only a syntax sugar.

rel p(s, x)
if(s = ‘a’) //this is sugar for [(s=’a’ and x=0) or x=2]
x = 0
else
x = 2

rel main()
once p('a', x)

(There is no difference between “statements” and “conditions inside IFs”, which reduces the core syntax.)

Be aware that this is logic code. It will behave “imperatively” as long as we take proper measures (keep the ORs inside of IFs, restrict to only using the last parameter of a relation as output etc.) and only pick the first answer (remember that logic relations may give many answers due to nondeterminism). In this example, we used the keyword once to pick the first answer.

The fact these two syntaxes are allowed is also interesting:

$cosmos -i
> x = math.inc(1) //”function” syntax
| x = 2
> math.inc(1, x) //”logic” syntax
| x = 2

The soft-cut (choose) has a syntax similar to the IF — although it’s not syntax sugar.

rel q(s, x)
choose(s = ‘a’)
x = 0
else
x = 2

rel main()
q('a', x)

Soft-cut “chooses” a single branch, hence we didn’t need once. IF goes through all branches.

$ cosmos -i
> p(s, x)
| s = ‘a’ and x = 0
| x = 2
> q(s, x)
| s = ‘a’ and x = 0

Semicolons and Whitespace

Yes, Cosmos is whitespace sensitive. You may be wondering why.

  • This makes sense from an usability perspective. Everything else remaining the same, not having to type redundant characters is objectively better.
  • Everything else is pretty much the same.

There is no downside to this, except perhaps if the language goes too overboard in the complexity of the whitespace rules — thus going against minimalism — or in the rare occasion that your editor is bugged. Cosmos remains minimalist and bugged editors should get fixed either way.

The upside is that since you don’t have to worry about matching braces or forgetting a semicolon you have less things to keep track in your mind. This is not much by itself but combined with all other design decisions and Cosmos’ minimalist syntax it makes for a very smooth programming experience.

  • That is not the only reason why the language is whitespace sensitive. There is a deeper reason.

Consider the following code, written in Prolog:

p(X) :- X=1, true.

Prolog has a peculiar quirk to its syntax. “Statements” are separated by commas (they are not really “statements” since this is a logic programming language but you get what I mean) and the entire body ends with a period rather than being delineated with braces. How, then, would we convert this code to a more imperative-ish syntax such as Cosmos’?

rel p(x) x=1 and true;

Due to being a logic programming language, Cosmos shares its semantics with Prolog. While useful to know, it’s convenient to visually abstract this quirk. It would be very annoying to type all the ANDs, too. This is where the whitespace rules come in.

rel p(x)
x=1
true

What if we dropped the whitespace? Then we would have to write all the ANDs and that would get tiring. Why not replace and for a semicolon to solve this? Well, semicolons would look weird in the insides of an IF (if-statements are syntax sugar thanks to logic programming, remember?). Why not add multiple aliases for ANDs or add more syntax rules? Well, that would add unnecessary complexity to the language and it works fine as-is.

It is then that you realize that Cosmos is more than the sum of its parts. The whitespace-sensitive syntax, minimalist design philosophy and logic programming support all combine into a coherent, cohesive whole. A cosmos, if you will. Upon this revelation, you finally realize the unescapable truth: Cosmos is the best language.

    McAlias

    Written by

    McAlias

    An egg, A+