7 ms·
When I looked into Go I found it a bit surprising that someone had created a non-expression-based language as late as ~2009. I have not familiarized myself wit
by arwhatever 2y ago
When I looked into Go I found it a bit surprising that someone had created a non-expression-based language as late as ~2009.
I have not familiarized myself with the arguments against expression-based design but as a naive individual contributor/end-user-of-languages, expressions seem like one of the few software engineering decisions that doesn't actually "depend," but rather, designing languages around expressions seems to be unequivocally superior.
- TurningCanadian 2y agoinstead of "expression" you meant "exception", right?
- whalesalad 2y agoexpression as-in s-expression (ex: lisp)
- Jtsummers 2y agoIn the statement/expression-oriented axis of languages, Go is a statement oriented language (like C, Pascal, Ada, lots of others). This is in contrast to expression oriented languages like the Lisp family, most, if not all, functional languages, Ruby, Smalltalk and some others. Expressions produce a value, statements do not. That's the key distinction. In C, if statements do not produce a value. In Lisp, if expressions do. This changes where the expression/statement is able to be used and, consequently, how you might construct programs.
- runevault 2y agoA simple example for anyone who might not appreciate why this can be so nice. In languages where if is a statement (aka returns no value), you'd write code like int value; if(condition) { value = 5; } else { value = 10; } Instead of just int value = if(condition) {5} else {10} Some languages leave ifs as statements but add trinary as a way to get the same effect which is an acceptable workaround, but at least for me there are times I appreciate an if statement because it stands out more making it obvious what I'm doing.
- ahci8e 2y agoHow would this work if I need to update multiple variables? int value1 = 0; int value2 = 0; if (condition) { value1 = 8; value2 = 16; } else { value1 = 128; value2 = 256; } Would I have to repeat the if expression twice? int value1 = if (condition) { 8 } else { 128 }; int value2 = if (condition) { 16 } else { 256 };
- tomtheelder 2y agoDepends on the language a bit, but a common feature in these languages is the tuple. Using a tuple you would end up with something like: let (value1, value2) = if (condition) { (8, 16) } else { (16, 256) } Or else you’d just use some other sort of compound value like a struct or something. Tuple is just convenient for doing it on the fly.
- runevault 2y agohah we gave basically the same example on the same minute. I love destructuring so much, I don't know if I'd want to use a language without it anymore.
- tomtheelder 2y agoIt’s actually so painful to go back to languages without destructuring and pattern matching.
- runevault 2y agoAs someone who writes a fair bit of c# making switch and if's into expressions and adding Discriminated Unions (which they are actually working on) are my biggest "please give me this." Plus side I dabble in f# which is so much more expressive.
- throwaway9502 2y ago
- IAmGraydon 2y agoI'm assuming you mean "non-exception". Apologies if I assume incorrectly. In case I'm correct, this is from Andrew Gerrand, one of the creators of Go: The reason we didn't include exceptions in Go is not because of expense. It's because exceptions thread an invisible second control flow through your programs making them less readable and harder to reason about. In Go the code does what it says. The error is handled or it is not. You may find Go's error handling verbose, but a lot of programmers find this a great relief. In short, we didn't include exceptions because we don't need them. Why add all that complexity for such contentious gains? https://news.ycombinator.com/item?id=4159672 https://news.ycombinator.com/item?id=4159672
- Smaug123 2y agoYou appear to have misread "expression" as "exception"; this is completely unrelated. An expression-based language is one that lets you do `let blah = if foo then bar else baz`, for example.
- bigstrat2003 2y agoI don't think he misread, because I also was puzzled. I had never heard of the term "expression" used in this way, and I imagine I'm not alone. I do greatly appreciate the clarification from you and jtsummers though. I knew of the distinction, but I didn't know of a term for it until today.
- refulgentis 2y agoi honestly struggle with this because its a "i know when i see it" thing, ex. here, const boo = foo ? bar : baz suffices which brings in ~every language I know. My poor attempt at a definition, covers it in practice in languages I'm familiar, but not in theory, I assume: a language where switch statements return a value
- School-Cotton 2y agoGo doesn’t have a ternary operator, you are supposed to write something like boo := bar if foo { boo = baz } One of the many cases where Go’s designers decided they would ban something they disliked about C (in this case, complicated ternary operator chains), but thought Google programmers were too stupid to understand any idea from a more modern language than C, so didn’t add any replacement. (I’m not exaggerating or being flippant: Google programmers being too stupid to understand modern programming languages has literally been cited as one of the main design goals of Go).
- kuschku 2y agoI used to be skeptical about introducing complex expressions to C-syntax languages for a long time until I saw how well Kotlin handled `when`. Now every time I use typescript or go I have trouble trying to express what I want to say because `when` and similar expressions are just such a convenient way to think about a problem. In go that means I usually end up extracting that code into a separate function with a single large `switch` statement with every case containing a `return` statement.
- throwitaway1123 2y agoYeah expression based languages are a pleasure to work with. A lot of what was good about CoffeeScript was absorbed into ES6, but the expression oriented nature of it was never fully replicated in JS/TS.
- jiggawatts 2y agoMore to the point, the comment in the code mentions the “combinatorial” explosion of conditions that have to be carefully maintained by fallible meat brains. In most modern languages something like this could be implemented using composition with interfaces or traits. Especially in Rust it’s possible to write very robust code that has identical performance to the if-else spaghetti, but is proven correct by the compiler. I’m on mobile, so it’s hard to read through the code, but I noticed one section that tries to find an existing volume to use, and if it can’t, then it will provision one instead. This could be three classes that implement the same interface: class ExistingVolume : IVolumeAllocator class CreateVolume : IVolumeAllocator class SeqVolumeAllocator : IVolumeAllocator The last class takes a list of IVolumeAllocator abstract types as its input during construction and will try them in sequence. It could find and then allocate, or find in many different places before giving up and allocating, or allocating from different pools trying them in order. Far more flexible and robust than carefully commented if-else statements! Similarly, there's a number of "feature gate" if-else statements adding to the complexity. Let's say the CreateVolume class has two variants, the original 'v1' and an experimental 'v2' version. Then you could construct a SeqVolumeAllocator thus: allocator = new SeqVolumeAllocator( featureFlag ? new CreateVolumeV2() : new CreateVolumeV1(), new ExistingVolume() ); And then you never have to worry about the feature flag breaking control flow or error handling somewhere in a bizarre way. See the legendary Andrei Alexandrescu demonstrating about a similar design in his CppCon talk “std::allocator is to allocation what std::vector is to vexation”: https://youtu.be/LIb3L4vKZ7U https://youtu.be/LIb3L4vKZ7U