Lots of resources bill themselves as "how things work" repositories, and spell out the principles of internal combustion engines, cathode ray tubes, dry cell batteries, ball bearings, extraction of polymers from crude oil, wrist watches and so on. Both artifacts and industrial processes may be covered.
One approach to early math learning is to adopt a similar approach, or call it a "what makes them tick?" strategy. Our goal is to investigate the inner workings of various "math gizmos" (devices), to take them apart in the mind's eye, to see how they work and go together.
To this end, I would advocate building a rational number much as one builds a radio in shop class. Wire it up and test it. Do lots of little tests as you go, checking out new features, making sure they work. For example, multiplying two rationals is a pretty simple process: a/b * c/d = ac/bd. Nothing much to it. Addition, on the other hand, requires the LCM, which might in turn mean implementing a GCD algorithm ala Euclid's. Now we're cookin'.
Of course it sounds ridiculous to conventionally-trained teachers to suggest "building a rational number like we build a radio". What on earth can that mean? Which is where the computer language enters in: we build a rational number class in object-oriented code, and then interactively play with our new rational number objects (fractions), each an instance of our class.
So we're back to my big theme these days. I'm beating the drums for phasing "dot notation" into mainstream math class. The utility of this notation is especially evident in linear algebra. A matrix is a pretty complicated piece of data, what with having rows and columns and all. On top of that, you get all these methods: row exchange, ops between two rows, transpose. And that's just the beginning. Then you have determinant and inversion. And not only do these gizmos play solo, they know how to play with each other, by adding, multiplying, even dividing in some cases (because inverse defines a multiplicative inverse).