This post is part of an ongoing series about Affordances and Programming Languages.

Programming languages provide different facilities for defining functions and methods. In particular, they provide more or less flexible ways of specifying parameters.

Smalltalk’s simple, consistent syntax provides only one option: ordered, required parameters. If you want to provide optional default values, arbitrary parameter ordering, or a variable number of arguments, you need to use separate explicit methods.

Arguments in Smalltalk
"The basic version"
findFontNamed: aString size: anInteger style: aSymbol
"find the font"
"Alternate ordering"
findFontNamed: aString style: aSymbol size: anInteger
^self findFontNamed: aString size: anInteger style: aSymbol
"Default arguments"
findFontNamed: aString size: anInteger
^self findFontNamed: aString size: anInteger style: self defaultStyle
findFontNamed: aString style: aSymbol
^self findFontNamed: aString size: self defaultSize style: aSymbol
findFontNamed: aString
^self findFontNamed: aString size: self defaultSize style: self defaultStyle
defaultStyle
^:normal
defaultSize
^12
"Variable arguments"
fontsInFamilies: aCollection
"list all fonts in families listed in aCollection"
"Short-hand for single families"
fontsInFamily: aString
^self fontsInFamilies: (Array with: aString)

Smalltalk’s keyword method syntax makes for very regular, readable code and that is a significant advantage. But it gets tedious to define a flexible API, as you can see above.

C++ is more flexible, in that it allows for optional default values. In C++11, there are also several ways to allow a variable number of arguments, including varargs, variadic templates, and std::initializer_list. I use the latter here:

Arguments in C++11
const int DEFAULT_SIZE = 12;
const FontStyle DEFAULT_STYLE = FontStyle::NORMAL;
// The basic version, including default arguments
Font find_font(const std::string& name, int size = DEFAULT_SIZE, FontStyle style = DEFAULT_STYLE)
{
// Find the font
}
// Alternate ordering, again including default arguments
Font find_font(const std::string& name, FontStyle style = DEFAULT_STYLE, int size = DEFAULT_SIZE)
{
return find_font(name, size, style);
}
// Variable arguments
FontList fonts_in_families(std::initializer_list<std::string> names)
{
// List all fonts in families listed in names
}

Creating a flexible API in C++ involves less code than in Smalltalk, which is an advantage. But with a complex API, it can be hard to know what the various parameters mean because of the lack of keyword arguments.

Ruby provides the best of both worlds, especially in Ruby 2.0 with its keyword arguments. It’s possible to use a Hash in Ruby 1.9 and earlier to have the same effect, but it involves a bit more code.

Arguments in Ruby
# The only version needed
def find_font(name, size: 12, style: :normal)
# Find the font
end
def fonts_in_families(*names)
# List all fonts in families listed in names
end

In Ruby, we only need one variant of the find_font method, because keyword arguments can be specified in any order and also have default values. This allows for very flexible APIs. The cost is that, when designing an API, you have to decide which facilities to use.

I’d be remiss if I didn’t give a nod to Common Lisp’s argument facilities here.

Arguments in Common Lisp
(defun find-font (name &key (size 12) (style 'normal))
"find the font"
)
(defun fonts-in-families (&rest names)
"list all fonts in families listed in names"
)

As with most things in programming languages, there are tradeoffs to be made. Smalltalk’s simple, consistent syntax makes for very clear, readable code. However, it doesn’t provide good affordances for flexible APIs. C++, Ruby, and Common Lisp provide more affordances, making it easier to write flexible APIs. However, that puts more pressure on the programmer to decide which facilities are best to use where. Doing that well takes experience.

With great power comes great responsibility.