4 ms·
I would rather go had real enums, and I would _prefer_ if there were sum types. I agree it's more verbose, but I don't find that that verbosity really bothers
by maccard 2y ago
I would rather go had real enums, and I would _prefer_ if there were sum types.
I agree it's more verbose, but I don't find that that verbosity really bothers me most of the time. Is
res= [x for x in foo if "banned" in x]
really actually more readable than
var result []string
for _, x := range foo {
if strings.Contains(x, "banned") {
result = append(result, x)
}
}
? I know it's 6 lines vs 1, but in practice I look at that and it's just as readable.
I think go's attitude here (for the most part) of "there's likely only one dumb, obvious way to do this" is a benefit, and it makes me think more about the higher level rather than what's the most go-esque way to do this.
- aaomidi 2y agoThe new lines were eaten so it does make the python version more readable but I agree with you.
- maccard 2y agoThanks - updated!
- randomdata 2y agoGo has real enums. All an enum does is count. You're probably thinking of value constraints? Or, perhaps, exhaustive case analysis? Go certainly lacks those future. And, indeed, they sound like nice features, but, to be fair, not well supported in any popular programming language. At best we get some half-assery. Which always questions if the popular languages are popular because of their lacking type systems?
- maccard 2y agoThis topic has been beaten to death, and being pedantic about the definition of an enum to say "actually go has them" isn't helpful. There are dozens of articles from the last decade which explain the problems. Those problems don't exist in plenty of programming languages. No language is perfect, but go's particular set of bugbears is a good tradeoff
- randomdata 2y ago> being pedantic about the definition of an enum to say "actually go has them" isn't helpful. Incorrect. The term "real enums", where used to imply that enums are something other than the basic element of the same name, encompasses a number of distinct features that are completely independent of each other. In order to meaningfully talk about "real enums", we need to break it down into the individual parts. If you're just trolling in bad faith, sure, leave it at "real enums" to prevent any discussion from taking place, but the rules of the site make it pretty clear that is not the intent of Hacker News. > Those problems don't exist in plenty of programming languages. Plenty, but none popular. Of the popular programming languages, Typescript seems to try the hardest, but even then just barely shows some semblance of supporting those features – still only providing support in some very narrow cases. The problems these features intend to solve are still very much present in general.
- IshKebab 2y agoDude. Everyone knew what "real enums" meant including you. Please stop. And yes popular languages do have real type safe enums. C++, Typescript, Rust, god even Python.
- randomdata 2y ago> And yes popular languages do have real type safe enums. Right, as we already established, but which is only incredibly narrow support within the features in question. While you can find safety within the scope of enums and enums alone, it blows up as soon as you want the same safety applied to anything else. No popular language comes close to completing these features, doing it half-assed at most. We went over this several times now. Typescript goes a little further than most popular languages, but even it doesn't go very far, leaving all kinds of gaps where the type system does not provide the aforementioned safety. You clearly know how to read, by your own admission, so why are you flailing around like one of those wacky blow up men at the used car lot? Are you just not familiar with programming?
- 2y ago
- thornewolf 2y agoI argue: - The list comprehension is ever slightly more readable. (Small Positive) - It is a bit faster to write the code for the Python variant. (Small Positive) So this would be a small positive when using Python. Furthermore, I believe there is this "small positive" trade-off on nearly every aspect of Python, when compared to Go. It makes me wonder why someone might prefer Go to Python in almost any context. Some common critiques of Python might be: - Performance in number crunching - Performance in concurrency - Development issues in managing a large code base I believe the ecosystem is sufficiently developed that Numpy/Numba JIT can address nearly all number crunching performance, Uvicorn w/ workers addresses concurrency in a web serving context, ThreadPool/ProcessPool addresses concurrency elsewhere, and type hints are 90% of what you need for type safety. So where does the perceived legitimacy of these critiques come from? I don't know.
- maccard 2y ago> The list comprehension is ever slightly more readable. I disagree - it's terse to the point of being hard to parse, particularly when you get smart ones like: [x for x in t if x not in s] > It is a bit faster to write the code for the Python variant. Code should be written to be read. Saving a few keystrokes vs time spent figuring out the `not in in not with` dance gives the the edge to Golang here. It's "high context" > - Performance in number crunching > - Performance in concurrency And "performance in all other areas". See the thread last week about massive speedups in function calls in python where it was still 5-10x slower than go. > So where does the perceived legitimacy of these critiques come from? I don't know. It's pretty hard to discuss it when you've declared that performance isn't a problem and that type annotations solve the scalability of development problem.
- laserlight 2y agoLuckily, we are writing Python, not Go, so we can use variable names with more than one letter: [word for word in sentence if word not in bannedWords] Suddenly, nothing is hard to parse.
- 2y ago
- halfcat 2y ago”Is <python code> really actually more readable than <go code>?” I mean, I mostly work in Python, but, yes absolutely. There’s something to be said for locality of behavior. If I can see 6x as many lines at once that’s worth a lot in my experience. This becomes blatantly apparent in other languages where we need 10 files open just to understand the code path of some inheritance hierarchy. And it’s not nearly that extreme in go, but the principle is the same. But there is something to be said for the one way to do it, and not overthinking it.
- skitter 2y agoI agree that list comprehensions aren't any easier to read. A proper streaming interface on the other hand lets you easily follow how the data is transformed: foo .stream() .filter(x -> x.contains("banned")) .collect(Collectors.toList()); As an aside, Go conflating lists and views irks me, in part due to what weird semantics it gives to append (e.g. if you have two disjunct slices and append an element to one slice, that might modify the other slice).
- mjevans 2y agoYou could write your own syntax sugar functions with signatures like... func copyArrStringShallow(x []string) []string { return x } // strings are immutable in go, but for []byte etc also deep copy that. func copyArrStringDeep(x []string) []string { ret := make([]string, 0, len(x)) copy(ret, x) return ret }
- valzam 2y agoThe problem with this is that people again get way to clever with it. it's not just stream -> filter -> collection, there will be a bunch of groupbys in there etc. If you have to debug or extend the functionality it's a nightmare to understand what all the intermediate representations are
- sethammons 2y agoElixir gives you tooling to inline inspect
- Bognar 2y agoInspecting intermediate representations is trivial by just collecting them into a variable? More complicated scenarios are exactly what streaming APIs excel at, by treating each step as a single transformation of data. Lack of a proper group by function is one of my classic examples for how Go forces you into an imperative style that's harder to understand at a glance.
- jodrellblank 2y agoFiltering a container by a predicate is 50 year old technology and a very common thing. It's unbelievable that a "modern" language has no shorter or clearer idiom than that convoluted boilerplate filled Ministry of Silly Walks blob. Python had filter() and then got list comprehensions from Haskell list comprehensions. PowerShell has Where-Object taking from C# LINQ .Where() which takes from SQL's WHERE. Prolog has include/3 which goes back to sublist(Test, Longer, Shorter) and LISP-Machine LISP had sublist in the 1970s[1]. APL has single character / compressing an array from a bitmask result of a Boolean test in the original APL/360 in 1968 and it was described in the book in 1962[2]. Brian Kernighan gave a talk on the readability of code and not getting too clever[3] "Elements of Programming Style" where he talks about languages having one way to write one thing so that you can spot mistakes easily. I am aware he's one of the Go designers and I will mention that again in a moment. In Python the non-list-comprehension version might be: result = [] for x in foo: if "banned" in x: result.append(x) Which is still clearer than the Go, simply by having less going on. I usually argue that "readable" merely means "familiar" and Python is familiar to me and Go isn't. Your Go code makes me wonder: - "var" in C# does type inference. You declare []string but don't declare types for _ x or the tuple _,x what's up with the partial type inference? What is "var" adding to the code over classic "int x" style variable declarations? - What is "range" doing? From _ I guess it does enumeration and that's a throwaway for the index (if so it has an unclear name). If you have to enumerate and throw away the index into _ because there isn't another way to iterate then why does keyword "range" need to exist? Conversely if there are other ways to iterate and the keyword "range" is optional, why do it this way with a variable taking up visual space only to be thrown away? (The code equivalent of saying "for some reason" and drawing the audience's attention to ... nothing). And why is range a keyword instead of a function call, e.g. Python's "for (idx, elem) in enumerate(foo):" ? - Why is there assignment using both := and = ? - Why string.Contains() with a module name and capital letter but append() with no module and all lowercase? Is there an unwritten import for "strings"? - The order of arguments to Contains() and append(); Prof. Kernighan calls this out at 10m10s in that talk. C# has named arguments[3] or the object.Method() style haystack.Contains(needle) is clear, but the Go code has neither. It would be Bad Prolog(tm) to make a predicate Contains(String1, StringA) because it's not clear which way round it works, but "string_a in string_1" is clear in Python because it reads like English. AFAIK a compiler/type system can't help here as both arguments are strings, so it's more important that the style helps a reader notice if the arguments are accidentally the wrong way around, and this doesn't. We could ask the same about the _, x as well. - "result =" looks like it's overwriting the variable each time through the loop (which would be a common beginner mistake in other languages). If append is not modifying in place and instead returning a new array, is that a terrible performance hit like it is in C#? Python list comprehensions are explicitly making a completely new list, but if the Go code said "result2 = append(result, x)" is it valid to keep variable "result" from before the append, or invalid, or a subtle bug? The reader has to think of it, and know the answer, the Python code avoids that completely. - And of course the forever curly brace / indent question - are the closing } really ending the indented block that they look like they are ending judging from the dedent? I hear Go has mandatory formatting which might make that a non-issue, but this specific Python line has no block indentation at all so it's less than a non-issue. - The Python has five symbols =[""] to mentally parse, pair and deal with, compared to twenty []_,:={.(,""){=(,)}} in the Go. Step back from those details to ask "what is the code trying to achieve, and is this code achieving the goal?" in the Go the core test ".Contains()" is hiding in the middle of the six lines. I'm not going to say you need to be able to read a language without learning it, but in the long-form Python what is there even to wonder about? B. Kernighan calls that out about 12:50 in the talk "you get the sense the person who is writing the code doesn't really understand the language properly". You say code is meant to be read more than written, and I claim it's more likely that a reader won't understand details, than will. Which means code with fewer details and which "just works" the way it looks like (Pythonic) is more readable. As BWK said in the talk "It's not that you can't understand [the Go], it's that you have to work at it, and you shouldn't have to work at it for a task this simple". [1] https://old.reddit.com/r/ProgrammingLanguages/comments/14tvuta/where_did_filter_map_reduce_originate/ https://old.reddit.com/r/ProgrammingLanguages/comments/14tvu... [2] https://keiapl.org/archive/APL360_UsersMan_Aug1968.pdf https://keiapl.org/archive/APL360_UsersMan_Aug1968.pdf 3.38 or PDF page 94, dating back to the original A Programming Language (APL) book in 1962, source https://aplwiki.com/wiki/Replicate#History https://aplwiki.com/wiki/Replicate#History [3] https://www.youtube.com/watch?v=8SUkrR7ZfTA https://www.youtube.com/watch?v=8SUkrR7ZfTA [4] https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
- poorlyknit 2y agoIMO the Python version provides more information about the final state of res than the Go version at a glance: It's a list, len(res) <= len(foo), every element is an element of foo and they appear in the same order. The Go version may contain some typo or other that simply sets result to the empty list. I'd argue that having idioms like list comprehension allows you to skim code faster, because you can skip over them (ah! we're simply shrinking foo a bit) instead of having to make sure that the loop doesn't do anything but append. This even goes both ways: do-notation in Haskell can make code harder to skim because you have to consider what monad you're currently in as it reassigns the meaning of "<-" (I say this as a big Haskell fan). At the same time I've seen too much Go code that does err != nil style checking and misses a break or return statement afterwards :(