←back to thread

1087 points smartmic | 1 comments | | HN request time: 0.239s | source
Show context
1-more ◴[] No.44310959[source]
This concept is really interesting when you think about statically typed, pure functional languages. I like working in them because I'm too pretty and stupid to think about side effects in every function. My hair is too shiny and my muscles are too large to have to deal with "what if this input is null?" everywhere. Can't do it. Need to wrap that bad boy up in a Maybe or some such and have the computer tell me what I'm forgetting to handle with a red squiggly.
replies(1): >>44311458 #
9rx ◴[] No.44311458[source]
Formal proof languages are pretty neat, but boy are they tedious to use in practice. It is unsurprising that effectively no code is written in them. How do you overcome that?

Where the type system isn't that expressive, you still have to fall back to testing to fill in the places where the type system isn't sufficient, and your tests are also going to 'deal with "what if this input is null?" everywhere' cases and whatnot by virtue of execution constraints anyway.

replies(1): >>44311689 #
1-more ◴[] No.44311689[source]
I'm just talking null-less FP languages such as Haskell and Elm, not a full proof system such Lean and Agda or a formal specification language such as TLA+.

I'm not sure I agree with your prior that "your tests are also going to 'deal with "what if this input is null?" everywhere' cases and whatnot." Invalid input is at the very edge of the program where it goes through a parser. If I parse a string value into a type with no room for null, that eliminates a whole class of errors throughout the program. I do need to test that my parser does what I assume it will, sure. But once I have a type that cannot represent illegal data, I can focus my testing away from playing defense on every function.

replies(1): >>44311742 #
9rx ◴[] No.44311742[source]
Assume that there was room for null. What is your concrete example of where null becomes a problem in production, but goes unnoticed by your tests?
replies(3): >>44312349 #>>44313810 #>>44314847 #
1-more ◴[] No.44312349[source]
In a world where I am writing a language with null and have half-implemented a grown up type system by checking for null everywhere and writing tests that try to call functions with null (EDIT: and I remembered to do all of that), I guess we could say that I'm at the same place I am right now. But right now I don't have to write defensive tests that include null.

You're asking about a circumstance that's just very very different from the one I'm in.

replies(1): >>44312565 #
9rx ◴[] No.44312565[source]
> But right now I don't have to write defensive tests that include null.

You seem to misunderstand. The question was centred around the fact that unexpected null cases end up being tested by virtue of you covering normal test cases due to the constraints on execution. Explicitly testing for null is something else. Something else I suggest unnecessary — at least where null is not actually part of the contract, which for the purposes of our discussion is the case. Again, we're specifically talking about the testing that is necessary to the extent of covering what is missing in the type system when you don't have a formal proof language in hand.

> You're asking about a circumstance that's just very very different from the one I'm in.

But one you've clearly had trouble with in the past given your claims that you weren't able to deal with it. Otherwise, how would you know?

Perhaps I can phrase my request in another way: If null isn't expected in your codebase, and your testing over where the type system is lacking has covered your bases to know that the behaviour is as documented, where are the errant nulls going to magically appear from?

replies(1): >>44312613 #
1-more ◴[] No.44312613[source]
Oh the errant nulls only ever came up when I was writing dynamically typed languages. Is that what you're asking about?
replies(1): >>44314034 #
9rx ◴[] No.44314034[source]
Not specifically, but if that is where your example comes from, that’s fine. It’s all the same. What have you got?
replies(1): >>44315202 #
1-more ◴[] No.44315202[source]
On a PHP project I ran into cases where I changed the shape of an object (associative array) I was passing around and forgot about one of the places I was using it. Didn’t turn into a production bug but still was the kind of thing I would rather be a squiggly line rather than remembering to rerun all the paths through the code. Didn’t help that we were testing by hand.

Same thing on the front end in JS: change the shape of some record that has more places using it than I could remember. Better tests would have caught these. A compiler would be even better.

FWIW I’ve written a lot of tests of code written in all of the languages I like. You absolutely need tests, you just don’t need them to be as paranoid when writing them.

replies(1): >>44315644 #
9rx ◴[] No.44315644[source]
> A compiler would be even better.

Right, but the condition here is that the languages that are expressive enough to negate the need for testing are, shall we say, unusable. In the real world people are going to be using, at best, languages with gimped type systems that still require testing to fill in the gaps.

Given that, we're trying to understand your rejection of the premise that the tests you will write to fill in those gaps will also catch things like null exceptions in the due course of execution. It remains unclear where you think these errant nulls are magically coming from.

I'm not convinced "Didn’t help that we were testing by hand.", "Better tests would have caught these." is that rejection. Those assertions, while no doubt applicable to your circumstances, is kind of like saying that static type systems don't help either because you can write something like this:

    data MaybeString = Null | Str String
    len :: MaybeString -> Int
    len (Str s) = length s

    main :: IO ()
    main = do
        print (len Null) -- exception thrown
But just because you can doesn't mean you should. There is a necessary assumption here that you know what you are doing and aren't tossing complete garbage at the screen. With that assumption in force, it remains uncertain how these null cases are manifesting even if we assume a compiler that cannot determine null exception cases. What is a concrete example that we can run with to better understand your rejection?
replies(1): >>44320313 #
1-more ◴[] No.44320313[source]
In the example you gave you have an incomplete implementation of len. We had either a language extension or a compiler flag to disallow incomplete implementations in Haskell (pretty sure it's the flag -Werror), and Elm has no way of allowing them in the first place. I should have specified that that was the case, because "Haskell" is a rather broad term since you can turn on/off language extensions on a per file basis as well as at the project level.

To head off (hah) discussion of taking the head of [], we used a prelude where head returned a Maybe. As far as I know, there were no incomplete functions in the prelude. https://hackage.haskell.org/package/nri-prelude

replies(1): >>44320615 #
1. 9rx ◴[] No.44320615[source]
> We had either a language extension or a compiler flag to disallow incomplete implementations in Haskell

"Better flag choices would have caught it" is a poor take given what came before. Of course that's true, but the same as your "better tests would have caught it". However, that really has nothing to do with our discussion.

Again, the premise here is that you are writing tests to close the gaps where the type system is insufficient to describe the full program. Of course you are as the astute observation of "My hair is too shiny and my muscles are too large to have to deal with [error prone things] everywhere. Can't do it." is true and valid. Null checks alone, or even all type checks alone, are not sufficient to satisfy all cases of [error prone things]. That is at least outside of formal proof languages, but we already established that we aren't talking about those.

So... Given the tests you are writing to fill in those gaps (again, not tests specifically looking for null pointer cases, but the normal tests you are writing), how would null pointer cases slip through, even if the compiler didn't notice? What is the concrete example that demonstrates how that is possible?

Because frankly I have no idea how it could be possible and I am starting to think your rejection was just made up on the spot and thrown out there without you giving any thought, or perhaps reiterating some made up nonsense you read elsewhere without considering if it were valid? It is seemingly telling that every attempt to dismiss that idea has devolved into bizarre statements along the lines of "well, if you don't write tests then null pointer exceptions might make it into production" even though it is clear that's not what we are talking about.