> error is an interface in Go which can be easily cast/checked for the underlying type.
Yeah it's done during runtime and the compiler won't be able to help you with it if you fail to do exhaustive type checking. It's a problem anytime you refactor your code. ADTs and pattern matching is pretty much the bare minimum language feature i expect from any statically typed language.
I'm not sure I understand why this is true for Go rather than a statically typed FP language. If anything, I'd consider languages like Haskell or ML to be better at this.
- The obvious case is handling `nil`. In Haskell, you'd treat this as `Maybe a`, and it's obvious that you're not handling the nil case since the pattern match is incomplete. In Go, you get a nil pointer exception at runtime. You could check for the nil case for every pointer, but that's too onerous, so now you're left with relying on the programmer on checking
- Go doesn't have exceptions, which removes some of the hiding of bad code (see https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36...), but you can still accidentally use a result when an error is return (in fact, you have to return something!). In Haskell, you're forced to pattern match on an `Either`, which again exposes bad through an incomplete pattern match.
I don't think these are arguments for FP per se, but I do think that statically typed FP languages (and the languages they inspire) are much better about removing footguns and making bad code hard to write, simply because the typechecker is so restrictive. As a more general principle, instead of making bad code screamingly obvious, make it impossible to write!
Now, you mention later in the thread that you've already used Haskell before, so you're probably aware of all this and I may misinterpreted what you meant. Did you mean a different form of bad code?
>> The language uses the type system to propagate such errors.
...
> Erlang for example follows that pattern.
Erlang is its own beast; if you characterize pattern matching as part of a type system, which I guess it is, then...maybe if you squint?
Normal errors are indeed typically reflected in the return value, but if the error pattern doesn't match the code's expectations then a runtime exception is thrown...if you're lucky. If the error pattern happens to fit with the pattern match but the code doesn't know about it, eventually some other error will occur.
Anyway, Erlang definitely doesn't fall into the "No runtime exceptions, ever" category, and as such doesn't really fit with the options the OP presented.
> A language that uses sum types (whithout any further guarantees) for error handling cannot formally enforce that invariant.
Huh? The invariant is enforceable:
type result =
| Success of result_you_want
| Error of error_info
You just need to be consistent about what you want. First you ask for returning partial successful results alongside error information, then you ask for the exact opposite. You can make a typeful model of lots of things, but first you need to make up your mind.
> trade conceptual clarity for efficiency.
Could you describe where exactly the efficiency gain comes from? If indirection is the problem, well, if anything, the Go approach requires more indirection.
this is the sort of silliness that is common in the Go world. as noted elsewhere, errors are values in lots of languages - Rust, C, Scala, Haskell, etc etc, but Go explicitly has no way to handle them nicely, no specific syntax and no fancy type system stuff like sum types.
it is my very strong belief that this will eventually be fixed in Go and when it does, almost all the people currently saying "I like errors being values [and it's fine that Go makes it very annoying]" will quickly prefer having some actual language help for these values.
> This really irritated me when I started working with go, but it stopped bothering me and now I even mostly like it.
Don’t get me wrong I like a good unused code warning.
What frustrates me is that Go’s is dumb / unreliable, and it will stop you from working entirely until you’ve complied with this whim, which has a fraction of a percent chance of identifying a real bug.
> Basically writing go without `staticcheck`[1] is not recommended.
So why have these things as mandatory compiler errors?
They are strings. They are implementation of interface with a single `to_string` method, after you got `error`, you can't say (you could use switch on type, but you should guess which type could it be), so basically you have an opaque object with a single `to_string` method, which is equivalent to string.
I can't fancy a more error prone way than that which Go choose. How you match the errors, you match strings? You match types? You don't even know what types could there be, since all what the type system says is that function returns error.
> There is only the runtime to print a stack trace
Types are not exclusively for verification, and even if you decide it is, you can do a lot more with a type error than exiting the program.
That stance most people keep repeating is actually ridiculous. The most common usage of static type systems is to verify badly-written ad-hock dynamic ones that handle user errors.
> You can test the interface. A type is just an interface around memory, albeit more consrained.
Wow, again, that's not the problem here. Errors in the standard libraries are defined at values. There is no point testing it as interfaces, it will not give you the nature of the error, since they are all defined with fmt.Errorf . Do you understand now the problem ? the problem is being consistant across codebases between errors are values and errors as types.
> you have code that will have runtime failures in the middle of your computations.
Preferably during running of your unit-tests.
In practice much of the software we use today has runtime errors. How the code handles those varies. Static typing does not prevent runtime errors does it?
> I don’t think I’ve ever seen a case where a Go function returned neither a value nor an error, or both a value and an error.
That's kind of the point. The type system should be powerful enough to disallow those cases then.
In practice, I've seen both, always accidentally. I've also (more commonly) seen a lot of confusion and annoyance around:
Okay, so this has to return a pointer for the error case, should the caller check that? If not, how do we square that with checking for nil pointers being generally a pretty good rule? If we do check, our unit test coverage has a blemish for every call since nothing can hit that. If we skip it being a pointer, then it's a zombie object.
It's just a lot of cognitive load and bikeshedding around an issue that shouldn't exist.
> When type errors occur in production, the end result is the program crashing, nothing you can do about it.
Uh, you can catch the type error and recover (e.g., as with any other runtime failures, fail the incoming request that triggered the failure, unwinding the stack and reclaiming memory).
There are many reasons to prefer static type checks; I just find this specific argument against runtime type checks flawed. Yeah, static type checks are vastly better than runtime type checks; but runtime type checks are vastly better than, uh, no type checks.
Yeah it's done during runtime and the compiler won't be able to help you with it if you fail to do exhaustive type checking. It's a problem anytime you refactor your code. ADTs and pattern matching is pretty much the bare minimum language feature i expect from any statically typed language.
reply