Howto:Use vectors and foreach loops to write shorter code

From FlightGear wiki
Jump to navigation Jump to search


We demonstrate here how to use vectors and foreach loops to write shorter code in Nasal. Vectors are resizable one-dimensional arrays Nasal can use for storing data.

By looking for similarities and generalize code one can rewrite repetitive code into loops. This will help maintenance by reducing the typing required when changing or fixing code.

We will examine three repetitive code snippets taken from Howto: Implement a Flight Management Computer in Nasal and rewrite them. Finally we will combine the rewritten snippets into one generalized helper function that we can call to do the same thing as the original snippets.

First snippet

Consider the following snippet of code:

   setprop("/controls/cdu[" ~ cdu ~ "]/l1-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l2-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l3-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l4-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l5-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l6-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/l7-type", "click");

As you can see, this is a fairly repetitive piece of code, which could be easily generalized, so that only a single setprop() call is used in a loop (foreach). It could be rewritten like this:

 var lines = ["l1-type", "l2-type", "l3-type", "l4-type", "l5-type", "l6-type", "l7-type"];
 foreach(var line; lines) {
   setprop("/controls/cdu", cdu, line, "click");
 }

Then, you could also use a small helper function (method):

 var init_lines= func {
  var lines = ["l1-type", "l2-type", "l3-type", "l4-type", "l5-type", "l6-type", "l7-type"];
  foreach(var line; lines) {
   setprop("/controls/cdu", cdu, line, "click");
  }
 }

The nice thing is, that we get less code, which is more general - i.e. it will be much easier to change things later on, so that support for more CDU/FMC lines could be added easily for example.

Second snippet

Now, when looking through more of the code, we can find code which looks fairly similar:

   setprop("/controls/cdu[" ~ cdu ~ "]/r1-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r2-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r3-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r4-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r5-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r6-type", "click");
   setprop("/controls/cdu[" ~ cdu ~ "]/r7-type", "click");

The only thing different here is that we have "r1..r7-type" rather than "l1..l7". So, we could just take our previous function and generalize it some more:

 var init_cdu_types= func(what) {
 foreach(var w; what) {
   setprop("/controls/cdu", cdu, w, "click");
 }
}

This could now be easily called during initialization to set up the properties like this:

  var lines = ["l1-type", "l2-type", "l3-type", "l4-type", "l5-type", "l6-type", "l7-type"];
  init_cdu_types(what:lines);

  var lines = ["r1-type", "r2-type", "r3-type", "r4-type", "r5-type", "r6-type", "r7-type"];
  init_cdu_types(what:lines);

Third snippet

Now, looking further through the code, there are more opportunities to generalize our little helper function:

  ## Field Values
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l1-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l2-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l3-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l4-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l5-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l6-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/l7-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r1-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r2-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r3-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r4-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r5-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r6-label", "");
   setprop("/controls/cdu[" ~ cdu ~ "]/display/r7-label", "");

The only thing that's different here, is the final setprop argument - which is empty in this case, so we could just change our function to support an additional argument:

 var init_cdu_types= func(what,value="click") {
 foreach(var w; what) {
   setprop("/controls/cdu", cdu, w, value);
 }
}

Note, that we have provided a default value here (which is "click"), so that the previous function calls don't need to be changed.

Combining the rewritten snippets

With the changes in place, our init code could be made even shorter:

  var lines = ["l1-type", "l2-type", "l3-type", "l4-type", "l5-type", "l6-type", "l7-type"];
  init_cdu_types(what: lines);

  var lines = ["r1-type", "r2-type", "r3-type", "r4-type", "r5-type", "r6-type", "r7-type"];
  init_cdu_types(what: lines);

  var lines = ["/display/l1-label", "/display/l2-label", "/display/l3-label", "/display/l4-label", "/display/l5-label","/display/l6-label", "/display/l7-label","/display/r1-label", "/display/r2-label", "/display/r3-label", "/display/r4-label", "/display/r5-label","/display/r6-label", "/display/r7-label"];
  init_cdu_types(what: lines,value: "");

Obviously, there are some more opportunities - such as automatically creating these vectors, too. Which is also trivial to do:

 var create_with_suffix = func(prefix,start,end,suffix) {
 var result = [];
  for (var i=start;i<=end;i+=1) {
    var s= prefix ~ i ~ suffix;
    append(result,s);
  }
  return result;
 }

So, now we have a helper function, which automatically creates these vectors. Which can be used like this:

 var left = create_with_suffix(prefix:'l', start:1,end:7,suffix:'-type');
 var right = create_with_suffix(prefix:'r',start:1,end:7,suffix:'-type');

 var display_right = create_with_suffix(prefix:'display/l',start:1,end:7,suffix:'-label');
 var display_left = create_with_suffix(prefix:'display/r',start:1,end:7,suffix:'-label');

 init_cdu_types(what:left);
 init_cdu_types(what:right);
 init_cdu_types(what:display_right);
 init_cdu_types(what:display_left);