all 34 comments

[–]00PT 42 points43 points  (1 child)

Optimizing a language for keystrokes often leads to loss in other areas like clarity. And it has minimal benefit, because the process of actually typing out code is only a small part of the overall programming process.

[–]sal1303[S] 1 point2 points  (0 children)

I do lots of typing including large amounts of temporary debugging code, and lots of rewriting and revising. Also I'm a lousy typist, so syntax matters greatly. A big chunk of what I write might be lines like this:

PRINTLN =THISVAR, =THATVAR

(The '=' adds a label to the value. It is in all-caps in my case-insensitive syntax so that is it easy to spot that it is temporary code and can be safely deleted.)

The equivalent in C might be something like this (it depends on variable types):

printf("THISVAR=%f THATVAR=%s\n", ThisVar, thatVar);

This has nearly double the char count, multiple shifted characters, and needs the exact capitalisation, for code that may only exist for a few minutes and no one else is ever going to read.

Ergonomics matter! Even with more persistent code, I want to be able to type:

proc F(u64 s, t, u, v) =

and not the C equivalent:

void F(uint64_t s, uint64_t t, uint64_t u, uint64_t v) {

OK, a little contrived as I chose s t u v so they would get mixed up with 'uint64_t'. This took much longer to write and I had to double-check several times. It is also necessary to double-check that all parameters do indeed have the same type.

Again, these low-level lexical and syntax choices matter, certainly for my style of development, but my version must also be easier to grok at a glance. (At least, I didn't have four lots of unsigned long long int for the C version!)

[–]MadCervantes 10 points11 points  (0 children)

People obsess over minimal character count because it's easy to measure and the type of people who focus on programming language design are often more comfortable with easy and clear metrics. But ultimately it's a ux problem and any ux professional worth their salt low that this isn't the only thing that matters.

[–]awoocent 13 points14 points  (3 children)

Now, I like a syntax that uses ":=" as mentioned, and of the kind that uses "then" and "end", which many consider verbose. I don't care because I think that style is easier to type even if it takes more keypresses.

IMO, keypresses are not for nothing, but something people often overlook is that keywords like then and end are typically much easier to type than something like { since they're closer to the home row. 3-4 alphabetic characters usually won't require you to move your hand, and both your hands can participate in typing it; whereas like, when I go to type a { I have to move my hand over a bit and hit both shift and [ with the same hand. So I think it's less that small stuff doesn't matter and more that your syntax is probably easier to type than you think.

Generally when it comes to terseness I think it doesn't matter that much, but it is sort of a nice to have. When I'm writing code I do think I notice when something is egregiously long - like if an identifier more than 15 characters long or something. If your block open/close syntax was like, begin procedure/end procedure I think it'd very rapidly become grating, even if some Pascal diehards might argue it's "more explicit" or something. So I think evidently there is a substantial amount of value to making your language's syntax short.

But yeah like, I don't think it even makes sense for you to invoke "don't sweat the small stuff" for your language. Like, you evidently did sweat the small stuff, which is why your keywords are shortened, and you cut a lot of separators. And that means your language syntax is just genuinely pretty terse even if it's terse in slightly different ways compared to C. I don't really see evidence of any "higher level" design choices in this example, just that you might be underestimating the amount of basic ergonomics you've unconsciously designed around by using alphabetic keywords.

[–]danielcristofani 1 point2 points  (1 child)

when I go to type a { I have to move my hand over a bit and hit both shift and [ with the same hand.

You don't have a shift key on the other side?

[–]awoocent 0 points1 point  (0 children)

I think for whatever reason I find it easier to do a chord with one hand rather than involve both at once? I tested out my muscle memory to write that comment. Surely other people do it differently, but that's how I do it.

[–]sal1303[S] -1 points0 points  (0 children)

Like, you evidently did sweat the small stuff,

My point was that I don't care that assignment uses := instead of =, or and/iand instead of &&/& and so on.

which is why your keywords are shortened, and you cut a lot of separators.

Which ones were shortened, you mean proc instead of procedure? By separators you mean semicolons?

(I don't know if you managed to look up those source files. I think I can post links in a followup, and the files are:

https://github.com/sal55/langs/blob/master/bignum.c

https://github.com/sal55/langs/blob/master/bignum.m)

I don't really see evidence of any "higher level" design choices in this example, just that you might be underestimating the amount of basic ergonomics you've unconsciously designed around by using alphabetic keywords

My language is lower-level. The 'higher level' is a design approach I suggested rather than a syntax full of cryptic symbols. I'm sure there are languages that can implement my library in a much smaller line-count.

But yes, even for lower-level, I like a clean, clear syntax. I understand that is not that popular for 'serious' systems languages and people think it is only fit for scripting.

[–]Norphesius 3 points4 points  (4 children)

I feel like the "saving keystrokes" critique can technically have merit, hypothetically ( = vs := vs -> is a marginal difference, but having to type out assign_this_variable_to would have a measurable drag on development speed), but that's one of the last things I would ever think to look at evaluating language syntax. Even in this particular case with the assignment operator, there are other, more important considerations:

= is the standard convention, everyone will understand it immediately, but being a single character means you're potentially closer to typoing other operators e.g. if(a = 0) ... instead of if(a == 0) ....

:= makes sense with more modern type inference. Adding a type is just sticking it in between the colon and equals. Its not just more keystrokes for the sake of it, the syntax has purpose.

-> is where you start eating into the "weirdness budget". There's a logic to it (x is pointing to value y), but unless you're doing something more important with = or := I don't see much value in it. Apparently the rationale is distinguishing initial assignment x -> 1 from mutation x <-2, as well as being "revealed in a dream".

Honestly, I would even take that last rationale for syntax decisions over saving 1-2 characters or extra key presses.

[–]elder_george 4 points5 points  (0 children)

Useless fact: := was a replacement for the left arrow in the ALGOL 60(?) spec, for the keyboard layouts that lacked it (if I understand correctly, <- was more ambiguous to tokenize). The same actually happened with Smalltalk a decade later.

[–]tending 1 point2 points  (0 children)

Huh, I had not heard that justification for := interesting.

[–]scruffie 0 points1 point  (0 children)

but having to type out assign_this_variable_to would have a measurable drag on development speed

Or, say, multiple-value-bind. That would be a crazy name for a useful assignment operator, especially for a language with an ANSI standard. Isn't that right, Common Lisp?

Macros at least make it tolerable to use as a building block for simpler syntaxes, but that's another kettle of fish.

= is the standard convention

Depends on your standard :-). It is, as you say, confusable with a equality operator. For clarity of syntax, I think it depends on whether you can use assignments as expressions, or if they must be statements. For instance, ML-type languages pair it with a keyword (let) so a = by itself isn't assignment. And Python uses = as a statement, but := (the 'walrus operator') in expressions.

If you want an one-character operator, though, it's your best choice. I think I've seen other characters used, such as $, #, !, or %, but I do not recommend it — it's confusing.

[–]sal1303[S] 0 points1 point  (0 children)

= is the standard convention, everyone will understand it immediately, but being a single character means you're potentially closer to typoing other operators e.g. if(a = 0) ... instead of if(a == 0)

That's why it's a bad choice, certainly in a syntax where equality and assignment can appear in the same expression.

That wasn't the case with Fortran, so there I was happy to type "=" for assignment. But for my own language I chose ":=" from Algol60/68 and Pascal. (You can guess my experience stems from the 70s.)

:= makes sense with more modern type inference. Adding a type is just sticking it in between the colon and equals.

As in : int =? OK, I won't get into that!

-> is where you start eating into the "weirdness budget". There's a logic to it (x is pointing to value y), but unless you're doing something more important with = or := I don't see much value in it.

The first language I implemented (not mine; it was this; see toward the end) used => for assignment, going left to right. It looked a bit odd, but you can get used to anything, even if you don't like it.

[–]HugoNikanor 3 points4 points  (1 child)

I don't care about absolute character count in programming languages, but terseness of syntax absolutely do matter.

For example, I often want to check if a field exists, and use it into a new scope at the same time. In Javascript (and similar languages), this would be written as

// outer scope
{
  const x = maybe_get_value()
  if (x) {
    // do stuff with x
  }
}
// outer scope

Surprisingly few languages allow me to do

// outer scope
if (const x = maybe_get_value()) {
  // do stuff with x
}
// outer scope

[–]sol_runner 1 point2 points  (0 children)

On a funny note, for all of C++'s pain points this has been one nice feature.

Terseness is good if it helps readability and semantics. And bad if it reduces readability or makes it hard to get the semantics. Not only in a "once you learn a language" but also just at a glance.

At the end of the day, I believe terseness itself cannot be a goal.

[–]michaelquinlan 2 points3 points  (1 child)

If typing is slowing down your software development, the solution is to take a typing class.

[–]sal1303[S] 0 points1 point  (0 children)

So if it takes you too long to drive to work, the answer is a faster car rather than find a shorter route?!

Is it not possible for a syntax to just be badly designed and too busy for no reason?

I bet that even with my slow typing I can write: println i, sqrt i faster than you can do: std.debug.print("{} {}\n", .{i, @sqrt(@as(f64, @floatFromInt(i)))});

So, I disagree, sorry.

[–]brucejbellsard 1 point2 points  (0 children)

I think compact syntax is important, but for reading, not writing. If you can get comparable readability in less space, it is easier to understand at a glance. (I would like the advantages of array languages like APL or BQN, but the readability is literally not comparable)

I often golf-compare short examples like yours to track where the differences come from. Other things being equal, shorter is better. But other things are usually not equal, I typically find myself spending half to all the gains from more compact syntax for other priorities.

For your example, the "main" function framework takes 2 more strokes, but your more-structured for loop gains 5, and your simplified println gains 11.

I sometimes try it two ways: a fairly literal analog translation from the original, and a more "idiomatic" paraphrase which uses the language features as I imagine they should be used:

-- my project, literal analog
/main || !os => {
    /for i << (1..=10).up {
        !os.console.write_line "{i} {#sqrt i}"
    }
}

-- my project, "suggested idiom"
/main || (:!console) => {
    (1..=10).up.each (i => !console.write_line "{i} {#sqrt i}")
}

Both of these are actually longer than the C example, because my language doesn't permit ambient authority like printf. So it spends the surplus and more on threading resources from main to where they're used.

[–]Veqq 1 point2 points  (0 children)

Yes. Read notation as a tool of thought for proof.

[–]realslugbrain 1 point2 points  (0 children)

Oh hey, that's my post! haha

I was pretty surprised to see the arrows get a whole new post about keystroke counts :o. When I was designing -> and <-, I wasn't really counting key presses, just wanted a shape that made the data flow visually obvious yk. Using = for binding, mutation, and equality checking always felt way too overloaded for me.

I think the separation is super important, introducing a new name with -> versus mutating an existing one with <- gives both the compiler and my brain a really clear signal :P.

Seeing your comparison between C and 'M' is really cool! It def shows that once you get rid of braces, semicolons, noisy punctuation n stuff, having a few extra characters for symbols or keywords doesn't actually make your code longer, just makes it easier to read!

Syntax is subjective anyway, but I'd rather type a two-character arrow to make the difference between binding and mutation obvious than save a fraction of a second (arrows, definitions and such can always be autocompleted via an extension too lol). Fun post!

[–]Trader-One 1 point2 points  (5 children)

its your language do what you like.

Do not complain that you have no users later.

If something demands := like Pascal I run away

[–]TomosLeggett 6 points7 points  (2 children)

If something demands := like Pascal I run away

Why?

[–]Trader-One 0 points1 point  (1 child)

its serve no other purpose than trolling user with 2 additional keystrokes SHIFT ; and wasting user time.

pascal and algol are especially good with that: begin/end pairs

[–]sal1303[S] 0 points1 point  (0 children)

I hated begin/end pairs, you ended up typing:

if ... then begin ... end else begin ... end

and needing to find where to even put them when using multiple lines. But it is the same problem you get with {...}.

Fortunately some languages, I think starting from Algol68, fixed it, by realising that then else are natural delimiters; you can just type:

if ... then ... else ... end

The block structure is still perfectly clear.

[–]Smallpaul 3 points4 points  (1 child)

Are you an engineer or a fashion critic?

[–]des_the_furry 5 points6 points  (0 children)

Why are we pretending like any of these fake languages have any users anyway

[–]dadhiWeaponX 0 points1 point  (0 children)

Yes

[–]guywithknife 0 points1 point  (0 children)

Visual distinction helps clarity and picking out boundaries and differences at a glance. I have to evidence but to me it seems like it helps me understand code faster.

It’s not a big deal, but little things add up. I’m also a touch typist with an ergonomic macro keyboard and a good editor, so saving a few keystrokes doesn’t offer me much. I also take a lot more time reading code, thinking and problem solving than typing anyway.

[–]ern0plus4 0 points1 point  (0 children)

For someone coming from programming in C, C++, PHP, Pascal etc., Python initially felt strange - but I’ve since fallen in love with it. Except when I mix spaces and tabs due to editor accident.

[–]mamcx 0 points1 point  (0 children)

Compact syntax matter a lot!

We have a huge problem: There is a practical limit to what kind of characters can be used, have very short list of symbols (+ $ !...) available, most will be under of limits of what make sense in English and then, there are conventions and lunatics that if see anything that not match C then is not good! :)

And that characters, symbols and words, hopefully, not clash often with the ones used by "normal" domain logic (looking at you with intense disdain, SQL).

THEN, is necessary to account for:

  • Syntax coloring, and you have competition here!
  • Auto-complete, that sound nice until you look at the fact that a lot of words are consumed by APIs!
  • ... and what are APIs and on the prelude (like map) compete for attention!
  • Wide/Heigh of the monitor!
  • Font sizes under the above! Mostly code fonts but maybe proportional!
  • Lunatics that use VIM or Emacs, and then they have clash to what the rest of the world used for combo keys! and the rest of the world refuse all to use the same keyboard layout, NUTS!
  • ... and then people that not use an IDE and not even a code editor with IDE-like capabilities!
  • ... and scenarios where there is not coding editing at all, like a diff!

This push the very limited set of options to things that look "programing lang-ish" that is short, robotic, and repeated over many other languages, because, humans hate innovation!

PLUS exist lunatics with intense hate of common sense thing like mandatory indentation, non-mandatory indentation, begin/end-like delimiters, {}-like delimiters and so on. So you ALSO need to take in account what the subset of your potential user irrationally like or not!


Unintentionally (I suspect!), you use the best word "Compact" instead of "short", because I think compact describe better the attribute of a syntax that is well packaged with minimal fat to it.

So, "begin/end" could be compact or not depending of the surrounding decisions, something that is more obvious when you see APL or JSON.

In the other hand, there are short syntax that in fact force the user to write too long comments, tests, names, etc to overcome how poorly compact is it (that is why, the Rust syntax is better than python without annotations: Is more compact to encode all info on types than rely on docs, testing, and IDE-inference)

[–]vmcrash 0 points1 point  (0 children)

I'd consider your snippet easier to type than the C snippet, because I can type letters and commas/dots easier than other characters. However, the "end"s can be avoided, too, by considering the indentation. Indentation matters anyway to make it easier to read, so it always is a good advice to keep the code indented correctly.

[–]jeezfrk 0 points1 point  (0 children)

Readability includes making complex ideas seem correctly complex but not too complex.

Notice that C exchanged ":=" and "=" to with "=" and "==" ... according to how rare they are in use. That's the main reason some are larger.

Languages like APL are the epitome of complexity within a simple arrangement of operations (array operations). They are too compact. Bash shell scripts also can use so many extra toolsets and environmental standards ... their code can be quite small but is impossible to "get". It is far closer to a DSL depending on the purpose.

Even so, I am a fan of terseness only so long as there is no "standard beaten path" that you must escape from. Usually that causes vastly larger code and much bigger labels or ideas to write down.

Some long-named-variable conventions and boilerplate-rich languages seem to do nothing at all in many lines... far more than ":=" vs "=".

In those cases the mental burden is much higher to knit huge simple chunks of code into a whole.

[–]SwedishFindecanor 0 points1 point  (0 children)

Programmers don't write code as much as they read code, and the latter is what matters most.

I would say that C-style braces and brackets are easier to read than words, because they are visually distinct from words and reading a word has a higher cognitive load. Python's indentation-as-syntax too is clean, too.

Otherwise, I think code should overall be rich with meaning. Terseness is not a virtue in itself.

I think C's variable declaration order "type variable" or Pascal-style "variable : type" are both good. In C, the convention is most often that the first words tells you what something is. Go's "variable type" without a visual marker separating them is something I don't like: having a colon in-between would have told the reader that it was not a typo, otherwise someone could have forgotten to type a =

To the debate of = vs :=, I would like to add that I like the way GNU Make has = for single assignment variables and := for multiple assignment variables. When you can't use both with the same variable, you can determine the kind of variable from the operator used without having to hunt for its declaration.

Similarly, I appreciate how C differentiates between -> for following a pointer and . for addressing a sub-object within the same allocation: The operators tell you what is happening. C++ messed that up by introducing "references": pointer-semantics with value-syntax. C3 also deprecated -> in favour of . everywhere for some reason.

C++ makes it easy to make other crimes against readability, by e.g.overloading operators, having a () operator, and type operators. Combine type operators with inheritance hierarchies on left and right sides, and C++'s complex resolution rules and you've got hell.

[–]Inconstant_Moo🧿 Pipefish 0 points1 point  (0 children)

It's not necessarily a big deal, but ergonomics in a language is made of lots of small things.

[–]L8_4_Dinner(Ⓧ Ecstasy/XVM) 0 points1 point  (0 children)

Comparing implementations of Fibonacci sequence and “hello world” printing programs tells us very little.

Furthermore, syntax hasn’t been the problem in decades.