Remix.run Logo
cubefox 4 hours ago

I know this is not the point of the article, but I find the anecdote in the beginning about null pointer errors somewhat ironic. Haskell's solution to null pointers are option types (`Maybe x` in Haskell), but these are known to be suboptimal.

In languages with option types, if you want to weaken the type requirement for a function parameter, or strengthen the guarantee for a return type, you have to change the code at every call site. E.g, if you have a function which you can improve by changing

- a parameter Foo to Option<Foo> or

- a return value Option<Bar> to Bar

you would have to change the code at all call sites. Which could be anything between annoying and practically impossible.

In languages that solve null pointer errors instead with untagged union types (like TypeScript or Scala 3), this problem doesn't occur. So you can change

- a parameter Foo to Foo | Null or

- a return value Bar | Null to Bar

and all call sites of the function can remain unchanged, since the type system knows that weakening the type requirement for a parameter, or strengthening the promise for a return type, is a safe change than can't cause a type error.

So yes, option types do avoid null pointer exceptions, but they solve the issue in a very suboptimal way.

qsera an hour ago | parent | next [-]

>you would have to change the code at all call sites.

Actually I think you can just change concrete argument `Foo` to type constraint in Haskell as well using a type class. So the function would be something like `foo :: ToMaybeFoo a => a -> .. ->`. And you would implment `ToMaybeFoo` instance for `Foo` and `Maybe Foo`.

Agree that this is more involved than typescript, but you get to keep `null` away from your code...

wazHFsRy 4 hours ago | parent | prev [-]

Mostly though if you do anything with the returned value at the call site you need to change that code anyways? If it is not just passing it on, and even then you might need to adapt its signatures. E.g. if you change from String | Null to String you remove the null handling. If you add Null you need to add Null handling?

cubefox 3 hours ago | parent [-]

No that's not right.

If you were calling a function which might return null (String | Null), you will already have null handling at the call site, but if you now change that function such that it never returns null (String), you still have the (now unnecessary) null handling, but this doesn't hurt and you don't have to change anything at the call site.

Likewise, if you were passing a String to a function that doesn't accept null (String), the call site already made sure that the parameter isn't null, and if you change the function so that it does now accept null (String | Null), again nothing needs to be changed at the call site.

django77 3 hours ago | parent [-]

I agree that this can be nice when done right (Clojure), but null is a high price to pay for this convenience.

I must admit I’ve never had this problem in application development. In fact, I do want to change my callers because strengthening the contract is an opportunity to simplify the callsites - they no longer have to handle the optionality. The change might carry some semantic meaning too, why are you getting x instead of Maybe x all of the sudden? Are there some other things you should reconsider in the callers? I can see how it could be useful in library development, but there are also patterns to account for this that are idiomatic to Haskell.

cubefox 2 hours ago | parent [-]

> I agree that this can be nice when done right (Clojure),

I don't think Clojure has untagged union types like TypeScript or Scala.

> but null is a high price to pay for this convenience.

Why would it be? Untagged unions prevent null pointer errors just as much as option types do, only they don't have the discussed disadvantages of option types.

Munksgaard an hour ago | parent [-]

> Why would it be?

That's literally what they explain in the rest of the comment.

cubefox an hour ago | parent [-]

No, they don't reference any "high price to pay", only that they personally didn't need the advantages of untagged union types so far, and that Haskell (allegedly) has patterns that would play a similar role for libraries.