I know, but my point was that in my experience this class of bug is nearly always a problem at its heart in requirements anyway (i.e. an unspecified edge case), meaning you have to test it in whichever language you use to be confident of the program's correctness.
E.g. you'll still need to write a test for your haskell code to determine that the behavior is correct if the IP address is missing, and in your python code if you don't specify behavior it is the kind of thing that will likely lead to one of those nasty "cannot call a method on none" errors.
Since the act of testing which is necessary anyway catches it or confirms correct behavior, the additional benefit of a static type checker confirming the presence of such a bug is perhaps lower than it might appear at first glance.
This is clearly not true of weakly typed languages where you have to write a myriad number of extra tests just to achieve the same confidence in your code to cover all the unexpected use cases caused by weird type conversions you didn't even realize you were doing.
I believe eru's point was that those kinds of tests are not necessary in Haskell because if a function takes something of the type IpAddress, None simply isn't possible. So everywhere IpAddress is used, you don't have to do any of those tests.
It sounds like your point is that even in Haskell there will inevitably be boundaries where the None case is an issue. And that's true, namely the point where you call:
parseIpAddress :: String -> Maybe IpAddress
Somewhere your app will be sucking in a string and you need to have a test for the empty string case. That's true. But the surface area of the application that is susceptible to this problem is reduced (often dramatically). All code that works with IpAddress is free and clear. Whereas in most other languages, every function that takes an IpAddress probably needs to be tested with the None case. If you try to argue that you don't need to test every one, then you're plagued by the difficulty of knowing which need it and which don't. Haskell's types solve that for you completely.
Any typesystem that offers algebraic data types solve issues of this kind. As crdoconnor rightly puts it, handling these cases is ultimately a burden your spec has to bear. Haskell nudges you to handle them upfront.
What I hate most, is people using unusual but cromulent values as sentinels. Eg a function that takes a timeout will often interpret 0 as infinity. That's just wrong, and a symptom of working around an inadequate type system.
I get the point, but I still feel like the problem surface is not really reduced that much by static typing it away, and the bulk of the problem is simply moved elsewhere rather than solved outright since the root of these issues is incorrect specification anyhow.
As in, I take issue with the word "dramatic", not the word reduce.
Additionally - it might just be my python bias talking - but I tend to find that most useful "real-life" libraries need to have an opinion about what happens when a variable is None, because it is almost always meaningful. I guess if I were writing haskell my code would end up looking like a bad carly rae jephsen song :)
The key with Maybe/Option types is pattern matching ensures you don't forget to handle the case where it is none (it can be meaningful!). If you have a value of `Maybe a` you are required to pattern match on it.
case myMaybe of
Nothing -> foundNothing
Just v -> ...
In Python and Ruby any value can be None or nil at anytime.
In stark contrast languages like Rust and Haskell (that have no concept of None/nil/null) this is very useful as you know you always have a value in your variable and not a null.
This means when you do introduce an Option/Maybe type you are saying the null case should be treated with special semantics, exactly what you usually want in a dynamic language.
> I get the point, but I still feel like the problem surface is not really reduced that much by static typing it away
Ok, that's a valid point, which means that we need to precisely characterize what is meant by "problem surface". Let's continue with our IpAddress example. Imagine you are the author of the IpAddress package. Imagine we have an IpAddress type accompanied by an API of say 15 functions that take an IpAddress and do something with it. How many of those functions need to have tests that cover the case of passing None? I believe that number represents our problem surface.
If you agree that this is the problem surface, then I can confidently say the reduction is dramatic. (If you don' agree, then I refer you to the previous point about Maybe making it very clear what needs None tests.) The Python IpAddress package has to have None tests on every single function that takes an IpAddress. The Haskell package has to have None tests on zero. It only needs the equivalent of a None test on the functions that return a Maybe IpAddress. This is going to be a very small number. This brings us to your other point.
> Additionally - it might just be my python bias talking - but I tend to find that most useful "real-life" libraries need to have an opinion about what happens when a variable is None, because it is almost always meaningful. I guess if I were writing haskell my code would end up looking like a bad carly rae jephsen song :)
I'm pretty sure this is indeed largely caused by your Python bias. In my experience, Haskell code does not end up littered with Maybes. I think you don't appreciate the full weight of the statement that when you have an IpAddress, you never need to worry about the None case. Never ever. Period. There are cases where you need to deal with a Maybe IpAddress. Those cases don't end up polluting all your code, contrary to what you seem to think. In fact, it's the other way around. The pure cases tend to be the ones that are the most common, making the Maybe cases clearly visible.
Even in the situation where they do start to pollute some things, Haskell gives you dramatically better tools for dealing with the problem. Yes, you'll still have to have a test for it, but the code you're testing will have been much more concise and less prone to error. Furthermore, Haskell can make it so you don't even have to think about testing that case. You write an Arbitrary instance for IpAddress and the Arbitrary instance for "Maybe IpAddress" comes for free! This means that your QuickCheck test for the Maybe IpAddress function will automatically be sure to try it with Nothing.
From what you've said it is clear to me that your python bias is somehow making it hard for you to see/believe the benefits that we're talking about. I'm concerned that this gap seems to be so big and we seem to be having such a hard time crossing it. Give Haskell a try and see for yourself. But don't go off into a cave and play with it alone. There are abstractions and best practices for dealing with some of these things that took awhile to emerge. So you should be sure to talk to people who have experience using them while you're learning.
> I'm pretty sure this is indeed largely caused by your Python bias. In my experience, Haskell code does not end up littered with Maybes.
I think the Maybes and other optional/sum types tend to be limited to the boundaries, where you do IO or handle errors, while your internal functions can deal with the concrete type. A code base littered with Maybes is certainly a code smell.
> A code base littered with Maybes is certainly a code smell.
It's not because Maybe (and usually more practical Either) have functor instances. What this means is you can use fmap to apply pure functions to Maybes, Eithers, or any other type that has a Functor instance.
E.g. you'll still need to write a test for your haskell code to determine that the behavior is correct if the IP address is missing, and in your python code if you don't specify behavior it is the kind of thing that will likely lead to one of those nasty "cannot call a method on none" errors.
Since the act of testing which is necessary anyway catches it or confirms correct behavior, the additional benefit of a static type checker confirming the presence of such a bug is perhaps lower than it might appear at first glance.
This is clearly not true of weakly typed languages where you have to write a myriad number of extra tests just to achieve the same confidence in your code to cover all the unexpected use cases caused by weird type conversions you didn't even realize you were doing.