Parameterized Functions

I was recently playing around with Perl 6 , where I encountered dynamic variables , which are looked up in the dynamic scope at runtime. This means that it is possible to shadow variables on the call stack instead of through lexical scoping. When I was thinking about the implications of that concept, I realized that it should be possible to use it to parameterize functions without explicitly passing arguments.

Some languages supply similar concepts explicitly. Personally, I know this concept from Racket , where it could be used to write something like this:

racket
#lang racket

(define parameter (make-parameter "defaulted"))

(define (parameterized #:parameter (param (parameter)))
	; well, this was easy
	(displayln param))

; usage:
(parameterized)
(parameterize ([parameter "value1"])
	(parameterized)
	(parameterize ([parameter "value2"])
		(parameterized)
		(parameterized #:parameter "value3"))
	(parameterized))
(parameterized)

This code snippet combines three important features:

  1. It is possible to define a default parameter (defaulted) that is used in absence of any user-defined parameters.
  2. The default-parameter can be shadowed in the dynamic scope. The parameter is not permanently changed, but only overridden for the lifetime of the parameterized invocation.
  3. The function can still be called with the parameter as an optional keyword argument that is then used instead.

So, how would this look in Perl 6? Even simpler, as it turns out:

perl6
sub parameterized(:$parameter = $*parameter // "defaulted") {
	# well, this was easy
	say $parameter;
}

# usage:
parameterized();
{
	parameterized();
	my $*parameter ::= "value1";
	parameterized();
	{
		my $*parameter ::= "value2";
		parameterized();
		parameterized(:parameter("value3"));
	}
	parameterized();
}
parameterized();

The code has the exact same semantics as the one used in the Racket version. Note that $*parameter is not even lexically defined on the outmost scope!

At this point it should be noted how important it is for either of the previous implementations that the default argument is evaluated every time the function is called.

I tend to spout commonalities like "C++ can do anything all the other languages do, it just might be less convenient!". Sooo… Time to put my money where my mouth is!

The basic idea is rather simple again: Use a thread_local variable that contains the current parameterization and constructors and destructors to change it to and fro. This way the parameter stays changed for the lifetime of an object, and is reverted afterwards1. I have to use an optional positional argument instead of using a keyword argument, as keyword arguments are not natively supported in C++ (of course there always is Boost …).

Naturally, while it is possible to build it, it is significantly more complicated to do so than it was in either Perl 6 or Racket:

cpp
#include <vector>
#include <iostream>

void parameterized(char const*);

class parameter_t {
	friend void parameterized(char const*);
	static thread_local char const* parameter;
	char const* old_parameter;

public:
	parameter_t(char const* str) : old_parameter(parameter)
	{ parameter = str; }

	~parameter_t() { parameter = old_parameter; }
};

thread_local char const* (::parameter_t::parameter) = "defaulted";

void parameterized(char const* str = ::parameter_t::parameter) {
	// Finally, the actual function!
	::std::cout << str << "\n";
}

// usage:
// parameter_t custom_default("custom default"); // this works!
int main() {
	parameterized();
	{
		parameterized();
		parameter_t _("value1");
		parameterized();
		{
			parameter_t _("value2");
			parameterized();
			parameterized("value3");
		}
		parameterized();
	}
	parameterized();
}

Use cases

So, where does one actually use such a construct? What purpose does it serve beyond simple entertainment of creating it?

My favorite example is that of functions that create output. I have lost track of how often I had to pass ::std::cout explicitly2, instead of just specifying it as the output stream once. While it is possible3 to change the standard output, it is very non-generic, requiring additional efforts on the behalf of the programmer.

Another common case in which this might be interesting is for programs that are built as a big library with a small driver – like for example Clang. Since the library should not have state with static storage duration, unless absolutely necessary, program options have to be chained through all functions until they are needed.

Footnotes

  1. This is (only) correct due to the fact that destructors are called in reverse order than constructors, allowing us to just store a single variable in the object instead of having to build our own stack, or similar constructs.

  2. Don't forget that ::std::ostream& operator<<(::std::ostream&, some_type const&); is a function as well.

  3. E.g. by changing the streambuf buffer  or by reopening stdin .