Понимание законов функторов: это функтор?

Следующий код написан на JavaScript.

Этот вопрос включает в себя попытку погрузиться в какую-то теорию категорий, может, мне поможет хакеллер или кто-то более знакомый с математическими аспектами этого вопроса?

Я пытаюсь понять, что функтор - это отображение между категориями, которое сохраняет структуру. Точнее говоря, согласно моему пониманию, функтор в языке программирования является эндофунктором. Под этим подразумевается, что функторы в языках программирования - это морфизмы, которые отображают типы и функции в подкатегории в более широкой категории типов и функций, как это определено в целом в языке программирования.

Насколько мне известно, функторы (или эндофункторы) также должны придерживаться определенных законов, которые позволяют сохранять структуру посредством облегчения композиции и идентичности.

Я считаю почти невозможным создать функтор, который, как я чувствую, сохраняет структуру и придерживается законов функторов. Это усугубляется тем фактом, что я серьезно программировал только на javascript, поэтому о теории типов я никогда не задумывался (в конце концов, JS нетипизирован).

Я сделал этот простой пример, который поднимает целые числа в минимальный контекст, где сопоставления не будут работать с четными числами. Другими словами, вы можете отобразить свою композицию, но как только вы нажмете четное число, шоу закончится.

Это похоже на "Возможно":

class noEvens {
  constructor(x) {
    this._val = x;
  }
  static of(x) {
    return new noEvens(x);
  }
  isEven() {
    return this._val % 2 === 0;
  }
  map(projF) {
    return this.isEven() ? noEvens.of(null) : noEvens.of(projF(this._val));
  }
}

Но очевидно, что это не будет заменять композиции, применяемые к целым числам в обычной категории JS в определенных ситуациях. Рассмотрим функцию проекции, которая просто добавляет единицу к целому числу.

Если я подниму четное число в этот контекст noEvens, а затем добавлю его, то получится пустое число noEvens. Но если я сначала добавлю единицу к четному числу, а затем подниму результат, это приведет к noEvens нечетного числа.

Насколько я понимаю, оба эти пути должны коммутировать по законам функторов. Они явно этого не делают, потому что одинаковые отображения в каждом контексте не приводят к одному и тому же результирующему "noEvens.of(value)" после отмены.

Итак, я думаю, мой вопрос, означает ли это, что это не функтор? Что такого в такой ситуации (типовой или как-то еще), которая заставляет ее вести себя странно?

Я думаю, я просто сбит с толку, потому что кажется, что все, что делает noEvens, это поднимает значения в новый контекст (подкатегорию, что угодно), где четные числа не существуют, но очевидно, что некоторые пути не будут ездить.

Я нахожу идею "перенести значение" в новый контекст отображения довольно интуитивно понятной, и она предоставляет вам множество возможностей для работы с условиями без необходимости реализации тонны избыточного кода. Но я не хочу делать это под ложным предлогом, что я придерживаюсь какой-то формализованной системы "законов функторов".

Что такого в системах типов и законах функторов, которые я упускаю при анализе этой ситуации?

1 ответ

Решение

В дополнение к моему комментарию...

Вы можете заметить, что ваш почти функторный класс также не соответствует закону об идентичности.

const id = x => x;

new noEvens(2).map(id) // != new noEvens(2)

Моей первой мыслью было, что ошибка состояла в том, что сначала был создан объект noEvens, содержащий четное число. Если вместо этого в конструкторе была сделана проверка isEven, то вы могли бы удовлетворить закон ID.

class noEvens {
  constructor(x) {
    if (x % 2 === 0) {
      this._val = null;
    } else {
      this._val = x;
    }
  }
  static of(x) {
    return new noEvens(x);
  }
  map(projF) {
    if (this._val === null) {
      return noEvens.of(null);
    }

    return noEvens.of(projF(this._val));
  }
}

const id = x => x;

new noEvens(1).map(id)._val // ok: 1
new noEvens(2).map(id)._val // ok: null

Но оказывается, что это решение все еще не удовлетворяет закону композиции.

const plusOne = x => x + 1;

// fmap f . fmap g == fmap (f . g) ?
new noEvens(1).map(plusOne).map(plusOne)._val // null
new noEvens(1).map(x => plusOne(plusOne(x)))._val // 3

В конечном счете, я считаю, что фатальный недостаток заключается в том, что noEvens ограничивает, какие данные он может хранить. Как сказал Берги, "обычный функтор мог бы содержать любые произвольные данные". Так что noEvens, по своей сути, как концепция, не может быть функтором, который подчиняется закону композиции.

Другие вопросы по тегам