>In Python (and many other languages), it’s very easy to unintentionally hide tons of work in a single line of built-in syntax. Some lines do almost no work, other lines do a moderate amount of work, other lines do what I’d describe is tens of lines worth of work—a lot of work. It varies a lot, even in beginner code. In Go, I’ve found that this variability is much more diminished.
Just because Go has less powerful primitives, it doesn't mean that you don't use opaque code, libs, etc. all the time. Sure, you can look into what each function does -- but then again, you can also immediately tell that a list comprehension in python is doing at least O(N) work compared to a simple addition, without even having to open any other source file.
>Is this good? I believe it is. I believe this code is more proportional to the complexity of the work that it’s doing.
We invented programming languages on top of machine code so that our code can be LESS proportional to the "complexity of the work that it’s doing".
>When reading proportional code, it’s easier to notice where the interesting bits are.
Not really. It's the inverse: the terse code only keeps the interesting bits. The "proportional code" gives you all the non-interesting, and getting in the way, mechanics.
>When a friend was reading through the source code for ssh-chat, he was surprised that I implemented my own Set type. I explained that while Go doesn’t have a built-in Set, it’s just a few lines of boilerplate to make your own on top of a map. In fact, I noticed that my version of Set evolved to be fairly specific to how it was being used. In retrospect, I’m glad that I was tweaking my own implementation iteratively rather than spending time working around whichever limitations a generic library might have had.
Pray tell, what "limitations" a generic Set would have?
And why does those kinds of post remind one of Stockholm Syndrome?
> Just because Go has less powerful primitives, it doesn't mean that you don't use opaque code, libs, etc. all the time.
True, but that's not what the article's arguing.
> We invented programming languages on top of machine code so that our code can be LESS proportional to the "complexity of the work that it’s doing".
No, we invented programming languages so that our code can be smaller and less arcane, not less proportional. The actual point of the article, which I believe you missed, is that if we focus on 'cleverly' compressing large amounts of logic into dense nuggets of code, it interferes with our ability to appropriately factor the code so that it produces the desired behaviour in the simplest and most readable way.
The article also states (and I agree) that if your code is wildly variable in the amount of semantic content per line of text, it's much harder to properly grok and (more importantly) to modify what's going on.
> True, but that's not what the article's arguing.
Reading it twice, I think this is a very veiled argument against generics in Go on the grounds that well, uh... proportionality?
I agree with the post above you. I do not like the premise of this or of Go. I see it as a deliberate attempt to walk away from empowering programmers. It's not solid technology, it's the technical manifestation of business decisions.
>No, we invented programming languages so that our code can be smaller and less arcane, not less proportional
Smaller also means less proportional to the work that it's doing, since the abstraction, sugar and techniques we use to make code smaller do not yield the same compression ratio from the underlying instructions in all cases.
So, smaller = less proportional -- unless you suggest we should also strive to artificially constrain how succinct we make our code so that there's a constant ratio everywhere.
That is, so that 10 lines of code in a language always are (or at least, always strive to be) 10×n times of machine code. I don't see how that's feasible -- some abstraction could yield a 1/100 less code than machine code in one line, while another can yield a mere 1/5 or 1/10 less code.
But even if we could achieve that, I don't see it being even useful. Having higher level code strive to be more proportional to machine instructions is not necessarily telling us much about the cost of such code. It might tell us that it's complex and slower than another, but that doesn't mean much. It only matters what role it plays in the overall runtime of our program. Just because something A 5 times bigger than something else B, doesn't mean A needs more optimizing. A could be just fine as it is, e.g. if A is called rarely, and B is in some tight loop.
Gauging code by "code proportionality" (the only semi-legitimate use I could think of such a feature) is bogus. We should always profile.
>The article also states (and I agree) that if your code is wildly variable in the amount of semantic content per line of text, it's much harder to properly grok and (more importantly) to modify what's going on.
On the contrary. The part of "What's going on" that matters when it comes for modification is the high level stuff that goes on -- which a more terse code shows better, as it doesn't hide it with boilerplate.
In the end, that point seems both trivial and non-sensical: yes, boilerplate is easier to modify. That's because it doesn't matter much -- it's the stuff that you want to do that's hidden inside the boilerplate that matters.
e.g. I can write a loop to filter 100 numbers in tons of ways. And I can always modify it from one to one of the other ways.
But something like:
value = filter(my_array, (val) => val > 5)
Just does what it says is does, and since it encapsulates the intention 100% there's no much room for wiggling.
In general, we're less concerned about "the work that it's doing" in the sense of raw processor instructions, and more concerned about the semantic complexity for the programmer. So if a line says "set A to true if B or C are true", that's simple. If it says "filter array A to only include elements where this inlined lambda function returns 5 or more, and then return the results with an even index" it's a bit harder to figure out the intent behind it.
>If it says "filter array A to only include elements where this inlined lambda function returns 5 or more, and then return the results with an even index" it's a bit harder to figure out the intent behind it.
I'd argue the contrary though.
That the "semantic complexity" is less when you only keep intent-code, than when you spread it over 20 lines of boilerplate which you have to visually parse to get to the point.
Your examples are not analogous, because they do different things.
Let's make them implement the same example, Go/C/etc style and "terse" style:
array_2 := make(int64, 0)
over_five_index = 0
for i, val := range(my_array) {
if val >= 5 {
over_five_index += 1
if over_five_index % 2 == 0 {
append(array_2, val)
}
}
}
vs:
array_2 = my_array
.filter((val) => val >= 5)
.filter((_, i) => i % 2 == 0)
I don't see how there's less "semantic complexity" in the former for the programmer to parse. It's just less per line, but worse overall -- in that he has to now reconstruct the full intention from all the individual lines.
I call BS. Consider these 2 Go lines:
Just because Go has less powerful primitives, it doesn't mean that you don't use opaque code, libs, etc. all the time. Sure, you can look into what each function does -- but then again, you can also immediately tell that a list comprehension in python is doing at least O(N) work compared to a simple addition, without even having to open any other source file.>Is this good? I believe it is. I believe this code is more proportional to the complexity of the work that it’s doing.
We invented programming languages on top of machine code so that our code can be LESS proportional to the "complexity of the work that it’s doing".
>When reading proportional code, it’s easier to notice where the interesting bits are.
Not really. It's the inverse: the terse code only keeps the interesting bits. The "proportional code" gives you all the non-interesting, and getting in the way, mechanics.
>When a friend was reading through the source code for ssh-chat, he was surprised that I implemented my own Set type. I explained that while Go doesn’t have a built-in Set, it’s just a few lines of boilerplate to make your own on top of a map. In fact, I noticed that my version of Set evolved to be fairly specific to how it was being used. In retrospect, I’m glad that I was tweaking my own implementation iteratively rather than spending time working around whichever limitations a generic library might have had.
Pray tell, what "limitations" a generic Set would have?
And why does those kinds of post remind one of Stockholm Syndrome?