Tuple Examples


Complex Resolution

[ int, int ] foo1( int );			// overloaded foo functions
[ double ] foo2( int );
void bar( int, double, double );
bar( foo( 3 ), foo( 3 ) );

The type resolver only has the tuple return types to resolve the call to bar as the foo parameters are identical, which involves unifying the possible foo functions with bar's parameter list. No combination of foos is an exact match with bar's parameters; thus, the resolver applies C conversions. The minimal cost is bar(foo1(3),foo2(3)), giving (int, int, double) to (int, double, double) with one safe (widening) conversion from int to double versus (double, int, int) to (int, double, double) with one unsafe (narrowing) conversion from double to int and two safe conversions.

Go Comparison

Comparison of C∀ and Go tuples.

C∀Go
#include <fstream.hfa>


[int, int ] f() {
    return [3, 7];
}
void g( int a, int b ) {
    sout | a | b;
}
int main() {
    sout | f();
    g( f() );
    int a, b, c;
    [a, b] = f();
    sout | a | b;
    c = f().1;
    sout | c;
    c = f().0;
    sout | c;
    [10] int w = { a, b };
    sout | nlOff;
    for ( i; 10 ) sout | w[i];
    sout | nlOn;
    sout | nl;
	// Go does not have tuple variables
    [int, int, int] t = [1, 2, 3];
    sout | t;
    [a, b] = [t.2, t.0]; // reorder and drop tuple elements
    sout | a | b;
}
package main
import "fmt"

func f() (int, int) {
    return 3, 7
}
func g( a, b int ) {
    fmt.Println( a, b )
}
func main() {
    fmt.Println( f() )
    g( f() )

	a, b := f()
    fmt.Println( a, b )
    _, c := f()
    fmt.Println(c)
    c, _ = f()
    fmt.Println(c)
    w := [10]int{ a, b }


	fmt.Println(w)






}

Variadic Tuples

Variadic functions are provides by the new kind of parameter type, ttype (tuple type). Matching against a ttype parameter consumes all the remaining argument components and packages them into a tuple, binding to the resulting tuple of types. There is at most one ttype parameter that occurs last, which matches normal variadic semantics, like CC11 variadic templates. Unlike C variadic functions, it is unnecessary to hard code the number and expected types.

For example, a type-safe variadic print function to replace printf, which also supports user-defined types. This mechanism is used to print tuples in the C∀ I/O library.

forall( otype T, ttype Params | { void print(T); void print(Params); } ) void print(T arg, Params rest) {
	print(arg);  print(rest); 	// recursive usage
}
void print( int x ) { printf( "%d", x ); }
void print( double x ) { printf( "%g", x ); }
void print( const char * x ) { printf( "%s", x ); }
// all other basic types
struct S { int x, y; };			// user type
void print( S s ) { print( "{ ", s.x, ",", s.y, " }" ); }
int main() {
	print( 5, " ", 3.5, " ", (S){ 1, 2 }, "\n" );
}

It is possible to use ttype polymorphism to provide arbitrary argument forwarding functions. For example, it is possible to write new as a library function.

forall( dtype T | sized(T), ttype Params | { void ?{}( T &, Params ); } ) T * new( Params p ) {
	return &(*malloc()){ p };					// run constructor
}
forall( otype R, otype S ) struct pair {
	R first;	S second;
}
forall( otype R, otype S ) void ?{}( pair(R, S) &, R, S );
pair( int, char ) * x = new( 42, '!' );

The new function combinations type-safe malloc with a C∀ constructor call, forcing construction of dynamically allocated objects. This function provides the type safety of new in C++, without the need to specify the allocated type again, due to return-type inferencing.