Intro to Gogetter, Part 2: A Quick Sketch of How It Works

Starting with just the finance charge calculation

Here’s how we would code the calculation of our finance charge in Gogetter. For this first go-round, we’ll leave out the other parts of our credit-card statement. The syntax here is an idealized Gogetter syntax that is only pseudocode at this point in time; I’m sure it will take some tweaks to make it realizable in C++.

gogetter_system statement () {

        types (
                date_span_t
                int_rate_t
                money_amt_t
        )

        whatsa days_in_period (
                date_span_t input 
        )

        whatsa interest_rate (
                int_rate_t input 
        )

        whatsa average_daily_balance (
                money_amt_t input 
        )

        whatsa finance_charge (
                money_amt_t calc (
                        let avg_bal <<= average_daily_balance;
                        let int_rate <<= interest_rate;
                        let days <<= days_in_period;

                        << avg_bal * int_rate * days / 365.0;
                )
        )
}

I’ll explain this code more fully below, but, in a nutshell, it defines to Gogetter a single calculation – of finance charge – in terms of three inputs. You can think of the code above as living inside Gogetter, though it is in fact client code. The general term for what is defined here is a gogetter-system. That’s ‘system’ in a logical-mathematical sense, an interconnected system of value-senses, some of which are inputs while the others are calculable in terms of the inputs.

For Gogetter to do something with this information, we need some outside client code – meaning client code that sits outside Gogetter. There are three steps this outside code must perform.

First, we create a gogetter:

gogetter stmt(statement);

The ‘statement’ inside the parenthesis tells the gogetter what gogetter-system it is to implement.

Second, we supply inputs:

stmt.inset(days_in_period) << some_date_span;
stmt.inset(interest_rate) << some_interest_rate;
stmt.inset(average_daily_balance) << some_balance;

What happens here is pretty straightforward: We use a construct called an inset. This takes two very different kinds of parameters. The first is a name that has been defined within our gogetter-system. That tells the gogetter which value-sense’s value-referent is being supplied. The second, shown after the ‘«’, is the value-referent to be assigned to that value-sense for this calculation.

In production contexts, insets can only be performed on names that are declared as ‘input’ in the gogetter-system.

Finally, we harvest outputs – or, in this case, the output, since our gogetter-system defines only one calculable value-sense:

// Here are several ways of retrieving the finance charge:
auto fin_chg_v <<= stmt.goget(finance_charge); // Initializer of an 'auto' declaration
money_amt_t fin_chg_v_again <<= stmt.goget(finance_charge); // Initializer of exlicitly typed declaration
const money_amt_t & fin_chg_ref &= stmt.goget(finance_charge); // Initializer of a const ref
const money_amt_t * fin_chg_ptr *= stmt.goget(finance_charge); // Initializer of a const pointer

money_amt_t fin_chg_assign_to;
fin_chg_assign_to <<= stmt.goget(finance_charge); // Assignment to a correctly typed variable

const money_amt_t* fin_chg_ptr_assign_to;
fin_chg_ptr_assign_to *= stmt.goget(finance_charge); // Assigning to a const pointer

// One other small point: You can retrieve inputs as well, not just calculables.
int_rate_t irate <<= stmt.goget(interest_rate); // Refs and ptrs and so on are also available.

The construct used in all these cases is the goget, which behaves much like a regular initializer or assignment, except that it has an extra element tacked on – namely (!) the name of a value-sense defined in the gogetter-system. This can be an input or calculable value-sense, it matters not.

Note also that there are both inner and outer gogets. The inner gogets are found inside the definitions of calculable values inside the gogetter-system. The outer ones are found in outside client code that asks for values from the gogetter.

One very important feature of Gogetter is that it is fanatically lazy. No storage space inside the gogetter is allocated for any value until it is needed, whether because the value was supplied as an input or because a calculable value was asked for. Obviously, then, the value is also not calculated until that moment. This turns out to be a valuable optimization feature.

But equally important, its laziness means that once a value has materialized inside the gogetter, that value is immutable. This feature turns out to be important for a number of reasons. We’ll come back to this later. (There are certain rare and controllable exceptions, but by default the values are left alone after they are computed.)

Gogetter, like C++ in general, supports value semantics. And just as in C++, you can abuse those value semantics by declaring pointer-typed variables and storing addresses in them. Thus, just as in C++, you are urged to use the value semantics if you want to live a long and happy life, but are permitted to deviate from this salutary course when you judge that you must. Which you inevitably will, this being C++! And then you, as always with C++, will have the rest of your tenure with that codebase to regret your decision.

