upvote
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?
reply
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.

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

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

reply
> Why would it be?

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

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

reply
This is a neat idea, but it does require that you know up front the largest union that could ever be supported in that argument, so that you have the ability to narrow it down later. Worse, it in the limit it requires a combinatorial explosion of type classes, with one for each possible union! The `ToXYZorW` classes form a powerset over the available types.
reply
See fundeps.
reply
Admittedly I don't really understand your construction. But this solution, if it works, doesn't look practical enough that it could be routinely used in practice like Foo|Null could be. By the way, some languages even shorten "Foo|Null" to "Foo?" as syntax sugar.

> but you get to keep `null` away from your code...

I don't think this would be desirable once we have eliminated null pointer exceptions with untagged unions.

reply
>Admittedly I don't really understand your construction.

It is quite simple. Instead of accepting a concrete type `Foo`, the function is changed to accept types that can be converted to `Option<Foo>`. Since both `Foo` and `Option<Foo>` can be converted to `Option<Foo>`, the existing call sites that passes `Foo` would not require changing.

https://play.haskell.org/saved/g4idq2zv

reply