4 ms·
Hi. I wrote that article. I've never seen a Java application that hasn't succumbed to an unexpected NullPointerException at least once over its entire develop
by implicit 13y ago
Hi. I wrote that article.
I've never seen a Java application that hasn't succumbed to an unexpected NullPointerException at least once over its entire development cycle. That doesn't mean that the language isn't a reasonable choice. It's just a common problem that you have to accept on that platform.
Space leaks in Haskell are similar. You're going to run into them every so often, and you'd really rather not, but they're easy enough to deal with.
- virtualwhys 13y agoI'd take a NullPointerException over a memory (edit: space) leak any day of the week; the former is instantly resolved, the latter, ?. Anyway, in Java 8 they've started taking steps to address the null problem with the new Optional type, and, FWIW, in Scala nulls are a more or less a non-issue when you use the FP side of the language.
- thirsteh 13y agoI think there may be some confusion about what a space leak in Haskell is. When you apply a function f to a, that is not actually evaluated. Rather, a "thunk" is created that will evaluate `f a` only when that value is actually needed. If, in your program, you never need anything, or don't "force" your function calls and data structures ("pretend" to need something) in intermediary stages, then the thunks may take up a non-trivial amount of memory. This is not a memory leak in the traditional sense, just temporarily increased memory usage. It is very easy to (pre-emptively) handle most space leaks in Haskell, but you do need to know how they arise. There are two very simply rules you can follow that take care of the vast majority of space leaks: 1. Make data fields strict unless you actually want them to be lazy, i.e. instead of: data Foo = Foo { bar :: String , baz :: Int } write data Foo = Foo { bar :: !String , baz :: !Int } 2. When you write recursive functions that depend on values which are not forced (e.g. pattern matched against) in each function call, use either `seq`/$! or bangpatterns to make sure the value is evaluated (to HNF) rather than building up excessive thunks. For example, instead of: acceptLoop :: Socket -> Int -> IO () acceptLoop sock connNum = do econn <- accept sock _ <- case econn of Left err -> printf "Error accepting connection %d: %s" connNum err Right conn -> forkIO $ runConn conn acceptLoop sock (connNum+1) write either acceptLoop :: Socket -> Int -> IO () acceptLoop sock !connNum = do econn <- accept sock _ <- case econn of Left err -> printf "Error accepting connection %d: %s" connNum err Right conn -> forkIO $ runConn conn acceptLoop sock (connNum+1) or acceptLoop :: Socket -> Int -> IO () acceptLoop sock connNum = do econn <- accept sock _ <- case econn of Left err -> printf "Error accepting connection %d: %s" connNum err Right conn -> forkIO $ runConn conn acceptLoop sock $! connNum+1 to make sure that connNum is always just a single value rather than a series of unevaluated thunks. That way you won't get a space leak if you rarely have problems accepting new connections.
- virtualwhys 13y agoThanks, helpful explanation. Now, that begs the question, why lazy by default and not opt-in lazy? From the outside looking in it seems that deep expertise is required in order to launch a Haskell production app with any degree of confidence (i.e. to quickly dig yourself out of runtime issues like space leaks where the means to avoid them may be known, but the means to resolve them when they occur, non-trivial).
- thirsteh 13y ago> Now, that begs the question, why lazy by default and not opt-in lazy? Very good question. Actually, I think most haskellers agree that laziness complicates things more often than not, and if we could start over we wouldn't make Haskell lazy by default. (Although that's not to say there won't be even simpler ways to "strictify" things in the future. Also, many libraries already provide functions that are strict in their arguments by default.) However, there is also agreement that Haskell's laziness is the reason the language got purity right: there was simply no other way, since laziness meant evaluation order was unclear. > From the outside looking in it seems that deep expertise is required in order to launch a Haskell production app with any degree of confidence (i.e. to quickly dig yourself out of runtime issues like space leaks where the means to avoid them may be known, but the means to resolve them when they occur, non-trivial). As someone who writes Haskell for a living, I really just follow a few rules like this without thinking too much about laziness, and I tend to not have any problems. I have had maybe one nasty space leak in the past five years. Yes, sometimes they do come up, but it takes ~5-10 minutes to pinpoint the problem spot with the heap profiler. It is not nearly as messy as using Valgrind to find actual memory leaks.
- codeflo 13y ago> I think most haskellers agree that laziness complicates things more often than not, and if we could start over we wouldn't make Haskell lazy by default. Following Haskell's evolution somewhat from the outside, this is surprising. (And also somewhat disappointing, as laziness always seemed an important part of Haskell's elegance.) Is laziness now considered something of a failed experiment?
- lkrubner 13y agoI am surprised that your comment was downvoted. I upvoted you. I feel that you are 100% correct when you write: "I'd take a NullPointerException over a memory (edit: space) leak any day of the week; the former is instantly resolved, the latter, god knows." If someone really feels that your remark should be downvoted, I hope they post an explanation about why.
- efnx 13y agoI think it was because - in the words of The Dude, "Yeah, well, you know, that's just, like, your opinion, man." > I'd take a NullPointerException over a memory (edit: space) leak any day of the week. That's your preference and I can understand that preference from your point of view because in order to debug a space leak in a Haskell program you would first have to learn Haskell. I'm not the one who downvoted, but I also didn't think it contributed much =(
- virtualwhys 13y agoSorry, it's not an opinion, it's a fact ;-) Why? NPEs are easily solved, even for beginners. Stack trace says blah blah blah occurred at line X in class Y. Easy fix, totally low hanging fruit for beginners and experts alike. The space leak issue, OTH, may be easily avoided (if one is well versed in Haskell best practices), but resolving them when they occur is something else entirely. Seriously, you have to break out a memory profiler in order to find out where the issue _may be_, not exactly where it is on line X of class Y. So, yes, I never get them (in Scala), but I'll stand by taking an NPE over a space leak any day of the week.
- thirsteh 13y agoThis is really quite silly since the choice between NullPointerExceptions and space leaks is a false one. You can have both in either language.
- efnx 13y agothirsteh summed up my feelings in his response to your comment but I'd also like to add that a preference is an opinion and not a fact. You may prefer apples over oranges. It is not a fact that apples are better than oranges. For instance, what if your null pointer in language x causes a silent fail? Now your apples are beginning to look like oranges.
- thirsteh 13y agoThe choice between NullPointerExceptions or occasional space leaks isn't really apples-to-apples. You could similarly ask if you'd rather have solely impure functions and no isolation of effects, or occasional space leaks. Clearly (probably?) the answer would be the latter.
- groovy2shoes 13y agoYeah, an apt comparison would be race conditions/spooky action at a distance vs. space/resource leaks. Pick your poison.
- mcv 13y agoIf you're going to have errors anyway (and you are), then it's better to have errors that fail fast and early and in a clearly identifiable way. NullPointerExceptions are easy.
- deleted 13y ago[deleted]