In other words, Gogetter provides a very C++ish working environment: You are given the tools to do safe and sensible software development, but you are also given the tools to do high-risk, crazy things in the pursuit of performance objectives or just plain self-destructive thrill-seeking behavior. As Tim Sweeney has said of C++, so Gogetter “gives you enough rope to shoot yourself in the foot with a can of worms”.

One area where Gogetter improves on C++ a bit is in having constness on by default. It does allow you to override this default if you think you know what you’re doing. Caveat emptor and all of that.

Scaling up

Now let’s see the gogetter-system for our full-blown, from-the-ground-up calculation of finance charge. This will provide hints of some the richer capabilities of Gogetter, though I don’t intend to dig too deeply into those at this point.

gogetter statement () {

        types (
                date_range_t
                date_span_t
                int_rate_src_t
                int_rate_t
                money_amt_t
                transaction_t
        )

        whatsa date_range (
                date_range_t input
        )

        whatsa interest_rate_source (
                int_rate_src_t input
        )

        whatsa starting_balance (
                money_amt_t input
        )

        whatsa [charge_index] (
                const consecutive
        )

        whatsa charges[charge_index] (
                transaction_t input
        )

        whatsa [cash_advance_index] (
                const consecutive
        )

        whatsa cash_advances[cash_advance_index] (
                transaction_t input
        )

        whatsa [payment_index] (
                const consecutive
        )

        whatsa payments[payment_index] (
                transaction_t input
        )

        whatsa [other_credits_index] (
                const consecutive
        )

        whatsa other_credits[other_credits_index] (
                transaction_t input
        )

        whatsa days_in_period (
                date_span_t calc (
                        let dt_range <<= date_range;
                        << dt_range.end() - dt_range.begin();
                )
        )

        whatsa interest_rate (
                int_rate_t calc (
                        let dt_range <<= date_range;
                        let rate_src <<= interest_rate_source;
                        << rate_src.interest_rate_during(dt_range);
                )
        )

        whatsa average_daily_balance (
                money_amt_t calc (
                        let start_bal <<= starting_balance;
                        let charges_r &= charges;
                        let cash_advances_r &= cash_advances;
                        let payments_r &= payments;
                        let other_credits_r &= other_credits;
                        let dt_range <<= date_range;

                        auto transactions{ 
                                merge(charges_r, cash_advances_r, payments_r, other_credits_r) 
                        };

                        << avg_daily_balance(start_bal, transactions, dt_range);
                )
        )

        whatsa finance_charge (
                money_amt_t calc (
                        let avg_bal <<= average_daily_balance;
                        let int_rate <<= interest_rate;
                        let days <<= days_in_period;

                        << avg_bal * int_rate * days / 365.0;
                )
        )
}

A few comments on this:

We’ll skip the declaration of the gogetter object, as that is unchanged from the first section of this post, and go straight to the insetting of inputs:

stmt.inset(date_range) << some_statement_date_range;
stmt.inset(interest_rate_source) << some_interest_rate_source_object;
stmt.inset(starting_balance) << some_starting_balance;

for (size_t i = 0; i < some_collection_of_charges.size(); ++i) {
        stmt.inset(charges[i]) << some_collection_of_charges[i];
}
stmt.close(charges);

// ... and so for the other collections of transactions

Retrieval via gogets is unchanged from before, except of course that you have a lot more choices of gogettums to ask for. So we won’t bother with code for that.

A few more comments:

Something more needs to be said at some point about what actually happens when a goget is executed. Here are the steps:

The big picture

Out of all of this there emerges an ontology, if you will. Let’s summarize it, mostly by way of review.

So: What is all of this, collectively?

I think of it mostly as a ‘framework’, for lack of a better term. It’s built around the concepts of the gogettum and (especially) the goget. Everything else more or less follows from those.

It gives rise to a programming paradigm, with a quite different take on how to think about code from the other paradigms out there. (Somewhat strangely, it feels more like functional programming than any other paradigm; it seems to have some deep affinities to FP that I don’t feel that I have fully understood yet.)

It probably seems underwhelming to those who first read about it! Apart from the jargon, it’s all pretty simple and obvious. My suspicion is that this is a strength, in fact, but of course time will tell. It has been my experience that it is hard to get people intrigued by it, but that once they start to use it, they tend to become enthusiastic about it.

The key construct is the goget. It differs from the assignment of a value from one variable to another in that the right-hand side doesn’t have to have the value handy going into its execution. In that respect, it more resembles an assignment with a function call on the right-hand side. Its differences from a function call (which is of course how it’s implemented) are more subtle. The name plays a mysterious role in the whole business. In some ways, because the name can itself be a run-time, binary object – a value, in fact – the goget feels more like calling a function that is stored in a variable of some callable type.

But why should you read about this? A fair question, which I will take up in Part 3.

[This post revised and expanded 12 Sep 2020]