On 7.1, if you are defining a sub-procedure in the same program you are using it, you don't need the prototype. You only need the prototype if you're consuming a sub-procedure from a service program or bound module (Don't bind modules to a program!)
I don't personally find the prototype burdensome to create. I copy the PI, change it to PR, put it into a /copy member and use it in many programs like this:
/copy buck/qprotosrc,buildform
...
buildform(form: mode);
The only time I need to 'double define' is in the service program where the PI is located. A way to avoid most of that is to use conditional compilation. Here's an example:
qprotosrc(buildform)
// Build form prototype and start of interface
// Service program will complete the interface with P E
/if not defined(buildform_proto)
/define buildform_proto
D buildForm PR
/else
P buildForm B
D buildForm PI
/endif
D formType 10A CONST
D mode 4A CONST
qrpglesrc(mysrvpgm)
/copy buck/qprotosrc,buildform
...
/copy buck/qprotosrc,buildform
// body of buildform here
...
return;
p e
The first time the /copy is processed, it inserts the prototype - this is what you'd want for all your consumer programs. As part of processing, it defines buildform_proto. In your service program, you'd then put a second /copy. Because buildform_proto is defined, the compiler inserts the P...B and D...PI specs. You'd have to supply the procedure body and the P...E spec.