The former encompasses situations like contract development, signal processing, control & embedded systems, compilers, maintenance programming, etc. I've found that strictly typed languages like Haskell, Ada, or Rust can be very helpful here, even for exploratory programming. In this situation, you know (and have little control over) the data that goes into your program and the requirements that it must meet, and if it doesn't do so, it's because you made a mistake somewhere.
But many types of exploratory programming involve unknown inputs or unknown outputs. Think of data mining or scientific computing, where the issue is often that you have unclean data and your task is to figure out how it's messed up and how to fix it as quickly as possible. Or UI software (both web and mobile) and tech entrepreneurship, where the big unknown is often how the user will react to the software and where they will find value in it. I've found that dynamic languages are great for this, because the whole point is you don't know what the data will look like, let alone how it will be structured.
It was interesting to me that the two alternatives you mentioned were "write lots of little tests" or "let the type system guide what needs to be fixed". For a lot of the exploratory programming I've been doing lately, the most efficient approach is just "let it break". Go make your changes, and if some part of the system doesn't work anymore, just take note of that. Because the act of letting things break gives you important information. If you don't care, then obviously that feature wasn't very important to the system to begin with, and you should rip it out entirely. If you do care, you should fix it, and then write some tests to make sure it doesn't break again. This way, you can build a rough rank ordering of how important features are, so that when you're faced with engineering trade-offs (and you always are), you have a solid, experience-based intuition about what you really need to support and what you can let slide.
With all that said, I like your distinction at the beginning of your comment a lot. A lot of my recent work has focused very much on a problem domain where the input data is very well understood, but the transformations applied to that data are complex and not all that well understood. I've found that Haskell has worked extremely well for me in this case, but I think you make a compelling point that it may not work quite so well in cases where the input data is the unknown, but the transformations are fairly well understood already. Perhaps this is why languages like Python have dominated the machine learning space, and strictly-typed languages just never really took off there.
I'm not arguing universally against what you're proposing by any means, but it doesn't seem like a clearly superior approach to me. The bottom line is that "let it break" takes many forms. There's your version, there's my type coherence version, there's Erlang's version with supervisor trees, etc. I think it's hard to argue that any of these is universally superior to the others.
This is far from the whole software industry, but it's a segment that tends to be of disproportionate interest to HN readers, given the startup-focused origins of this site.
This works fine in Haskell, thanks in part to laziness. There needn't be a tradeoff here.
In a strict language, a type error would mean the bricked off expression that doesn't type-check would spread like an infection to any expression that incorporated it.
In Haskell, I can dance around sub-expressions that don't type-check at runtime and evaluate things optionally.
The technique doesn't work in practice otherwise.
I don't see why. Functions in a strict language are evaluated lazily. Any type error within a function wouldn't spread beyond that function.
Even strict-by-default languages that let you easily make values explicitly lazy would mean type level churn, if not term level.
The value part is important because it means you can't really talk about partially implemented hypothetical results where you know how to handle one branch of a function, but not another. This is a super common divide-and-conquer technique in Haskell.
Could possibly half-ass it if you have some kind of panic functionality, but it'll fail unconditionally in a strict language when the result of the function is forced. In Haskell, if you don't use the bottom, it doesn't blow up.
A sufficiently expressive type system will guide you and hold your hand over the course of the refactoring. It will determine for you the precise cascading effect of any code changes you make. And if you forgot to write a unit test to handle some edge case, the chances are pretty decent that the type system will detect it for you anyway.
In terms of writing a new piece of software whose design you haven't fleshed out yet, my approach in a language like Haskell is in fact very experimental, but quite different from something like Python. Rather than writing lots of little tests and trying to get bare-bones functionality implemented passing those tests, I often completely ignore tests and let the type system guide me. I try to flesh out the types that my program will need at a high level and just go from there. I call it "type-driven development", and it's extremely useful for early-stage exploratory programming, because it lets you play around with the structure of the program without worrying about any details. This is only possible in a language those type system is powerful enough to actually encode nontrivial aspects of a program's high-level structure.
I make sure at every point that I have a codebase that passes the type checker. It may not be interesting to actually run it at an early stage, but in many ways that actually makes exploratory problem solving even easier.
In many cases, this approach will reveal architectural issues early on that you may not have realized existed until you'd already written thousands of lines of code in something like Python. The real point is that either way you will have to face those issues if you want to end up with a working piece of software. But it's a lot easier to face them early on, when little code depends on them, rather than later on.