IntentSimplifies control flow, while improving code readability. Also Known AsEarly Out Algorithm MotivationIn any program that maintains a database of some kind, there will often be a need to search through that database for specific record instances. To find a match for a record in the database, two or more fields must match a set of search criteria. The traditional way of thinking about searching through a database might be something like this: : matches? field1 data1 = field2 data2 = and field3 data3 = and if ... ; The disadvantage of this method is that all the relavent fields are tested, thus wasting time if we know for certain that we don't need to check other fields if even one mismatch disqualifies the current record. We can solve this particular problem by using nested IF statements, like this: : matches?
field1 data1 = if
field2 data2 = if
field3 data3 = if
YES exit
then
then
then
NO ;
For non-trivial database queries, however, this can yield code which is unwieldly. This is especially true for Forth source stored in blocks instead of files. We can kill two birds with one stone by exploiting the visibility of Forth's return stack to implement an early-out algorithm of data matching. In particular, each field test is factored out, thus allowing it to be more easily re-used. In addition, definitions are kept short – often fitting on a single line, which is a significant advantage for block-based source representations. Secondly, we can change the more passive reading code to a more active voice, which enables greater comprehension by keeping the code imperative, rather than mixing imperative and declarative constructs in the same definition. : f1? field1 data1 <> if r> drop then ; : f2? field2 data2 <> if r> drop then ; : f3? field3 data3 <> if r> drop then ; : ?match f1? f2? f3? YES ; : foobar ... ?match NO ... ; A more concrete example from FS/Forth for Linux's target compiler is as follows: Screen 24 0 ( FS/Forth::Vocabulary::Forth::Search ) 1 2 : entry1 cells table1 + ; 3 : entry2 cells 2* 2* table2 + ; 4 : <> = 0= ; 5 : length? dup entry1 h@ here @ <> if r> drop then ; 6 : bump 4 + swap 4 + swap ; 7 : eq? 2dup @ swap h@ <> if 2drop r> r> 2drop exit then bump ; 8 : name? dup entry2 here 4 + eq? eq? eq? 2drop ; 9 : check length? name? r> drop ; 10 : fw? begin dup 0< if exit then check 1- again ; 11 : forthword? >r >r 0 r> r> image fwli @ 1- fw? ; 12 : cfa@ entry2 12 + h@ ; 13 14 15 Screen 25 (shadow comments) 0 1 entry1,2 n-a Returns HOST address for row 'n' in vocab tables 2 check n-n Checks vocab entry 'n' to see if word matches image. 3 If it does, it doubly-returns. Otherwise, it just returns. 4 forthword? au-r Table row for word (a u). If none found, -1. 5 cfa@ r-a Returns CFA bound to word identified by row r. 6 fwli -a Variable: Forth Word List Index 7 8 9 10 11 12 13 14 15 The main entrypoint to this code is forthword?. It returns a table row ID for a word in the FS/Forth "forth" vocabulary, given its name in standard (caddr u) format on the data stack. It is used to look for a word in the target Forth dictionary, and if found, acquire its code field address using the cfa@ word. After creating an image of what to look for at HERE (the image word builds said image), it enters a BEGIN/AGAIN loop looking for the word, found in fw?. Note how the loop handles the specific case of a failed match only – the case of a successful match doesn't distract the reader, thus allowing easier comprehension of the intent of the code. The code, therefore, becomes self-documenting. Note that eq? uses a double instance of the Return Pattern. In this case, it's a match-word that is itself invoked by a match-word (name?); hence, to early-out of this situation, two levels of subroutine contexts must be cleaned off the return stack. ApplicabilityUse the Return Pattern when
ParticipantsCollaborations
ConsequencesThis pattern can easily be abused. Use of this pattern incorrectly can yield software which is harder to maintain, harder to debug, and with poor performance. ImplementationUsing r> drop causes the previous execution context to be discarded without affecting other task state, including the data stack. The resulting "quick return" enables the invoking word to concentrate strictly on the cases that the invoked word doesn't. In the event multiple conditions can cause an early-out condition, it might be helpful to define a text substitution macro. For example, in GForth: : next s" r> drop" evaluate ; immediate : f1 field1 data1 <> if next then ; : f2 field2 data2 <> if next then ; ..etc.. This might yield increased readability if the r> drop phrase appears often. Known Uses
Related Patterns |