The mainstream representation of number, the one that took the world by storm, is positional notation. It's a representation that's tied intimately with the concept of quotient and remainder operations, which you're seeing somewhat from your recursive function defintion. Why is that?
Let's take a quick aside: positional notation is not the only viable representation for number. One way that comes up every so often is a tallying approach, where a number is either zero or one more than a number. We can use sticks. Since we're talking about programs, let's use a data-type.
Number :== Zero
| Successor(n) where n is a number
Read this as "A number is either Zero, or the successor of another number". Or, to code it up in a Scheme that supports structured representations (like Racket), we can write this:
(define-struct Zero ())
(define-struct Successor (n))
For example, representing three with this notation would be (Successor (Successor (Successor (Zero)))
. (This representation is called Peano, if I'm remembering right.)
Functions that deal with this kind of structured datatype often have the same shape as that of the datatype itself. That is, a function that works on a representation in Peano will look something like this:
;; a peano-eating-function-template: peano-number -> ???
(define (a-peano-eating-function-template a-num)
(cond [(Zero? a-num)
...]
[(Successor? a-num)
...
(a-peano-eating-function-template (Successor-n a-num))
...]
where the ...
will be something specific to the particular problem you're trying to solve on Peano numbers. It's a matter of functions following the structure of the data that they're working on. As an concrete example of a Peano-eating function, here's one that turns a piano into a bunch of stars:
;; peano->stars: peano-number -> string
;; Turn a peano in a string of stars. We are all made of stars.
(define (peano->stars a-num)
(cond [(Zero? a-num)
""]
[(Successor? a-num)
(string-append "*"
(peano->stars (Successor-n a-num)))]))
Anyway, so datatypes lead naturally to functions with particular shapes. This leads us to going back to positional notation. Can we capture positional notation as a datatype?
It turns out that we can! Positional notation, such as decimal notation, can be described in a way similar to how the Peano number description worked. Let's call this representation Base10, where it looks like this:
Base10 :== Zero
| NonZero(q, r) where q is a Base10, and r is a digit.
Digit :== ZeroD | OneD | TwoD | ... | NineD
And if we want to get concrete in terms of programming in a language with structures,
(define-struct Zero ())
(define-struct NonZero(q r))
(define-struct ZeroD ())
(define-struct OneD ())
(define-struct TwoD ())
(define-struct ThreeD ())
(define-struct FourD ())
;; ...
For example, the number forty-two can be represented in Base10 as:
(NonZero (NonZero (Zero) (FourD)) (TwoD))
Yikes. That looks a bit... insane. But let's lean on this a little more. As before, functions that deal with Base10 will often have a shape that matches Base10's structure:
;; a-number-eating-function-template: Base10 -> ???
(define (a-number-eating-function-template a-num)
(cond
[(Zero? a-num)
...]
[(NonZero? a-num)
... (a-number-eating-function-template (NonZero-q a-num))
... (NonZero-r a-num)]))
That is, we can get the shape of a recursive function that works on Base10 pretty much for free, just by following the structure of Base10 itself.
... But this is a crazy way to deal with numbers, right? Well... remember that wacky representation for forty-two:
(NonZero (NonZero (Zero) (FourD)) (TwoD))
Here's another way to represent the same number.
((0 * 10 + 4) * 10 + 2)
Pretty much the same idea. Here, let's get rid of a few more parentheses. We can represent forty-two with the following notation:
42
Our programming languages are hardcoded to know how to deal with this notation for numbers.
What's our equivalent for checking Zero? We know that one.
(= n 0) ;; or (zero? n)
What's our equivalent for checking NonZero? Easy!
(> n 0)
What are our equivalents for NonZero-q and NonZero-r?
(quotient n 10)
(remainder n 10)
Then, we can pretty much plug-and-play to get the shape of recursive functions that deal positionally with their numeric inputs.
(define (a-decimal-eating-function-template n)
(cond [(= n 0)
...]
[(> n 0)
... (a-decimal-eating-function-template (quotient n 10))
... (remainder n 10)]))
Look familiar? :)
For more of this, see a textbook like How to Design Programs.