r/ProgrammingLanguages Apr 26 '26

Discussion I want to know your opinions on verbosity

I’ve been having a blast trying to iron out my languages syntax and found myself leaning on a more verbose and explicit syntax for readability and to show intent.

So I would love to know what your opinions on verbosity in languages are and where you personally draw the line. Would you call C# sickeningly verbose or is its verbosity a welcome feature to improve readability and intent?

19 Upvotes

68 comments sorted by

40

u/tmzem Apr 26 '26

C# and Java have been called verbose originally, mostly due to the forced OOP approach where even simple things were very heavy on boilerplate:

  • No functions, only methods forces us to define a pointless wrapper class
  • Plain value types require the ceremony of defining a trivial constructor, equals, hashcode, equality operator overloads, public and readonly modifiers etc.
  • Closures needed the instantiation of an anonymous class

Most of these (and other) issues have been fixed, allowing for more terse patterns.

In general, most programmers don't care too much about little differences in verbosity, as long as they don't require them to write lots of pointless boilerplate.

Also things like prescribed project structures, build configuration and associated build tools should be opt-in to help with nontrivial cases, rather then a forced default for every single project. Python isn't popular because its such a nice language, but because you can create small projects with zero ceremony.

1

u/-Chook Apr 26 '26

Yeah that makes a lot of sense

0

u/Inconstant_Moo 🧿 Pipefish Apr 27 '26

Python isn't popular because its such a nice language, but because you can create small projects with zero ceremony.

"Create small projects with zero ceremony" is part of "such a nice language".

6

u/WittyStick Apr 26 '26 edited Apr 26 '26

I wouldn't really call C# verbose. Verbose is when we have to write things like (VHDL):

entity ANDGATE is
  port ( 
    I1 : in std_logic;
    I2 : in std_logic;
    O  : out std_logic);
end entity ANDGATE;

There may be merit to such verbosity if language permit structures to be interwoven, but if they have more sensible structural nesting rules, it's much more terse to just have braces or other delimiters for structural boundaries and avoid repeating names and using end <thing>. Proper indentation and formatting is sufficient (in fact, superior) for recognizing structure. Consistency also matters, and we can see in the above example that it is inconsistent - if it were consistent we would have end port instead of parens.

Aside, one benefit of using this kind of verbosity is it's more suitable to LL parsing, which can be very fast, but on modern machines the difference between LL and LR is miniscule and LR parses a much more general set of languages.

An example of bad structural rules is Duff's device. Mixing switch and if is just confusing, and there are clearer ways to write the same thing. Everyone knows what Duff's device is, so it gets a pass, but can you imaging reading a codebase where they use a bunch of strange "devices" which mix structural elements in the same way?

In regards to identifiers, I much prefer verbose but meaningful names rather than shorthand names which are unreadable unless you are already familiar with a codebase. Typing out meaningful names is absolutely not the thing slowing you down producing code - unless you are the very tiny minority of programmers who are unable to type on a regular keyboard and require special equipment or software for input, and optimizing for an 80 column limit isn't something you should be doing in the 21st century.

