"A good set of data structures, interfaces, and functions" is great at the design stage. It's also great for implementing an elegant reference implementation (i.e. one that is meant more for documentation than use — one that you'd describe in a journal paper to communicate the algorithms and data structures involved, for example.)
But I bring up choice of (programming) language as an issue, because in many languages, once you need to operationalize your codebase — make it efficient and scalable, optimizing it with respect to real use in the field, then you'll begin to need things like:
• static data in user-defined complex data structures (incl. ones like graphs with cycles) burned into the program at compile time
• memoization / dynamic programming caches to accelerate costly computations of otherwise-pure functions, esp. for computations done to instances of data structures but where the memoization only helps if done globally across all data structures
• ADTs that are actually implemented in terms of multiple different underlying data structures depending on the properties of the data itself; where, upon mutation of the data, the implementation — and thus the representation of the data — can be swapped out transparently (think: lists in CPython; or Integers vs BigIntegers in Ruby; or most data structures in Redis with their "ziplist" variants)
...and so forth; then often the language will start actively fighting you, as the language has been designed to do everything at runtime; and especially has no consideration for mixing compile-time, runtime-memoized, and runtime-pure sourced data using the same set of algorithms. So you have to write two or three different implementations of the same code, and store your static data in special ways, and mess up your ADTs with indirections, and your data structure constructors with heuristics, and define global caches where much of your data actually lives, turning your concrete modules inside out and making it very hard to reason about them... and so on.
This all gets fixed if you define a DSL. You're basically turning all of the above operationalized logic, into just an opaque "platform" runtime. But you still do have to maintain that "platform."
But some programming languages are already essentially such an operationalized "platform" runtime! Though, such languages are almost always only operationalized for specific use-cases. They're not quite domain-specific languages (insofar as a domain is a vertical); but they are always extremely "opinionated" languages.