It looks like Oz [1] declarative variables could help implement a lot of this stuff. Essentially, its variables are pointers that can be assigned to only once. As such you can pass an unbound variable to a function and have it bound to produce a result.
You can also do "unification" between two variables, meaning that it will try to make the two variables equal to each other. Typically, one or both of the two variables hold a partial data structure - a data structure that references unbound variables - and it will binds some of these variables to make the unified variables compatible, if possible.
Which is not mentioned in my comment or in the wikipedia page :)
But I get your point. Oz is a nice language which suffers from poor marketing and lack of support. It's a really lovely language though. It's the closest take I've seen so far of enabling expressibility by using a small number of orthogonal primitives. That's basically all the "multi-paradigm" talk is about.
If someone is interested to seriously hack on Oz, I think Peter Van Roy [1] would be interested, esp. if you can pitch it as a research project somehow (easier to get funding). I can make introductions if necessary.
This is a genius writeup. My earlier limited experience with logic programming resulted in lots of confusion and "what's the use?". Because of this, I would probably have me not even click the link if the word "logic" had been in the title (and I suspect I'm not alone in that).
Instead, the author starts with the familiar concepts and very gradually and comfortably flows into logic programming concepts, making me go "hey! that's handy!". That's a feeling I've never had with logic programming before, and I didn't even give Cosmos a try yet.
Hats off! I'm definitely going to play around with this.
1. I might have gotten a bit carried away when writing some parts of the article.
2. "Functors" are the same as functors from Prolog. Note that Prolog lists are functors too. They also may enable the language to have an 'Option' type like in some functional languages.
2. I completely didn't understand how a functor can make a list. Note, to me, a functor is a C++ class that overrides operator(). I loved how your writeup allowed me to understand Cosmos concepts _without_ learning Prolog, so an introduction to functors (maybe a later blog post?) would be warmly appreciated :-)
What the designer calls "functors" just appear to be positional records (or labeled tuples). In Lisp, a list is a series of nested pairs with an empty list as the last element: `(a . (b . ())`. The data structures the designer calls "functors" are just labeled pairs. The Lisp empty list, `()`, is the Cosmos term `Cons`, the Lisp pair `(a . ())` is the Cosmos compound term `Cons(a, Cons)`.
I think I'm having an internet crisis ... I read your article, and examined your project, and I've spent the last hour convinced that Cosmos is a very smart joke.
Forgive me if this is insulting, because either way (real or a prank), your project is impressive.
Can you help me out? Otherwise, I'll spend all night thinking that nondeterministic monads are the new hotness and then I'll need to quit programming and start selling scones on craigslist.
Modelling nondeterminism with monads is a fairly standard use of monads. Specifically, you can model multiple possible results with a (lazy) list, and list is a monad. That's about it.
I %100 support this project: though I am no expert, I am a PL enthusiast and I have high hopes for the future of logic programming. It's also just great to see projects like this exploring the space of PL design. The more the merrier! That being said, I tend to side with those who find the jocular arrogance of the writeup irritating and off putting rather than charming. My 2 cents, fwiw.
Stylistic quibbles aside, I have some reservations about the choices you've made in diverging from Prolog.
Your use of 'functor' seems to be different from Prolog's. You are therefore adding a forth or fifth meaning to the term, will have to compete with those already given by of Carnap, Quine, category theory, and OCaml. You are, thus, further muddying the sense of an already over-loaded term (http://pinealservo.com/posts/2014-10-22-ManyFunctionsOfFunct...). You seem to use 'functor' to refer to labeled tuples, or positional records. Prolog 'functors' are rather the atoms used to label such structures, e.g., `p(a)` and `p(a,b)` are different compounds with the same functor, 'p'. As a matter of fact, Prolog's compound structures do also serve as labeled tuples, but that is only a symptom of Prolog's explicit evaluation and homoiconicity. In fact, Prolog functors are "function like" in that we can call a functor on arguments, thereby instantiating a predicate. Your "functors" seem to be inert, and I think it would keep things much clearer if you just called them "records" or something. 'Functor' makes sense in the context of Prolog, where labeled compounds can be both data structures and propositions/propositional functions; for in that case, the atom used for labeling also serves a role akin to the string 'sin' in the function `sin(x)`.
This brings me to my other reservation: one of Prolog's real strengths, in my estimation, is the combination of explicit evaluation, unification, and homoiconicity. That combination gives us many higher order behaviors for free and facilitates meta programming. Thus, explicit evaluation and data-as-code means the compound `p(a)` can be mapped over a list, giving us partial application of `p(a,X)`. We also get the equivalence of first-class functions, allowing predicates to be passed as arguments to other predicates. We are also permitted remarkably deep reflection, including unification-matching on predicates and arbitrary data structures: e.g., I can do `p(X,q(r,S)) = p(2+2, Y)` to get the assignments `X = 2+2, Y = q(r,S)` and then `Sum is X * 2`, to get `Sum = 8`, and `S` will still be free until bound to something later.
This is why we differentiate the unification operator `=/2` from the arithmetic evaluation operator `is/2` and various equality operators. Contrary to your assertion, `X = 1+2` works very well, allowing me to do `X =.. [F|Args]` to get the assignments `F = +, Args = [1,2]`, so I can choose to multiply the arguments instead (e.g., `Y =.. [*|Args], Product is Y` getting `Product = 2`), inspect he functor (in this case, `+`), or simply pattern match to get the values in X (`2+Y = X` yielding the assignment `Y = 2`).
As a Prolog user, I have to say, I wouldn't dream of giving up this flexibility just to save the trouble of writing commas for explicit conjunctions and gain the (dubious) convenience of an operator that conflates assignment and equality.
Lastly, as an SWI-Prolog user, it is disingenuous to ask "where is list.map or string.concat?", as I'm sure you are well aware that both of those predicates (and many more besides) are supplied in the standard library. SWI, great as it is, still wants for features, but basic operations like those are well provided for.
So, there's some criticism from an inexpert enthusiast. Truly, I am inspired by your effort and look forward to trying out your language!
It's really neat that someone is trying to make logic programming accessible, but the introduction and the first fews sections are really offputting. There are lots of highly performant new languages competing for attention, and statements like "Today I want to talk about programming. It is interesting to note that I have solved it. Yes, I solved it." will drive a lot of the potential audience away.
I recommend breaking this into two posts. One's a highly readable introduction to a new language (e.g. this post's 2nd half). The other's the author's opinions about the state of programming languages (1st half).
Well done for the technical work behind this project and the bravery for doing something about fixing problems you find in the world.
Specifically, I liked it because there's a huge number of opinions on programming languages and (as the author notes) people ranting against other people's opinions on programming languages. I think it's very valuable to say: I understand the ecosystem of opinions on programming languages and purport to be above that.
You know he's not serious when he says it is the best language (the word 'opinionated' is even in the HN title, though not the article itself?), but you know it means that he's trying to go beyond bickering over functional vs imperative, typed vs untyped, etc. It got my attention and started me out with a good impression.
It.. somewhat delivered. I don't know Prolog, which makes it harder to decide. Some of the opinions are very reasonable and reflect awareness of language-advocate-bickering (for example I agree that 'optional typing' is the only defensible way to handle types if you are trying to unify lots of preferences on languages). But non-deterministic variables, while cool, look like black magic to me and I can't judge their good-vs-bad-ness from this article.
rel p(s, x)
if(s = ‘a’) //this is sugar for [(s=’a’ and x=0) or x=2]
x = 0
else
x = 2
Why does it transform to
[(s=’a’ and x=0) or x=2]
and not
[(s=’a’ and x=0) or (s!=’a’ and x=2)]
(or whatever "not equals" is in Cosmos)?
In any language I know, "else" means "do this when the guard was _not_ true", and not "nondeterministically maybe do this other thing instead, if you want". Did I simply misunderstand it? (note that I got lost at the "soft cut" part, but did I get it right that "choose ... else" is closer to what I'd expect "if .. else" to do?)
Given that accessibility to people used to imperative languages is a core goal, I'm not sure that the choice of making "else" mean "yeah whatever" is the best one. Maybe it's better to replace it by a different keyword entirely ("if.. or.." maybe). On a related note, just in case you're not familiar with it, I believe that maybe the "if.. fi" construct in Dijkstra's Guarded Command Language[0] is an easy way to get imperative-thinking people to embrace nondeterminism. It feels a lot like normal imperative "if", except there is no "else". If you want "else", you need to add another guard explicitly, with the negation of your first guard. This way, it's easy for people to grasp that the order of the guarded commands have no meaning. It also makes introducing nondeterminism very explicit.
You're thinking imperatively. It is not "do this" but "generate the set of possible solutions".
The "soft cut" part I'm presuming is similar to Prolog, where a "cut" indicates that once you've gotten past it, you can't backtrack (roughly - my knowledge of Prolog is rather limited) to try other arms - so "choose" is forced down one path by the guard, and then not allowed to also evaluate the other arm, while "if" can generate both alternatives.
"if" here isn't a perfect analog to a traditional imperative language's if statement.
A better set of keywords to describe the logic might be "when ... allow". Because there is an implication here, it's just in reverse.
Because the relation finds all possible solutions that meet the relationship, this code really reads "when s is the letter a, allow x to be 0, and call that a solution. Otherwise, any pairing where x is 2 is a solution."
So if you were to call p(s, 0), you would find that you only get one solution, where s must be 'a'. From an imperative-ish POV, that "sets" (constrains) s to 'a'.
OP is using if, combined with the notions that you take only the first of all relation solutions, and that you treat the rightmost relation member as an output, to achieve an imperative-like behavior in a non-deterministic logic language. Which is mentioned in the paragraph following the example.
rel q(y)
y = true
y = false
rel p(s, x)
if(s = ‘a’)
x = 0
else
x = 2
rel main()
k = q()
if(k)
s = 'a'
else
s != 'a'
p(s,x)
println(x)
Actually, I'd like to know what Cosmos would resolve this to as things stand. Is it just going to decide that | x = 0, | x = 2? Also can I throw probabilities into this code?
One property of Cosmos is that there is no boolean type and relations are themselves booleans (q() would be false in this case). Hence, the code would be rather like this:
rel q()
true
false
...
rel main()
if(q())
...
Indeed, having an 'else' that negates the condition would get tough for complicated conditions. Negation-by-failure and soft-cut ('choose') have their own problems in certain circumstances.
Yeah, it's going to decide that | x = 0 | x = 2 (there is no CLP for strings).
I'm not sure either. But it could be a ruby style and, and a mix between assign and equal operator.
Then
(s='a' and x=0)
would mean: if s=='a' then set x to 0, but if s != 'a' he would never jump to x=0, since and does stop when the result before is falsy - it is following the logical and behaviour.
The or would prevent the x=2 to be executed when the prior block is true, one true is enough.
Well, I've been writing Lua code for a few years now and I think that the ways you can go right with Table usage far outweigh any liabilities that newcomers to the language will suffer as they learn to do powerful things with the simple features. As long as you know what modules, arrays, or maps are, and what uses they can provide, then you can't really screw things up too much with the Lua table. But of course, that's expecting a lot from a modern language user ..
I don't disagree with any of that. I just think that combining concepts doesn't necessarily reduce the number of ways things can go wrong (even if, as you say, it increases the number of things that can go right as well).
For an extreme case of what I mean, consider Church numerals.
Well, tables can and do often "go wrong", but alas the success-case is rarely accounted.
I would warrant that there must be something to them, or else we wouldn't be discussing them as persistent features of a social phenomenon, which is something I think we all take for granted about software language: it is entirely social.
Whereas your extreme case is mathematically derived, our social habits occur because of decisions.
"My other car is a cdr" is an old joke. It is a play upon a kind of humourous bumper sticker ("my other car is a Ferrari") which was popular at one time.
Does Cosmos have any characteristics that would allow easy JIT speed optimizations? I would love to see language design go green by allowing easy optimizations that help us save both our valuable electricity and our time.
Are there design patterns that would allow us to more easily enable JIT for performance or even multithreading or GPU usage? V8, FTL and SpiderMonkey have all significantly sped up javascript performance making for much more efficient execution and better user experience. Python and PHP seem harder to adapt to JIT compilers as Facebook keeps working on HHVM for PHP and JITs for Python like PyPy lots of compatibility issues with existing libraries.
p.s. I for one really appreciate the entertainment value of exaggerated and opinionated style as long as the author realizes it is partly for entertainment, calls it out in the title and clearly isn't taking themselves too seriously. I already read enough serious and earnest tech articles...
Why do so many programming languages have common words or single letters as names? Doesn't that make it difficult to search for discussions on these languages? Why not Kozmoz instead of Cosmos?
It is cute, but you can't design a language for toy problems or you run into problems with bigger software.
I kind of stopped reading here:
"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!"
Uh huh. Try doing that with a nontrivial game that needs to be performant, and let me know how that works out.
I should clarify before people respond antagonistically ... as someone who is designing a programming language, I think it's great any time someone is making languages and trying out new ideas. That part is great, if you want to make a functional imperative declarative loosely strongly typed language then hey, go for it.
But I think one has a responsibility not to try and sell one's project as something it's not, which especially means being careful with claims. I know this is sometimes hard because a lot of language design stuff comes from the academic community which notoriously overclaims (because it is their job to overclaim), and it is easy for that culture to rub off.
But if you are going to say something like "just by doing this our code becomes 200% better", with a straight face, about something that most practitioners know is going to be terrible in most cases without a tremendous amount of additional work and solving of unsolved problems (solution not shown), you're just telling the reader that they can't take you seriously. It's a bad thing to do.
> But if you are going to say something like "just by doing this our code becomes 200% better", with a straight face
I think this is something a lot of people here are having a hard time with: The article is not being said with a straight face. The unsolved problem of elegant I'm-holding-back-a-funny-face font technology has confounded the Internet for forty five years and still counting.
I kind of like the style. One shouldn't ever read an opinion piece seriously anyway, he's the author, he's going to be telling you it's great. Of course it's not going to be 200% better, just like it's not the best programming language ever designed.
To me it feels like he's trying to open up the audience a little, if he's enthusiastic enough, perhaps people will go "this guy is so crazy for it, maybe there's something there". I mean it is a tough sell. A semi-functional programming language with optional types based on SWI-Prolog of all things? I bet he hasn't even solved how to deploy and run prolog in a transparent manner yet. This thing is going to be tough to polish.
On a side note: I'm super curious about your programming language, do you have anything on paper yet? I'd love to see even a short blog post of what the language looks like in your mind right now.
I'm working on a language for games as well, sort of, I call it Nancy, or Non-ANSI-C. It's basically C (literally using the Clang compiler) modified to have less inconsistencies, replacing header files with modules, and perhaps a little bit of structure. In my opinion games don't particularily need all the fancy abstract features of C++, they just need the bare metal access C gives, plus easy access to libraries and a good way to interact with an entity system. That last point isn't quite designed in my head yet, but I think it's a critical feature for game development. Every game project has their own entity management system, you either design it yourself, or fight with one someone else designed, they're less trivial to implement then you initially think, and the syntax of the language never cooperates. I think it would be cool if there was a language that was designed specifically to accomodate CES/ES.
(Jay for jumping off-topic to force my opinion onto internet celebrities ;) )
A fun paper, but it neglected to explain its means of modularization. I had to read the language implementation to discover the 'require' keyword used to import functions.
Michael Hendrix described Mercury as Haskell + Prolog, which is accurate, I think, and an indication that Mercury is pretty far removed from what Cosmos is trying to do. (I'm more interested in Mercury). Cosmos looks like it wants to be a multi-paradigm scripting language, whereas Mercury is aiming for logical purity (no cuts), strong and static typing, and high speed (...for a Prolog). I think Picat (http://www.picat-lang.org/) or Ciao (http://ciao-lang.org/) are probably closer matches to Cosmos.
Am I correct in saying that Cosmos is both Turing complete and incomplete? In addition, in the world splitting example, does this result in a fork or exec?
I wrote quite a lot of TurboProlog code for a while some 25 years ago. I always wondered why there were no syntax sugar for nested 'or' Horn clauses.
Now it's fixed. Cool. So is the rest of Cosmos syntax sugar, cool, kudos.
I guess some OO would be a nice addition. And, that's the thing of the day: an efficient JS transpiler and a decent IDE with a stepping debugger. That's asking a lot. I know.
Prolog is neat. There is probably a Prolog embedded in some of the C++ type checkers. The problem is that the world at large is often in a flux and can't be conveniently described in terms of static relationships, which makes Prolog programming at large challenging to do (or you augment Prolog with features that make it more or less imperative).
Any article that intends to explain why logic programming is the One True Way without covering "how to prevent super-exponential backtracking from eating your face" is missing an important part.
So, you came out pretty strongly for strong typing. But you didn't include anything about creating new types. Did you not want to focus on that in this article or...?
Is it just me or does this resemble visual prolog quite a bit? Though the syntax of Cosmos seems nicer, it seems as if most of the ideas are implemented in visual prolog.
If your justification for creating a new language is "I made snake in a bunch of different languages!", I am extremely skeptical.
And, as is often the case, it is impossible to have a universally beneficial compromise in programming language features. There are some things you simply cannot have if you don't lean strongly one way or the other, and you end up with a mediocre in-between with none of the benefits of either extreme. Case in point: type systems and the ability to compile to tight native machine code. In a dynamically typed/untyped language, you need to have multiple layers of indirection.
It's a pretty overloaded term. I haven't looked into the Cosmos semantics much, but since it's mentioned in the "logic programming" part of the language, I suspect it's referencing the Prolog sense of the term: http://www.cse.unsw.edu.au/~billw/prologdict.html#functor
So I think that there are a lot of good ideas in here. Unfortunately, since I've never seen an application written in your language, I have no idea whether it's good or not.
Lemme tell you about TI-BASIC (for the TI-84). That stuff was the bee's knees. One day I wrote a really slow loop to translate whatever key was pressed into a number if the key corresponded to a button on the number pad. [1]
Way too complicated, way too slow for an interpreted game program on a 6MHz Z80. But TI-BASIC had some cool higher-order functions! It didn't even have to be a loop at that point, check out how succinct it became:
var codes = [103,92,93,94,82,83,84,72,73,74],
pressed = getKey(),
digits = [0,1,2,3,4,5,6,7,8,9];
var output = sum((codes == pressed)*digits);
(Yes, this isn't "really TI-BASIC" but that stuff is a pain to type on a computer. Sorry.)
How? `codes` is an array, which, when tested against a scalar, produces an array of 0 or 1 to indicate false or true. These, multiplied by digits, produce an array of 0 and a number that is pressed. The summation gets that number because adding by 0 just returns the number. (Also, getKey can't return more than one key at a time.) And because the loop happened inside the operators, the loop was run in assembly, which made it about 30x faster.
This is why I believe that TI-BASIC is the best programming language. (I don't, really. But I'm more believeable than the unexplained Functor malarkey and how it can somehow put me into outer space ;)
You can also do "unification" between two variables, meaning that it will try to make the two variables equal to each other. Typically, one or both of the two variables hold a partial data structure - a data structure that references unbound variables - and it will binds some of these variables to make the unified variables compatible, if possible.
[1] http://en.wikipedia.org/wiki/Oz_%28programming_language%29