Let us return to the topic of functions which manipulate lists, and see how to write them in idiomatically correct Mythryl.
We have presented such functions previously in these tutorials, but they were written in a “C written in Mythryl syntax” style which would make any experienced Mythryl programmer wince.
Recall that the fundamental operator for constructing lists is the Mythryl ’!’ operator — what Lisp calls cons. By repeatedly using ’!’ to prepend values to the empty list, we can build up any valid Mythryl list:
linux$ my eval: "abc" ! []; ["abc"] eval: "abc" ! ("def" ! []); ["abc", "def"] eval: "abc" ! ("def" ! ("ghi" ! [])); ["abc", "def", "ghi"]
Recall also that Mythryl functions allow pattern matching against arguments.
In fact, we now know that all the “argument lists” we have been using in our functions in the tutorials have really been extracting values from argument tuples via pattern matching. (I warned you that pattern matching keeps popping up in Mythryl where you least expect it!)
Put those two facts together with our new knowledge that Mythryl functions may accept arguments of any type — in particular, lists — and that Mythryl function syntax can encode implicit case statements, and we are now able to understand one of the list idioms dear to the Mythryl programmer’s heart:
#!/usr/bin/mythryl fun sum_list list_of_integers = sum_it (list_of_integers, 0) where fun sum_it ( [], sum) => sum; sum_it (i ! is, sum) => sum_it( is, sum + i); end; end; printf "%d\n" (sum_list [1,2,3,4] );
Running the above will give you:
linux$ ./my-script 10
The above is a list-processing idiom that you will see over and over again, and if you write any significant amount of real Mythryl code, you will write it over and over again.
Three points to note:
We could have written the above as the entirely equivalent
#!/usr/bin/mythryl fun sum_list list_of_integers = { fun sum_it ( [], sum) => sum; sum_it (i ! is, sum) => sum_it( is, sum + i); end; sum_it (list_of_integers, 0); }; printf "%d\n" (sum_list [1,2,3,4] );
but the preceding version is clearer because it motivates the sum_it function before it defines it, which makes it easier for the reader to understand why the function is being defined and thus how to interpret it.
Lists are recursive datastructures, and recursive datastructure processing calls for recursive functions, but typically the recursive function doing the work needs extra result-so-far state arguments beyond those supplied by the original caller.
These leads to a bog-standard idiom in which the externally visible function is just a wrapper for the recursive function which does the work.
In the above example, the external caller supplied only the list argument, but to compute the sum we needed an extra argument containing the sum of the list values seen so far.
Look again at the initial parameter patterns in the sum_it function:
fun sum_it ( [], sum) => sum; sum_it (i ! is, sum) => sum_it( is, sum + i); end;
The [] case detects end-of-iteration and returns the final result.
The i ! is case (read as “'i'
and more 'i'
s”) pries one element
off the start of the list; we process it, combine what we learn from
it with one or more of our result-so-far state parameters, and then
finish up by calling ourself recursively on the rest of the input list.
You will see this general pattern over and over again, until you can recognize it at a glance.
A frequent variation of it accumulates the result-so-far in a list. In this case, by the time we reach the terminating [] case on input, our result-so-far list is in the reverse order of of the original input list. We have been taking values from the front of the input list and adding them to the front of the result list, so in the end the first value processed, derived from the first element of the input list, is now at the end of the result list.
Consequently, the [] case will almost always reverse the result list before returning it:
#!/usr/bin/mythryl fun list_to_upper list_of_strings = f (list_of_strings, []) where fun f ( [], results_so_far) => reverse results_so_far; f (s ! ss, results_so_far) => f( ss, (string::to_upper s) ! results_so_far); end; end; map (printf "%s\n") (list_to_upper [ "abc", "def", "ghi" ] );
When run, the above produces
linux$ ./my-script ABC DEF GHI linux$
Note how the reverse in the [] clause makes the results come out in the expected order.
Note also how the helper function is this time simply called f. I do not particularly approve of this idiom, but it is one you will see quite a bit in production Mythryl code, so it is good to get used to it. It certainly has the virtue of brevity.