Tags

I need to make a quick variation of a function. The calculation, or behaviour, differs slightly on the needs of the caller. I throw in a bool parameter to do this switch. It’s fast and easy, yet I’m almost always disappointed later. It’s a bit hard to read from the call side. An enum, or distinct functions, would be cleaner.

Setup

The basic example here is you have a function:

1
2
3
defn calc_formula = ( a : int, b : int )->(:float) {
    ...
}

And you have a new call-site that needs to slightly alter the behaviour. So you stick a boolean on to trigger that switch:

1
2
3
4
5
6
7
defn calc_formula = ( a : int, b : int, is_gain : bool )->(:float) {
    ...
    if (is_gain) {
        //something slightly different
    }
    ...
}

The function itself looks fine, but what happens to the caller?

1
var v = calc_formula( ia, ib, true )

When I’ve just modified the calc_formula function then I’ll know what that true means. But what happens when I come back months later. Or what if somebody else sees this code. They’ll have to flip to the function definition to understand the mysterious true value. It’s not a typical parameter to this formula, thus it can’t be inferred from context.

Enum

This looks easier to read:

1
var v = calc_formula( ia, ib, calc_formula_type.is_gain )

I can immediately see I’m using an is_gain variant calculation. All I’ve done is swap out the boolean parameter for an enum.

1
2
3
4
5
6
7
8
enum calc_formula_type {
    standard
    is_gain
}

defn calc_formula = ( a : int, b : int, opt : calc_formula_type )->(:float) {
    ...
}

I’d like a language that can contextually resolve enums, so I can just type something like calc_formula( ia, ib, is_gain ). Having to remember the enum type names is kind of annoying. I’ll probably have some way to do this in Leaf.

Distinct functions

An alternative is to use distinct function wrappers and hide the underlying function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
defn calc_formula = ( a : int, b : int ) -> {
    return calc_formula_impl(a,b,false)
}

defn calc_formula_is_gain = ( a : int, b : int ) -> {
    return calc_formula_impl(a,b,true)
}

defn calc_formula_impl = ( a : int, b : int, is_gain : bool ) -> {
    ...
}

The callers look good: calc_formula(ia, ib) or calc_formula_is_gain(ia, ib). It still involves a boolean on the actual function implementation. Though it isn’t as bad since the only callers of the calc_formula_impl know about it, and they’re defined right beside it. The context makes it easy to understand that the boolean parameter is.

Deciding which to use

Both solutions make the calling code easier to understand.

The distinct functions form comes at the cost of boilerplate wrapper functions — I’m never a fan of boilerplate code. However, if the formula really is distinct, then it makes sense to have separate functions. If the two functions truly feel like two separate functions then I do prefer this form.

Consider for example sin and cos. They are basically the same function with a phase offset. I’d truly hate to see something like this in code:

1
var q = sin( angle, sin_mode.cos )

Sure, the implementation is nearly identical, but they feel like very distinct functions.

I also don’t like flags that change the purity of the function:

1
2
3
var bond = calc_bond( params );

var rbond = calc_bond( params, bond_mode.register_global );

The second form both calculates a bond and registers it in a global table, whereas the first bond is a clean function without any side effects.

That is, I put up with the boilerplate code if the flags approach “feels” wrong.

Virtuals

If the function is a virtual member in a class I’ll nonetheless lean toward the flags approach. A series of functions can often place a burden on derived classes: the boilerplate multiplies.

A nice combined solution is to make a protected enum virtual and expose the wrapper functions in the base class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class shape {
    defn calc_nominal_bounds = -> {
        return calc_bounds( bounds_type.nominal )
    }

    defn calc_render_bounds = -> {
        return calc_bounds( bounds_type.render )
    }

    protected abstract defn calc_bounds = ( how : bounds_type ) -> ( : rect );
}

This lets me call the functions with the nice form my_square.calc_render_bounds() but avoids having to overload multiple definitions in each derived class.

Advertisements