Optional Arguments - Spicery/Nutmeg GitHub Wiki

Overview

In Nutmeg, procedure calls have optional, keyword arguments that are applied separately from mandatory arguments. It looks like this:

def incrBy{ increment = 1 }( n ):       ### define a procedure with optional argument 'increment'
    n + increment
end

println( incrBy( 3 ) )                  ### prints 4

println( incrBy{ increment=2 }( 3 ) )   ### prints 5

### You can create new procedures by setting new values for the optional arguments
const incrBy100 := incrBy{ increment=100 }
println( incrBy100( 99 ) )              ### prints 199

Revisions

The substitution of the optional arguments is independent of the mandatory arguments and creates a new 'revision' i.e. a new procedure with different defaults for its optional arguments. And revisions can be further revised freely, again and again. The actual procedure body is not actually called until the mandatory arguments, possibly empty, are supplied.

To continue the above example, incrBy100 can be used as a base to create decrBy1 like this, even though it is already a revision of incrBy:

const decrBy1 := incrBy{ increment = -1 }
println( 100.decrBy1 )                  ### prints 99

Options

Sometimes options are best regarded as boolean flags and there is a shorthand for that. Let's suppose we have a procedure that concatenates a list of strings into a single string using a separator, optionally trimming the strings. We might represent the optional trimming as follows:

def concatenate{ --trim, sep='' }( list ):
    if trim:
        concatenate( list.map( trimString ) )
    else:
        String(
            for i in list || f in trueThenFalses:
                if not( f ): sep.explode end
                i.explode
            end
        )
    end
end


### will print: "the dog sat on the cat" (without the quotes)
println( concatenate{ --trim, sep=' ' }( ' the dog', 'sat on ', ' the cat' ) )  

Mandatory Named Parameters

Sometimes we want to insist on an argument being both named and provided.

def power{exponent}( x ):
    var sofar := 1
    repeat exponent times
        sofar <- sofar * x
    endrepeat
    return( sofar )
end

### will complain of missing named parameter
power( x, 3 )

Alternative syntax

def incrBy( n, increment = 1 ):
    n + increment
end

const decrBy1 := 
println( incrBy( 3 ) )                  ### prints 4

println( incrBy( 3, increment = 2 ) )   ### prints 5

### partial evaluation
const incrBy100 := incrBy$( _, increment=100 )  
println( incrBy100( 99 ) )              ### prints 199

def concatenate( list, sep='', --trim ):
    if trim:
        concatenate( list.map( trimString ) )
    else:
        String(
            for i in list || f in trueThenFalses:
                if not( f ): sep.explode end
                i.explode
            end
        )
    end
end

### will print: "the dog sat on the cat" (without the quotes)
println( concatenate( ' the dog', 'sat on ', ' the cat' -&- --trim, sep = '') )

def power( x, exponent=_ ):
    var sofar := 1
    repeat exponent times
        sofar <- sofar * x
    endrepeat;
    return( sofar )
end

Performance Guarantees

Each time a procedure with optional parameters is revised, a completely new, revisable procedure is created. It is guaranteed that revisions do not keep pointers to the procedure that they were created from - they only refer to the base procedure and the new values that are supplied. In particular the following loop will not leak space:

var p = incrBy;
for i in [ 0 ..< infinity ]:
    p <- p{ increment = i }    ### the reference to the old value of p is lost & can be garbage collected
endfor

Simple uses of optional parameters will not incur an overhead. When the Nutmeg compiler can identify the procedure being revised at compile-time it will optimise the optional-and-mandatory arguments into a single function call. The Nutmeg compiler is also free to take advantage of the values of the optional arguments to produce specialised and more efficient versions of the procedure - and will do so transparently.

When revisions are made at runtime and passed around as parameters, inevitably there is a small overhead in dynamically checking the supplied optional arguments match the defined optional arguments. And the creation of a new revision also entails creating a new closure-type object.