Jupyter C++

One beauty of iPython is the interactivity. You can construct test cases and try them interactively. I wanted to experiment with this functionality, but for C++.

For the Coursera Bioinformatics class, the level of difficulty has gotten to the point where a simple int main() program reading from stdin and stdout is not cutting it - I need to be able to test all the pieces modularly.

One approach I have found is to use xeus-cling. It is quite easy to install using Miniconda. Just don’t think too hard about all the language boundaries between JavaScript(i/o), Python(backend), Clang(compiling your C++/interpreting it on the fly).

Now one can develop little C++ functions interactively.

For the coursera class, where autograder needs the code to read from stdin/stdout, a simple go-inspired test harness like this works for me:

struct TestCaseInputs {
    // The input to your logic function.
    std::string in;

    // What you want to get out of your logic function.
    std::string want;

    // Description of the input/test case.
    std::string desc;
};
struct TestCases {
    std::string name;
    std::function<std::string(std::string)> logic;
    std::vector<TestCaseInputs> cases;
};

void RunTests(TestCases const test_cases) {
    std::cout << "Testing: " << test_cases.name << std::endl;
    for (auto const& test : test_cases.cases) {
        std::cout << test.desc;
        std::string got = test_cases.logic(test.in);
        if (test.want != got) {
            std::cerr << "->FAIL: " << std::endl << test.desc << ": want=" <<  test.want << " got=" << got << std::endl;
        } else {
            std::cout << "->PASS" << std::endl;
        }
    }
}

The user can specifiy some string input, some wanted output, and then some logic to make the input go to the output. For example, to test my cyclospectrum logic, I use the harness with some inputs like this:

RunTests({
  .name = "CyclospectrumTest",
  .logic = [](std::string input) {
      auto m = PeptideToMasses(input, INTEGER_MASS_TABLE);
      return StrJoin(Cyclospectrum(m));
    },
    .cases = {
      {.desc = "Empty", .in = "", .want = "0"},
      {.desc = "Single", .in = "N", .want = "0 114"},
      {.desc = "Single", .in = "L", .want = "0 113"},
      {.desc = "Single", .in = "E", .want = "0 129"},
      {.desc = "Pair", .in = "NL", .want = "0 113 114 227"},
      {.desc = "Triple(rot0)", .in = "NLE", .want = "0 113 114 129 227 242 243 356"},
      {.desc = "Triple(rot1)", .in = "ENL", .want = "0 113 114 129 227 242 243 356"},
      {.desc = "Triple(rot2)", .in = "LEN", .want = "0 113 114 129 227 242 243 356"},
    }
});

Running the above cell will provide some outputs like this:

Testing: CyclospectrumTest
Empty->PASS
Single->PASS
Single->PASS
Single->PASS
Pair->PASS
Triple(rot0)->PASS
Triple(rot1)->PASS
Triple(rot2)->PASS

There are certain C++ features that don’t seem to work for me in cling; for example, static data in functions has not worked for me yet (which makes sense, I think).

My biggest tip is: if cling repeatedly fails to compile/evaluate a cell (Function definition not allowed here, or various type errors), the kernel may have gotten corrupted / in a bad state, so just use Kernel -> Restart And Run All Cells.

Shared libraries

To build a shared library using clang++, you can run a command like:

clang++ -Wall -Werror -std=c++17 -g -fpic -shared util.cc -olibutil.so

Then, to load the library into your notebook, you can load the library in cling with the following:

#pragma cling load("path/to/the/shared/library/libutil.so")
#include "path/to/the/shared/library/util.h"

Optimization

The “interpreter mode” may will run slowly by default, in -O1. To set the optimization mode, you can try using:

#pragma cling optimize(3)

But, there will be some inherent limitations to using C++ in an interpreted context. Many advanced optimizations are only really possible with more context (code-inlining, de-virtualization, etc).

Further, the wall time to run your notebook may go up, as compilation time in -O3 may begin to dominate the runtime of the cell, if your function is very simple and not computationally intensive.