Writing functional code in C++ V – Miscellaneous and conclusions

-

Just a cou­ple of triv­i­al­i­ties and my part­ing thoughts.

Nested functions

If your lan­guage has lamb­das, you don’t need nested func­tions sup­port be­cause you can im­ple­ment them us­ing it.

I am a heavy user of nested func­tions, but I’m of two minds about it. On one side, I like that they sit close to where they are used, avoid­ing go­ing out­side the main func­tion body to un­der­stand them. I also like that you don’t need to pass a lot of pa­ra­me­ters to them, as they cap­ture the func­tion lo­cals. On the other side, they end up cre­at­ing the im­pres­sion that your func­tions are very long and so, in my eyes, they oc­ca­sion­ally re­duce read­abil­ity. The IDE helps you out there (at least VS 11) by al­low­ing you to col­lapse the lamb­das.

An ex­am­ple of triv­ial case is be­low:

BOOST_AUTO_TEST_CASE(NestedFunctions)
{
    int x = 3;
    auto sumX = [&] (int y) { return x + y;};
    BOOST_CHECK_EQUAL(sumX(2), 3+2);
}

And here is a more re­al­is­tic one (not writ­ten in a func­tional style), where read­abil­ity is re­duced by the three nested func­tions (among many other things):

bool condor(
            boost::gregorian::date now,  // date to evaluate
            double spot,                 // spot price underlying
            double v,                    // ATM volatility
            double r,                    // risk free rate
            double step,                 // % of spot price to keep as distance between wings
            int minCallShortDist,        // min distance from the short call strike in steps
            int minPutShortDist,         // min distance from the short put strike in steps
            int minDays,                 // min number of days to expiry
            int maxDays,                 // max number of days to expiry
            double maxDelta,             // max acceptable delta value for shorts in steps
            double minPremium,           // min accepted premium as % of step
            Condor& ret                  // return value
            )
{
    // convert params to dollar signs
    auto stepPr            = round(step * spot);
    auto toUSD             = [stepPr] (double x) { return round(stepPr * x);};
    auto minCpr            = toUSD( minCallShortDist );
    auto minPpr            = toUSD( minPutShortDist );
    auto premiumPr         = toUSD( minPremium );
    // calc strike values for short legs
    auto atm               = round(spot / stepPr) * (long) stepPr;
    auto callShort         = atm + minCpr;
    auto putShort          = atm - minPpr;
    auto addDays           = [](boost::gregorian::date d, int dys) -> boost::gregorian::date {
        using namespace boost::gregorian;
        auto toAdd         = days(dys);
        auto dTarget       = d + toAdd;
        return  dTarget;
    };
    // calc min & max allowed expiry dates
    auto minDate           = addDays(now, minDays);
    auto maxDate           = addDays(now, maxDays);
    auto expiry            = calcExpiry(now, 0);
    // find first good expiry
    while(expiry < minDate)
        expiry          = calcExpiry(expiry, +1);
    Greeks g;
    auto scholes           = [=, &g] (double strike, int days, bool CorP) {
        return blackScholesEuro(spot, strike, days, CorP, v, r, g, true);
    };
    // find a condor that works at this expiry
    auto findCondor        = [=, &g, &ret] (int days) -> bool {
        ret.shortCallStrike                = callShort;
        ret.shortPutStrike                 = putShort;
        auto shCallPremium                 = 0.0;
        auto shPutPremium                  = 0.0;
        // find short call strike price < maxDelta
        while(true) {
            shCallPremium                  = scholes(ret.shortCallStrike, days, true);
            if(g.delta <= maxDelta)
                break;
            else
                ret.shortCallStrike        += stepPr;
        }
        // find short put strike price < maxDelta
        while(true) {
            shPutPremium                   = scholes(ret.shortPutStrike, days, false);
            if( (- g.delta) <= maxDelta)
                break;
            else
                ret.shortPutStrike         -= stepPr;
        }
        // check premium is adeguate
        ret.longCallStrike                = ret.shortCallStrike + stepPr;
        ret.longPutStrike                 = ret.shortPutStrike  - stepPr;
        auto lgCall                       = scholes(ret.longCallStrike, days, true);
        auto lgPut                        = scholes(ret.longPutStrike,  days, false);
        ret.netPremium                    = shCallPremium + shPutPremium - lgCall - lgPut;
        return ret.netPremium > premiumPr;
    };
    // increases the expiry until it finds a condor or the expiry is too far out
    while (expiry < maxDate) {
        auto days        = (expiry - now).days();
        if(findCondor(days)) {
            ret.year     = expiry.year();
            ret.month    = expiry.month();
            ret.day      = expiry.day();
            return true;
        }
        expiry           = calcExpiry(expiry, +1);
    }
    return false;
}

That is quite a mouth­ful, is­n’t it? But re­ally the func­tion is not that long. It is all these nested func­tions that makes it seems so.

Tuples

Tuples are an­other fea­ture to­ward which I have mixed feel­ings. They are re­ally use­ful to re­turn mul­ti­ple re­sults from a func­tion and for rapid pro­to­typ­ing of con­cepts. But they are easy to abuse. Creating Records is al­most al­ways a bet­ter, safer way to craft your code, al­beit more ver­bose.

The stan­dard C++ has an ex­cel­lent tu­ple li­brary that makes work­ing with them al­most as easy as in main­stream func­tional lan­guages. The be­low func­tion shows cre­at­ing them, get­ting their value, pass­ing them to func­tions and de­con­struct­ing them:

BOOST_AUTO_TEST_CASE(Tuples)
{
    auto t = make_tuple("bob", "john", 3, 2.3);
    BOOST_CHECK_EQUAL(get<0>(t), "bob");
    BOOST_CHECK_EQUAL(get<2>(t), 3);
    // yep, compiler error
    //auto k = get<10>(t);
    auto t2(t);
    BOOST_CHECK(t2 == t);
    // passing as argument, returning it
    auto f = [] (tuple<string, string, int, double> t) { return t;};
    auto t1 = f(t);
    BOOST_CHECK(t == t1);
    // automatic deconstruction
    string s1; string s2; int i; double d;
    std::tie(s1, s2, i, d) = f(t);
    BOOST_CHECK_EQUAL(s1, "bob");
    // partial reconstruction
    string s11;
    std::tie(s11, ignore, ignore, ignore) = f(t);
    BOOST_CHECK_EQUAL(s11, "bob");
}

Conclusion

I’m sure there are some fun­da­men­tal func­tional fea­tures that I haven’t touched on (i.e. cur­ry­ing, more pow­er­ful func­tion com­po­si­tion, etc…).  Despite that, I think we have enough ma­te­r­ial here to start draw­ing some con­clu­sions. To start with, where is C++ de­fi­cient com­pared to main­stream func­tional lan­guages for the sake of writ­ing func­tional code ?

Overall, am I go­ing to use C++ now as my main pro­gram­ming lan­guage? Not re­ally. I don’t have one of those. My gen­eral view is to al­ways use the best lan­guage for the job at hand (yes, I even use Javascript for web pages).

C++ has some unique char­ac­ter­is­tics (i.e. speed, cross com­pi­la­tion & sim­ple C in­ter­fac­ing). I’m go­ing to use it when­ever I need these char­ac­ter­is­tics, but now I’ll be a hap­pier user know­ing that I can write de­cent func­tional code in it.

Tags