Exercising Ranges (part 7)

(Start at the beginning of the series if you want more context.)

Calculus operations

It’s relatively simple to do differentiation and integration of power series, since they’re just regular polynomial-style. No trigonometric identities, logarithms or other tricky integrals here. Just the plain rule for positive powers of x. For differentiation, we have:

\frac{d}{dx}kx^{n} = knx^{n-1}

Which is straightforwardly expressed with ranges: we simply take the tail of the power series and pairwise multiply with n, the power:

template <typename Rng>
auto differentiate(Rng&& r)
{
  return ranges::view::zip_with(
      std::multiplies<>(),
      ranges::view::iota(1),
      ranges::view::tail(std::forward<Rng>(r)));
}

Integration is almost as easy. The fly in the ointment is that up until now, we have been working entirely with integers, or whatever underlying arithmetic type is present in the range. Here is where Haskell wins with its built in rational numbers and polymorphic fromIntegral for conversion from integral types. Integration brings division into the picture:

\int_{}{} kx^n dx = \frac{kx^{n+1}}{n+1}

Which means we need to turn a range of ints into a range of floats or doubles. Or use a rational number library. But anyway, sweeping these concerns under the rug, the actual code for integration is as easy as that for differentiation:

template <typename Rng>
auto integrate(Rng&& r)
{
  return ranges::view::concat(
      ranges::view::single(0),
      ranges::view::zip_with(
          [] (auto x, auto y) { return (float)x / (float)y; },
          std::forward<Rng>(r),
          ranges::view::iota(1)));
}

We prepend a zero as the constant of integration.

Calculus: in real life, more difficult than arithmetic, but with ranges, considerably easier. That was a welcome break from extreme mental effort. In the next part, I round out my range experiments for now, with a look at a couple more functions from the Haskell Prelude.

Leave a Reply