Haskell's json/sql marshalling do not use runtime reflection but instead ad hoc polymorphism, so when I create (or even derive automatically!) a marshalling instance, it is pretty easy for me to reason about what will happen statically. Haskell's Generic & newtype-deriving go a long way here, and are good examples of principled abstractions that do not leak.
Haskell's conduit (and other streaming libraries) is another good example. I use them to create programs that process things in constant memory, and when I compose them (e.g. with operators like =$= or .| in conduit), the resulting program streams in constant memory. I have built entire systems (CLIs, batch jobs, event processors, etc) on top of this abstraction and conduit itself has never leaked.
(Yeah, it can’t compile easily distributable self contained binaries. It’s not (yet) designed for that.)
Not intending to start a language war, but your argument is basically the beaten wife claiming this is just standard treatment from husbands. This is my counterexample.
It just have so much depth with concurrency. The actor model is amazing and easy to think about too.
I'm not entirely sure they overlap completely but the concurrency model is superb. I will probably only use Elixir for web application from now on (unless they don't have packages for certain API). Chris have made web development possible and many awesome people have contributed (POW package is just lovely).
You're praising Erlang's concurrency model, there's no such thing as Elixir concurrency model.
But it's also inescapable to ignore Erlang when you dive deeper into the actor model. Several good actor model books are Erlang only. Unfortunately many people, including myself, ever actually do anything hardcore with Actor Model. There is a good blog post about several skill stages of erlang/elxir and OTP was at the very top in term of few people actually uses it. I can't recall it the author =/.
Actually, I like Elixir package management system and command line tool is much better. They recently have the release command line tool. Elixir have several more differences?
As for the reason why it has less complains it's pretty simple no one uses Erlang and it's a niche, it's not a generic purpose language. I can't even tell a single known application or library written in Erlang.
To my knowledge Ericsson still writes most things in Erlang. Yahoo uses it for a couple large services. My company use PagerDuty which is also written on BEAM.
I'm sure there are many, many other things in not familiar with, but claims of Erlang's demise are greatly exaggerated.
That's bullshit. When I worked in the Ruby on Rails space, I saw critisms quite often... for example of the "one size fits all" way-too-large-surface-area library design (see: ActiveSupport) or the "fat model" design trend at the time... I even PRODUCED some of these criticisms myself. Heck, the bug that made me leave the space took a month to track down and had to do with nondeterminate behavior when merging a Hash with a HashWithIndifferentAccess (this was not my code, I would have not written it the way it was written, but it was a bug I was assigned to of someone else's code) because the Ruby language did not see these as two distinct "types" and simply went ahead as if they were both regular Hashes, which caused HWIA keys to get overwritten unexpectedly/nondeterministically... that was the last straw at the time.
And when I worked in .NET/ASP prior to that, I saw (and witnessed) MANY criticisms of the language and API such as how easy it was to produce difficult-to-test spaghetti code.
And while all this was happening I was also working on frontend code in JS and needless to say there have been A LOT of JS criticisms over the years and I've seen most of them.
So yeah, maybe try again with less BS. The biggest criticism I can produce of Elixir (and more generally the BEAM VM) is that too few people understand its advantages and too many people actively misinform others about it. (OK, one more criticism, but it is a more general criticism of functional immutable languages: A handful of algorithms perform suboptimally in a language that does not even permit mutation, relative to a language that does.)
I did use Go professionally for about five years, and read lots of criticisms of it (and still do out of curiosity). Most of them struck me as just not a big deal in practice. When I had to write some list-processing code that could be greatly simplified with generics (a few times per year), I just sighed and typed it out and moved on to a more interesting thing. When I had some repetitive error-handling code, I refactored it or wrote some helper functions. The dependency stuff was annoying when starting a new project, but once you pick a dependency manager/vendoring tool it's fairly straightforward (and of course now this is included).
Certainly, the language and ecosystem has warts and frustrating things. Maybe Elixir has fewer warts, I don't know. But overall the experience of using Go is fairly smooth and boring and productive. The things that people like to complain about don't really register in day-to-day usage. That's not BS, that's my personal experience.
For the record, I really love Elixir but it’s quite easy to swallow/ignore errors there as well. (To translate one of the Go’s criticisms.)
So I get what you’re saying. But IMO the outsiders’ perspective is valuable because it outlines stuff we have gotten used to, and they might not be willing to do that.
So such criticisms might be minor for you and me but they add much-needed nuance in the long run, I believe.
And truth be told, a lot of the issues I encountered in the languages I mentioned were from other people’s flawed code designs, not my own.
State, for example, doesn't leak. You never worry about the fact that it's a function `s -> (s, a)` and it gets optimized away like nuts.
In the unlikely event that there was a change to the implementation of the State monad, or to one's compiler, such that the State monad was not optimized away, it would be disruptive to users. It would probably be treated as a bug, even if the only change in behavior was in additional CPU and memory usage.
This is a corollary of Hyrum's Law.
In the real world, executing most SQL statement could be made to return a semi-useful integer according to simple and consistent rules (e.g. affected row count, -1 if there's no meaningful integer).
But the official Go documentation
https://golang.org/pkg/database/sql/#Result
makes it quite clear that the Go design committee decided to imitate a remarkably limited and inelegant MySQL function that returns the value of an auto-increment column, not even realizing that only a few statements have auto-increment columns to begin with. I'd call this a negative amount of design effort.
LastInsertId returns the integer generated by the database
in response to a command. Typically this will be from an
"auto increment" column when inserting a new row. Not all
databases support this feature, and the syntax of such
statements varies.
(Of course, MySQL's LAST_INSERT_ID() is only bad as a building block and inspiration for a general API; in SQL queries assumptions aren't a problem and overspecialized tools can be occasionally very useful)UUIDs are not meant to be generated by databases.
Cut 2 of 1000
It's still fundamentally caused by Go's shitty design choices.
encoding/json is at fault as well, which is also in the stdlib and a flagship library (basically part of the language - the maintainers wouldn't even extend its struct tag parsing to allow for required fields it's so fundamental!)
Proof that this issue affects JSON: https://play.golang.org/p/erfcSIe-Z7b
I mean, come on. In your playground example, one way of using the UUID type inherits its methods and another doesn't. Inheritance is inherently complicated, and if you're relying on it you need to know what you're doing, no matter what language you're using. I wouldn't call that a poor design choice.
newtype NotRobPike'sUUID = NotRobPike'sUUID UUID deriving newtype (ToJSON, FromJSON)
^ not inherently complicatedThe database package uses a type assertions to find the methods, not reflection.
Go types have one level of underlying type, not multiple levels as you seem to be assuming. Go is simplistic compared to other languages in this regard.
A type definition defines a new type using the underlying type of some other type.
I can understand the complaint that Go does not have the aliasing feature that you want, but the database/sql and encoding/json packages work exactly as expected given Go's simple model.
Personally I would never create different UUID types for each DB struct, and have never seen that done.
type DogId struct { uuid.UUID }
is type composition (Sorry if I'm not using the right term there). Isn't this exactly how you're supposed to do what you're trying to do in Go?Where, specifically, did Go promise you that? I know of no languages where you don't, sooner or later, have to have weird specific knowledge of the semantics.
I think it's perfectly fair to say that this behavior is not in line with Go's philosophy.
Proof that `type X Y` causes the issue: https://play.golang.org/p/erfcSIe-Z7b
This is a useful feature and there's nothing weird or special case about how this works. It's just not the aliasing feature you expected.
If you had done e.g. type X Y you would have had more success.
type X Y however does create an entirely new type which is physically identical to the original but logically unrelated (without any of the methods or anything).
Which is fine in the sense that it's what OP was looking for (completely independent types) but less so in that it doesn't forward any method, and it's not necessarily clear how you'd do that (type conversion, which looks really shitty when it involves pointers)
`type X Y` is still affected - you will 100% _not_ have more success :)
Proof (using JSON this time): https://play.golang.org/p/erfcSIe-Z7b
type X Y neither inherits methods nor forwards anything (what struct embedding does), so you need to know about the protocol (which you're not told because it decides to just encode the bytes), and then you need to implement its methods on the newtype explicitly forwarding them to the underlying one.
TBF this is a pretty awful case as Go just goes and generates garbage without prompting.
Say I want to take a uuid.UUID [1] and use it as my id type for some database structs.
At first, I just use naked UUIDs as the struct field types, but as my project grows, I find that it would be nice to give them all unique types to both avoid mixups and to make all my query functions clearer as to which id they are using.
I go to run my tests (thank goodness I have tests for my queries) and everything breaks! Postgres is complaining that I'm trying to use bytes as a UUID. What gives? When I remove the type definition and use naked UUIDs, it works fine!The issue is Go encourages reflection for this use-case. The Scan() and Value() methods of a type tell the sql driver how to (de)serialize the type. uuid.UUID has those methods, but when I use a type definition around UUID, it loses those methods.
So the correct way to wrap a UUID to use in your DB is this:
Go promised me that I wouldn't have to deal with such weird specific knowledge of its semantics. But alas I always do.[1] https://github.com/google/uuid
EDIT: This issue also affects encoding/json. You can see it in this playground for yourself! https://play.golang.org/p/erfcSIe-Z7b
EDIT: I wrongly used type aliases in the original example, but my issue is with type definitions (`type X Y` instead of `type X = Y`). So all you commenters saying that I did the wrong thing, have another look!