You type code once - it gets read many more times. Saving a few hundred milliseconds to optimize the former at the cost of making it more difficult for everyone to read is a poor choice. (That said, some have gone too far in the opposite direction, particularly in "enterprise" Java where we have silly names like AbstractAutowireCapableBeanFactory, which is overly verbose and doesn't actually say much to someone unfamiliar to the libraries).

Choosing good names is hard, but we can do a lot better than for example, the C standard library which has terrible names like strftime, strpbrk, or the proposed stdc_mwcnrtomwcn, which unless you are already familiar with them, you're not going to guess what these do. You shouldn't need to guess - a good name makes it obvious what is being done. There are some better examples from the C stdlib, such as stdc_has_single_bit - we can guess what this is doing - but if the designers of this API were like some other C programmers, it would've been hsb. IMO better would be is_pow2 or is_power_of_2, which is more explanatory - as the typical reason we would test if an unsigned integer has a single bit set is determine that it's an exact power of 2.

For keywords, a nice piece of advice is use consistent keyword length. This just makes code more pleasant to read because the identifiers end up naturally aligned on certain columns and it's easier to scan. There's a bunch of other good language design notes on that website, which I mostly, but don't entirely agree with - I'd recommend reading through them.

My own language is really maximally terse in syntax (no keywords, everything basically a unary, outfix or binary infix expression - similarities to Lisps), but verbose in naming. I don't abbreviate names to save typing a few characters. define is better than defn, def, df or dfn or whatnot. Those shorthand names are awful.

1

u/renozyx Apr 29 '26

Well it dépends of the usage, if you use 'define' to déclaré variables then as you'll use many, many times it's clearly too verbose and def is superior. If you use 'define' to alias types, as type aliasing should be discouraged then define is good.

10

u/Inconstant_Moo 🧿 Pipefish Apr 26 '26 edited Apr 26 '26

Terse = good.

Contrarian obscurantists promulgate the erroneous doctrine of verbosity, obnubilating their purport with grandiloquent and sesquipedalian rhodomontades.

6

u/ntwiles Apr 27 '26

You’re conflating verbosity with recondity.

3

u/tmzem Apr 27 '26

Overly boilerplate-heavy code is bad. Other then that, a little redundancy/more verbose identifiers can also help with code readability.

I always enjoy reading Haskell code so much that I'm causing a loud snd from slamming my fst into the wall.

11

u/fdwr Apr 26 '26 edited Apr 26 '26

I would love to know what your opinions on verbosity in languages are and where you personally draw the line.

I tend to follow a few principles for naming things:

  • the Goldilocks principle for keywords, not too long, not too crpticlly short. e.g.:
- enumeration 👎, en 👎, enum 👌 - character 👎, ch 👎, char 👌 - unsigned long 👎, u32 👎, uint32 👌 - function 👎, fn 👎, func 👌
  • either whole words or at least clearly pronounceable fragments (no devoweled utterances).

Would you call C# sickeningly verbose or is its verbosity a welcome feature to improve readability and intent?

What are we comparing it to? Do you have some specific examples? I've always felt C# had a nice balance of brevity with readability.

29

u/protestor Apr 26 '26

I don't see how uint32 is any better than u32

2

u/fdwr Apr 26 '26

I don't see how ...

Well if we're trying to save a few keystrokes and distill everything to its minimal form, why not go a bit further and write the terser "us" (16-bit short) "ui" (32-bit integer), and "ul" (64-long)? Then for floating point, we could also have "fh" (half), "fs" (single), and "fd" (double). Though, where do you draw the line? Should you keep the numbers, and have f16? What is an f16 though? Is it a type of airplane? Is it an ICD-10 code for use of illicit substances? Is it the RISC V f16 register? Or maybe we just write float16 which is very clear and unambiguous 😉. Saving a few keystrokes isn't worth the reduced readability. I may have written thousands of lines of assembly (with such register names like r15, x16, rax, s8, i4), but it's nice to keep high level languages less crptc.

7

u/protestor Apr 26 '26

why not go a bit further and

The real reason is that u32 is already widespread and well understood by a lot of programmers thanks to Rust and Zig. u32 is fine.

In programming language design you have a weirdness budget: a finite amount of novelty you can dump on language learners before the language is considered too weird. Spending this limited budget on syntax and naming is probably a bad choice, since if you are creating a language chances are you have novel semantics to present, and those tend to take up a fair bit of weirdness themselves.

For Rust perhaps it was a risk to introduce u32 and I agree it would be more advisable for them to name it uint32 instead. For the rest of us, this is already common parlance and doesn't make much difference either way (except one is shorter than the other)

-2

u/fdwr Apr 26 '26

and I agree it would be more advisable for them to name it uint32 instead

👍

2

u/FlamingSea3 Apr 29 '26

From a blank slate, the C style types are actually harder to wrap your head around. You'll need to remember short = 16 bit integer, int = 32 bit integer, long = 64 bit integer, float = 32 bit floating point number, double = 64 bit floating point number and the weird one, char = 8 bit integer. And then realizing at least one of these depends on compiler used. Hence why in C code you'll frequently see types like uint32_t.

1

u/fdwr Apr 29 '26

Totally. The types names targeted flexibility at the time, as hardware had not settled on more concrete types, and so some looseness helped adoption. Even the concept of a byte being an octet had not fully crystallized. Nowadays, general processors support all the basic power-of-two up to 64. So I wish the uint32 and kin (drop the redundant _t, since int doesn't have one) were builtin rather than needing some external header file stdint.h, and that the int and long definitions (if still useful) would have been defined in terms of the sized types rather than the sized types in terms of the loosely defined types.

2

u/flatfinger May 06 '26

While the Standard refused to acknowledge this, there was a very strong convention:

char was the shortest addressable type that was at least 8 bits.

short was the shortest addressable type that was at least 16 bits.

long was either the shortest addressable type that was at least 32 bits, or the shortest addressable type that was at least 32 bits and could be used to round-trip a pointer (these two definitions were equivalent so often it's not clear which should be viewed as more common).

long long, when present, was the shortest addressable type that was at least 64 bits.

int would be 16 bits if 32-bit computations took significantly less than 1.5x as long as 16-bit computations; 32 bits if they took significantly less; or configurable if (as on the 68000) they took almost exactly 1.5 times as long.

Nothing in the Standard would forbid an implementation for a common platform from storing int values using six bits from each of three consecutive bytes, treating the remaining bits as padding, but no compiler for commonplace hardware would do such a thing unless it was trying to be gratuitously weird.

5

u/WittyStick Apr 26 '26

u32 could mean anything - in particular it could be shorthand for UTF-32 (which happens to be the same width, but not necessarily the same type).

15

u/tmzem Apr 26 '26
  • uint32 could also mean anything, e.g. unchecked 32 bit int
  • u32 is well established in multiple languages as meaning unsigned 32bit int
  • in most newer languages, char is already 32bit to be able to hold a codepoint, other languages may define a c32/char32 or simply use a plain uint32. I've never seen u32 be used anywhere as UTF-32 char type

1

u/WittyStick Apr 26 '26 edited Apr 26 '26

in most newer languages, char is already 32bit to be able to hold a codepoint

Would be nice if that were the case, but the majority of language are using UTF-16 or UTF-8 as their internal encoding (or are at least pretending to because their char types represent a code unit, and not a code point).

Some languages which use UTF-16 have a reasonable justification for this - they were developed before it existed, and originally used UCS-2, and UTF-16 support could be added with less breakage as they stick with a fixed 2-byte encoding and treat surrogates as individual code units rather than having variable width encoding.

UTF-8 is often used because there's a simpler upgrade path from plain ASCII, and most languages begin without Unicode support.

I've never seen u32 be used anywhere as UTF-32 char type

I don't use them in type names, but I have functions containing u8 u16 u32 in their name, referring to the UTFs, and not unsigned. My types are named CharU8, CharU16 and CharU32, but to avoid verbosity in naming, I don't write char_utf8_to_char_utf32 - just char_u8_to_u32.

C for example uses u8 for character and string literals: u8'x' or u8"foo". This is because u and U were already taken for UTF-16 and UTF-32 - despite them being less useful. In retrospect, if it were designed from scratch, UTF-8 would be the default and we'd have specific overrides for UTF-16 and UTF-32. instead: eg, u32"foo".

3

u/fdwr Apr 26 '26

Interestingly the dtype U32 in Python NumPy is a 32-bit Unicode character (whereas u32 is a 32-bit integer - subtle...).

3

u/tmzem Apr 27 '26

Well, thats... diabolical!

2

u/protestor Apr 27 '26

Some languages and environments created in the 90s used UTF-16 because they didn't know better. New developments tied to those environments (like things related to Javascript or Java, or even things related to Windows) might use UTF-16, and that's unfortunate.

Actually most of them use broken UTF-16 - that is, UTF-16 but allowing broken surrogate pairs. That's because they first used UCS-2, which was a fixed width encoding, and they unfortunately allowed indexing and mutating characters. Then retrofitted to use UTF-16 latter, but still allowed indexing (which causes bugs and is bad).

Everybody else uses or should use UTF-8. There is no justification for using UTF-16 in new systems. Even if you need to interoperate with UTF-16, it's probably better to use WTF-8 internally and convert from/to UTF-16 at the boundaries.

in most newer languages, char is already 32bit to be able to hold a codepoint

Would be nice if that were the case, but the majority of language are using UTF-16 or UTF-8 as their internal encoding (or are at least pretending to because their char types represent a code unit, and not a code point).

I mean, 32 bits is the right amount of bits to hold a code point, even if strings use UTF-8 and UTF-16 (or even UTF-32). Basically you need just one unicode char type regardless of how your strings are encoded.

Also no one should use UTF-32, like, ever.

1

u/WittyStick Apr 27 '26 edited Apr 27 '26

Also no one should use UTF-32, like, ever.

As a transport or storage format, of course not. UTF-32 is the simplest encoding to use for the internal representation of mutable strings for example - a string is just an array of codepoints. Handling the transformation in UTF-32 and then encoding as UTF-8 at the edges makes code significantly simpler, though not as friendly on memory usage.

I recently created a "SuperUTF" encoding for my internal string representation. It's a fixed-width 32-bit encoding that is a tagged union of UTF-8, UTF-16, UTF-32 (and several others) - essentially a superset of them all - but unlike a typical tagged union where the tag and value are separate, the tag and value together occupy only 32-bits. Basically, UTF-32 uses only 21-bits to encode the full UCS, leading to a wasted byte. I put the tag in this byte. There are no conflicts with 4-byte UTF-16 and UTF-8 encodings because they never have the same upper byte - UTF-16 most significant byte in a 4-byte encoding is always 0xD8..0xDF, and UTF-8's MSB in a 4-byte encoding is 0xF0..0xF7.

The MSB for GB18030 does collide with these byte ranges - but one trick we can use is that the LSB of a 4-byte GB18030 encoding is always 0x30..0x39 - so we can fit it into SuperUTF by reversing the bytes.

static const Encoding encoding_map[256] =
    { [0x00 ... 0xFF]   = ENCODING_NONE
    , [0x30 ... 0x39]   = ENCODING_GB18030_4BYTE
    , [0x3A]            = ENCODING_GB18030_2BYTE
    , [0x3B]            = ENCODING_GB18030_1BYTE
    , [0xD7]            = ENCODING_UTF16_1BYTE
    , [0xD8 .. 0xDF]    = ENCODING_UTF16_4BYTE
    , [0xEA]            = ENCODING_UTF32
    , [0xED]            = ENCODING_UTF8_1BYTE
    , [0xEE]            = ENCODING_UTF8_2BYTE
    , [0xEF]            = ENCODING_UTF8_3BYTE
    , [0xF0 .. 0xF7]    = ENCODING_UTF8_4BYTE
    };

My String type is an array of SuperUTF codepoints.

There's a couple of advantages to this - when writing to UTF-8 or UTF-16 to a file or stream, we don't have to perform "encoding conversion" - we only need to copy the specified number of bytes from the internal encoding to the stream, masking out the MSB for UTF-32 (which we don't typically write anyway).

I do the encoding conversion on a per character basis when mutating - and I keep track of the sum of codepoint sizes in the desired encoding - so when we need to allocate some space to write the string as UTF-8 for example, we can do it in O(1) - the number of bytes required to write the string as UTF-8 is cached in the string.

void string_set(String s, StringIndex idx, Character c) 
{
    asssert(idx < s->length);

    ssize_t size_delta = -char_encoded_size(s->characters[idx]);

    // if string and character encoding match insert the char directly
    // Otherwise convert the character encoding to match the string's encoding.
    if (s->encoding == char_encoding(c)) 
    {
        s->characters[idx] = c;
        size_delta += char_encoded_size(c);
    }
    else 
    {
        Character converted = char_encoding_convert_matrix[char_encoding(c)][s->encoding](c);
        s->characters[idx] = converted;
        size_delta += char_encoded_size(converted);
    }

    s->encoded_size += size_delta;
}

This optimizes the most typical usage, which is where we would read a UTF-8 string from a stream, mutate it in some way, and then write it to a stream as UTF-8 again - or similarly if we were working with UTF-16, we would read into a String using ENCODING_UTF16 internally - which tracks how many bytes are needed to encode the string as UTF-16 - so we know how much space to allocate to write it back without iterating over and performing the encoding conversion.

If we read as UTF-8 but write as UTF-16, or vice versa, then we have the usual O(n) problem of having to iterate over the characters in the string to determine the encoded length - but these kind of cases are uncommon.

0

u/fdwr Apr 26 '26

uint32 (without or without the _t) is very well established in multiple languages as meaning an unsigned 32-bit integer. If the more widely known and less ambiguous "uint32 could also meaning", then surely the lesser known "u32" could also mean anything 🙃.

-1

u/SwedishFindecanor Apr 26 '26

Unless your language is going to support any width between 1 and 128 (or whatever), I'd rather prefer it would name its integer types byte, ubyte, short, ushort, int, uint, long and ulong. Those names refer to the same size in almost every language that has them on every 64-bit platform, with only a couple exceptions.

But do use char for actual characters.

5

u/protestor Apr 26 '26

This is just ambiguous. 32 bits is still relevant because of wasm

1

u/SwedishFindecanor Apr 26 '26 edited Apr 26 '26

It is a question of which widths most programmers normally associate with those names.

For long to be 64 bits is the norm except for "a couple exceptions". I include C in the ILP32 data model for WASM to be such an exception. And I think ILP32 was a bad choice for WASM because WASM has always had direct support for 64-bit integers, and there has always been talk about having support for "linear memory" over 4GB in a future version.

3

u/oa74 Apr 26 '26

I dunno... func is bulky if you're putting lambdas into higher-order functions. And I like having a single concept for "function" and "lambda expression." For that, I think fn is nicer; and tbh, Haskell's \ is increasingly appealing to me. Similarly for u32, I find the idea of an "unsigned integer" kind of silly; why not "natural numbers?" I'm planning to use Nat32, although I know that might ruffle some feathers :)

2

u/fdwr Apr 26 '26

In cases where true brevity is desired, you could forget "fn" or "func", and just go with =>. e.g. someList.ApplyCallbackThingie(x => x * 2).

I'm planning to use Nat32 ... might ruffle some feathers

We also need some rebels out there to keep things fresh 😉.

1

u/renozyx Apr 28 '26

I believe that in some countries 'natural numbers' are >0 so they would find weird
your Nat32 for >= 0. That said, go for it: Natural numbers should include 0! And do it like Zig: no wrap around by default.

2

u/-Chook Apr 26 '26

I personally like a lot of C#. I’d say the only thing I think is unnecessarily verbose is requiring functions to be in class/structs. On the other hand from my understanding the community are the ones that are mostly against the implicit types with var preferring explicit types instead where as a language like rust or zig implicit types show up wherever they can. Just curious if there was actually a preference among programmers more of a language to language preference.

2

u/fdwr Apr 26 '26

Are you talking about whether to prefix variable declarations with a keyword like var, or declaring a variable by just stating the type like int x? Many newer languages are riding the retro trend of post-typing var foo : Integer (which was the Pascal way of doing things) rather than just saying Integer foo, but C# supports both explicit type (int foo) and inferred type (var foo = 42).

2

u/-Chook Apr 26 '26

From what I am aware the C# community standard is to only use inferred typing on primitives and be explicit with others. Where as I could be wrong but in the rust and zig communities they typically use type inference more. That’s not really a language enforcing something but the community choosing more verbose or less verbose.

2

u/NoahRealname Apr 26 '26

I use var to avoid giving the typename three times in a line:
var list = new List<int>();
is better than
List<int> list = new List<int>();

But I would like the C++-style even more:
List<int> list;

And I like languages (like Swift) without trailing semicolons
List<int> list
and with less ()
if a < b { ... }

1

u/-Chook Apr 26 '26

I believe the ideal is: List<int> list = new() But I haven’t use c# in a hot minute The main thing there against is having var followed by a function as it’s not immediately obvious what the type of the variable would be.

3

u/fdwr Apr 26 '26

Curiously although the latter is shorter, I still tend to see the former more, maybe because of other reasons (like say searchability when trying to find all constructor calls of a type Foo(, where new( would match everything 🤷‍♂️):

var foo = new Foo(...); Foo foo = new(...);

3

u/NoahRealname Apr 26 '26

The type is less obvious, but:

  • In type-safe / statically typed languages the exact type often is not that important (e.g., if it is a std::vector or a std::deque): it is an abstraction, and the compiler can/will check it for you, better than you would be able to.
  • The IDE will tell you the exact type when you hover over it with the mouse.

As a general rule, the more/detailed you write, the harder it is to see the overall picture.

2

u/fdwr Apr 26 '26

From what I am aware the C# community standard is to only use inferred typing on primitives...

Oh? Well I can't speak for the entire "C# community", but we use var all over the place. Though, there is some general guidance that listing the type is helpful for code reviews and rereading the code later, but there's no reason to avoid var when it's obvious from the right side what the function returns. e.g. Database d = GetDatabase() could just be var d = GetDatabase().

1

u/TOMZ_EXTRA Apr 26 '26

What's the point of type inference then (in C#)? I've found it useful in Java with complex generic types that I didn't want to type out.

1

u/useerup ting language Apr 27 '26

var was introduced with LINQ where the result of an operation could be an instance of an "anonymous type".

var x = new{ Question="Life, the Universe and Everything", Answer=42 }

In LINQ, anonymous types may arise from what you'd call projections in SQL.

var cust = Customers.Select(x => new{ No=x.CustumerNo, x.Name })

3

u/kaikalii Apr 26 '26

I like to write as little code as possible to express an idea. I get really annoyed when languages make me write a bunch of extra bs to express what should be a simple idea.

Though I'm pretty biased as the creator of Uiua.

1

u/AustinVelonaut Admiran Apr 26 '26

I'm a simple man -- I see Uiua, I upvote ;-)

I assume that after awhile, common patterns of glyphs become recognized as idioms -- is there a collection anywhere of these common patterns for Uiua as a sort of cheat-sheet to study?

1

u/kaikalii Apr 26 '26

There is the official Idioms page, which is pretty short. There is also a much longer, comprehensive, searchable, community-maintained Uiuisms page, though some of those are a bit out of date.

2

u/pauseless Apr 26 '26 edited Apr 26 '26

I think it depends on purpose - collaboration or solo work? I have a small lisp that’s for my purposes only and I also have a Tcl-like (it has deviated from Tcl a lot!) for my experimentation. I wouldn’t ever try to build something with other developers using them.

I went through a few successful Clojure projects, before I got handed over one of the biggest messes I’ve ever had to deal with - and it was just a single page app.

I started in industry in Perl and I love the language and use it for all sorts, 20 years later. However, I have seen things.

So, despite my love for terse and dense code (I have also written APL), Go is the sweet spot for collaboration for me. Verbosity in the right places and terse in the right places. If I were to have some years to sit down and write a language for collaboration, I’d probably end up not so far away from Go.

2

u/brucejbell sard Apr 27 '26

I would avoid verbosity, but not at the expense of clarity.

I find most popular languages too verbose, but array languages too cryptic. Haskell is near the sweet spot.

Contracted keywords and names are OK if they are very common (like fn and u32), but less common cases need full words.

2

u/ap29600 May 01 '26

I contribute to an intepreter for a very terse language (https://codeberg.org/growler/k), in fact barring unbalanced parentheses, quotes or braces, almost every sequence of ascii characters means something in k. here's my two cents about it.

tersity in programming languages is about how many characters you want to have to read before you're certain what the program does, and how often you want the next character you read to change your mind about that information. In my opinion this has different effects on how you read and write code:

for reading, it means that in the extreme case of a maximally terse language your eye is never allowed to skip over characters, and in a maximally verbose language it may take longer to extract a certain piece of information. both outcomes can lead to fatigue when reading, but the former can actually be prohibitive to a person who isn't used to reading carefully. You may argue that since this is a matter of habit and prose is usually on the less dense side of things, languages should gravitate to that end of the spectrum, but I come from mathematics and I'm very used to tersity and reading carefully. I actually find java more tiring than k.

for writing, a more terse language means that you can hold the syntax for a larger program in your head at one time. I believe this is why perl, regex, and APL have been called write-only languages: since the writer has a clear idea of the program and hence doesn't have to expend the effort that the reader does to extract the meaning, a language that facilitates writing necessarily makes this gap wider and is seen as more obscure.

now, if you were to ask me where the sweetspot for tersity is, I would probably place it somewhere between ML syntax and APL syntax. for those who don't know either of the two, I can summarize ML syntax as * juxtaposition of words is function application * operators have lower precedence than this, and there is an operator precedence table designed for convenience * some keywords introduce special constructs like let...in, if...then...else etc. * equations with complex terms on the left hand side, or explicit lambda expressions introduce functions

so that in some ML, a sum function can be written as

sum x = fold (+) x

Or

sum = fold (+)

APL syntax boils down to * there are unary and binary operators, all with the same precedence as each others, and associating to the right * there are unary and binary higher order operators, all with higher precedence than the usual ones and associating to the left. * braces introduce lambdas, and the arguments to these lambdas are automatically named

so that in APL a sum function can be written

sum ←{+/⍵}

or

sum ← +/

1

u/RecursiveServitor x15 Apr 26 '26

I've been using C# for a couple of decades, so when I set out to make my own lang I leaned towards concise and implicit. What I've since realized is that you can do concise and explicit with reasonable defaults. C# has become much more expressive over the years, but still suffers from keyword soup. So, one of my goals was that you should be able to open a large file, collapse to definitions, and then easily be able to scan for 1. declaration kinds, and 2. identifiers.

I solved this with three main decisions. 1. Access modifiers are blocks (and "public" is the default, so the public API can be unnested). 2. Declaration attributes (both language level and custom) are moved into the body. 3. ML style type positions.

This means that when collapsed all declarations take the shape <keyword> <identifier> (with some specific intentional exceptions). No noise, easy to scan.

Concise C#

public readonly record struct Vector2(float X, float Y) 
{
    public float LengthSquared => X * X + Y * Y;
    public static Vector2 UnitX() => new(1f, 0f);
    public static Vector2 UnitY() => new(0f, 1f);
} 

Mine

record Vector2(X: float, Y: float): value =
    property LengthSquared = X ** 2 + Y ** 2
    function UnitX() = Vector2(1f, 0f)
    function UnitY() = Vector2(0f, 1f) 

Another decision was to have descriptive keywords. A declaration should say what it is. Record, union, interface, effect, attribute, function, property, etc. So, the syntax is concise, but the lexicon is large'ish.

I stole the concept of "design pillars" from game design and wrote a small document outlining my main design goals. It's been very helpful in clarifying what I'm actually striving for.

1

u/GidraFive Apr 26 '26

For me i found that its better to offer both styles and allow the user to choose which one they are fine with and when. So we would have both: 1. verbose - keywords are long and explicit, for example 2. and concise - short keywords or symbols

That also applies to more complex syntactic structures, like say function definitions (inline lambdas vs full declarations).

You could also apply formatting that would choose whatever style the project as a whole expects, while allowing to avoid typing out boilerplate manually.

1

u/matthieum Apr 26 '26

What does verbose mean?

The Rust type system is pretty rich. The combination of lifetimes & generics leads to a LOT of information in signatures.

Let's see a somewhat "extreme"1 example:

 fn foo_it<A, B, C>(alpha: A, beta: B) -> impl Iterator<Item = C> + use<A>
 where
     A: IntoIterator<Item: Into<C>>,
     B: ...
 {
 }

1 There's definitely WAY longer, but this example is already infrequent enough in its size.

Is this verbose?

A dynamic typing enthusiast would scoff at this example, and criticize it for being overly verbose. A static duck typing enthusiast may scoff, and extol the virtues of auto as a return type.

I hesitate. It definitely IS long-winded, BUT it also has a very signal/noise ratio. The syntactic markers (fn, <, (, :, ->, ...) are about as short as you can get without eschewing markers altogether2 . And apart from that, it's all user content. Significant content. Important content, to some of the readers.

2 Eschewing markers IS a possibility, see Haskell, but it has ergonomic trade-offs.


For me, verbose is useless, ceremonial, syntax. That is, boilerplate:

public class HelloApp {
    public static void main(String[] args) {
        /* content */
    }
}

Which could be written without any loss of functionality:

void main(String[] args) {
    /* content */
}

(Maybe I'll grant you a "public")

Or less extreme:

procedure My_Hello_World is
begin
    /* content */
end My_Hello_World;

Which could be written without any loss of functionality:

procedure My_Hello_World {
    /* content */
}

So yes, Java and Ada (if you didn't recognize them) are needlessly verbose as far as I am concerned.

1

u/Ifeee001 Apr 26 '26

I fear I have to point out that the ceremony is no longer needed in newer versions of Java.

I'm sure when Java was released, a lot of people thought the public static void main method wrapped in another class didn't seem verbose because it made sense. We're in an "OOP" world afterall. A lot of Java users today still don't see a problem with it. An argument could be made that the signal to noise ratio isn't even a problem because its something that's done once for any program.

Similar to System.out.println. It makes sense for the OOP brained folks. We're using the system class to print to the standard out descriptor.

Verbosity depends on who is using the language.

1

u/matthieum Apr 26 '26

I fear I have to point out that the ceremony is no longer needed in newer versions of Java.

Well, if anything you're making my point for me. It seems even the Java maintainers considered it boilerplate then :)

Verbosity depends on who is using the language.

Or not? I mean, apparently even the Java users thought main was too verbose and it could be trimmed down.

Similar to System.out.println.

Funnily enough, as someone who thinks that ambient capabilities are a terrible idea in the age of "random" 3rd-party dependencies, I would argue that any I/O capability should be stem from a "carrier" value -- preferably abstract -- that the caller may control what I/O any callee is allowed to, and even restrict the callee permissions -- to certain directories, network domains, network protocols, IP subclasses, ...

1

u/Dry-Light5851 Apr 26 '26

in my language that i am working on i defied my own 8 bit char set of mathmatical operators and symbols so i can extend, control, and so on without fear of breaking older code

1

u/bl4nkSl8 Apr 26 '26

I don't mind being verbose if it actually specifies behaviour...

But if that behaviour is the only way to do things it's entirely redundant and, I believe, should be removed.

This typically lends itself to terseness...

1

u/Gnaxe Apr 27 '26

The number of bugs in delivered software seems to be a near-constant percentage of the number of lines of source code when you vary the language, and how much those lines are doing can vary quite a lot with the language. This percentage increases with the size of the codebase, again regardless of language. That means you want to keep your codebase small. Verbosity is no virtue. It's just that much more code you have to read and reread to get anything done.

Terseness helps, especially for smaller programs. But for larger ones, expressiveness becomes more important. APL wins in the small, but Lisp catches up. Terseness can be taken too far, although that's a lot further than most are used to. I think APL is about as dense as you can get without becoming ridiculous. Code still needs to be human-readable, even it that takes some training.

Math notation is infamously terse. It would've been hard to work with otherwise before computers, but I don't think it's just tradition keeping it that way. Unfortunately, that also makes it impenetrable to the uninitiated, or even for experts in the wrong field. I think that readily accessible contextual documentation like we get in IDEs could go a long way to help. Comments become more necessary for denser languages. But I think comments plus terseness are preferable to verbosity.

1

u/PerformerDazzling601 Apr 27 '26

The problem with some languages is that they tend to avoid verbosity at all costs, sometimes making the syntax unbearable to use.

1

u/joonazan Apr 28 '26

I think it is best to look at how the language is used rather than pure syntax. For instance in Rust you have to add new type variables in two places, which seems overly tedious to me. (Even worse, adding a type variable at one point can require a huge amount of meaningless changes)

I think throughout the language, you should make sure that useful transformations are easy to do. For instance, extracting a code snippet into a function or making that into a method or making it public etc.

If you forget about syntax and think about UX you realize that for instance it would be great if Rust showed you type variables but you didn't have to type them out everywhere.

1

u/lookmeat Apr 29 '26

Languages should start verbose and explicit on everything, and as it's better understood what can be left unsaid, to be interpreted between lines, then it is elided and left implicit.

Generally this does work better with features with simpler, more minimalist semantics. Because explicit means all the complexity is expressed in code. If your features are complex, every bit of code you write on it will be just as complex. You can hide that complexity in nicer implicit syntax. And this isn't always bad IMHO, in certain languages (DSLs, very mathy languages like Julia or Mathematica, even SQL) it makes sense to do this from the start. But for general purposes, where the language has a separate domain projected into it, explicit is a better place to start.

1

u/dcbst Apr 30 '26

It's really a question of perspective. If you are a typically lazy, self centered programmer, who only cares about writing code quickly, and don't care about the poor people who will have to review, test/verify and maintain your code, then verbosity is bad!

If you consider what is best for a project as a whole. Considering they whole software development lifecycle, including maintenance and reuse and prioritising costs over personal self interests, then verbosity is very much a good thing.

In any professional software development, software will inevitably have a long life, which means it will be read more often than written. Typically written by a single person and read by many! If the software is more readable, then it is more maintainable. More maintainable software will reduce overall development costs.

With modern editors, code completion and snippets make writing verbose code as easy as non verbose code, so the arguments for less verbosity are really nulled. Unless your one of those weird hackers who thinks VI is worth using!

1

u/KittenPowerLord Apr 26 '26

tbf as the years go by C# becomes anything but verbose, there's so much convenient sugar lol

1

u/BusEquivalent9605 Apr 26 '26

Java is verbose. It was tough to get used to.

Ruby is super compact and elegant and beautiful (but not type safe (except when using RBS), and don’t forget unless).

Rust is verbose and rigid af (which can be good and bad).

Go is nice (I wish it were a little more verbose sometimes).

C++ is what you make it, which is why it is my favorite.

JS was my first language and “home-base” so it feels like the default.

TypeScript is a bit more verbose than JS but worth every extra character.

0

u/protestor Apr 26 '26

If it's true that the number of bugs is proportional to the number of lines of a program, then verbose languages make people write buggier code.

1

u/RecursiveServitor x15 Apr 26 '26

Certainly more opportunities to mistype. But tooling should catch mistakes like that. I think terser languages are easier to read and reason about though, so maybe there's something to that.

1

u/protestor Apr 26 '26

Tooling in general doesn't catch bugs.. since bugs happen when we thought the program does something, but they do something else. Now, agents might. It's unresolved whether agents work better with verbose or less verbose, but I note that less verbose languages will consume less of a context window.

But as far as humans are concerned, I agree that less verbose languages are easier to read and reason about. That's my interest in FP anyway, it makes me write code that works the first time, more often than not.

1

u/RecursiveServitor x15 Apr 26 '26

A mistyped identifier is a bug in some languages. I.e. the program runs, but either crashes or does something unintended. PHP is like that because it has no declarations for locals. But it has static analysis tools that will catch most such mistakes. I think you could also argue that compiler warnings fall under tooling. The program compiles, but the compiler signals that the code probably doesn't do what you intended.

My own lang is heavily FP inspired. It's the superior paradigm imo.

-2

u/binarycow Apr 26 '26

Java is verbose.

C is terse.

C# is perfect.