Howto:Use vectors and foreach loops to write shorter code

From FlightGear wiki
Revision as of 15:19, 7 January 2015 by Philosopher (talk | contribs) (use separate arguments to setprop() instead of concatenation)
Jump to navigation Jump to search


  • Last updated: 02/2012
  • Contributors: Omega95, Hooray

Objective: Demonstrate how the use of Nasal vectors and foreach loops may help to create shorter and less redundant Nasal code.

Consider the following snippet of code, taken from Howto: Implement a Flight Management Computer in Nasal.

   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.

Now, when looking through the code in Howto: Implement a Flight Management Computer in Nasal, we can find more 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(lines);

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

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.

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(lines);

  var lines = ["r1-type", "r2-type", "r3-type", "r4-type", "r5-type", "r6-type", "r7-type"];
  init_cdu_types(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(lines,"");

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('l',1,7,'-type');
 var right = create_with_suffix('r',1,7,'-type');

 var display_right = create_with_suffix('display/l',1,7,'-label');
 var display_left = create_with_suffix('display/r',1,7,'-label');

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