Debugging in a strict FP language is pretty much exactly like debugging in an imperative language. Debugging in a lazy FP language requires somewhat different approaches, but still does not have the problems you are implying here.
> In the end, the result is wrong, good luck finding where.
This is not true. If this was true, nobody would be able to build significant things in FP languages.
> Optimization aside, what you code is what the computer runs.
> But then you end up with something that doesn't look at all like what your wrote.
No, you can trace code down through the compiler transformations and generally understand what is going on at each level of abstraction.
Compilers for imperative languages are just as "magical".
If you want an FP debugger the interface has to be able to step by step evaluate an expression. Since FP is just a giant expression, how is it evaluated step by step? Break points will be different and stepping through it will be different as well.
Take for example:
(1 + 2) * 3 + 2
Stepping through it with a debugger would look like this (imagine the expression transforming at each step): (1 + 2) * 3 + 2
(3) * 3 + 2
9 + 2
11
A break point would look like this (square brackets indicate a GUI marker) (1 + 2) + [(4 * 6)] + 1
A run until breakpoint will evaluate to this: (3) + [(4 * 6)] + 1
That's it. Debugging FP is about stepping through expressions. Debugging Imperative programming is about stepping through statements. And the unique thing about "stepping" through an expression is that the expression is getting reduced (aka changing) at every step.You need a complete UI overhaul for FP to work. This tends to be harder in terms of building a UI, because text by nature is a one to one mapping to procedural instructions as text Lines are equivalent to instructions. However for FP everything can basically be on one line, so text doesn't really fit and thus you actually need a more dynamic UI for a proper FP debugger.
I think you're doing something wrong.
Because your experience should be exactly the opposite. Imperative programming by distributing its state can only really be understood in context. You need to understand that instruction one after another, just like you said. You need to understand globals and the overall state.
In functional programming you understand code without context. Everything is local. There's nothing to trace.
> Then you let the compiler do its magic, it is actually really good at that and not slow. But then you end up with something that doesn't look at all like what your wrote (computers are imperative), and if, in the end, the result is wrong, good luck finding where
What does the compiler have to do with anything here?
You can print out whatever state you want, your debugger can provide you with whatever local state you want. There's nothing obscure or mysterious about functional code compared to imperative code. It's just a matter of not distributing your state widely.
The problem is people don't trust it's really local even though it is.
Pure functions are easy to debug. Run it, check the result against expectations. Step in if necessary.
I'm tired of seeing this analogy everywhere. Everyone knows it, there's nothing new here. When we talk about things like FP or OOP, the real opinions live at the extremes. Where one tool is definitively better than another tool or where some tools are complete garbage.
Saying that everything is a tool for different things or it's all apples and oranges says nothing about anything.
On imperative code, execution is easy to follow. Instructions go one after the others, loops loop, function calls and local variables go on the stack, globals are always visible. Optimization aside, what you code is what the computer runs. You often have a debugger to step through your code, or some logging facilities, even if it is just a printf.
On functional programs, the idea is usually that you manipulate stuff, but you don't know what you manipulate. You apply a functions to functions making other functions until you end up with the function that turns the input of your program into the output of your program. Then you let the compiler do its magic, it is actually really good at that and not slow. But then you end up with something that doesn't look at all like what your wrote (computers are imperative), and if, in the end, the result is wrong, good luck finding where. The debugger will give you the state of the program in a program that has no state, and logging is a side effect in a program without side effects.
Functional programming is not bad, I love pure functions, but in the end, I think it is just a tool. A solution to a set of problems, but not enough to stand by its own for most projects. And it is evident from most modern programming languages. Most of them have some functional paradigms, and could likely be used to write pure functional code, but is better suited as something to use just when you need it.