Passing optional arguments to a method

Almost everyone will agree that Active4D makes programming in 4D almost bearable. Most will also agree that collections and libraries are probably the most important ingredients.

One of the problems I’ve always had using the many powerful built-in library methods is remembering the position of parameters. For instance:

$result := RowSet.newFromSelection($t{"t_pointer"}; $t{"t_map"};$batch;$callback;$name;$timeout)

Every time I create a new circuit that will use Row Sets, I have to lookup the call. I can usually remember what the required parameters are, but if I don’t have a call back and want to pass $name, I have to set $callback to “”. Going back to my brief exposure to Pascal and Ada - those languages included the concept of named parameters. You could define a function:

procedure demo(x:integer; y:integer);  -- procedure specification


demo(x=>5, y=> 3*45);		-- when calling, associate formal 
					-- and actual parameters.

Because the association is made explicit (instead of the implicit association with positional parameters) there is no need to supply them in the same order as in the sub program specification. There is no restriction on the order in which named parameters are written. You can even mix named and positional parameters, providing the positional parameters are first.


demo( y => 3*45, x => 5);	-- the order of named parameters
					-- is irrelavent

In newer languages you can accomplish the same thing by using hashes or collections in the case of Active4D. While it would break a lot of code, newFromSelection could have been defined using a collection.

$options := new collection("name";"myname")
$result := RowSet.newFromSelection($t{"t_pointer"}; $t{"t_map"};$options)
// or
$result := RowSet.newFromSelection($t{"t_pointer"}; $t{"t_map"};new collection("name";"myname"))

Now I’ve never liked the “new collection” method using inline assignment and would usually revert to creating an empty collection and then assigning keys and values. I’ve been playing around with Ruby lately and it has something called a symbol, which is nothing more than a name starting with a colon (:somename). Ruby also uses a special assignment operator for hashes. The best definition I can find for a symbol that could relate to Active4d is:

Symbols are like constants whose name is their value. Just like the number 5 has both the name 5 and the value 5 so the symbol :howdy is the word howdy. It’s just like “howdy” but Better™

In Ruby the new collection would be something like:

result = {:name => "joe", :cost => 400}

Maybe you don’t agree, but I think the Ruby way is a lot clearer and more readable. So what if we created a library method “opt” that gave us something close to the Ruby symbol and kept operators you are familiar with?

method "opt"($argstring;$removelf=true)
  
  regex replace("/(\\s->\\s)/";$argstring;":=";$string) // replace -> with :=
  $replace := "return (char(34)+trim(\"\1\")+char(34))" // replace code to wrap symbol in quote
  //replace symbol with string (:+chars upto a ; or : or = or newline)
  regex replace("/:([a-zA-Z_\\.0-9\\-][^:=;\\n]+)/e";$string;$replace;$string)
  regex replace("/(:=|=\\>)/";$string;";";$string) // replace assignment with semicolon
  if ($removelf) // for heredoc argstring
    $string := replace string($string;"\n";"")
  end if
  $result := execute('return(new collection($string))')
  return($result)
end method

/***********************************************************************************
  opt - wrapper on new collection(key;value[;key;value..])
  $argstring  ->  Text  Arguments for new collection either in standard format or symbol format for strings
  $removelf   ->  Boolean Optional argument to remove newlines from parsed $argstring
                  Used if $argstring is a heredoc string. 
                  Turn off if not heredoc and a text value contains newlines
  Allows the concept of symbols :some text {end of symbol character[:=;\n]} would be parsed to "some text"
  Allows your preference of an assignment operator [:= => -> ] to be used between key and value
   instead of ; Semicolon can still be used. The pointer operator (->) must be wrapped in whitespace
  Example:    $c := opt(':firstname := :Steve; :lastname => :Alex; "age" -> 65; "city";"Gadsden AL"')
    Parsed to ->  '"firstname";"Steve";"lastname";"Alex";"age";65;"city";"Gadsden AL"'
    Returns as collection -> execute('return(new collection("firstname";"Steve";"lastname";"Alex";"age";65;"city";
    "Gadsden AL"))')
***********************************************************************************/

This is nothing more than a wrapper on new collection where there is only one argument, a string. The single or double quoted string contains keys and values for the collection that is parsed and executed. You can use symbols or strings for keys and values and use either “:=”, “->”, “=>”, or “;” as the assignment operator. Complicated explanation so lets demo:

//Start off with a new collection example
$c := new collection("name";"steve";"age";65;"lives";"Gadsden AL")

// Lets say I put the method "opt" and a library "u"

