So You Want to Write Your Own Language (2014)
drdobbs.com
drdobbs.com
I think there should be other metrics for success: a programming language can be useful to a dedicated community of experts, it can be useful to a single company, it can be great for a single use-case or even just great for you, it can push novel ideas or showcase a novel design philosophy, all without massive popularity or market success. (And it's not like most languages directly make lots of money anyhow.)
Popularity is a poor proxy for quality, and quality matters all on its own.
Keeping this in mind changes a lot of his suggestions, I think.
Completely unrelated note: what the article calls "lowering" I've more commonly seen called "desugaring" and is, indeed, a powerful technique both in terms of implementation but also in terms of understanding a language—if a piece of syntax is pure sugar, you don't have to understand anything new, just how the syntax it desugars to works.
"expansion"?
".... syntax it expands to"
The difference is minor: it's basically "does the code walker specially recognize this construct and invoke some code to expand it" versus "does the code walker recognize a macro call and invoke it to expand it".
;-)
https://en.wikipedia.org/wiki/Modula-3
...knows that one can have key features of C++ for systems programming with an easy-to-parse, safer, and [for a time] more efficient language. The SPIN OS team even modified it with type-safe linking so one could hotload code into the OS. What I saw there was "hotload code into running system" w/ associated benefits during updates. Or safer, better JIT. ;)
Anyway, combined with LISP-style macros & irritating parts removed (eg uppercase, verbosity), you get a system language that blows C++ away in productivity (esp w/ DSL's), compiler efficiency, maintenance, and possibly run-time efficiency. The world might have went in a different direction but they better be honest about parsing pain they created. And all the productivity opportunities lost due to that.
Though I'm not sure if that falls under syntax or semantics for purposes of this discussion.
Confirmation bias.
In this case, "easy parsing" is OK, but not when it conflicts with "tried and true".
Choosing Lisp as your "tried and true" is acceptable, though...
The difficulty of parsing Ruby's syntax (both for machines and for humans) comes from some unfortunate design decisions, not so much from its vastness. For example, using the same symbols, {}, to denote hash literals and blocks makes parsing/understanding the syntax of method calls more difficult:
def foo(*args)
puts "args: #{args}, block_given?: #{block_given?}"
end
# Parentheses can be omitted when calling methods:
foo # args: [], block_given?: false
foo 42 # args: [42], block_given?: false
foo 42, "bar" # args: [42, "bar"], block_given?: false
# *Except* when the first argument is a hash. This:
foo({}) # args: [{}], block_given?: false
# Means something completely different than this:
foo {} # args: [], block_given?: true
# But if the hash is not the first argument then it's OK, you can omit parens
# again :3
foo 42, {} # args: [42, {}], block_given?: false
This is not a problem of having dedicated syntax for blocks and hash literals. It's a problem of having chosen conflicting syntactic forms for them. If blocks could only be denoted with do/end, or with a different set of symbols than hash literals, this would not be a problem.And this is just one example of conflicting syntactic forms. In Ruby, sadly, there many cases of this. E.g., using * both for multiplication and for argument "splatting" means that
a * b
is not the same as a *b
when `a` is a method, but actually is the same when `a` is a local variable; or `foo "bar"` and `foo"bar"` being two perfectly valid ways of calling foo with "bar" as an argument, but `foo [42]` and `foo[42]` not being two perfectly valid ways of calling foo with [42] as an argument; and probably more.So, as long as syntactic forms are carefully chosen so that they don't have weird and conflicting edge cases between each other, i think a language can have a rather big syntax without it being difficult to parse/understand.
Disclaimer: i love Ruby; it's my bread-and-butter language. But i wish it didn't have as many syntax quirks as it does :)
(And sorry for not having replied earlier, i seems HN doesn't notify about replies any more.)
The point i was trying to make was mostly about the difficulty of understanding the syntax (i.e. "parsing" for humans), not so much about syntax ambiguity or having to use a more sophisticated parsing strategy. The comments on the code represent the "rules" that a human might follow to try and make sense of that syntax. They are not as simple as they could be had Ruby only allows blocks to be denoted with do/end.
However, about the complexity on the parser itself, doesn't this case of block syntax need at least some dedicated logic to take care of ´foo {}´ not parsing the {} part as a hash literal expression but rather as a block? (e.g., maybe adding some explicit priories to the parser rules, or making the lexer add hidden parentheses so that the parser sees ´foo() {}´ and doesn't have to make that distinction).
Simpler general parsing (eg Lisp) is more powerful, but that's a negative for learnability of the language.
The syntax of older-style BASIC, for example, hews close to the assembly parsing model, just adding a few special cases for different keywords. Algol syntax has a tradition of design based around algorithm description first, with generalized power second. The Forth or Lisp approaches, because they are very generic and powerful, naturally pull those languages in the direction of extensive metaprogramming.
In that sense one can see that the more sugary syntaxes treat the programmer more like an end-user, because they direct the workflow. Generalized ones encourage a DIY approach to everything. There are tradeoffs to each strategy.
I wrote an essay on a layered, bottom-up approach to constructing such a language here if anyone is interested:
https://www.schneier.com/blog/archives/2014/05/friday_squid_...
I'm also interested in feedback on my approach. It tries to solve probably a dozen problems at once with some basic principles and three, compatible languages.
Matter of fact, I think my default was to go to ASM with plugins for other stuff. That should prevent missed opportunities in optimization.
If you can eliminate all ; in most, or even all, circumstances in otherwise C-like syntax, what do you gain?
And most of the savings you see there are from having greater levels of abstraction available.
Assembly = arithmetic. You have to explicitly state each step, and on its own there's no understanding of what you're operating on, they're all bits (numbers).
C++ (ok, more C) = algebra. You have a bit more understanding of your structure, better names and concepts behind things (types = units, for instance). You're still operating on bits, but now most of those bits have a specifically, language encoded, meaning.
Haskell = calculus. Not only are your bits typed, so are your functions (ok, technically also typed in C and kin syntaxes, but not usually as useful) and you can pass them around and modify them.
EDIT:
I'm more picking on the class of syntax available.
When you include semantics OO languages offer a great deal more expressive power than most procedural languages. Probably should've just had C and not C++, but it is what I wrote.
More generally what I should have written was towards language classes: assembly (as most primitive procedural language), structured procedural languages (C, Go, Algol, Ada, etc.), OO languages (Java, C++, Smalltalk, etc.) and functional languages (lisps, Haskell, ML-family, etc.).
But then you have to break it down further. OO is not inherently more or less expressive than functional languages, it depends on the feature set of the OO and functional languages. Smalltalk is very expressive, easily comparable to the more expressive functional languages. Java, as it used to be, not familiar with developments post adding generics so I'm really rusty on it, was not so expressive. So a comparison between Smalltalk and Common Lisp would put them on a similar level of abstraction. But Common Lisp versus Java (again, with my outdated knowledge), Common Lisp is at a higher level of abstraction (both syntactically and semantically).
It also doesn't mean you can't, just that you shouldn't use it as a/the primary concern.
I seriously doubt the validity of this. 10 lines of assembly is usually reasonably clear-cut what is about.
10 lines of Haskell can take hours of mindfuck trying to peer through the functor and monad operators, trying to work out which operation does what and which operator takes precedence where. And then you need to start worrying about which data-type are you actually working on. And how does that type implement lift and bind. Etc etc.
Basically 10 seems like a BS number taken out of thin air, because it looks good in a base 0x0a number-system.
10 lines of assembly is very clear about what exactly is happening. It's a sequence of adds, loads, branches, stores, moves, etc. Each step is incredibly clear, but what are they operating on? Now it's not so clear. What is R2 at this point in the program? Am I calling a subroutine? On this architecture is there some way to automatically handle this with storing the PC and everything, or am I going to use a regular branch instruction? Regular branch? Ok, now I'm storing the PC and other information away per our calling convention.
C: Oh, I'm calling a function. I'm calculating a formula. I have named variables, so it's clear what I'm operating on (hopefully, this is at least possible here, even if not well done in practice). With typedefs, good naming conventions, structs and enums, you can maintain an order of magnitude greater complexity in the same time as an assembly programmer, in an order of magnitude lower amount of code.
Haskell: I have functions, I have types, I operate over these types transforming them from one to another. Collecting objects of a type here, transforming them there, zipping them with those, and back again. And I've written a function that would take dozens of lines of C or grosses of lines of assembly, and it took me an hour. Again, an order of magnitude more complexity can be entertained in another order of magnitude less code.
Don't get stuck on the specific number, that's incredibly pedantic and unhelpful. It's about the degree and magnitude.
Please stop. Can you even come up with one example that supports this? Alright, now how about 10 lines from a "real world"* Haskell project.
*real world meaning has at least 1 user besides it's developer
Abstraction also has a cost. You can cram a lot into 10 lines, but if you have to think a long time about the most elegant way to do that, you might not be winning in productivity.
NB. Initialize a global, but not if it's already been initialized
NB. Example: 'name' initifundef 5
initifundef =: (, ('_'&([,],[))@(>@(18!:5)@(0&$)) ) ux_vy ((4 : '(x) =: y')^:(0:>(4!:0)@<@[))
From the bottom of http://code.jsoftware.com/wiki/Phrases/Language_Utilities . NB. appears to be the comment marker, so the last line is the payload.Yeah, I picked the worst thing I could find, and my guess is that that code is doing something really nasty hacky, because it is too large to be doing only what it is described. On the other hand, the fact that I pretty much have to guess with almost no information is sorta my point.
There's creating a computer language to be concise and readable, and then there's just concise. You can also have a look at Perl, which can (and generally should) be programmed as a fairly normal dynamic object-oriented language, but has a subset of functions, operators, and implicit variables that allows you to spit out terribly small, dense lines of few characters that do horrible things, usually not just doing whatever they are putatively doing but also having other mysterious side effects in the process.
[1]: I'm reasonably confident I'm on the right track here, but don't want to put words in his mouth, so I'm hedging. Especially since there's a non-zero chance the original author will show up here. As I type this, that has not happened yet.
Likely in reference to https://en.wikipedia.org/wiki/Nota_bene
It's the equivalent of static typing for syntax: it takes effort to make bison run successfully, but when it does, I know that my grammar is free of ambiguity.
Funny; I recently wrote an Island parser that extends Regex to create a token stream: https://github.com/musesum/Par
Quoting the link to Rob Pike's post:
> Consider finding alphanumeric identifiers. It's not too hard to write the regexp (something like "[a-ZA-Z_][a-ZA-Z_0-9]*"), but really not much harder to write as a simple loop
Maybe I'm missing something, but does this suggest replacing a declaration with a procedure? Oh the horror!
Disagreements:
I think minimizing keystrokes is important, and I think the notion that code is primarily read, not written, is idealistic but completely wrong. The purpose of programming languages is to be writable by humans and readable by machines. There is value in making them readable by humans and writable by machines, but only secondary value.
I think there is value in easy parsing, if nothing else because every single editor out there can highlight your language even if it isn't popular. I think the focus on popularity as the measure of success is wrong.
Tried and true is overrated. There are plenty of examples of programming languages that break the tried and true rules and are successful either because of this or despite this. Either way, it doesn't seem to matter as much as people seem to think, but staring too hard at the success of Java might make you think it does.
Anyway, feel free to disagree with me, go ahead and design your own language. I sincerely hope you are successful, regardless of what your definition of success is.
LinuxWorld.com: What is your advice to designers of new programming languages?
Dennis Ritchie: At least for the people who send me mail about a new language
that they're designing, the general advice is: do it to learn about how to
write a compiler. Don't have any expectations that anyone will use it, unless
you hook up with some sort of organization in a position to push it hard.
It's a lottery, and some can buy a lot of the tickets. There are plenty of
beautiful languages (more beautiful than C) that didn't catch on. But someone
does win the lottery, and doing a language at least teaches you something.
Oh, by the way, if your new language does begin to grow in usage, it can
become really hard to fix early mistakes.[0]
[0] http://www.itworld.com/article/2826125/development/the-futur...Anyone knows which languages/compilers the author has worked on?
(I'm not trying to validate its arguments, it's just out of curiousity)
Although i have never though of actually write a language i can agree on those. Especially Minimizing keystrokes. I want the language to be verbose and easy to understand. Dealing with thousands of line trying to figure out what is going on there it is essential to easily pick up what is going on !
So, you basically want to write in assembler, which printed is very verbose, and it's very easy to understand each separate instruction.
What I want is the language to minimize language entities used to do a task (from parser's perspective those would be AST nodes), and I want those tasks to be general and high-level. This is what makes source code comprehensible, not verbosity.
> So, you basically want to write in assembler, which printed is very verbose, and it's very easy to understand each separate instruction.
Please don't do that: You took what he said, jumped to the most extreme case, and then attributed it back to him. He never implied that he wanted to write in assembler. In fact, based on the part you didn't quote, I think he would not want to work in assembler.
> In fact, based on the part you didn't quote, I think he would not want to work in assembler.
I have hard time to understand what he meant in this sentence:
> Dealing with thousands of line trying to figure out what is going on there it is essential to easily pick up what is going on !
Is it a praise of having plenty of code for a simple thing? Or is it a rant?
Is it better to concentrate hard on a system of equations taking 1/4 of a page (with lots of whitespace) until you grok it, or read five pages of prose at fairly normal speed and try to build a mental model of it? Which better confers the ability to quickly come to the realization of something like "that's a parabola" or "the minimum of that function is obviously seven" or whatever?
Analogies are only so useful, but I think there are enough parallels there to shed some light.
It's two axes. J is not verbose, but is impenetrable (certainly to a novice, others more familiar see a lot of benefits to it). Ada is fairly verbose, but is pretty easy to understand. A page of Ada code is probably a handful of lines, mostly to aid in clarity, of J code. Though the Ada code has type safety that the J code lacks. Assembly, as someone else referenced, is very verbose, but hard to understand once you get beyond a certain scale.
But different programmers have different abstractions in their heads, and find different things intuitive. That's why things like ColorForth and APL exist; to a tiny fraction of people and problem sets they're intuitive, to the rest obtuse. I suspect this is why people keep thinking Lisp is going to take over the world despite 30+ years of this not happening.
I feel this is like reading 100 years ago about how to make your own new type of horse & buggy, with tips like "make sure the reigns are well-fitted," when what you want to invent is a car.
The future doesn't belong to syntax because the future isn't text. The same people that would blanch at sending a string argument of "ONE" or "TWO" are indifferent about the fact that they send in "IF" to start a condition.
The reason you wouldn't send in "ONE" or "TWO" is because it makes it stiffer, more brittle and harder to manage. What can we say about the software we write? That it's stiff, brittle and hard to manage.
It's not that the future has no textual syntax at all, of course. An expression of 3+4*5 will always be easiest to express just that way. But we need to pull back from our gregarious, wide-mouthed babbling into something more meta, or I should say, more data, than text.
Please don't
Not sure what the future of programming languages is. But if humans are involved I suspect there will be a visual representation. And that means syntax. And that syntax will matter then just as it does now.
If you went to a horse and buggy convention 150 years ago and told them that it would all be gone soon, they would have virulent reaction, right? They would say that metal contraptions have been tried before and failed, that humans have been using horses for travel for thousands of years and if so if humans are involved, it will mean horses. This isn't to prove my point but to draw caution to yours.
Note that I understand your position quite well but that you clearly don't remotely understand anything that I've written. That should raise a flag. Instead of getting mad, you should be getting curious.
To keep your analogy, the idea that a PL needs syntax to be used is comparable to the idea that a vehicle needs acceleration to get moving. Different flavors of syntax correspond to different means of propulsion.
Perhaps we will find good way to get rid of context free grammars (the horses in your example) and replace them with automatic pruning of parse forests (the current Tesla car of compilers) or even something more fancy like semantic editors (a fusion engine in your example) but unless you change the laws of physics, you can't get a vehicle without an accelerating engine nor a PL without an input syntax.
What's your vision of a future, syntax-free, language?
I specifically added this statement to help clarify: "It's not that the future has no textual syntax at all, of course." The difference between a text file and a database is not that the database contains no text, but rather the relations between items is discretely managed.
What I'm saying is this: storing a program as a text file was understandable in the 1970's but now is what a brain-damaged, drooling Bonobo might do.
Would you store your payroll as text? Really? "Bob is too be paid eighty thousand a year;" (And I sure hope the compiler catches the misspelled 'too' or he might not get paid.)
Structure is syntax
No, structure is semantics represented by syntax. And that structure is all that counts, so we want to get there as discretely as possible. We want to get there as data. And when we do that, the syntax can be a decorator pattern; it can be what you want it to be and something else to someone else, all without modifying its core meaning. And what happens when it's just a decorator pattern? It loses its importance.
The idea that you can have multiple syntaxes for the same language is not new (eg multilingual pascal or basic) heck even different semantic models are possible (eg LLVM) but any UI that enables a programmer to tell a computer what to do is ultimately syntax.
Among other points is the trailing semicolon. If this is so good, then the authors of Go and Swift must be wrong.
In functional languages there are some reasonable needs that make not requiring a separator useful. But, it does make the code harder to parse and harder to read. Go, for instance, is not a functional language. Making separators optional (sometimes) seems capricious. But, then, Go is rife with capricious choices.
It may if you have to switch between languages. Getting syntax errors because of dummy separators can be quite frustrating at times. So the choice is mandatory separator or optional separators. I designed a language once in which most punctuation characters where treated as blanks.
Yes, for me it does. A large amount of symbols on screen makes my mind drift away and it gets harder to concentrate on the flow and meaning of code. Semicolons or lisp parens are good examples of such visual noise. While productivity decrease is not drastic, it's noticeable. The mind adjusts and this problem diminishes with time and practice of the particular syntax but it's still present.
{} [] () , ; get the same colors as whitespaces (I enable their visibility but make them just a bit brighter as the background)
https://golang.org/ref/spec#Semicolons
Go uses semicolons, they can be dropped in place of a newline (NB: So instead of a semicolon, a newline becomes the redundancy he speaks of) under specific conditions.
a + b *c - d
is it one or two expressions?
http://www.lua.org/manual/5.3/manual.html#3.3.6
This gets rids of almost all the syntactic ambiguities. The only big one that is left is statements that start with parenthesis.