Hi @adamyakes, and welcome!
The three procedures in your example must be distinguishable somehow, so that the compiler will know which one to call depending on the caller’s arguments. For example the third argument in foo might be integer, while it’s real in bar, and an array of 2 integers in baz; In this case:
call the_thing_callers_use(1, 2, 3) will call foo,
call the_thing_callers_use(1, 2, 3.) will call bar,
call the_thing_callers_use(1, 2, [3, 4]) will call baz.
In other words, at least one argument must be different (or even absent). All module procedures must be either subroutines or functions (you can’t mix both as module procedures). Assuming the module is private, the procedures are also private, in the sense the calling program has no access to them, it just has access to the generic the_thing_callers_use (provided this one is public, of course.)
Also note not all of them must be module procedures. For example, baz might be declared in another module; in this case it will be included in the interface as procedure baz, not module procedure baz.
It is a very efficient mechanism to create generic procedures. In many cases, one can achieve the same functionality with just one procedure having optional arguments. However interfacing module procedures is often preferable.