Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Glad to see the cave-man like debugging is brought up.

Haskell stops many classes of bugs, but lord help you if you've made a complex logical mistake somewhere along the line and are trying to figure out where you went wrong. It's high school printf debugging all over again except you can't even reason about order of execution due to lazy evaluation.

I'm reasonably proficient in a number of languages, including Python, JS, C, C++, Prolog, Java, I even have a decent amount of Forth under my belt. Nowhere else have I experienced the complete dearth of tooling that is the Haskell ecosystem. The LSP is a step in the right direction but merely a first step.



It’s especially bad given how much more tooling is in-principle-doable in haskell compared to other languages. In python, you can’t really reason about shit because of all the black magic you can incant. But tooling does a really solid job at getting really close. In haskell, you can absolutely reason about nearly every tool you could dream up, but none of them have been built into something really useable.


Actually I think debugging is fundamentally much harder in a lazy language. A line by line debugger would be nice to see but it would jump around all the time when it's evaluating stuff which I don't think would make for a very nice debugging experience.


In pycharm, when using generators and yield and that kind of thing, you get exactly the experience you’re describing. It actually makes for a totally fine debugging experience.


Note that the repl (ghci) has had a time-travelling debugger since ~forever https://donsbot.com/2007/11/14/no-more-exceptions-debugging-... What's missing is the same for your compiled binaries, with UI for inspection etc.

Maybe https://hackage.haskell.org/package/haskell-debug-adapter will some day be non-experimental.


> It's high school printf debugging all over again

If your C++ application is even semi complex or uses Qt you are basically in printf land again. I mean sure go ahead use the debugger for stacktraces and such but anything beyond that is finicky.


In 10 years of C++ I have never dropped back to pouring over logs once I have an idea of where the problem is. Drop a code breakpoint, inspect the stack, find the variable that ain't what it's supposed to be. The next step is to watch every change to that variable over its lifetime and figure out what went wrong.

The dumb way to do this is to drop printfs in the code on every state change of that variable. The tooling way is a data breakpoint. If you have a decent test suite for the codebase that can replicate the fault, the data breakpoint is IMHO always faster.

If you can't replicate the fault, you're going to need logging. So logging isn't obsolete in all cases, but with Haskell (at least in my experience) the excessive logging approach is your only option really.


With time travel debugging, data breakpoints become even more useful, because you can backtrack to the root cause once you've recorded the fault (rather than needing to run forwards and hope to replicate a corruption at the same location as last time).

The GHCi Debugger (https://downloads.haskell.org/~ghc/7.8.3/docs/html/users_gui...) mentioned in another comment (https://news.ycombinator.com/item?id=31911462) can time travel but I don't see anything like a data breakpoint.

I'm not quite sure what a meaningful functional equivalent would be given, semantically, no state is actually mutable in Haskell - but "where did this value come from?" still ought to be useful.


> If your C++ application is even semi complex or uses Qt you are basically in printf land again. I mean sure go ahead use the debugger for stacktraces and such but anything beyond that is finicky.

UDB, the time travel debugger I work on, supports C++ via GDB and there seem to be certain things that make life really awkward.

One of my team has been looking into the combination of GCC's debug info and and GDB's behaviour around inline frames. There are a number of quirky things we see there, which boil down to "inline functions make the debug experience go wild".

My understanding is that GCC isn't providing as helpful debug info as we'd like but GDB also isn't handling it in the best way. There are corner cases that can be very confusing from a user's PoV.

Even in C, this is quite visible. In C++, where inlining matters even more, we'd expect it to be exaggerated further.

I'd be really interested to know what things you find finicky (and are you even Linux / GCC / GDB, or is this a wider problem?)


I think you are exaggerating for some reason.

You said you have decent amount of Forth and Haskell for some reason not even compare to Forth.

The very fact that you can have typed Forth in Haskell shows that type system is a very good tool, much better than what you get nowhere else.

I managed to learn and achieve more from using Haskell than almost any other language, per effort spent. This is mainly due to tooling and libraries which I find the top notch.

And, of course, I am also reasonably proficient in different languages, including Python, JS, C, C++, C#, OCaml, Java, Prolog, a dozen of assemblers and couple of hardware description/design languages.


I have had some success stuffing a ReaderT [String] in my monad stack to create traces with a custom flow using:

    local ("a tag for the code block":) $ BLOCK.
The idea is that when a mistake is detected you read the trace and throw that along with whatever error information is available. It is a lot better than printing stuff, because it is independent of evaluation order, and follows the structure of your monadic code.

It is quite easy to set up and use. It is really just two functions and an extra level in the monad stack.


Well, Leksah used to be a good experience in regards to debugging.

https://github.com/leksah/leksah


… and rampantly introducing the IO monad only to remove it once the culprit is found.


Why not use Debug.Trace?


Absolutely second this.

Debug.Trace can use event log of RTS which is so cheap it is hard to believe.


On rare occasion you actually need to introduce additional sequencing to work out what's going on. In that case, you might well want to keep it after the fact, though, since your fix might depend on that sequencing.

But yes, most of the time that you need something like printf debugging, Debug.Trace works great.


If you need sequencing you can use `seq` or even `deepseq`


I think you can enforce eager evaluation in Haskell.


But then you can’t take advantage of the library ecosystem that assumes lazy eval.


You can enforce it per module, per function, per datatype or even just at a single site. Enforcing it wholesale is using a nuclear bomb when you need a scalpel.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: