Saturday, May 03, 2014

A thought on language design

Language designers often aim to make their languages simpler, more elegant, or smaller. If we are doing practical language design (as opposed to design for fun or research), these are not goals, only proxies. Programming languages should be easy to use and easy to learn.

'Easy to use' means (usually) easy to read, write, and operate on using tools. Easy to learn means not just learning the language's features, but learning the idioms/patterns/techniques of the language too.

Simpler, more elegant, and smaller are only good proxies for easy to use and learn because our natural tendency is to make languages too big and too complex ("I'll just add this feature and that feature and, oh, the feature is pretty cool too").

Language design is about balance and trade-offs. No one wants to write software in the lambda calculus and not many people want to do it in Cobol. We need to design languages which balance their complexity and size, not striving to be the smallest or simplest, but to be the easiest and most pleasant to use.

7 comments:

liam said...

You say that easy to use assumes a dev will be using tools, so I'd assume that language design takes that into consideration and tries to synergize language and tools as much as possible. Does it make sense to take that to the extreme? That is where the language must be used with certain tools (say, tools that enforce certain idioms/patterns through an ide)? That seems like it might lead to an interesting result.
BTW, I've really enjoyed your series on rust. If avoided looking deeply into the language before since it seemed formidable, but, thus far, you've explained the design really well.

Nick Cameron. said...

@liam

To clarify, I don't necessarily think a dev will use tools (well, probably a debugger and profiler, at least, but not necessarily an IDE). I think that making a language easy to use means making it easily usable by tools as well as easily written/read by hand/eye.

There are languages that have taken that approach, mainly from the Smalltalk/Self families. My personal preference is more towards a language which easy to use with many different tools, or none. Then others can innovate tools around the language and individual devs can have choice on the tools they use.

Thanks! Another post is coming soon...

Simon said...

That's one downside to using more dynamic languages... they're much harder to code tools for. Languages like Java or C# tend to have good IDEs and analysis tools, because they can (mostly) be evaluated just by looking at the code without running it.

Languages like Javascript or Python, not so much - it's so much harder to do code analysis when the code you're looking at invokes functions that don't exist because they're dynamically created at runtime...

I love those languages, but no question, the tooling for them is much worse than for more constraining languages...

Mook said...

Wouldn't having a smaller language initially also give more room for expansion? JavaScript's automatic semicolon insertion makes some language changes impossible, but the existing grammar can accommodate generators as '*function(){...}'. I'm pretty sure language design is hard enough that having a chance to change things after an initial version has been frozen is probably a good idea.

Nathan Myers said...

Language simplicity (elegance, clarity, etc.) is a variable whose reciprocal, monotonically increases with time. Every language expands until it reaches the limit of its users' comprehension. There it stagnates, eventually to be supplanted. The purpose behind trying to stay simple is to be able to say more with less, so at the mature limit the language can say more than the language it supplanted, and a larger set of problems can be solved using a readily taught subset of the language.

Staying simple is a tactic to reserve available comprehensibility for the features that will someday need it.

Nick Cameron. said...

@Mook, @Nathan Myers - thanks for the comments - they are both good points I hadn't thought of.

Anonymous said...

I agree with your post and Go language seem to provide that.

e.g
In Go Lang:
n, _ := strconv.Atoi(os.Args[1])

In Rust Lang:
let n: u64 = from_str(os::args()[1].as_slice()).unwrap();


Similarly:

In Go:
func abc(n int64) int64 {}

In Rust:
fn abc(n: u64) -> u64 {}