For languages with a reasonably sophisticated IDE, use the IDE itself to jump straight to the class/function declarations to understand the surface area of the API, and jump to the decompiled code for the implementation of those functions if you're the type to want to understand the machinery.
Other languages (like Haskell) may not have the IDE support but attempt to solve the problem with auto-generated docs so you can at least see the surface area of the API and can start prodding it.
~15 years experience across C, ARM assembler, Javascript, Lua, Ruby, Python, Erlang, Go, and Rust. Mostly doing Elixir these days.
Admittedly, I've never used an IDE; I write all my C in vim.
> ...you write compiled unit tests and/or test programs to poke around at the API.
That assumes you can get your tests to compile; or that the compilation errors are at-all helpful when they don't. If you've got a library that
1. ships as a binary blob + header files, and
2. most of its functions take handles to structs that are declared opaquely in the SDK headers; and yet
3. nothing you do with the badly-documented "create handle" parts of the API is giving you a struct that the "consume handle" parts of API will do anything with but crash...
...then you'll really wish that you had a REPL; or rather, a language runtime that enforced some level of introspect-ability on everything, so that a REPL could show you what's in the struct and whether your calls so far have failed to touch some obviously-necessary-to-fill-in part of it.
Without such support, you will have to rely on reading the decompilation results of both ends of the API to figure out what's really going on.
And now we get to the real meat of it.
It's not a REPL you want.
It's reflection.
It just so happens that the latter is frequently paired with the former.
And I agree, that feature, which separates C#, Java, Ruby, Perl, Python, etc, from legacy languages like C make them significantly easier to use as a developer as the compiler, debugger, and IDE can do a much better job.
But as a feature it's orthogonal to a REPL.
But when you are working in a language with runtime code generation—one where objects can build themselves new runtime-native function handles in response to messages—then a REPL will be able to do things for you that an IDE cannot†.
A REPL in a dynamic language (that takes advantage of its dynamicity) can be used to "explore" down CORBA/DBUS object trees; or fluently walk REST or SOAP endpoints without needing pre-baked WSDL descriptors to guide you to them. Heck, an Erlang REPL can hot-upgrade remote nodes. :)
† Or, at least, not a traditional IDE. You would need an IDE connected to a "live session" of your project—essentially a hybrid IDE-REPL. (Examples: Light Table; Emacs' CIDER extension for Clojure.)
Two dynamic and one static languages.
Yes. I write roughly equal amounts of C (microcontroller firmware) and Python (overall system control, running on embedded Linux).
I absolutely wish I had a REPL for C. When implementing with a new library in Python, I'll usually use iPython to explore it, and if I need to run a decent amount of data through, I'll use a Jupyter notebook. I'd put the difficulty of grokking a C library somewhere between 10x and 100x harder.
They were quite common advertising in The C User's Journal and Dr. Dobbs.
(Still, doesn't fit all use cases of a REPL for this. REPLs are especially nice for when you want to fit together highly generic APIs)
And if you are writing Coq or Agda—well, you certainly don't need a REPL at that point. The code writes itself. ;)
The true key to understanding REPLs is their role in consuming third-party libraries. While languages both with and without REPLs will have well-used packages in their package ecosystems that are well-documented; languages with REPLs will also manage to have well-used, poorly-documented packages: packages whose "documentation" consists only of the de-facto ability to reflect on the API in a REPL, doing the moral equivalent of exploring a filesystem using cd(1) and cat(1), until you figure out what functions—and what arguments—combine to produce an acceptable result for your use-case.
That may sound like a bad thing, but it's not! Those packages are packages that wouldn't exist otherwise; they're marginal packages, packages which people wouldn't have had time to write if they were required to also document them properly enough to make use of them without a REPL. They're needs being satisfied, that would otherwise—given the stricter requirement of useful API documentation—be going un-met.
If you look at e.g. Ruby's gem ecosystem, there are tons of packages that have more than a million downloads, and either no docs or awful docs. How? Because Ruby is extremely amenable to exploring APIs through a REPL. You take a module or class, ask it what its children are, what its class methods are, what its instance methods are. Find a class with instance methods that sound right, attempt to instantiate it. Figure out you can't do that, look around the class methods for something that sounds like a factory method. What arguments does said factory-function take? Well, feed it some at random and see what error you get. Etc.