The missing lazy numbers: implementation

Posted on November 19, 2022 by Stéphane Laurent
Tags: R, C++, Rcpp, maths, haskell

In Haskell, for any type T (for example Double), there is a corresponding type Maybe T. An object of this type either has form Just x, where x is of type T, or is Nothing. Thus one can use the Maybe T type to extend the type T to a type allowing missing values. One can test whether a Maybe T object y is not “missing” with isJust y, and, if so, one can extract its T value with fromJust y, which returns the object x of type T such that y equals Just x. There is also the function isNothing, whose name is explicit.

There is something similar in C++, available since C++ 17: the type std::optional<T>. I used it with Rcpp to allow missing values in vectors of lazy numbers.

The lazyNumber type is defined as follows in C++:

typedef CGAL::Quotient<CGAL::MP_Float> Quotient;
typedef CGAL::Lazy_exact_nt<Quotient>  lazyNumber;

Then one can define the maybeLazyNumber type:

typedef std::optional<lazyNumber>      maybeLazyNumber;

and the type lazyVector to deals with vectors of (maybe) lazy numbers:

typedef std::vector<maybeLazyNumber>   lazyVector;

Now, what is the equivalent of the Haskell elements Just x and Nothing and the functions isJust, isNothing, and fromJust?

Well, Nothing corresponds to std::nullopt. The equivalent of isJust y is obtained as follows. A maybeLazyNumber object y is either std::nullopt or a pointer to a lazyNumber object. To test whether y points to a lazyNumber object x, one simply does if(y). And if so, the lazyNumber object x is nothing but *y. Thus the equivalent of the Haskell command fromJust y in C++ is *y.

For example, here is the implementation of the C++ function which converts a vector of double numbers with possible missing values to a vector of (maybe) lazy numbers:

lazyVector doubleVector_to_lazyVector(Rcpp::NumericVector dvector) {
  int n = dvector.size();
  lazyVector lvector;
  lvector.reserve(n);
  for(int i = 0; i < n; i++) {
    if(Rcpp::NumericVector::is_na(dvector(i))) {
      lvector.emplace_back(std::nullopt);
    } else {
      lvector.emplace_back(lazyNumber(dvector(i)));
    }
  }
  return lvector;
}

And here is the function performing the conversion in the other direction:

Rcpp::NumericVector lazyVector_to_doubleVector(lazyVector lvector) {
  int n = lvector.size();
  Rcpp::NumericVector dvector(n);
  for(int i = 0; i < n; i++) {
    maybeLazyNumber y = lvector[i];
    if(y) {
      dvector(i) = Rcpp::NumericVector::get_na();
    } else {
      dvector(i) = CGAL::to_double<Quotient>((*y).exact());
    }
  }
  return dvector;
}