What is important is not really size but consistency and "self-containment" - are the language constructs and semantics (including the std library) semantically and logically consistent with one another.
If that's not the case it gets much harder to get your head around how it all works as you cannot effectively apply some simple pattern to all the stuff you see and you need to juggle different ways of thinking when doing basic stuff. And this propagates even further and harder to other libraries.
There's also the problem of determining how semantics and comprehensibility of the whole are affected by the interaction of parts.
Adding new features to a language is always costly because you not only have to deal with the new semantics of the new feature, but with the emergent semantics of how that feature interacts with the existing feature set. The cost of adding some new semantic and syntax is not just N, but N*M, where M is the subset of existing features that the new feature may interact with in some meaningful way. For example, if you add support for declaring a constant (separate from a variable) this is not just the addition of some new, isolated meaning, but introduces semantic difference—the programmer now needs to reason about variables as opposed to constants and also needs to discern this difference in all scopes in which the distinction is valid...
It started out as a very usable, mid-sized language, with many tooling issues. Now it is a kitchen-sink language with almost every imaginable feature (and more coming!) that still has tooling issues. It's no longer as pleasant to read unless you keep up on the latest additions. You can't easily read code that uses the myriad of new features without a long weekend learning what every property wrapper in this particular program means and how to use the new feature of the month.
In my opinion while it was still a mid-size language, they should have fixed all of the tooling issues, and then very incrementally over a long period of time added new features. Instead it's more of a "move fast and break things" culture. Which is not what I want in the evolution of a programming language.
I completely agree. It feels like the designers of Swift gazed into the C++ abyss too much. I really wanted it to be an ergonomic systems language. Zig now fills that void for me.
Some core designers of Swift, at least Doug Gregor, John McCall, Dave Abrahams, were very much part of the C++ world. Abrahams mentions Gregor's proposals for C++ here: https://youtu.be/6JYAXADQmNQ?t=1157
I have the feeling Swift is more complex than it needs to be, too, but can new high-level languages really be simple? Nowadays, you need a memory model, async, generics, value and reference types, move semantics, concurrency, full Unicode support, etc.
Go tries to solve that, but gets criticized for its simplicity.
Also, I suspect part of the friction writing Swift isn’t language complexity but tooling issues. to what effect that’s true is hard to say of course. For example, compilation slowness introduces friction, but I wouldn’t know whether that’s inherent or solvable.
There also are a lot of “the compiler could be smarter” things that do not change the vision of the language, but do affect its ease of use. Such warts make the language harder to write than necessary. If those issues can be fixed, the language can become easier to write. An example is https://github.com/apple/swift-evolution/blob/main/proposals....
If that process cannot stop, I think it’s better to leave a few warts that programmers will soon encounter and learn about than many very rare ones that, when encountered, will make programmers pull all their hairs out because googling them only produces “I changed Foo and the compilation error went away” results or StackOverflow pages without answers.
I looked at Kotlin and Swift at the same time some years back and they seemed extremely similar. I didn't enjoy ios development and am currently an Android developer. I've been using Kotlin for a while now and it's easily my favorite language.
Did Swift take a wrong turn somewhere that set it on such a different path from Kotlin?
Golang is really the best example of a minimal language (Yes yes I know late to generics and all that). I think it works because they have spent huge amounts of effort to create a well designed and fairly comprehensive standard library. The best example of this is io.Copy. You give it a reader and writer and it does an optimal copy in almost all cases.
A minimal language that doesn't have resources like Golang to put in the stdlib is just not gonna work.
Your example exposes an edge of Go that speaks to this essay.
Yes io.Copy works with any Reader and Writer but dig into the documentation and there's a runtime check for the revised ReaderFrom and WriterTo interfaces that you might also implement for faster performance. It's just something you have to know, otherwise you will not get an optimal copy.
Go 1 is right on the edge of being a big language, but what might stop it is that Google don't need it to get any bigger.
> I think it works because they have spent huge amounts of effort to create a well designed and fairly comprehensive standard library. ... A minimal language that doesn't have resources like Golang to put in the stdlib is just not gonna work.
Yes. Rust suffers from this problem. The language is now reasonably solid, but library quality and stability is all over the place. I've been annoying people in the Rust community by saying that it's time to get all widely used crates (over a million downloads, or depended upon by a widely used crate) to version 1.x. After that, no breaking changes without a major version change. Crates with re-exported types from lower levels force groups of crates into lockstep update.
The big advantage of Go is that the libraries for doing server-side web stuff are the ones used internally by Google. So they've been thoroughly exercised. The obscure case that comes up once in a trillion transactions probably happened a hundred times in the last hour in a Google server farm.
I want a step further than upgrading libraries to 1.0. I really want the Rust foundation to package up the essential libraries (bikeshedding to ensue, but something akin to Python or Go), and release those as a single, annual distribution. Even better if they could standardize the API across all of the packages, ensure minimal unsafe, etc. The `rust_stdlib_v2023` could only depend upon itself, and would be a basis for which other projects could then build from there.
It annoys me to no end the huge number of packages I pull in for doing something "simple". It is great that packages can be so small and focused, but now I have a real trust problem. How am I possibly supposed to ensure there are no malicious actors in my dependency tree?
As someone considering ramping up on Rust, this sounds disturbingly similar to the JavaScript+NPM ecosystem that is frequently criticized here.
How far along the ridiculous dependency spectrum towards NPM is crates.io?
For example, if I release a piece of nontrivial software built with Rust, how many copyright notices am I going to have to include? (To say nothing of vetting the set of transitive dependencies!)
There's already a standard library. After that, there's just whatever anybody decided to upload to "crates.io". Nothing in between. Some form of curation and quality control is needed, at least for crates depended upon by other widely used published crates.
The Rust foundation's crate manager job is apparently vacant until at least April, so nobody is officially in charge of this.
Well that is exactly what I think should change. I do not want to have to pull in a package for regex, parsing json, generating a random number, counting the number of available cpus, etc. Quite a lot of standard functionality required across a broad swath of projects. I understand Rust does not want to bring them into core, but I think there should be a middle ground that offers richer functionality to the language without the wild-wild-west of crates.io.
I think that's a great comparison. Establish a better baseline standard, but one can always reach out to a cutting edge crate if the library set is insufficient.
It's a useful comparison, although Boost has traditionally been the home of cool template code you're not supposed to understand but can use. For Rust, the problem is widely used crates such as "hyper" (http), "tokio" (support for async), and "glam" (2D and 3D vectors and matrices.) These are widely used, but not part of the standard library. They're not basic enough to be standard, and they all have alternatives. But if you use them, you need them to be stable.
For me, C-like languages like Golang don't come close to be as minimal as lisp-like languages. I agree that the standard library of Golang is smaller compared to it's contemporaries, but the language itself is not small, there is a bunch of syntax to be learned compared to languages like Clojure, where the actual language is tiny, and the standard library on the bigger side.
Technically it's true. But Lisp-like language (e.g. Scheme) is so minimal, to a point that everyone basically invented their own DSL inside it to get the jobs done. And the you ended up with a minimal language... and several sub-languages.
A language like scheme is much simpler yes, but scheme or even Common Lisp don't have anywhere near as comprehensive a lib as Golang, which is what you need to create real systems.
Even GNU make has $(filter ...) and $(filter-out ...).
It'a terrible name, by the way. A filter separates; it has two outputs. Does a function named filter return the filtrate or the residue? And I just had to look up filtrate to check which one of the two it refers to and what is the opposite term. From now on I will remember that there is residue captured in a filter, and filtrate is not that.
That would be terrible, those loops are crazy dangerous. I would avoid writing a loop and rather come up with a whole different programming paradigm. Maybe a macro system or something based on category theory?
The problem is that find first, find last, find all, filter, map, flatmap, and reduce all look pretty much the same when written as loops, and writing and reviewing any of these more than once is a poor use of time.
I agree. I've grown to really love Go the more I've used it and gotten the chance to compare it to other languages. There's a certain "completeness" and philosophical consistency to it that's really hard to find in other languages these days imo (some older languages, like Standard ML, C, Scheme, and some others also achieve this) because I think a lot of language designs now evolve more organically instead of being formally thought out up front.
Go evolved very organically. Look at the inconsistency in the way nil slices vs. maps work, for instance, or the way comparisons against nil for interfaces work. Go may be simple to explain at a high level, but the details are very complex.
I am a team lead in c++ shop. Every interview, I ask: tell me about c++’s std::move().
Not a single interviewee has even had a good guess in 2 years of open job recs.
It really has me reconsidering. I’m a language nerd through and through. I love C++. But I also have to be honest and say there’s such a thing as “the average amount of c++ the team knows”.
To me, the biggest threat to C++ isn’t rust, but rather the speed at which the language is changing. It’s already past the tipping point.
In my domain, embedded programming, I’m keeping an eye on zig with fingers crossed.
I've been reading through the proposed C23 spec. Looking at what's happening with enums, it's amazing how suddenly a simple feature became a far more complicated one. I read the spec on enums over and over, and still don't understand it.
> I read the spec on enums over and over, and still don't understand it.
That's from someone who has written C++ compilers.
Some of the insane complexity being added to C++ comes from trying to emulate Rust move semantics without a borrow checker. An xvalue is what's left behind after you do a move. In Rust, the compiler guarantees that you can never access the remnants after a move. But in C++, someone will somehow manage to leak a raw pointer to something, then move it. So users have to know about xvalues.
(Could be worse. Trying to emulate Javascript async semantics in everything else has resulted in some real horrors. Only Go has a sane solution.)
> why do you still ask a question you've gotten zero signal from?
Interview questions aren’t just for comparing candidates with each other. It’s also useful to know how a candidate’s knowledge compares to the knowledge of the team. The best of a bad bunch might still be unhirable. It’s often better to leave a role vacant than fill it with someone mediocre.
If the GP can’t find anyone in the hiring pool who knows C++ well, that’s a valuable insight. Maybe they need to expect less C++ from candidates and plan for more on the job training.
I have the same issues with C#. I came up in the days of .net 4.5, in the years since I've watched Microsoft bolt an entire language worth of features on top of C#. I didn't use the language for the last five or so years, and I feel so lost that I avoid using it now.
And that doesn't even begin to touch on the absolute disaster of the net framework/core/standard/mono/uwp runtime situation.
C# as a language just doesn't seem very stable. I really don't enjoy using it anymore.
I have quite opposite opinion. Every extension increased readibilty and cut the cruft. For example, even simple changes, like native tuples or expression bodied members tremendously changed how the language should be used and how the "correct" code looks.
In what way do you think it isn't stable? I've upgraded code written in C# v1 to the latest and greatest and never had a breaking change (well except for one time where it was my own fault for abusing reflection).
Ultimately you can use as much of the language as you want to, the "old" ways haven't been removed and you can still code like it's 2002 :)
I'm aware that I'm yelling at a cloud, but what really did it for me was nullable reference types. I get it, I understand why nulls are a problem and how annoying they can be. I've done my time in the trenches tracing nullrefs in production.
But I just really, strongly dislike the way Microsoft went with the problem. Declaring types nullable with the `?` just puts me off. `Nullable<T>` was always something to be avoided, and I find the syntax just as annoying as null checks.
Ultimately most of my complaints can be boiled down to "too much changed and now things are different"
Honest question: what do you expect the answer to be? Pedantically, the "correct" answer is that it statically casts its argument to an xvalue. Is this a way to tell if the candidate understands value categories?
I think it would be more elucidating to consider the myriad common incorrect answers you expect to get when asking that question--which is what I expect they are dealing with, given the more about how rare even having "a good guess is"--as opposed to whether they are expecting a single particularly-correct answer.
Anything that’s not that is generally incorrect in some way, though. std::move doesn’t move, or take ownership. People use it anyways. The same applies to constructs like std::forward, or the dozen ways to do initialization. C++ has a standard that incomprehensible to most people because it bends over backwards to try to come up with a unified set of rules for disparate behavior. Unless you’re a library author knowing these things off the top of your head doesn’t show much about your ability to understand some sort of understanding of the how C++ works, because there is no consistent understanding to be made. It might make sense to ask how std::move might work in Rust but it’s basically trivia for C++.
Yeah, but like, the egregiously wrong answers you are likely to get that aren't even close--not to the underlying behavior but even to the way it is commonly used--are apparently so wrong you don't even list them in your list of wrong answers ;P.
Someone who doesn't know how it works but successfully have ever written a move constructor (which is a bit more advanced than merely using one) are going to come up with an answer that I would personally (as not the person who said this... for all I know they also will be super pedantic) claim is "close".
But, I would fully expect the majority of people you ask that question to will, for example, actually claim stuff like that the prior value is completely destroyed... not even merely deconstructed (which is already problematic), but somehow deallocated even if located on the stack, which is way more wrong than merely thinking std::move "takes ownership".
I don't know if you play chess at all, but a book I really enjoyed (and read probably about when I was your age? and the existence of which--honestly more than the specific content--massively informed my teaching style) was The Amateur's Mind, which was a chess master going back and attempting to not as much teach chess from his vantage point to an amateur but to go back and explore the misunderstandings amateurs have about chess by way of interviews with amateurs about games and then trying to work within that mental model. I think that, even though we were all amateurs at one point--or even might have a long way to go still--it can be difficult to really conceptualize the gulf between what an amateur thinks and what you might even assume is the mistake they are making.
You can use std::unique_ptr without explicit std::move for scope bound pointers, with class members and you can even return unique_ptrs without std::move due to RTO. The only time you need std::move is when passing ownership via a function call. That's quite a lot of practicla use cases available.
But I think the concern is not about being able to use it, but to understand what it does, std::move is nothing but a cast. Itself it doesn't move anything. And then there is the whole fun with the state of the noved-from object afterwards. Quite some depth to uncover.
One can get really far without ever touching `std::move` or `std::unique_ptr`. Especially when taught about `new`/`delete` first. Lots of courses still teach C-esque C++ first and students just keep to it because it's more explicit.
You might be surprised at how little some (many?) programmers with C++ experience actually know about the language. Or any language, really. Our industry doesn't reward that kind of knowledge very much.
Perhaps that should be a hint that you are asking the wrong questions. The job of a software developer is to solve customer problems. Not being C++ language experts. So instead ask how they have solved real customer problems, what the tech trade-offs were, why they chose the solutions they ended up with, what other possible solutions they could have used, the hardest bugs they have fixed, how they fixed it etc.
I have 30+ years of experience, and have hired many C++ developers, while working for different companies around the world. And my success rate in hiring good developers have been very high. Using exactly the questions I mentioned above.
man I use C++ on personal projects for opencv (and python perfomance is pretty bad for my use case) but oh god, I don't I could ever get a C++ job, there is a universe of features I won't dare to touch anytime soon. I just use the "modern" and simple stuff and code look pretty good (imo), don't have to deal with gibberish compiler errors (except when std::format goes crazy). std::move is cool though, rvalue and lvalue is bit too much but the performance, clear "giving up" resource and dont having to use pointers is worth it (I just use it for rvalue passing, prob there is more to it).
You could get a C++ job. One of the issues with C++ is most people using it for anything useful only need to know some particular subset, only actually know that subset, and then get bit when the subset they know changes.
The core language should be small enough so that a programmer can remember all its features. This makes for efficient and quick programming. The rest should be in standard or community libraries. In my book over the last few years only Go and Lua meet the criteria.
To me, Objective-C 1.0 was the perfect size (before Apple added properties and stuff in 2007). As long as you avoided the weirdest undefined corners of C, it felt like you could understand every construct available and its performance implications without needing to read the docs or ask Google.
Maybe it’s just the rose-colored memories of a first love.
I totally agree. I loved that you could understand more or less the entire language's set of additions to C by reading one relatively short document.
JavaScript used to have the same appeal, before it started evolving. Despite the rough edges and some poor decisions, you could still grok the entire thing pretty quickly and appreciate how it works.
Of course, I have my own bias here, but it really did feel like they fit well together in Objective-J.
What is very, very tricky is that the language get bigger the harder is to do composability. And "composability" is far bigger and broader than the normal understanding.
For example, consider Rust that is built around the idiom of composability (aka: Traits).
Is nice, until you get async, custom allocaters + runtimes, const, how do macros, type reflection, code generation, etc...
Each thing is something user want to make code more composable and flexible. But each thing is also a big thing: You can make a WHOLE LANG around just one of that!
---
Lisp, Forth and similar make easy "syntax composability" -not solve all the rest- that is probably the bare minimum. This is the MAJOR "flaw" of algol-like languages.
For example, I wish Rust allow:
struct Person {name:String}
struct User: +Person {pwd:String} //not inheritance: Copy of fields!
let u = User {..}
let p = Person::copy_from(u)
for f in p.iter_fields(): //walk over name:String, pwd:String
With lisp/relational/array like paradigms this stuff is easy. But algol-like languages are too "nominal" and lack the ways to manipulate the construct without resorting to macros, that are for most cases a band-aid.
Type theorists have been talking about this too. The search term is "rows" or "row polymorphism". They're awesome but no mainstream language uses rows. I wrote about them in my blog post about 4 issues facing functional programming [0]. Also, check out Unison [1], which is mostly structural but "pretends" to be nominal in a way that I feel would be very appealing to most programmers.
What this example show is that you can't manipulate the "things" that are expressed in syntax as data, so you can't have the same way to deal with both types and values:
In short, you get what inheritance promise without the OO, easy reflection, easy code generation, simpler macro implementation, easy way to do generics, easy way to copy to/from different types that are almost similar (ie: structurally)...
The amount of stuff you will simplify is massive.
Some of this feel is what make the "dynamic" languages their power. Now making it work on static lang is more tricky (you wanna keep everything as if you were coding it by hadn't and the types all resolved at compile time) so is likely required to be restricted and only a second-order citizen, but it will make some much easier!
Seems like you want a code generator. For some problems I admit I have missed a proper, integrated code generator. C macros is, like you say, hacky.
However, personally I strongly dislike dynamic languages for anything beyond simple programs, ie <100 LOC. Gets way to hard to reason about the code, since you never know what a function returns. In Python for example I invariably end up sprinkling dir(...) all over when modifying code.
Haskell was a small clean language but now with the innumerable language extensions has become large
Bash is a small clean language that has maintained its clean core
Golang is the archetype of a small clean effective language that has tried intentionally not to become cpp and it shows the amount of wrangling needed to add generics to golang is a testament to their focus on a small language albeit imperative
In the design of Starlark (https://github.com/bazelbuild/starlark), I often had to push back against new feature requests to keep the language simple. I explicitly listed simplicity as a design goal.i
Of course, the scope of the language is not the same as general purpose languages, but there's always pressure from the users to add more things. I also think many people underestimate the cost of adding new features: it's not just about adding the code in every compiler/interpreter, specifying every edge-case in a spec, updating all the tooling for the language and writing tutorials; it's also a cost on everyone who will have to read any of the code.
> it's also a cost on everyone who will have to read any of the code.
Everyone who will have to read the code in the compiler or interpreter? Or everyone who reads code in the language?
I'm not so sure the latter should be a consideration. If you don't give people some feature they would use, they will develop their own idioms to accomplish the same with what the language does provide. And the reader then has to become familiar with those idioms, and repeat the process for every new codebase they encounter in the language. Better to have one good way to do it.
Of course, the feature itself brings value, so you have to balance the cost and the value.
If something is used a lot, you can expect the reader to learn the pattern quickly. If something is not super common and not intuitive, it can make the code less readable (and the reader might spend a lot of time searching in the documentation).
Two examples I've seen this week:
- Python has a new keyword `except*`
- C# has a prefix operator ^
It's not obvious to me that these features are beneficial in general.
It's absolutely a tradeoff requireing a cost-benefit analysis.
Prior to Java 5, the language was quite simple, the spec readable and understandable without requiring deep knowledge of compilers.
The introduction of generics and lambdas massively complicated the language specification. But those features also were huge improvements that pretty much anyone would consider worth the cost.
It has some kind of compatibility with Python, but the language is much more minimalist.
New features can be discussed on the GitHub issue tracker, but I expect very few additions in the short-term. I consider the language quite stable now, and it has three mature implementations.
C is about right. Common Lisp is too big and Scheme is too small.
The language itself should be small but its standard library should be extensive, and largely written in itself. If the language's own implementors don't want to program in it neither do you.
CL too big? I dunno, the spec doesn't include very standard stuff that we get through the de facto standard libraries like ASDF, UIOP, Bordeaux threads, et cetera.
Sorry, I don't quite follow, is your point that CL has two complicated macros and is therefore a "too big" language?
Yes, LOOP has a lot of syntax to make it look somewhat like "hey, you can code this in English" and FORMAT is discount Turing complete, but there are a lot of language features out there with complicated syntax (regular expression, for example) and X86 Assembly's MOV is also discount Turing complete.
Some would argue that R6RS is too large, while others argue that R7RS (small) is too small. Maybe there's a Goldilocks Scheme in the middle that's just right. :-)
Of course, most types of Lisp-ish languages let you grow the language in a way that C doesn't really (yeah, there's the C preprocessor, but it's far more limited that the macro facilities provided in most Lisp-ish languages).
I've always figured you can ignore the features of a language that you don't like, but a drawback of a giant language is that it's hard for beginners to read code, or to make sense of tutorials that all use different ways of doing something.
You can always put extra features into a library, and identify certain libraries as "standard." So it seems reasonable to demand that actual language changes are limited to those where the readability benefit outweighs the need for everybody to learn the new feature in order to read code.
As mentioned in other posts, a huge sprawling language makes it hard to gauge whether someone is proficient in a language when applying for a job. I don't really know how important that should be.
In my case, I got a low score on a Python knowledge test because I've been programming in Python for a decade but haven't kept up with the latest features. Not that I'm looking for a job, but I was just curious, and it was part of figuring out what kind of training I should get.
I'd written C# and python as a student, but Go was the first language I worked with professionally. I cannot overstate how much relatively easy learning and reading new code was as a beginner, because of how there's often only one or two valid ways to solve a given problem, so knowledge/context often easily transferred between different tutorials and codebases.
A good watch for the fine people in this thread (and please ignore explicit or implied linkages to Java--it's full of the broad concepts, not narrowly JVM stuff)
There is something very appealing about learning a small(ish) language. However, a small language does not always mean simple. And it means code can be difficult to unravel.
Niklaus Wirth, the creator of Pascal, Modula-2 and Oberon, believe strongly that teaching programming is most effective when students can grasp the entirety of a small language.
From a 2018 interview with Wirth on the topic:
> "In order to do a good experience, you have to have a clean language with a good structure concentrating on the essential concepts and not being snowed in."
> "That is my primary objection to the commercial languages – they’re simply too huge, and they’re so huge that nobody can understand them in their entirety. And it’s not necessary that they’re so huge."
An interesting example of this I came across recently is Pico:
> Pico is a tiny but expressive programming language that was especially designed to teach advance computer science concepts to students in other sciences than computer science … Currently Pico is no longer used as a tutoring language for its target group, but this is more a consequence of politics than of the astounding results we got with teaching Pico. Today, Pico is used as a means to teach principles of language design, interpreters and virtual machines in a sophomore course for computer scientists and in an adjacent course in a masters program in applied computer science.
We, as a field, should be very careful about forgiving our tools of the sin of complex and tedious design because those flaws can possibly be remediate with LLMs.
The complexity of boilerplate does not only exist for the author but every future author.
It is not my experience that LLMs and other complex automations are nearly as good as refactoring and changes as they are at generating boilerplate in the first place. In the end this code lives on as a human concern despite the automation.
I agree. I also don't think there is a single perfect programming language yet.
I do think however that programming languages and libraries are the assembly language for AIs to interface with legacy systems targeting human usage.
So it's important for these to be easily understandable.
Go fills that sweetspot better imo. Until something else appears perhaps.
But you're absolutely right that we should make sure to still improve these languages and it is right to worry that LLM don't incentivize new languages (smaller code corpus, less data?). As for Go, it is improving steadily so I'm still confident in that regard.
I am new to go, but to me, the language looks bigger than it needs to be. Why do slices and maps need to be part of the language and not just part of a standard library? I suppose the reason is special syntax, but having to learn that special syntax makes the language feel bigger.
They are part of the language, because they require generics to be implemented if you want to implement them in the standard library. Go didn’t use to have generics, so those constructs were hacked into the core language.
I see. That makes sense. So maybe it is generics that are so fundamental that they need to be part of any language. Seems that Java, C#, and Go have all learned this lesson.
> There's nothing new in terms of keywords about (implicit using or file scoped namespaces)
And I didn't imply that they were new keywords, they were examples of the second category, "new ways of doing things". The text "do the same thing" alludes to that.
> and makes code more readable and shorter, that's it.
Correct, but each new ways of doing things comes at the cost of there now being (at least) two ways to do the same thing.
> It just simplifies stuff, can we honestly call it an expansion of language?
If a source file that was not legal in language version 9, is now legal in language version 10, then yes we honestly can and must call it that. It is literally an expansion of the subset of all possible source files, that are legal source files in the language.
Does it "simplify stuff" if there are now 2 ways of doing a thing, when previously there was only one? The parser has to deal with both now. It depends on how you look at it, I think. e.g. It depends on if your code mixes them. If not then the code can be simpler, but as coders we have to still know both as we might encounter both.
C# does not mandate having one class per file. (Java doesn’t for non-public classes either.) While I suppose many (most?) devs/projects have adopted this rule, there are cases where it makes more sense to keep related stuff in one file.
C# also did not mandate having one namespace per file. Devs have adopted a "one namespace per file" rule because it's generally a good idea. So now the new "file scoped namespaces" allows only 1 namespace per file; and if you want more than that, well then your namespace can't be file-scoped, by definition. You can still use namespaces with { } in that case.
The same _could_ be done with type declaration such as classes and interfaces. If it would be beneficial idea is another thing.
Sure, but “one namespace per file” is a much more obvious idea than “one class per file” (especially with C# IDEs aggressively tying namespaces to the filesystem layout).
I programmed in C# for roughly the first 15 years of my career. Then, Ruby, Go, TypeScript. Going back to C#, it does feel bloated. It’s accreted a lot more complexity since I stopped using it (just before .Net Core).
I’d love to have a TypeScript language with the standard library, single binary output, and build speed of Go.
I think they are referring to reserved words. Regular c list of reserved words has stayed rather static except for various data types. Where python kept added reserved works as time went on.
As far as I know, the C standard reserves words that
- start with two underscores
- start with one underscore (file scope)
- start with is, to, str, mem, atomic_ or a bunch of other prefixes and continue with a lower case letter, consist of E followed by a number, and more.
So the list of reserved words is rather long (infinite), and includes words like `string`.
I don't see problem with growing languages as long as designers are doing their job.
There's difference between adding features randomly and putting effort into making it obvious, transparent, integrated well into the ecosystem and overall UX.
UX is something that languages and API designers must put more effort into.
Start creating APIs with assumption that people have no access to documentation - it makes everybody's life easier. Just take a look at .NET BCL - it's very well designed. They even wrote a book about framework / libraries design basing on their decades of experience.[0]
Also "small language" is relative.
If you have started doing X lang development decade ago then that version of language is "small" to your perception. For somebody who started 5 years ago it was small 5 years ago.
The point is that at every point language have some features which aren't popular because they serve some specific cases which are required for somebody.
Very often those features are required for base class libraries makers so they can create either good API, or have good performance/safety, or anything else.
For me it seems like programming languages and its ecosystems lack of deprecation process which actually removes stuff.
[0] - Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .Net Libraries
C# is getting "too big", IMO. (20+ years as my primary lang but doing a lot of TypeScript over the last 3)
There's really good new stuff like switch expressions and pattern matching.
But it feels like TypeScript is in a better place. I would love to see a "T#" - a trimmed down, more modern C# that makes certain tradeoffs and defaults.
Maybe that's F# (spent some time with it), but it diverges a bit too much from C family syntax.
I'm facing this dilemma now with the Dogma metalanguage [1]. On the one hand, I want it to be small so that it can be picked up quickly and mastered easily (very important since its primary use case is documentation). On the other hand, it needs enough power to actually be useful in the real world.
In the end I opted to avoid most conveniences since they can easily be built using a few common macros such as `u16(v) = ordered(uint(16,v))` and the like, except for cases where it would be just silly not to (like strings as syntactic sugar for concatenated codepoints, and regex-style repetition chars ? * + instead of {0|1} {0~} {1~}).
But even then the spec is over 2000 lines long (admittedly the examples take up a fair bit of room, though).
Surprised to see no mention of Elixir so far. Its designers have explicitly focused on "a compact and consistent core". And indeed I found it very pleasant and rewarding to learn for that reason. Everything fits together nicely.
> Since v1.0, the language development has become focused to provide a compact and consistent core. The Elixir team focuses on language features that:
>
> 1. are necessary for developing the language itself
> 2. bring important concepts/features to the community in a way its effect can only be maximized or leveraged by making it part of the language
In other words, as I've heard it put, the language is mostly not growing anymore. It probably helps that it has strong metaprogramming facilities.
Limited experience with Haskell personally but my impression is that Elixir really only has the core, whereas in Haskell it is effectively standard to use language extensions? I don't know how that changes the experience.
I primarily work in a language out of the APL family.
When I first started it, the entire reference page of every function/command/flag fit on a double sided printout in regular sized font.
Past a certain point you just end up with overlapping versions of the same thing maintained for backward compatibility or slight differences of opinion & behaviour.
I feel the same way about framework ecosystems. Some languages almost have too many, to the point that they are constantly being introduced, changed in ways that make old code non-portable, and then sunsetted. It's hard to keep internal corporate software running when if you choose something mainstream it's less than 5 years from EOL, but if you choose something emerging it may never go mainstream. A lot of it seems like reinventing the wheel.
I haven't paid attention in many years, but at least for the first few years (after it grew from a data format to a scripting language), each release of Lua was more powerful, faster, and smaller in executable size, than its predecessor.
There are two different ways to go about this question:
- asking about the scope of a programming language is asking about the intended programming situations one wants to cover.
- what is a good balance between convenience (sytactic sugar, "features" and abstractions) and asking the programmers to write out things explicitly.
- more subtly: who is using the language, what concepts can be expected to be known to the programmers.
These are of course connected: a particular type of recurring programming situation may lead one to think of patterns and a need to abstract them, whereas to someone who intentionally constrains the scope to a specific area, it can be easier to keep the number and kind of source or "programmer intent" abstractions low.
Often these discussions happen in the context of a general purpose language but there is little consideration that some programming situations are better served by DSLs.
Also a decision on "MIT vs "New Jersey" (worse-is-better) style affects the whole thing, as the notion of correctness is at some level always one of programming situations (a higher level discussion on using the API right way, checking error conditions etc)
Thus is a long-winded way to say: in the end the starting point matters. C++ can be considered a failure language design if you consider it from the readability/cognitive load angle but design choices seem to consistently be about permitting all possible choices. This is why Stroustrup can claim things like "you can write memory safe C++" and not even be wrong. Java can be considered a failure in concision but tool support means that programmers do not pay the cost of writing everything out explicitly.
Haskell and anything in the academic tradition will emphasize abstractions and demand more and more knowledge from users whereas industry tradition will be biased towards low (or lower) cognitive overhead, but if one is willing to specialize, there is an infinite number of nuances of possible points in the design space. We will see more programming languages and come to accept that there will be many that we use at the same time.
So rather than asking how big, maybe we should wonder how to understand the involved trade-offs.
Glossed over in the brief analysis of Haskell is that the expansion of features are:
1) Not in the language spec, just options in the de facto-standard compiler
2) Are often shunned by industrial users, see “Simple Haskell”
3) Are, among other reasons, there to support experimentation and research which is an important use case for the language
4) Are opt-in and largely unnecessary in practice, in my code I generally only use a couple of well known extensions.
If a language ignores common problems we all face, it’s too small.
If a competent professional can’t become fluent in a year, it’s too big. But I have pretty low regard for people who complain when it takes longer than a weekend. This is part of what we’re paid to do, and the investment in your toolbox will pay off for the rest of your career. I still use ideas that came from languages I can’t officially use right now.
I started Python with 2.4 or 2.5. It’s not big, it’s been many years since I’ve been actually surprised about something.
Evolution, or incrementing version numbers, don’t necessarily equal increased size.
For example Python now has async functions. But it had them in Python 2.5 as well when it introduced generators (see the twisted.deferred decorator) - you’d just use “yield” rather an “await”.
At a feature level there are things like descriptors and metaclasses, which are complex and rarely-used.
There's a huge number of obscure magic methods/attributes: __origin__, __orig_bases__, __prepare__, __mro_entries__, etc.
It's full of gotchas: methods are looked up on the instance, except magic methods which are looked up on the class, except except __getattr__ and __dir__ on module types. There are both __getattr__ and __getattribute__ and they do different things. Most `typing.Foo` (and also modern `list[int]`) aren't `isinstance`-able. The use of += with tuples. The lack of any coherent numeric tower. Etc. etc.
I think it's very clear that Python has grown organically, and is now struggling under the weight of all its bolted-on extra bits, or sheer unnecessary complexity. I would love to throw it all out and start again.
Guido pushed back on most new features for a long time, but almost a decade ago changed his tune to yes for anything reasonably useful. Guessing due to envy of all the nifty features dropping from Ruby and JS to C# and Kotlin.
Personally, I'm not enthusiastic about the things that came after the f-string. (Although dict unions should have landed a decade prior.)
There are some valid points buried in this comment, but they are deeper than you perhaps intended or even know.
Metaclasses are widely used, but unless your specific problem calls for their use then you won’t use them. Usually these problems are related to authoring a library.
How would isinstance work with List[int]? Would it do O(n) isinstance calls on each element? Python isn’t typed like that, and a list is not and never will be an instance of List[int].
The magic method resolution order is also pretty natural and fits (slots?) nicely into the mental model you develop whilst using Python.
A programming language is almost always built for a purpose - to solve a problem or fit into a certain domain. We've all seen the needless complexities in programming languages that accrete new features, year after year, just to have new features.
> A programming language is almost always built for a purpose - to solve a problem or fit into a certain domain.
Is it? Most general programming languages have certain design goals, but I find these often unrelated to the domains in which the language ends up being used.
This makes me think of how the R language has been "slow" to make (some) changes and many people now use packages from the tidyverse. Writing R with the tidyverse can be practical unrecognizable to people writing with base R (and vice versa).
If you feel the need for a new feature in a language, you should ask why the language does not make it easy to build that feature in the language itself.
PHP always had everything you needed built into the language. Coming from a C background and moving to a Javascript lifestyle php always offered you everything you need out of the box and extensions for that little bit more.
Fewer "decorations". I imagine it would look more like Ocaml than Rust or C++ because the grammar of Ocaml is such that you don't really need brackets or braces or parentheses most of the time. And "system" languages like C++ and Rust are notorious for being symbol soup, which is unavoidable if you want to control every single detail of every single thing.
I don't know, but there has to be a reason Gamemaker Language is so easy to pick up and play with. Imagine if C let you get away with murder and still compiled.
var _xx = 100;
mystruct =
{
a : 10,
b : "Hello World",
c : int64(5),
d : _xx + 50,
e : function(a, b)
{
return a + b;
},
f : [ 10, 20, 30, 40, 50 ],
g : image_index
};
It's been a long time since I've look at GML but it looks like it's turned into Javascript-lite. I do wish C had flexible array shorthand and something higher than function pointers to work with.
There's a pre-supposition in the community that bigger languages are typically worse, but I'm not sure that's true. There are also multiple ways to measure language "size" and most arguments I've seen conflate language features and standard library (which can often have overlap).
As counter-examples, I'd raise the Wolfram language (and maybe Matlab, but I haven't used that) as an example of a truly vast language – in terms of "standard library". On the other hand almost all criticism of Go has historically revolved around it being too small.
If that's not the case it gets much harder to get your head around how it all works as you cannot effectively apply some simple pattern to all the stuff you see and you need to juggle different ways of thinking when doing basic stuff. And this propagates even further and harder to other libraries.