One of the issues DSLs give me is that the process of using them invariably obsoletes their utility. That is, the process of writing an implementation seems to be synonymous with the process of learning what DSL your problem really needs.
If you can manage to fluidly update your DSL design along the way, it might work, but in my experience the premature assumptions of initial designs end up getting baked in to so much code that it's really painful to migrate.
APL, on the other hand, I have found extremely amenable to updates and rewrites. I mean, even just psychologically, it feels way more sensible to rewrite a couple lines of code versus a couple hundred, and in practice, I find the language to be very amenable for quickly exploring a problem domain with code sketches.
That means your DSL is too specific. It should be targeted at the domain, not at the application.
But yes, it's very hard to make them general enough to be robust, but specific enough to be productive. It takes a really deep understanding of the domain, but even this is not enough.
Another way of putting it is that, in practice, we want the ability to easily iterate and find that perfect DSL, don't you think?
IMHO, one big source of technical debt is code relying on some faulty semantics. Maybe initial abstractions baked into the codebase were just not quite right, or maybe the target problem changed under our feet, or maybe the interaction of several independent API boundaries turned out to be messy.
What I was trying to get at above is that APL is pretty great for iteratively refining our knowledge of the target domain and producing working code at the same time. It's just that APL works best when reifying that language down into short APL expressions instead of English words.
Forth and Smalltalks are good for this. Self even more so. Hidden gems.
APL (or Clojure) way is about making your base types truly useful, 100 functions on 1 data structure instead of 10 on 10
If this is indeed this simple and this obvious, why didn't other languages followed this way?I think most people haven't experienced the whole "100 functions on 1 data structures instead of 10 on 10" thing themselves, so there is no attempts to bring this to other languages, as you're not aware of it to begin with.
Then the whole static typing hype (that is the current cycle) makes it kind of difficult because static typing kind of tries to force you into the opposite of "1 function you can only use for whatever type you specify in the parameters", although of course traits/interfaces/whatever-your-language-calls-it helps with this somewhat, even if it's still pretty static.
The entire point being to restrict what can be done in order to catch errors. The two things are fundamentally at odds.
Viewed in that way typed metaprogramming is an attempt to generalize those constraints to the extent possible without doing away with them.
I would actually expect array languages to play quite well with the latter. A sequence of transformations without a bunch of conditionals in the middle should generally have a predictable output type for a given input type. The primary issue I run into with numpy is the complexity of accounting for type conversions relative to the input type. When you start needing to account for a variable bit width things tend to spiral out of control.
I wouldn't say 100 functions over one data structure, but e.g. in python I prefer a few data structures like dictionary and array, with 10-30 top level functions that operate over those.
if your requirements are fixed, it's easy to go nuts and design all kinds of object hierarchies - but if your requirements change a lot, I find it much easier to stay close to the original structure of the data that lives in the many files, and operate on those structures.
The high regularity of APL operators, which work the same for all functions, force the developer to represent business logic in different parts of the data structure.
That was a good approach when it was created; but modern functional programming offers other tools. Creating pipelines from functors, monads, arrows... allow the programmer to move some of that business logic back into generic functions, retaining the generality and capacity of refactoring, without forcing to use the structure of data as meaningful. Modern PL design has built upon those early insights to provide new tools for the same goal.
(1) https://secwww.jhuapl.edu/techdigest/content/techdigest/pdf/...
It is! (https://github.com/cnlohr/rawdrawandroid) - and that's just writing a simple an android app in C. If you want to access the numerous APIs to do anything useful, it's more pain.
If you push this into every kind of application, you will end-up with people recreating objects with lists of lists, and having good reasons to do so.