1

I was trying to build a function that folds and array of dates to the smallest date. I am using fp-ts for this, and I want to do it in a functional way.

I first tried to build it using Monoids, but there was not an easy way to build a min monoid using the existing primitives. Then I tried with semigroups because they seem to have better building blocks for that, and I managed to make it work like this:

const dates = [
  new Date(2020, 1, 1, 1, 1, 1, 1),
  new Date(2021, 1, 1, 1, 1, 1, 1),
  new Date()
];

const minD = semigroup.concatAll(semigroup.min(date.Ord))(new Date())(dates);

Then I also managed to make it work using Monoids but I had to build a Bounded instance for the Date type, which requires both a top and a bottom values

const bdate: bounded.Bounded<Date> = {
  top: new Date(),
  bottom: new Date(0),
  compare: date.Ord.compare,
  equals: date.Ord.equals
};

const minDate = monoid.min(bdate);
const smallestDate = monoid.concatAll(minDate)(dates);

Both solution work to certain degree, but they share a limitation which is that they need initial values to compare to. In the case of the bottom, that's easy, I can just use the first Date the JS Date object is able to support. However, when you expect future dates to be within the array of dates to process, it is impossible to know which value should be the top. In this example I just provided the current date, but that is a bold assumption, it could happen that the minimum date in a list of dates is tomorrow, and instead I will be getting the bottom value.

Is there any functional primitive I am missing that is able to accumulate comparing the next date with the previous and return the smallest one? I wanted to use monoids and/or semigroups because fp-ts has great primitives to build Monoids around data structures, and I was expecting to create some data pipeline processors to compute statistics easily folding data structures with monoids that resemble their shape, but I am stuck with this first issue around dates.

Danielo515
  • 5,996
  • 4
  • 32
  • 66

1 Answers1

1

The reason that monoid and semigroup are forcing you to define boundaries is because they have to handle an implicit "empty" case. If you passed in an empty list of dates, what value should the concat function return?

It sounds to me like you know that you have a NonEmptyArray of Dates in which case the problem becomes very easy to solve using the helpers from that section of the library. For example:

import { pipe } from "fp-ts/lib/function";
import * as NEA from "fp-ts/lib/NonEmptyArray";
import { Ord as DateOrd } from "fp-ts/lib/Date";

const dates: NEA.NonEmptyArray<Date> = [
  new Date(2020, 1, 1, 1, 1, 1, 1),
  new Date(2021, 1, 1, 1, 1, 1, 1),
];

const minimum = pipe(
  dates,
  NEA.min(DateOrd),
);

console.log(minimum) // Logs the 2020 date
Souperman
  • 5,057
  • 1
  • 14
  • 39