I agree, but I think it is the reality. Software development is a job, not divine appointment. And assuming it is reality, I think the best short term gain those developers isn't doubling down on type systems.
> It also strikes me as strange that people can't be bothered to use the type system provided by the language, but they can be bothered to maintain and add unit tests for their programs. Correctness enforced by types is fused with the code; if you get the types right, you should be all set. There's no additional maintenance beyond making sure that the code compiles.
I'm totally with you. I remember a friend being so impressed when a hypothesis test caught a bug in an example that I set up. The funny thing was that I made the example from a function over an F# Union. A lot of people seem to get super irritated by compiler errors but seem to be really impressed by clever testing. I think it has to do with the error messages. Property based testing frameworks tend to show you the simplest concrete example of your error. Type checkers tend to show you the most abstract example of your error. Maybe if type checkers started to to incorporate more concrete examples of error, people would generally find them more appealing.
I think this is a really important insight! And in fact, there is some recent research about making type checkers produce counterexamples: http://lambda-the-ultimate.org/node/5355. Ergonomics of type systems is an unfortunately understudied area.
Also, there are cases where using strings is more appropriate than using enums. Where passing a blob is preferred over guaranteeing the correctness of the blob's structure. Where "null" really is better than an Option type.
And conversely, there are companies who hire crappy developers or don't give their good developers enough time to learn the idioms and best practices of their tools.
In my experience, the opposite is the case. If I can offload some checks to my tooling, I don't have to be constantly performing them mentally. And with type inference at a repl, I can ask about the shape of things that can't possibly run yet (though I don't think I get that with C#).
WAT? Give me an example.
Theoretically it's not necessary, but given real-life constraints, there are most definitely times when null is more appropriate than e.g. Java's Optional.
My point was not specifically aimed at typed nulls and compiler optimization, it was that there will always be practical constraints that force us to compromise static guarantees in order to get things done in a reasonable amount of time. As software engineers, our problems are first practical before they are theoretical.
Error handling with null is wrong and broken, regardless of whether the null is "typed" or not.
null might have some very limited other uses, but they will never compete with those structures designed for error handling, because they do something fundamentally different.
Java is the platform with NPEs and null checks everywhere. If that's your definition of practical, sure, go ahead.
someOptional.map(a).map(b) NOT EQUAL TO someOptional.map(a.andThen(b)Because of the order in which side effects occur, or is there something else as well? (I haven't Javaed in anger since before Optional).
scala> val str: Function[String, String] = s => if (s.length > 3) s else null
str: java.util.function.Function[String,String] = $$Lambda$1369/859456754@2c668c2a
scala> val num: Function[String, Integer] = s => if (s == null) -1 else s.length
num: java.util.function.Function[String,Integer] = $$Lambda$1376/1887041776@121cf6f4
With Optional, you receive different results depending on whether you call map twice, or combine the functions first: scala> Optional.of("Foo").map[String](str).map[Integer](num)
res12: java.util.Optional[Integer] = Optional.empty
scala> Optional.of("Foo").map[Integer](str.andThen(num))
res15: java.util.Optional[Integer] = Optional[-1]
This is incorrect.
(They decided to disallow null as Optional's value for whatever insane reason.)It also leads to all sort of weird usability issues: The old "`someMap.get(...)` returned null ... now does that mean the entry doesn't exist, or the entry exists and it is null?"
In fact they are currently working on special hacks to Java 10's Generics, because they realized that important collection classes don't work well with specialization, and even their new Optional can't save them. (Not directly related, but a good example how design mistakes combine and cause even more pain down the road.)
This sounds like C# as written by people that don't really know or like C#. If you're stuffing everything into strings or a dynamic, you missed the point of the type system in a very serious way.
> In addition to this failure to use the type systems, testing is becoming a much more rich environment. I think popular resources on effective testing are much better and numerous than popular resources on effective typing. There are great books and tools around helping developers test their software more thoroughly and efficiently. Comparing these testing resources with typing resources, the closest popular book I can think of for typing is Design Patterns, and I don't think most popular type systems are built with the program correctness as a top priority.
There is some overlap between problems that can solved by type systems and test suites, but there are many problems that can only be caught by one or the other.
It also strikes me as strange that people can't be bothered to use the type system provided by the language, but they can be bothered to maintain and add unit tests for their programs. Correctness enforced by types is fused with the code; if you get the types right, you should be all set. There's no additional maintenance beyond making sure that the code compiles.