Apparently, the author of this blog post had a change of heart: https://memo.barrucadu.co.uk/blub-crisis.html > I have realised in recent conversations about programming languages, and in reflection of my very negative and kind of arrogant blog post about Go, that I have become trapped by the Blub Paradox. I have become dismissive of non-Haskell languages. I think in Haskell. Languages less powerful than Haskell are…
Such a weird thing to frame this in terms of the Blub Paradox. Regardless of what you think about the paradox, this is what it states: > But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn’t realize he’s looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as…
Three Months of Go from a Haskeller’s perspective (2016)
101–110 of 162 posts
Re: Three Months of Go from a Haskeller’s perspective (2016)
#102Earlier quoted context omitted.
Libraries and library-like code. Any code that fetches data from generic storage/protocols. Prime examples: - http requests. For an API it's almost always a generic request parametrized by some type. For example, you API always returns `{result: ...some data..., nextPageToken: ...}`. Well, that's a `PagedResponse ` - cache Caches store objects. In go any `.get` from a cache will return an `interface{}` that you have…
Re: HTTP requests, you mean the exact HTTP response, which in your case is a JSON object that can be parsed via a different package; the two should not be conflated, and the HTTP package should not be polluted with assumptions about what (if any) data is being passed. Re: caches, what kinda caches do you mean? For the most simple use case you have maps, which in Go are already generic. Of course, anything more advanc…
I'm not talking about the HTTP package itself. In Java and C# you do something along the lines of
// Java
CompletableFuture> getContracts(...);
CompletableFuture> getClients...(...);
CompletableFuture> getBooks...(...);
// C#
JsonSerializer.Deserialize>(responseBody);
JsonSerializer.Deserialize>(responseBody);
JsonSerializer.Deserialize>(responseBody);
And that's basically it. With Go you end up having fifteen identical PagedResult types for every single type that can be returned from the API because you can't parametrize anything: // Go
type ContractsResult struct {
Result []*entities.Contract
NextPageToken string
}
type ClientsResult struct {
Result []*entities.Client
NextPageToken string
}
type BooksResult struct {
Result []*entities.Book
NextPageToken string
}
> Or they use code generators, which is also pointed out in the article.Code generators are just bandaid for glaring holes in the language. Worse still, I don't know if you can even specify code generating tools in your go.mod. For example, wire's installation instructions say "you need to install wire globally and have it on your $GOPATH" [1] So your go build will just fail mysteriously until you have all the necessary tools installed.
> Of course, anything more advanced would be greatly helped with generics
That... that is exactly what I'm talking about.
> Option types to avoid nil, Either types to replace the value/error tuples
Indeed. I forgot about those :) Yup, that would be a great use case for generics.
Re: Three Months of Go from a Haskeller’s perspective (2016)
#103Earlier quoted context omitted.
Such a weird thing to frame this in terms of the Blub Paradox. Regardless of what you think about the paradox, this is what it states: > But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn’t realize he’s looking up. What he sees are merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as…
I think he's not saying Golang is up the power continuum, but that languages like Idris and Coq are up the power continuum. He doesn't bother learning those because he's "stuck".
Also interesting: it's his only article tagged Go, from 2017. He never mentions Go again. Since he has several more articles about Haskell, I wonder if he eventually quit his job, or whether he simply didn't have anything else to say about Go.
Re: Three Months of Go from a Haskeller’s perspective (2016)
#104Earlier quoted context omitted.
Go is incredibly readable I find. Yes, you tend to find yourself writing a lot of code because of the lack of generics, but that is being fixed as we speak. Generics has a draft and it looks nice from a Go developers perspective. And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaring…
Every isolated Go piece is pretty readable. The problem is that getting most things done requires enough code that it's a lot of work to take it all in. Run all the for loops in your head. Etc. Higher level (and esp FP) languages like Haskell or Scala will be the opposite. That bunch of function compositions may take a little bit of work to digest, but once you understand it you understand a lot. When people disagree…
Re: Three Months of Go from a Haskeller’s perspective (2016)
#105Earlier quoted context omitted.
Go is incredibly readable I find. Yes, you tend to find yourself writing a lot of code because of the lack of generics, but that is being fixed as we speak. Generics has a draft and it looks nice from a Go developers perspective. And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaring…
> And Go let's you communicate by copying. That's what a Channel is. Pass a struct and that is copied. Pass a pointer and the pointer is copied. The thing it points to isn't copied for glaringly obvious reasons. I'm not sure what those glaringly obvious reasons are. In Erlang, you just send a copy of the whole struct. Not some kind of references or pointers. See eg https://play.golang.org/p/P3qUtFenp2q But as I am sa…
Here's a playground for reference:
Re: Three Months of Go from a Haskeller’s perspective (2016)
#106Earlier quoted context omitted.
I've never written Haskell or another pure functional language more than a couple of lines, but the more I write code the more convinced I become that the ability to reason about mutability and side effects is a major force multiplier in writing robust software.
I agree that being able to reason about mutability and other effects is useful. However, that doesn’t necessarily imply an all-or-nothing approach where either you’re in a pure function or you can do anything with anything. In a sibling comment, gwd mentioned const in C, which is one example of something in between. Rust’s ownership semantics and borrow checker are another.
To elaborate this point I'd say that the most important practical use of all that "monad mumbo-jumbo" in Haskell is that you can tag your functions with what they can and can't do and then the type system tracks this for you:
-- pure function
f1 :: Text -> Int
-- can fail
f2 :: Text -> Maybe Int
-- can read from some MyEnv record
f3 :: Text -> Reader MyEnv Int
-- can keep a set of bool as state around
f4 :: Text -> State (Set Bool) Int
etc... and of course to go nuclear: -- can do anything
f5 :: Text -> IO Int
The tracking part is that you can't call f5 from within f1, the type checker says no. It enforces separation between all these various effect boundaries.Also we don't have to stop here. A natural next step is defining exact effects one is after. For example say I want my function to be able to get some entity from a DB:
-- any type that is an instance of Entity can identify itself by uuid
class Entity e where
identify :: e -> UUID
-- The effect we want, ie. get an entity via its uuid
class (Monad m, Entity e) => GetEntity e m where
getEntityById :: UUID -> m (Maybe e)
now we can say things like: data User = MkUser {uId :: UUID, uName :: Text}
-- a User can identify itself
instance Entity User where
identify = uId
-- as User is an Entity so we can get it via its uuid (if it exists)
getUserById :: (GetEntity User m) => UUID -> m (Maybe User)
getUserById = getEntityById
Also notice how getUserById does not say anything about IO or a DB. All it states is that whatever context it will run in that context must know how to get a user via its uuid. You can then plug in whatever actual context you want, say: newtype Prod a = MkProd {unProd :: ReaderT ProdDB IO a}
deriving newtype (Functor, Applicative, Monad, MonadReader ProdDB, MonadIO)
-- get a user from a DB for real
instance GetEntity User Prod where
getEntityById :: UUID -> Prod (Maybe User)
getEntityById eid = do
ProdDB {..}
or newtype Mock a = MkMock {unMock :: State (Map UUID User) a}
deriving newtype (Functor, Applicative, Monad, MonadState (Map UUID User))
-- get a user from a mock DB
instance GetEntity User Mock where
getEntityById :: UUID -> Mock (Maybe User)
getEntityById eid = gets (Map.lookup eid)
All in all the programmer have fine-grained control over what various parts of their code can or can't do.Re: Three Months of Go from a Haskeller’s perspective (2016)
#107>The way in Go to handle possibly-failing functions is to have multiple return values: an actual result, and an error. If the error is nil, then the actual result is sensible; otherwise the actual result is meaningless. This means you can forget to check the error and use a bogus result and, because there are no compiler warnings (another wtf), you will know nothing of this until things fail at runtime. ? You can't f…
While you're correct, you can still avoid handling an error and not get any feedback from the compiler or linters by doing something like: val, err := something() val2, err := somethingElse() if err != nil // etc You can - and all the examples usually do - reuse and shadow an 'err' variable, and tooling won't complain as long as something is done with it at least once. If Go wants to enforce not ignoring errors, it s…
Re: Three Months of Go from a Haskeller’s perspective (2016)
#108 f := os.Open("file") or {
return err
}
After a couple of years with Scala, I'm also really missing proper FP, pattern matching, optional types, immutability, etc. Still, I'm pretty happy with Go and it has made me more excited about coding again.Re: Three Months of Go from a Haskeller’s perspective (2016)
#109Earlier quoted context omitted.
It did to me. He slams go for not doing stuff the Haskell-way (e.g. pure code vs effectful code). This is not how you approach new things
I've never written Haskell or another pure functional language more than a couple of lines, but the more I write code the more convinced I become that the ability to reason about mutability and side effects is a major force multiplier in writing robust software.
In Rust, for example, mutable data is allowed, but, when the owner of mutable data shares a reference to it, it gets to decide whether the borrower is also allowed to mutate the data. This doesn't eliminate the more challenging things you can do with shared mutable variables, but it does mean that enabling them requires mutual consent.
Nim does an interesting thing, too. It has a two-color function mechanism where "procedures" are allowed to have side effects, and "functions" are not. But even functions are allowed to use mutable variables behind closed doors. That can arguably be an ergonomic win. Many people find that, within a sufficiently bounded context, an implementation that uses mutable variables might be more maintainable than a purely functional implementation.
The main reason Haskell goes even further, and bans mutable variables from the insides of functions as well, was never really about maintainability, per se. It was done that way because Haskell, as a lazy language, couldn't allow anything that might require statements to be evaluated in a particular order. That design decision turned out to lead to an impressive bounty of interesting and useful discoveries. But there also seems to be something of a tendency to swaddle the bathwater with the baby.
Re: Three Months of Go from a Haskeller’s perspective (2016)
#110Earlier quoted context omitted.
Definitely! I couldn't get my head around why people like untyped languages, but I keep an open mind on it. I won't close off that they could be better, if the right patterns/practices are used (whatever they are!)
I joked with a friend that as people get older they start to prefer static typing. I personally don't have a preference, it depends on the application. I think it's obvious what are the advantages of static typing so let me rant about what is for me the main disadvantage: as soon as you have an advanced type system people will try to get creative writing code in the most abstract possible way. It's inevitable. It's t…
Almost all of your “whys” have sensible, practical, answers. The practical bit is the sticky bit that gets set when you get “older”.