Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> I would ask if the function should return null or throw an exception when there was no user with that ID.

My answer would be “neither”. Both of these options are bad. Null is maybe less bad because it at least roughly approximates the algebraic type that accurately represents the image of the function. Ideally you would return some type like Optional<User>. I suspect this is what you’re referring to with the Rust reference.

A slightly more generalized version of this is to use the Either/Result monad, which can nicely handle multiple types of exceptional behavior at once.



Hrm, not the OP but, personally, it depends.

If you’re using a language without algebraic data types, you’d have to ask whether it’s perfectly normal for a record not to be found by ID at that point.

If it’s unusual, you should probably just throw the exception at that point because:

1. You check null and throw anyway, which gives you a stack trace just a little off the mark. That isn’t too bad but it’s more code for little gain.

2. There’s every chance you could forget to handle the null (It’s not the nil, it’s my discipline, I know, but it happens, we’ve all been there) and then you get a null pointer exception at some point later on in the flow, which is like the above but even worse.

If you’re using algebraic data types, you wouldn’t use an optional. There are a number of ways a DB look up can fail so Optional isn’t really ideal at all. Your suggestion of Either/Result is probably the ideal there.

Chances that you expect an explicit DB look up by ID to return nothing is pretty rare. Where has the program or client code got that ID from in the first place? Either you want to know about the error in your own code or you want the client code to know the error so you probably want some form of exception or Either.

In the rare case it is intended behaviour, yeah you could use null and push the responsibility for handling it to the calling code. Still, at that point, 2 kicks in - might be fine now but that assumption could easily change as the code evolves, which it can and often does.

You could argue you can implement something like Optional in, say, Java w/o algebraic data types but they are simply not ergonomic when the language does not help you use them.

But I don’t think you can hold up a single approach and say that’s the ideal. I doubt it was your intention but it’s rather dogmatic and doesn’t really play out nicely in real world code, especially legacy projects (not everything is written in Haskell|Rust).


> it’s rather dogmatic

I found it correct. I hold the same "anti null" and "anti exception" beliefs, and think Either/ResultObject/Maybe/Optional is ideal compared to them...

Your range of programmer thought seems to be limited by what is possible/idiomatic in popular languages.

> not everything is written in Haskell|Rust

The Truth is not measured in mass appeal.


The convention in C# is to make two versions of the method: GetFoo that throws, and TryGetFoo that returns a bool and places the result in an out parameter

For example, with the C# dictionary, you can either do: foo = dictionary["foo"]; Or: if(dictionary.TryGet("foo", out foo))

If Option<T> is your preferred pattern then you should use a language where that is supported.

Trying to recreate Option<T> in languages without compiler null checking has issues because either there's a risk that the Option<T> is null, or the value inside of it is null.


does C# support sum types? GP was talking about C# specifically.

Agree with your analysis though: it's essentially a partial function, so the range (codomain) of the function includes "no defined output for the specified input". It's therefore not a bug to return "not found". An algebraic datatype like Either/Optional/Result allows that to be encoded in the type system, and so provides explicit support syntactically and semantically for handling the situation.

I don't think C# supports sum types (though may well be wrong there, not so close to the language). Assuming not, the question is how to achieve the desired outcomes. Those might reasonably be one or more of:

* minimise the risk of null de-reference

* maintain performance

* be idiomatic

* be understandable

* be explicit

If (and it's a big if) the only language options are nulls and exceptions, there's not a clear winner there. So exploring the tradeoffs does seem like a reasonable discussion.


Effectively every OO language does. Inherit and override behavior. Add a piece of indirection that allows the result to specify the type of view, etc. Think of `UserSearchResult SarchUser()` as returning either `ARealUserResult { int userId; string display = "showUserView" or `UserNotFoundResult { string display = "notFoundView" }` (you could easily add MultipleUsersMatchResult or UserRequiresAdminPrivilegesResult ...)


Agreed. This is the most obvious way fwd. Rust, Haskell, Elm, even Kotlin seems to come along nicely.

Exceptions and null for error cases are horrible and Java and Go are doomed. (semi-jokingly)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: