Это монада?

Я пытаюсь понять концепцию монадов, и я хочу знать, является ли этот код реализацией этой концепции (в JavaScript).

У меня есть функция M, которая возвращает новый объект, который установил метод, который создает метод обёртки

var foo = M().set('getX', function() { 
  return this.x; 
}).set('setX', function(x) { 
  this.x = x;
}).set('addX', function(x) { 
  this.x += x;
});

А потом я могу связать метод foo

foo.setX(10).addX(20).addX(30).getX()

вернет 60

и то же самое, если у меня есть объект с методами и вызовите M с этим объектом.

var foo = {
  x: 10,
  add: function(x) {
    this.x += x;
  }
};

M(foo).add(10).add(20).add(30).x

вернет 70

Функции обернуты внутри объекта M, поэтому этот контекст внутри метода всегда является объектом M.

f = M({x: 20}).set('getX', function() {
   return this.x; 
}).set('addX', function(x) {
   this.x += x;
}).addX(10).getX

так что f - это функция с контекстом объекта, обернутого M - если я вызываю f(), он вернет 30.

Я правильно понимаю это? Является ли монада монахом?

EDIT изменен код на github https://github.com/jcubic/monadic

Ответ 1

Это моноидный шаблон. Каждая операция обновления состояния, такая как .setX(10), .addX(20) и т.д., Представляет собой вычисление, которое преобразует один объект. (Чтобы быть синтаксически корректным, вам нужно написать его как однопараметрическую функцию function(x) {x.addX(20);}, но я думаю, что яснее, если я использую короткую форму.)

Две вещи делают это моноидом. Во-первых, существует элемент идентификации: .addX(0) ничего не делает для своего объекта. Во-вторых, любые две операции могут быть объединены. Например, .setX(10).addX(20) - это также вычисление, которое преобразует один объект.

Это не монада. Вычисления, поддерживаемые вашими методами, ограничены написанием и обновлением this.x. (.getX() не является членом моноида, потому что после него вы ничего не можете связать). Например, с монадой вы можете иметь одного члена цепочки операций, чтобы выполнить if-then-else, чтобы решить, что будет дальше в цепочке. Ваши методы не могут этого сделать.

Ответ 2

Отменяемость; к моему пониманию, то, что вы написали, ближе к прикладному функтору, чем монада или моноид.

Опять же, насколько я понимаю, моноид - это группа (в абстрактном алгебраическом смысле), замкнутая под одной операцией, отображающая один тип для себя. Если бы вы реализовали только add, вы могли бы сказать, что ваша цепочка прототипов реализовала моноид. Но даже тогда вам придется вручную указывать сокращение вручную, как двоичную операцию, между каждым и каждым аргументом, например:

M({x:0}).add(1).add(2)...add(100) === 1050; // or _.reduce([1..100],add)

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

Если вы нашли какой-то способ для компоновки областей всех функций, работающих на M, то вы по-прежнему будете ближе к монадической реализации:

var bigOpFromLittleOps = 
       M({x:0})  .bind(function(x0){
return Madd(1)   .bind(function(x1){
return Madd(2)   .bind(function(x2){
...
return Madd(100) .bind(function(x100){
return Mreturn(x100);
}); ... });});})() === 1050; // Overkill

Такие реализации сложны, но дают вам возможность срезать и нарезать их на маленькие кусочки и/или составлять более крупные из меньших.