A curated collection of Monoids and their uses

InstructorBrian Lonsdorf

Share this video with your friends

Send Tweet

A gallery of monoid definitions and some use cases

Sean Omlor
Sean Omlor
~ 4 years ago

Having trouble with the foldMap over the stats List.of. Getting acc.concat is not a function, any ideas? Here's my code: http://bit.ly/2hqhXZ8

Brian Lonsdorf
Brian Lonsdorf(instructor)
~ 4 years ago

Posted on Github, but posting here for others. Data.Either has not implemented concat/traverse yet. Here's our either

const Right = x =>
({
  chain: f => f(x),
  ap: other => other.map(x),
  traverse: (of, f) => f(x).map(Right),
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
  concat: o =>
    o.fold(_ => Right(x),
           y => Right(x.concat(y))),
  inspect: () => `Right(${x})`
})

const Left = x =>
({
  chain: f => Left(x),
  ap: other => Left(x),
  traverse: (of, f) => of(Left(x)),
  map: f => Left(x),
  fold: (f, g) => f(x),
  concat: o =>
    o.fold(_ => Left(x),
           y => o),
  inspect: () => `Left(${x})`
})

const fromNullable = x =>
  x != null ? Right(x) : Left(null)

const tryCatch = f => {
  try {
    return Right(f())
  } catch(e) {
    return Left(e)
  }
}

module.exports = { Right, Left, fromNullable, tryCatch, of: Right }
Sean Omlor
Sean Omlor
~ 4 years ago

Sweeet, thanks! FWIW I also added isRight: true, isLeft: false to Right and vice versa to Left in order to get First and find working. Really enjoying this course, thank you.

Hank
Hank
~ 2 years ago

Hi, I am having some doubts on the foldMap example that returns Left(null) on the video, should it not be Right(50) since the .concat on Right is defined as: concat: o => o.fold(_ => Right(x), y => Right(x.concat(y))) ?

From what I understand, this is happening because the accumulator at that point will have Right(50) as a value, so when calling .concat with a Left(null) it will keep the value of x that it is currently holding.

Here is a working demo:

console.log(
  Right(Sum(0))
    .concat(Right(Sum(40)))
    .concat(Right(Sum(10)))
    .concat(Left(null))
    .fold(e => `Left(${e})`, s => `Sum(${s.x})`)
);
Hank
Hank
~ 2 years ago

Sorry, I double checked the video implementation and the definition of Right.concat in it returns a Left(e) when doing Right(50).concat(Left(null)). The one Brian posted is the one that only returns Rights for Right.concat on the .concat.

Is that a matter of choice or is there a standard?

Jan Hesters
Jan Hesters
~ 2 years ago

There is no transcript, so here are all the examples:

const util = require("util");
import { List } from "immutable-ext";

const fromNullable = x => (x != null ? Right(x) : Left(null));

// Example 1
const Sum = x => ({
  x,
  concat: ({ x: y }) => Sum(x + y),
  [util.inspect.custom]: () => `Sum(${x})`
});

Sum.empty = () => Sum(0);

// Example 2
const Product = x => ({
  x,
  concat: ({ x: y }) => Product(x * y),
  [util.inspect.custom]: () => `Product(${x})`
});

Product.empty = () => Product(1);

// Example 3
const Any = x => ({
  x,
  concat: ({ x: y }) => Any(x || y),
  [util.inspect.custom]: () => `Any(${x})`
});

Any.empty = () => Any(false);

// Example 4
const All = x => ({
  x,
  concat: ({ x: y }) => All(x && y),
  [util.inspect.custom]: () => `All(${x})`
});

All.empty = () => All(true);

// Example 5
const Max = x => ({
  x,
  concat: ({ x: y }) => Max(x > y ? x : y),
  [util.inspect.custom]: () => `Max(${x})`
});

Max.empty = () => Max(-Infinity);

// Example 6
const Min = x => ({
  x,
  concat: ({ x: y }) => Min(x < y ? x : y),
  [util.inspect.custom]: () => `Min(${x})`
});

Min.empty = () => Min(Infinity);

// Example 7
const Right = x => ({
  chain: f => f(x),
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
  concat: o => o.fold(e => Left(e), r => Right(x.concat(r))),
  [util.inspect.custom]: () => `Right(${x})`
});

const Left = x => ({
  chain: f => Left(x),
  map: f => Left(x),
  fold: (f, g) => f(x),
  concat: o => Left(x),
  [util.inspect.custom]: () => `Left(${x})`
});

const stats = List.of(
  { page: "Home", views: 40 },
  { page: "About", views: 10 },
  { page: "Blog", views: null }
);

stats.foldMap(x => fromNullable(x.views).map(Sum), Right(Sum(0)));
// Left(null)

// Example 8
const First = either => ({
  fold: f => f(either),
  concat: o => (either.isLeft ? o : First(either)),
  [util.inspect.custom]: () => `Min(${x})`
});

First.empty = () => First(Left());

const find = (xs, f) =>
  List(xs)
    .foldMap(x => First(f(x) ? Right(x) : Left()), First.empty())
    .fold(x => x);

find([3, 4, 5, 6, 7], x => x > 4);
// Right(5)

// Example 9
const Fn = f => ({ fold: f, concat: o => Fn(x => f(x).concat(o.fold(x))) });

const hasVowels = x => !!x.match(/[aeiou]/gi);
const longWord = x => x.length >= 5;
const both = Fn(
  compose(
    All,
    hasVowels
  )
).concat(
  Fn(
    compose(
      All,
      longWord
    )
  )
);

[("gym", "bird", "lilac")].filter(x => both.fold(x).x);
// [lilac]

// Example 10
const Pair = (x, y) => ({
  x,
  y,
  concat: ({ x: x1, y: y1 }) => Pair(x.concat(x1), y.concat(y1)),
  [util.inspect.custom]: () => `Pair(${x}, ${y})`
});