EXIT is the "normal" termination of PROGRAM by the code-block itself. This is great when performing a "long" operation, such as generating a report, running through a single "batch" of a specific "recipe". The GenerateReport or RunBatch PROGRAM code-block knows best when it is done, not $Main. $Main can monitor RunBatch.Done to know when the batch is done. $Main typically would not HALT RunBatch cuz RunBatch knows best when it is done (however, see below).
HALT is a "supervisory" termination of code-block, typically for "abnormal" terminations. Say you needed to abort the currently running batch, $Main could HALT RunBatch.
HALT could be used for "normal" termination of a code block by its "supervisory" code-block (typically $Main, where the code-block's RUN instruction also exists; but actually any code-block can HALT any other code-block, but nobody can HALT $Main, not even $Main).
Rule of thumb: prefer EXIT for "normal" (self) termination over HALT. But, you can use HALT for "supervisory" termination.