> It doesn't take "elaborate types" - just the question of "what interfaces does this need to support?"
I suppose what counts as "elaborate" is a matter of opinion, but Haskell types in everyday code often become very involved and intricate. Here's an example from a piece of code I wrote a while back:
bindingsToSet :: Monad ctx =>
LEnvironment ctx ->
[Binding NExpr] ->
LazyValue ctx
bindingsToSet env bindings = do
let start = InProgress mempty
finish <- flip execStateT start $ forM_ bindings $ \case
NamedVar keys expr -> do
-- Convert the key expressions to a list of text.
keyPath <- lift $ mapM (evalKeyName env) keys
-- Insert the key into the in-progress set.
let lval = evalNExpr env expr
get >>= lift . insertKeyPath keyPath lval >>= put
Inherit maybeExpr keyNames -> forM_ keyNames $ \keyName -> do
-- Evaluate the keyName to a string.
varName <- lift $ evalKeyName env keyName
-- Create the lazy value.
let lval = evalNExpr env $ case maybeExpr of
Nothing -> mkSym varName
Just expr -> mkDot expr varName
-- Insert the keyname into the state.
get >>= lift . insertKeyPath [varName] lval >>= put
-- Convert the finished in-progress object into a LazyValue.
convertIP finish
Inside of this, we have a rather complex interplay between monad transformers, monadic bind, state, etc. Of course to some Haskell devs this might be no big deal to write, but for me, there's no way I could get all of that to be correct without a type checker. And to that end the Haskell type system and GHCi are an invaluable resource. However, I could very easily write equivalent code in python without a great deal of effort, and all of that crazy juggling of monad transformers would turn into very straightforward for loops and mutable assignment. Which isn't to argue that python is a superior solution to the problem, but...
> But in order for pdb to work, I have to have already written the code. It's inherently "push based", whereas in GHCi (or with typed-holes) I can "pull".
I disagree. In python you can start with a pdb that only gets so far, and figure out what y0u need to write based on inspecting the objects you have available in scope, testing out some functions on them, etc. In GHCi you can get help on what object is required to satisfy a type equation, but only after you first provide it with enough information that it has a meaningfully small set of free variables to solve. This of course doesn't mean that type holes are not super useful, but I would argue that their usefulness is in part due to how complicated Haskell's type system is. Of course, by the same token features like type holes turn that complexity into a strength. The functionality is incredibly useful in Haskell, no argument there -- but I don't find myself needing or missing it in python.