|
| 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.
| 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
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
___________________________________________________________________
(page generated 2024-11-17 14:00 UTC) |