$c := u.opt(':name := :steve; "age" -> 65; "lives";"Gadsden AL"')
// or
$c := u.opt(":name := :steve; \"age\" -> 65; 'lives';'Gadsden AL'")
// or being consistant!
$c := u.opt(":name -> :steve; :age -> 65; :lives -> 'Gadsden AL'")
// or just using normal new collection stuff
$c := u.opt('"name";"steve";"age";65;"lives";"Gadsden AL"')
// or even using all symbols
$c := u.opt(":name -> :steve; :age -> 65; :lives -> :Gadsden AL")

// and using opt to define optional parameters using the rowset example
$result := RowSet.newFromSelection($t{"t_pointer"}; $t{"t_map"};u.opt(:name := :myname))

Yes, there are too many options for the assignment operator, just didn’t know what one I would like the best. I kind of like the pointer operator myself. Why’d I write this? Nothing else to do! I don’t like typing shift-“ or even ‘! I like to see the difference between a key and a value in a new collection assignment. Putting stuff in strings may not be the best way, but it was the only way I could think of to get around 4D’s syntax.

Part two - using the options.

Again, this won’t work for existing library calls, but if you create a new method you just need logic to check for addition arguments. You don’t even have to use “opt” - you can just use new collection to create optional parameters. Lets build a form helper for input, select and textarea tags - I hate doing those by hand. Copy and paste brings its own problems. In most cases you only need three items, the type of tag, the name of the tag and the value, if any. Lets define a method and put it in some library to help us.

method "form_tag"($type;$name;$value="";$opt=0)
  // build form tags for input, textarea, and select tags (select options passed in value)
  $options := ""
  $id := 'id="$name"'
  if ($opt)
    $id := choose($opt{"id"};$opt{"id"};choose($opt{"noid"};"";'id="$name"'))
  
    // build option attributes that were not handled above
    for each ($opt; $key; $contents)
      if ($key !~ "(id|noid)")
        $options += ' $key="$contents"'
      end if
    end for each
  end if
  case of
    :($type = "textarea")
      $tag := '<textarea name="$name" $id $options>$value</textarea>'
    :($type = "select")
      $tag := '<select name="$name" $id $options>$value</select>'
    else
      $tag := '<input type="$type" name="$name" value="$value" $id $options />'
    
  end case
  return ($tag)
end method
// lets build some tags with options

writebr( form_tag("text";"username";"salex") ) // no options
writebr( form_tag("password";"password";"";opt(':onchange := "checksomething(this)"; :class := :f-left twinkle ' )))
writebr( form_tag("textarea";"whatchadoing";"Once upon a time";opt(':onchange := "thatsnice(this)"; :class := "ugly" ')))
// you can use heredoc strings and format the options someething like doing a rowset map
$heredoc := '''
:onclick := "x = setme(this);
  $(x).value = fixme(x);
  return false;";
:size := 50;
:class := :pink;
:readonly => true
'''
writebr( form_tag("text";"readonly";"This is a readonly field";opt($heredoc)) )
$chk := 7 = 7
writebr( form_tag("radio";"isitme";"yes";opt(':onchange := "setme(this)"; :checked := $chk; :noid => true'))+"Yes") 
writebr( form_tag("radio";"isitme";"no";opt(':onchange := "setme(this)"; :checked := not($chk); :noid => true'))+"No") 
$selopt := '<option value="one">One</option><option value="two" selected="true">Two</option><option value="three">Three</option>'
writebr( form_tag("select";"count";$selopt) )
writebr( form_tag("submit";"update";"Update") )

That would produce:

<input type="text" name="username" value="salex" id="username"  /><br />
<input type="password" name="password" value="" id="password"  class="f-left twinkle" onchange="checksomething(this)" /><br />
<textarea name="whatchadoing" id="whatchadoing"  class="ugly" onchange="thatsnice(this)">Once upon a time</textarea><br />
<input type="text" name="readonly" value="This is a readonly field" id="readonly"  class="pink" onclick="x = setme(this); 
   $(x).value = fixme(x);    return false;" readonly="True" size="50" /><br />
<input type="radio" name="isitme" value="yes"   checked="True" onchange="setme(this)" />Yes<br />
<input type="radio" name="isitme" value="no"   checked="False" onchange="setme(this)" />No<br />
<select name="count" id="count" ><option value="one">One</option><option value="two" s
  elected="true">Two</option><option value="three">Three</option></select><br />
<input type="submit" name="update" value="Update" id="update"  /><br />

Hope this helps someone. You should be able to just copy and paste any of the code above and experiment.