Berkin's blog

Berkin's blog

Variadics in Rant 4 and why I think they're better

In general I think that most programming languages do variadics wrong. To be clear, when I say "variadics" I mean functions that accept a variable number of arguments.

In most languages you usually do something like this:

(C#)

// Variadic parameter specified with `params` keyword + array
public static string CreateListString(params string[] items)
{
    return string.Join(", ", items);
}

(JS)

// Variadic parameter specified with `...` prefix
function createListString(...items) {
    return items.join(", ");
}

(Lua)

-- Variadic parameter specified with `...`
function create_list_string(...)
  return table.concat({...}, ", ")
end

For a lot of cases this is perfectly fine and it gets the job done.

Things get a bit trickier when you want the convenience of variadics but need some restriction: what if you need at least one item in the list at all times? You could add a sanity check at the start of the function or, in the case of statically-typed languages, accept a list type that enforces this rule. You could also just add a required parameter of the same type before your vararg and join it into the others somehow.

These are valid solutions, but while they technically work, their semantics rely on the assumption that the user has the function docs memorized, their data is always correct, or the developer always remembers to add these checks when writing the function. I think this problem is simple enough that a programming language should have a way to handle this already.

Variadics in Rant

In Rant 4 there are a lot of situations where passing an empty list to a function that requires a collection of something makes no sense: for instance, Rant's standard library has a variadic function [alt] that takes in a list and returns the first non-empty item from the list. It is pointless to pass it an empty list because... why would you?

I realized this was a common design pattern in variadic functions, so it deserved its own language feature. As a result, Rant has two flavors of variadic parameters:

Optional variadics

Optional variadics in Rant accept zero or more items, so they're optional. It's specified by a * character at the end of the last parameter:

# Accepts zero or more items
[$create-list-string: items*] {
  [join: ,\s; <items>]
}

This means you can call it without any arguments:

[create-list-string]            # returns empty string
[create-list-string: foo]       # returns "foo"
[create-list-string: foo; bar]  # returns "foo, bar"

Required variadics

Required variadics, on the other hand, need at least one element; otherwise, it will raise an error. Instead of *, required variadic parameters use +.

# Accepts one or more items
[$create-list-string: items+] {
  [join: ,\s; <items>]
}

Now it requires at least one value:

[create-list-string]            # error!
[create-list-string: foo]       # returns "foo"
[create-list-string: foo; bar]  # returns "foo, bar"

Familiar semantics

You may have noticed something familiar about Rant's variadic quantifiers: they're like Regex! This is on purpose. By borrowing Regex's quantifier syntax, it means one less wheel to reinvent for me, and one less weird syntax for you to learn. This, in my opinion, is a good thing.

Conclusion

Other programming languages need better ways to define quantity constraints on variadics. It seems that requiring a non-empty list is a common enough use case to warrant its own syntax. Making this a language feature also eliminates a lot of size-checking boilerplate that has to be written in such cases. Why this isn't done more often, I don't know; but hopefully, adopting this idea as a language feature will encourage others to do the same in the future.

 
Share this