[HN Gopher] Constraints in Go
___________________________________________________________________
Constraints in Go
Author : gus_leonel
Score : 148 points
Date : 2024-11-17 08:44 UTC (14 hours ago)
(HTM) web link (bitfieldconsulting.com)
(TXT) w3m dump (bitfieldconsulting.com)
| indulona wrote:
| i have been writing Go exclusively for 5+ years and to this day i
| use generics only in a dedicated library that works with
| arrays(slices in Go world) and provides basic functionality like
| pop, push, shift, reverse, filter and so on.
|
| Other than that, generics have not really solved an actual
| problem for me in the real world. Nice to have, but too mush fuss
| about nothing relevant.
| throwaway63467 wrote:
| Honestly so many things profit from generics, e.g. ORM code was
| very awkward before especially when returning slices of objects
| as everything was []any. Now you can say var users []User =
| orm.Get[User](...) as opposed to e.g var users []any =
| orm.Get(&User{}, ...), that alone is incredibly useful and
| reduces boilerplate by a ton.
| vbezhenar wrote:
| ORM is anti-pattern and reducing boilerplate is bad.
| makapuf wrote:
| I agree. The best language to handle data in a RDBMs is
| SQL, and in that case the best language to handle
| application logic is Go (or Kotlin, Python or whatever). So
| there must be some meeting point. Handling everything in Go
| is not optimal, and all in sql not always practical. So how
| to avoid redundant data description ? I often have structs
| in a model Go file that reflect _queries_ I do, but that 's
| not optimal since I tend to have to repeat what's in a
| query to the language and the query to struct gathering is
| often boilerplate. I also almost can reuse the info I need
| for a query for another query but leave some fields blank
| since they're not needed.. the approaches are not optimal.
| Maybe a codegen sql to result structs / gathering info ?
| bluesnews wrote:
| Could you expand on this?
|
| I don't like ORM because in my experience you inevitably
| want full SQL features at some point but not sure if you
| have the same issues in mind or not
| vbezhenar wrote:
| ORM is for object-relation mapping. Go is not object-
| oriented language and OOP-patterns are not idiomatic Go,
| so using ORM for Go cannot be idiomatic. That's generic
| answer. As for more concrete points:
|
| 1. Mapping SQL response to maps/structs or mapping
| maps/structs to SQL parameters might be useful, but
| that's rather trivial functionality and probably doesn't
| qualify as ORM. Things get harder when we're talking
| about complex joins and structs with relationships, but
| still manageable.
|
| 2. Introducing intermediate language which is converted
| to SQL is bad. Inevitably it will have less features. It
| will stay in the way for query optimisations. It'll make
| things much less obvious, as you would need to understand
| not only SQL, but also the process of translating
| intermediate language to SQL.
|
| 3. Automatic caching is bad. Database has its own caching
| and if that's not enough, application can implement
| custom caching where it makes sense.
|
| In my opinion the only worthy database integration could
| be implemented with full language support. So far I only
| saw it with C# LINQ or with database-first languages
| (PL/SQL, etc). C# and Go are like on opposite spectrum of
| language design, so those who use Go probably should keep
| its approach by writing simple, verbose and obvious code.
| indulona wrote:
| > Go is not object-oriented language
|
| That is most definitely not true. Go just uses
| composition instead of inheritance. Still OOP, just the
| data flow is reversed from bottom to the top.
| nordsieck wrote:
| >> Go is not object-oriented language
|
| > That is most definitely not true.
|
| I think at best, you could say that Go is a multi-
| paradigm language.
|
| It's possible to write Go in an object oriented style.
|
| It's also possible to write programs with no methods at
| all (although you'd probably have to call methods from
| the standard library).
|
| That's in contrast to a language like Java or Ruby where
| it's actually impossible to avoid creating objects.
| pjmlp wrote:
| Unless you happen to want to warm up the CPU, there is
| very little Go code that is possible to write that does
| anything useful without OOP concepts, like interfaces,
| methods and dynamic dispatch.
|
| Creating objects on the heap isn't the only defining
| feature how a language does OOP or not.
| randomdata wrote:
| Go has objects, but objects alone does not imply
| orientation. For that, you need message passing.
| kgeist wrote:
| I find libraries like sqlx more than enough. Instead of a
| full-blown ORM, they simply help hydrate Go structs from
| returned SQL data, reducing boilerplate. I prefer the
| repository pattern, where a repository is responsible for
| retrieving data from storage (using sqlx) using simple,
| clean code. Often, projects which use full-blown ORMs,
| tend to equate SQL table = business object (aka
| ActiveRecord) which leads to lots of problems. Business
| logic should be completely decoupled from underlying
| storage, which is an implementation detail. But more
| often than not, ORM idiosyncracies end up leaking inside
| business logic all over the place. As for complex joins
| and what not, CQRS can be an answer. For read queries,
| you can write complex raw SQL queries and simply hydrate
| the results into lightweight structs, without having to
| construct business objects at all (i.e. no need for
| object-relational mapping in the first place). Stuff like
| aggregated results, etc. Such structs can be ad hoc, for
| very specific use cases, and they are easy to maintain
| and are very fast (no N+1 problems, etc). With projects
| like sqlx, it's a matter of defining an additional struct
| and making a Select call.
| TheDong wrote:
| > reducing boilerplate is bad
|
| Programming is about building abstractions, abstractions
| are a way to reduce boilerplate.
|
| Why do we need `func x(/* args _/ ) { /_ body */ }`, when
| you can just inline the function at each callsite and only
| have a single main function? Functions are simply a way to
| reduce boilerplate by deduplicating and naming code.
|
| If 'reducing boilerplate is bad', then functions are bad,
| and practically any abstraction is bad.
|
| In my opinion, "reducing boilerplate is bad in some
| scenarios where it leads to a worse abstraction than the
| boilerplate-ful code would lead to".
|
| I think you have to evaluate those things on a case-by-case
| basis, and some ORMs make sense for some use-cases, where
| they provide a coherent abstraction that reduces
| boilerplate... and sometimes they reduce boilerplate, but
| lead to a poor abstraction which requires more code to
| fight around it.
| bobnamob wrote:
| Not liking ORM I can understand, db table <-> object
| impedance mismatch is real, but "reducing boilerplate is
| bad" is an interesting take.
|
| Can you elaborate and give some examples of why reducing
| boilerplate is generally "bad"?
| rad_gruchalski wrote:
| Not the person you're replying to. The orm sucks because
| as soon as you go out of the beaten path of your average
| select/insert/update/delete, you are inevitably going to
| end up writing raw sql strings. Two cases in point:
| postgres cte and jsonb queries, there are no facilities
| in gorm for those, you will be just shoving raw sql into
| gorm. You might as well stop pretending. There's a
| difference between having something writing the sql and
| mapping results into structs. The latter one can be done
| with the stdlib sql package and doesn't require an
| ,,orm".
|
| There are two things an sql lib must do to be very
| useful: prepared statements and mapping results. That's
| enough.
| bobnamob wrote:
| You haven't answered my question at all.
|
| The parent comment made two claims: ORM not great (I
| agree) and "boilerplate reduction bad" which still needs
| some elaboration
| rad_gruchalski wrote:
| I answered it, you just don't see it. One ends up with
| the boilerplate anyway as soon as one attempts to step
| out of the usual crud path. There's no gain, there's no
| difference in templating an sql string vs fighting an orm
| api.
| metaltyphoon wrote:
| Perhaps you have to yet use a good ORM? I could probably
| count on my fingers the times I had to drop to raw SQL in
| EFCore. Even when you do that you can still have mapped
| results, which reduces boilerplate.
| rad_gruchalski wrote:
| I'm doing this job for 25 years and I haven't seen a good
| orm. Sorry. Look, linq is nice. But linq is not enough of
| a productivity gain to switch the whole stack from go to
| .net. I used linq 15 years ago extensively and it feel
| like magic. But then again, how would you model jsonb
| select for a variable set of of properties and include
| nested or and and conditions using its notation? Maybe
| you could but how much longer is it going to take you
| rather than templating a string?
| LinXitoW wrote:
| Any ORM worth it's salt has an escape hatch that allows
| you to do all those fancy raw SQL queries.
|
| But the amount of queries that aren't fancy, and that an
| ORM is perfectly capable of abstracting away is (imho)
| 90% of all queries run.
|
| Why make 90% or queries more tedious and error prone,
| just to make 10% slightly easier?
| vbezhenar wrote:
| What I mean is reducing boilerplate is not something one
| should strive to achieve. It is not bad in the sense that
| one should introduce more boilerplate for the sake of it.
| But reducing boilerplate for the sake of it is not good
| thing either.
|
| If you need to make code more complex just to reduce
| boilerplate, it's a bad thing. If you managed go make
| code simpler and reduced boilerplate at the same time,
| it's a good thing.
|
| And boilerplate might be a good thing when you need to
| type something twice and if you would make error once,
| the whole thing wouldn't work, so basically you'll reduce
| the possibility of typo. It might look counter intuitive.
| Just unrelated example: recently I wrote C code where I
| need to type the same signature in the header file and in
| the source file. I made mistake in the source file, but I
| didn't make the same mistake in the header file and the
| whole program didn't link. I figured out the mistake and
| corrected it. Without this boilerplate it's possible that
| I wouldn't notice the mistake and "helpful" autocomplete
| would keep the mistake forever. That's how HTTP Referer
| header made it into standards, I guess.
| indulona wrote:
| understandable. thee are always valid uses cases. although
| ORM in Go is not something that is widely used.
| kgeist wrote:
| Just checked, in my current project, the only place where I use
| generics is in a custom cache implementation. From my
| experience in C#, generics are mostly useful for implementing
| custom containers. It's nice to have a clean interface which
| doesn't force users to cast types from any.
| BlackFly wrote:
| Containers are sort of the leading order use of generics: I
| put something in and want to statically get that type back
| (so no cast, still safe).
|
| Second use I usually find is when I have some structs with
| some behavior and some associated but parameterizable helper.
| In my case, differential equations together with guess
| initializers for those differential equations. You can
| certainly do it without generics, but then the initial guess
| can be the wrong shape if you copy paste and don't change the
| bits accordingly. The differential equation solver can then
| take equations that are parameterized by a solution type
| (varying in dimension, discretisation and variables) together
| with an initializer that produces an initial guess of that
| shape.
|
| Finally, when your language can do a bit of introspection on
| the type or the type may have static methods or you have type
| classes, you can use the generic to control the output.
|
| Basically, they are useful (like the article implies) when
| you want to statically enforce constraints. Some people
| prefer implicitly enforcing the constraint (if the code works
| the constraint is satisfied) or with tests (if the tests pass
| the constraint is satisfied). Other people prefer to have the
| constraints impossible to not satisfy.
| aljarry wrote:
| > From my experience in C#, generics are mostly useful for
| implementing custom containers.
|
| That's my experience as well in C# - most of other usages of
| generics are painful to maintain in the long run. I've had
| most problems with code that joins generics with inheritance.
| neonsunset wrote:
| C# generics are way more powerful than that when it comes to
| writing high-performance or just very, err, generic code.
| Generic constraints and static interface members are
| immensely useful - you can have a constraint that lets you
| write 'T.Parse(text[2..8])'.
|
| They are far closer to Rust in _some_ areas (definitely not
| in type inference sadly, but F# is a different story) than it
| seems.
|
| Of course if one declares that they are an expert in a dozen
| of languages, most of which have poorly expressive type
| systems, the final product will end up not taking advantage
| of having proper generics.
| tonyedgecombe wrote:
| I sometimes wonder if they should have implemented generics. On
| the one hand you had a group of people using go as it was and
| presumably mostly happy with the lack of generics. On the other
| side you have people (like me) complaining about the lack of
| generics but who were unlikely to use the language once they
| were added.
|
| It's very subjective but my gut feeling is they probably didn't
| expand their community much by adding generics to the language.
| cherryteastain wrote:
| I think a lot of the people who wanted generics wanted them
| more to be like C++ templates, with compile time duck typing.
| Go maintainers were unwilling to go that route because of
| complexity. However, as a result, any time I think "oh this
| looks like it could be made generic" I fall into a rabbit
| hole regarding what Go generics do and dont allow you to do
| and usually end up copy pasting code instead.
| wyufro wrote:
| I think "oh this looks like it could be made generic" is
| the wrong time to convert to generics.
|
| You should convert when you reach the point "I wish I had
| that code but with this other type". Even then, sometimes
| interfaces are the right answer, rather than generics.
| cherryteastain wrote:
| I mostly agree, hence the
|
| > end up copy pasting code instead
|
| bit of my original comment
| vbezhenar wrote:
| Generic containers are needed in some cases. Using generic
| containers with interface{} is very slow and memory-
| intensive. Not a problem for small containers, but for big
| containers it's just not feasible, so you would need to
| either copy&paste huge chunks of code or generate code.
| Compared to those approaches, generic support is superior in
| every way, so it's needed. But creating STL on top of them is
| not the indended use-case.
| sbrother wrote:
| Having recently had to work on a Go project for the first
| time, I think I agree with you here. I'd tried Go a little
| bit when it came out, had zero interest in what it offered,
| and then when I was asked to work on this project a couple
| months ago I thought it would be fun to try it out again
| since I had read the language had improved.
|
| No, it still feels like programming with a blindfold on and
| one hand tied behind my back. I truly don't get it. I've
| worked with a _lot_ of languages and paradigms, am not a
| zealot by any means. Other than fast compiles and easy binary
| distribution, I don 't see any value here, and I see even
| experienced Go programmers constantly wasting time writing
| unreadable boilerplate to work around the bad language
| design. I know I must be missing something because some
| people much smarter than me like this language, but... what
| is it?
| quinnirill wrote:
| Mad LoCs, dude, gotta make alotta lines, that's what
| productivity is!
| indulona wrote:
| > I see even experienced Go programmers constantly wasting
| time writing unreadable boilerplate
|
| if it is unreadable, in Go, probably the most readable
| language used today, i would question the aforementioned
| experience.
| DangitBobby wrote:
| What you're doing right now is called "coping".
| LinXitoW wrote:
| I don't think a language where for every 1 line of
| functionality, you need 3 lines of error handling
| boilerplate gets to be called readable.
|
| Heck, Go went out of it's way to "subvert expectations"
| more than the last season of Game of Thrones.
|
| 99% of decent C-ish languages either do "String thing" or
| "thing: String", but Go is so fancy and quirky, it does
| "thing String" for no freaking reason. Don't get me
| started on the nightmare that is map types.
| majormajor wrote:
| > Other than fast compiles and easy binary distribution, I
| don't see any value here, and I see even experienced Go
| programmers constantly wasting time writing unreadable
| boilerplate to work around the bad language design. I know
| I must be missing something because some people much
| smarter than me like this language, but... what is it?
|
| If you "other than" two huge-for-many-use-cases good
| things, sure, it might look bad. ;)
|
| But I would add good overall performance and in particular
| straightforward flexible concurrency support to the list of
| good things.
|
| And IMO once you're in the set of "things with good perf"
| there's generally a lot of "boilerplate" of one sort or
| another anyway.
| sbrother wrote:
| Yeah that's fair. In terms of "things with good perf" I'd
| rather be writing C++ or Rust, but there are significant
| issues with using either of those on a large team.
|
| I'm more comparing it against languages like Kotlin and
| Swift, or even Scala.
| LinXitoW wrote:
| It might be nit picking, but that's more the ecosystem or
| tooling that's great. The language is mediocre, but it's
| what everyone gushes about.
|
| I still remember people gaslighting everyone that any
| feature Go had was ESSENTIAL, and every feature Go didn't
| have was USELESS or too complicated for mere mortals
| "delivering value".
|
| And the fast compiles at least are in big parts because
| the language is so horrendously basic. Can't get hung up
| on checking type constraints if you barely have any.
| kaba0 wrote:
| Well, generics are mostly meant for library code. Just because
| you don't need it, doesn't mean that code you use doesn't need
| it.
| gregwebs wrote:
| There's an existing ecosystem that already works with the
| constraints of not having generics. If you can write all your
| code with that, then you won't need generic much. That
| ecosystem was created with the sweat of library authors,
| dealing with not having generics and also with users learning
| to deal with the limitations and avoid panics.
|
| Generics have been tremendously helpful for me and my team
| anytime we are not satisfied with the existing ecosystem and
| need to write our own library code. And as time goes on the
| libraries that everyone uses will be using generics more.
| jppittma wrote:
| The most frequent use case I and my coworkers run into where we
| use them is when we want type covariance on a slice.
|
| I.e., when you want to write a function that take some slice of
| any type T that implements interface I, such that []T is a
| valid input instead of just explicitly []I.
| eweise wrote:
| here you go.
|
| func Ptr[T any](v T) *T { return &v }
| Groxx wrote:
| That's kinda the point. Generics are mostly a library concern,
| improving end-user experience and performance. End-user
| _creation_ of generic types is relatively rare, and you can use
| them in very simple ways and that 's almost always good enough
| because you don't need them to be _maximally correct_ , only
| good enough.
|
| For libraries (that adopt generics): yes they can be
| complicated. But using them is mostly zero-effort and gets rid
| of a ton of reflection.
| slimsag wrote:
| Unfortunately not everyone shares that opinion of their
| restricted use-cases.
|
| I've seen ~100 line HTTP handler methods that are implemented
| using generics and then a bunch of type-specific parameters
| inevitably get added when the codepaths start to diverge and
| now you've got a giant spaghetti ball of generics to
| untangle, for what was originally just trying to deduplicate
| a few hundred lines of code.
| Groxx wrote:
| tbh I'll still take it over a similar Gordian knot with
| interfaces. At least you can tell the restrictions are met
| at compile time, rather than silently failing at runtime
| because you (and/or someone else in the past) didn't notice
| one edge case lodged somewhere surprising.
| peterldowns wrote:
| My most common use of generics is when testing -- check out my
| library for typesafe test comparisons. I find it really useful
| because I like having readable helpers for asserting in tests,
| but I also want compiler errors if I refactor things.
|
| https://github.com/peterldowns/testy
| whateveracct wrote:
| this is wild because i use parametric polymorphism by writing
| `forall` in basically every Haskell PR i do for work ever
|
| i think Go having a pretty bad implementation of parametric
| polymorphism (a programming concept from the 70s) is probably
| the root cause here
| pansa2 wrote:
| I'm surprised by the complexity of Go's generic constraints,
| given the language's focus on simplicity. Things like the
| difference between "implementing" and "satisfying" a constraint
| [0], and exceptions around what a constraint can contain [1]:
|
| > _A union (with more than one term) cannot contain the
| predeclared identifier comparable or interfaces that specify
| methods, or embed comparable or interfaces that specify methods._
|
| Is this level of complexity unavoidable when implementing
| generics (in any language)? If not, could it have been avoided if
| Go's design had included generics from the start?
|
| [0] https://stackoverflow.com/questions/77445861/whats-the-
| diffe...
|
| [1]
| https://blog.merovius.de/posts/2024-01-05_constraining_compl...
| rendaw wrote:
| There are tons of random limitations not present in other
| languages too, like no generic methods.
| bigdubs wrote:
| That's not a random limitation, there are very specific
| reasons[1] you cannot easily add generic methods as struct
| receiver functions.
|
| [1] https://go.googlesource.com/proposal/+/refs/heads/master/
| des...
| abound wrote:
| For someone not well-versed in language implementation
| details, it may very well feel random.
|
| I've been using Go as my primary language for a decade, and
| the lack of generics on methods was surprising to me the
| first time I ran into it, and the reasoning not obvious.
| burakemir wrote:
| Generics are a powerful mechanism, and there is a spectrum. The
| act of retrofitting generics on go without generics certainly
| meant that some points in the design space were not available.
| On the other hand, when making a language change as adding
| generics, one wants to be careful that it pulls its own weight:
| it would be be sad if generics had been added and then many
| useful patterns could not be typed. The design choices revolve
| around expressivity (what patterns can be typed) and inference
| (what annotations are required). Combining generics with
| subtyping and inference is difficult as undecidability looms.
| In a language with subtyping it cannot be avoided (or the
| resulting language would be very bland). So I think the answer
| is no, this part of the complexity could not have been avoided.
| I think they did a great job at retrofitting and leaving the
| basic style of the language intact - even if I'd personally
| prefer a language design with a different style but more
| expressive typing.
| jerf wrote:
| In practice, none of this impacts your program. The standard
| advice I give to people messing around with this stuff is,
| never use the pipe operator. The standard library already
| implements all the sensible uses of it.
|
| In particular, people tend to read it as the "sum type"
| operator, which it is not. I kind of wish the syntax has used &
| instead of |, what it is doing is closer to an "and" then an
| "or".
|
| By the time you know enough to know you can ignore that advice,
| you will. But you'll also likely find it never comes up,
| because, again, the standard library has already implemented
| all the sensible variants of this, not because the standard
| library is magic but because there's really only a limited
| number of useful cases anyhow. I haven't gone too crazy with
| generics, but I have used them nontrivially, even done s could
| tricks [1], and the pipe operator is not that generally useful.
|
| When the generic constraint is an interface with methods is the
| case that can actually come up, but that makes sense, if
| generics make sense to you at all.
|
| It probably is a good demonstration of the sort of things that
| come up on generic implementations, though. Despite the
| rhetoric people often deployed prior to Go having them, no,
| they are never easy, never without corner cases, never without
| a lot of complications and tradeoffs under the hood. Even
| languages designed with them from the beginning have them, just
| better stuffed under the rug and with less obvious conflict
| with other features. They're obviously not impossible, and can
| be worthwhile when deployed, certainly, but it's always because
| of a lot of work done by the language designers and
| implementations, it's never just "hey let's use generics, ok,
| that one sentence finishes the design I guess let's go
| implement them in a could of hours".
|
| [1]: Just about the edge of the "tricky" I'd advise:
| https://github.com/thejerf/mtmap
| tapirl wrote:
| > In particular, people tend to read it as the "sum type"
| operator, which it is not. I kind of wish the syntax has used
| & instead of |, what it is doing is closer to an "and" then
| an "or".
|
| I don't understand here. In my understanding, the pipe
| operator is indeed closer to "or" and "sum type" operator.
| Interpreting it as "and" is weird to me.
| Groxx wrote:
| I think they're reading it as "a bitwise-and of the
| functionality of the types passed", which is accurate
| (since you're getting the lowest common denominator of all
| |'d types).
|
| I'm... not sure which way I lean tbh, now that I've seen
| that idea. Both have merit, it's more of a problem for
| educational material than anything. If you present it as
| "these types", | makes sense. If you instead use "these
| behaviors", & makes sense. | is slightly easier to type for
| me though, and & has more meanings already (address-of), so
| maybe I'd still favor |.
| tapirl wrote:
| Okay, it is some reasonable if the operator is viewed as
| a behavior operator. But it is not, it is a type set
| operator.
| jerf wrote:
| And the real point I'm making here is that "the type set
| operator" is _not_ "a sum type". A sum type with, say,
| three branches is either the first, or the second, or the
| third, and to do anything with any of them, you have to
| deconstruct it, at which point you have full access to
| the deconstructed branch you are in. The | operator in a
| Go generic is more a declaration of "I want to operate on
| all of these at once", so, you can put multiple numeric
| types into it because you can do a + or a - on any of
| them, but while the syntax permits you to put three
| struct types into it, and it'll compile, it does not
| produce a "sum type". Instead you get "I can operate on
| this value with the intersection of all the operations
| they can do", which is more or less "nothing". ("Methods"
| aren't "operations"; methods you can already declare in
| interfaces.) Some people particularly fool themselves
| because you can still take that type, cast it into an
| "any", and then type switch on it, but it turns out you
| can _always_ do that, the | operator isn 't helping you
| in any particular way, and if you want to have a closed
| set of types, a closed interface is a much better way to
| do it, on many levels.
|
| It also doesn't currently do anything else people may
| want it to do, like, accept three structs that each have
| a field "A" of type "int" and allow the generic to
| operate on at least that field because they all share it.
| There's a proposal I've seen to enable that, as the
| current syntax would at least support that, but I don't
| know what its status is.
| tapirl wrote:
| There is actually a proposal to make type constraints act
| as sum types: https://github.com/golang/go/issues/57644
|
| But I doubt sum types will be supported perfectly in Go.
| The current poor-men's sum type mechanism (type-switch
| syntax) might be still useful in future Go custom generic
| age.
| tapirl wrote:
| The difference between types.Implements and types.Satisfies is
| mainly caused by a history reason. It is just a tradeoff
| between keeping backward compatibility and theory perfection.
|
| It is pity that Go didn't support the "comparable" interface
| from the beginning. If it has been supported since Go 1.0, then
| this tradeoff can be avoided.
|
| There are more limitations in current Go custom generics, much
| of them could be removed when this proposal
| (https://github.com/golang/go/issues/70128) is done.
|
| I recommend people to read Go Generics 101
| (https://go101.org/generics/101.html, author here) for a
| thoroughly understanding the status quo of Go custom generics.
| guilhas wrote:
| I like in Go how the code looks like a execution graph, by
| avoiding smarts and just copying code, when you have an error in
| the log you can generally just follow it through the code as
| there is only one path to get there. In C# I would have mostly to
| debug to understand where did it came from
|
| Not just because of the language, but of the simplify culture.
| Let's see how generics will change that
___________________________________________________________________
(page generated 2024-11-17 23:00 UTC)