> If you present a picture of a collection of data, a function that maps input values to outputs, and a picture of the output collection of data, that's fairly easy to grasp. The difficulty comes from bothering to describe how the computer does it by running a first-class function on the data
I think your describing the solution to the problem: this is how (pure) functions are introduced in high school or university, and that's also what they fundamentally are defined like!
Of course no function in a computer program is really a black-box, but introducing
f: A -> B
as a black-box gets you far enough for all practical purposes! Defining the properties of a mathematical function and the related categories of injective, surjective and bijective maps between sets is all one needs to know about a (pure) function.
And when it comes to e.g. f(x)=sin(x), how many programmers or high school students could write down an algorithm to calculate e.g. sin(1/4) out of their head?
I know it's a long-solved basic algebra question, but treating functions as black-boxes can be perfectly fine.
When it comes to programming, of course, shared mutable state (i.e. lexical scoping) and how actual functions in some language relate to these concepts is the meat of the problem.
But even this space can be explored without knowing complex FP concepts. And JS is a great language to do so :)
Any JS programmer learns about the difference between a pure function and an impure "function" in some intuitive fashion sooner or later, because lexical scoping is so much at the heart of the language.
So much that day-to-day work can even include a fair amount of shifting around logic between shared mutable state, function parameters and captured closures.