If you use at least one . in your function name, you can treat the function as a custom operator:
\function test.bg(param)
[<param>match[low]then[skyblue]]
~[<param>match[medium]then[purple]]
~[<param>match[high]then[red]]
~[<param>match[critical]then[gray]]
\end
This gives you some additional flexibility in parameter usage:
{{{ [test.bg{!!task-priority}] }}} or <$text text={{{ [test.bg{!!task-priority}] }}} />
{{{ [test.bg[high]] }}}
<$let variable="high">
{{{ [test.bg<variable>] }}}
</$let>
This is a general limitation of the <<short-form>> syntax shared by functions, procedures, and macros: in each case, they can only take literal strings as (named or sequential) parameters. So while <<test-bg {{!!task-priority}}>> isn’t valid, the following parameters are…
<<test-bg high>>
<<test-bg param:"high">>
And as you guessed, the following are both valid long-form “function-calls”:
<$macrocall $name=test-bg value={{!!task-priority}} />
<$transclude $variable=test-bg value={{!!task-priority}} />
I don’t typically find the widget syntax very useful for working with functions, though; it doesn’t lend itself to use in filters or attribute values. And conversely, all the above examples do still work with test.bg rather than text-bg, so there’s no real downside to using periods in your function names.