Live data from Hacker News

Three Months of Go from a Haskeller’s perspective (2016)

memo.barrucadu.co.uk

101–110 of 162 posts

Re: Three Months of Go from a Haskeller’s perspective (2016)

#101
post #97
post #10

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…

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".

Re: Three Months of Go from a Haskeller’s perspective (2016)

#102

Earlier 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…

> and the HTTP package should not be polluted with assumptions about what (if any) data is being passed.

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.

[1] https://github.com/google/wire

Re: Three Months of Go from a Haskeller’s perspective (2016)

#103
post #97

Earlier 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".

I think you're right! I misread it.

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)

#104

Earlier 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…

It is a lot like reading levels. Go feels like a grade 5 reading level. This means most people can read it. It also means complex concepts take a lot longer to explain because you have to avoid all sophistication.

Re: Three Months of Go from a Haskeller’s perspective (2016)

#105
post #51

Earlier 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…

Regarding the example in your first point, you are still sending a reference `m` of the map. If you wanted to send an immutable type, you could declare the channel to only accept struct values (and not pointers to structs) and you'll get your desired behavior.

Here's a playground for reference:

https://play.golang.org/p/U2tYZVTtUf-

Re: Three Months of Go from a Haskeller’s perspective (2016)

#106
post #81

Earlier 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.

> 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.

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
post #72

>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…

I see now. That is indeed one of the things I hated about Go, having to reuse err (or maybe, making the mistake of reusing err) several times in a single function.

Re: Three Months of Go from a Haskeller’s perspective (2016)

#108
I really agree with some points. In my project, we moved from Java/Spring because it was just too much magic happening behind the scenes. We wanted something that was just clear and straight to the point. We should be able to read the code and it should do exactly that, not something else because you annotated the class. I just wish it wasn't so bloated with error handling. It's difficult to see the actual code among all the if err != nil (even though GoLand does a pretty good job at hiding them). Something like this?

  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)

#109
post #81

Earlier 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.

I tend to agree, but, even then, one doesn't necessarily have to take things quite as far as Haskell does.

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)

#110

Earlier 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…

For some, it is “as developers get more mature they start to prefer static typing”.

Almost all of your “whys” have sensible, practical, answers. The practical bit is the sticky bit that gets set when you get “older”.

Post reply on HN