Comment

Comment by nunez

There was a recent discussion in /r/golang about chaining methods like this. The OP had a hard time writing tests for their implementation since each part of the chain returned interfaces that were relied on by following links within. The top answer in the thread was that Golang wasn't really meant to make "Promise-" type workflows possible due to it leaning heavily on code being intentional and explicit.

I agree with them.

One of the problems I have with things like LINQ or Promises is that they are immensely powerful but can be really difficult to debug when things go wrong because of implicit errors within the pipeline bubble up the stack without providing location context. While the standard boilerplate around errors in Go is annoying, I actually prefer it for two reasons:

1. It provides exactly where things went wrong while looking at an exception trace, and 2. From a readability perspective, it is much easier to understand what the author of the code meant to do and, more importantly, what the follow-on error s mean. Sometimes, errors aren't actually errors.

Either way, OCaml/Haskell/F#'s approach with match expressions and the `Result` type is the best way to deal with this, IMO. You get the best of both worlds: explicit declaration with a very expressive (triangular-like) "shape" to the code.

So something like this:

  var data string = script.File("test.txt").Match("Error").CountLines()
Can be expressed like this:

  var foundLines int32 = 0
  var numLines int32 = match file.Open("test.txt") with
    | Error e -> return e
    | Ok f -> match f.Readlines() with
      | Error e -> return e
      | Ok lines -> match lines with
        | /Error.*/ -> foundLines++
        | _ -> // do nothign
  return foundLines
instead of:

  foundLines := 0
  lines, err := ioutil.ReadFile("test.txt")
  if err != nil {
    return 0, err
  }
  for _, l := range lines {
    re, err := regexp.MustCompile(`Error.*`)
    if err != nil {
      return 0, err
    }
    matches := re.FindStringSubmatch(`Error.*`)
    if len(matches) > 0 {
      foundLines++
    }
  }
  return foundLines, nil
This way, you can see exactly where the error occurred but can still see that `numLines` is generated through a pipeline.

As far as the library itself, I personally wouldn't use something like this, though I see the appeal. I turn to statically-typed languages like Golang when a Bash script becomes too kludgey to stand on its own (usually when I need to begin relying on mild data structures) and when I care about type safety and portability more than what Ruby or Python can give me. When I'm writing a Go program, I want something that's testable that I know can work anywhere. With Ruby or Python, unless I'm distributing the script as a Docker image, I have to worry about versions, environments, etc., none of which are pleasant.

However, writing the error boilerplate is annoying, and I can see developers who want to write something quick but spend 99% of their time in Go using this to get something done with a tool they know well. I've seen similar things in the Java world; hell, that's the reason why Groovy and Kotlin exist!

TL;DR: The "wrong thing" in the Bitfield article isn't necessarily wrong; their library is useful, but niche; all hail pattern-matching and the Result type.

parent nunez 4 years ago

Discussion

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton