LessonsExpert: BeyondLesson 51

More from your handlers

Handlers that take any number of values, change the caller's variables, give back several values at once, and report failure cleanly with return for error.

  • Expert
  • Lesson 51 of 83
  • About 10 minutes
  • 8 examples

Any number of values

A handler can be called with more values than it names, or fewer. the paramCount says how many it was given, and param(n) gives the nth, named or not. A function that adds up whatever it is given:

ExampleRun it in the playground
function total
   local tSum
   put 0 into tSum
   repeat with i = 1 to the paramCount
      add param(i) to tSum
   end repeat
   return tSum
end total

put total(1, 2, 3)
put total(10)
put total()
Output
6
10
0

A named parameter that was not given is empty, and extra values beyond the names are still there for param. the params shows the whole call, which is handy in a trace:

ExampleRun it in the playground
command showCall pA, pB
   put the params && "with" && the paramCount && "values; pB is" && quote & pB & quote
end showCall

showCall "x"
showCall "x", 2, "extra"
Output
showCall "x" with 1 values; pB is ""
showCall "x","2","extra" with 3 values; pB is "2"

Values you may leave out

xTalk has no special syntax for a default value: test for empty, and fill it in at the top of the handler. Callers can then leave out the values that are usually the same:

ExampleRun it in the playground
function greet pName, pGreeting
   if pGreeting is empty then put "Hello" into pGreeting
   return pGreeting & "," && pName
end greet

put greet("Ada")
put greet("Ada", "Good morning")
Output
Hello, Ada
Good morning, Ada

Changing the caller's variable: @

A parameter is normally a copy: changing it changes nothing outside the handler. Put @ before its name and the parameter is the caller's variable, passed by reference, so the handler can change it. By custom such a parameter's name begins with r:

ExampleRun it in the playground
command addTax @rPrice, pRate
   multiply rPrice by 1 + pRate / 100
end addTax

command addItem @rList, pItem
   put pItem into rList[the number of elements of rList + 1]
end addItem

put 100 into tPrice
addTax tPrice, 20
put tPrice

addItem tFruit, "apple"
addItem tFruit, "pear"
put the number of elements of tFruit && tFruit[2]
Output
120
2 pear

By reference is also quicker for big values: a large array passed with @ is not copied. Use it for that, or when changing the caller's variable is the whole point, as with addItem; otherwise a function that returns a new value is easier to follow.

Giving back several values

A handler returns one value, but the value can be an array with as many elements as you like:

ExampleRun it in the playground
function summary pList
   local tResult
   put min(pList) into tResult["lowest"]
   put max(pList) into tResult["highest"]
   put average(pList) into tResult["mean"]
   return tResult
end summary

put summary("3,9,6,2") into tStats
put tStats["lowest"] && "to" && tStats["highest"] && "averaging" && tStats["mean"]
Output
2 to 9 averaging 5

Reporting failure: return for error

xTalk's own commands report a problem in the result (Reading and writing files), and yours can too. return ... for error leaves the problem in the result, and the call's value empty; return ... for value gives back the answer and leaves the result empty. The caller checks the result straight after the call:

ExampleRun it in the playground
function safeDivide pA, pB
   if pB is 0 then return "cannot divide by zero" for error
   return pA / pB for value
end safeDivide

repeat for each item tDivisor in "4,0"
   put safeDivide(10, tDivisor) into tAnswer
   if the result is not empty then
      put "10 /" && tDivisor & ":" && the result
   else
      put "10 /" && tDivisor && "=" && tAnswer
   end if
end repeat
Output
10 / 4 = 2.5
10 / 0: cannot divide by zero

A command works the same way, with its value in it:

ExampleRun it in the playground
command lookUpColour pName
   switch pName
      case "sky"
         return "blue" for value
      case "grass"
         return "green" for value
   end switch
   return "no colour for" && pName for error
end lookUpColour

repeat for each item tThing in "sky,sea"
   lookUpColour tThing
   if the result is not empty then
      put "problem:" && the result
   else
      put tThing && "is" && it
   end if
end repeat
Output
sky is blue
problem: no colour for sea

Return for error suits problems the caller should expect and handle, such as a missing file or a bad value typed in. For a mistake that means the program itself is wrong, throw (Handling errors) is better: it cannot be ignored by accident.

Try it yourself

Write a command parseDate that takes text such as 2026-03-14 and puts its parts into an array passed by reference (keys year, month, day), or returns for error when the text is not three numbers joined by -. Try it on 2026-03-14 and 14 March.

Show a solution
ExampleRun it in the playground
command parseDate pText, @rDate
   set the itemDelimiter to "-"
   if the number of items of pText is not 3 then
      return "not a date like 2026-03-14:" && pText for error
   end if
   repeat for each item tPart in pText
      if tPart is not an integer then return "not a number:" && tPart for error
   end repeat
   put item 1 of pText into rDate["year"]
   put item 2 of pText into rDate["month"]
   put item 3 of pText into rDate["day"]
   return empty for value
end parseDate

repeat for each line tText in "2026-03-14" & return & "14 March"
   parseDate tText, tDate
   if the result is not empty then
      put "problem:" && the result
   else
      put "day" && tDate["day"] + 0 && "of month" && tDate["month"] + 0 && "in" && tDate["year"]
   end if
end repeat
Output
day 14 of month 3 in 2026
problem: not a date like 2026-03-14: 14 March

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.