Меня смутило ассоциативное право функтора (Endofunctor) в течение нескольких недель.
Я знаю, что каждый эндофуктор формирует композицию/ассоциативную особенность.
Композиция ассоциативна. В основном это означает, что когда вы составляете несколько функций (морфизмы, если вы чувствуете себя фантазией), вам не нужны скобки:
h∘(g∘f) = (h∘g)∘f = h∘g∘f
Давайте еще раз взглянем на закон композиции в JavaScript:
Учитывая функтор, F:
const F = [1, 2, 3];
Следующие эквиваленты:
F.map(x => f(g(x)));
// is equivalent to...
F.map(g).map(f);
Однако, как показано ниже, код, особенно поздняя часть:
console.log("===================");
const take1 = a => a //any
.map(g)
.map(f)
.map(trace);
это приведет к ошибке типа:
// const r3 = v.map(take1); //TypeError: a.map is not a function
и, безусловно:
const take2 = a => Identity(a) //Identity(any)
.map(trace)
.map(g)
.map(trace)
.map(f)
.map(trace);
const r4 = v.map(take2);
работает.
Я ошибаюсь в функции take2
, необходимо преобразовать тип аргумента: a
to Identity(a)
самом деле не Identity(a)
функции.
Я также понимаю, что Монады должны избегать этой проблемы с композицией, и мне интересно, просто ли это из-за отсутствия "закона права Лоди-права Монадов", а законный ассоциативный закон все еще выполняется или могут быть разные слои ассоциативного законов и функтора, очевидно, выполняется некоторый слой ассоциативного закона, но другой слой ассоциативного закона нарушается, как видно из вышеприведенного примера.
Вы можете уточнить?
Образец кода, конечно, есть в JavaScript, но я по-прежнему отмечаю Haskell, так как сообщество сильное в этой теме, поэтому, пожалуйста, извините меня.
Спасибо.
const trace = x => {
console.log(x);
return x;
};
const Identity = value => ({
map: fn => Identity(fn(value)),
valueOf: () => value,
});
const u = Identity(2);
const f = n => n + 1;
const g = n => n * 2;
// Composition law
const r1 = u
.map(x => f(g(x)));
const r2 = u
.map(g)
.map(f);
r1.map(trace); // 5
r2.map(trace); // 5
console.log("===================");
const take1 = a => a //any
.map(g)
.map(f)
.map(trace);
const v = Identity(100);
// const r3 = v.map(take1); //TypeError: a.map is not a function
const take2 = a => Identity(a) //Identity(any)
.map(trace)
.map(g)
.map(trace)
.map(f)
.map(trace);
const r4 = v.map(take2);
PS/EDIT:
Еще одна причина спросить об этом, если мы просто рассмотрим последовательность функций f/g/h в качестве последовательности данных и вырезаем и вставляем как строки, структура становится:
h∘(g∘f) != (h∘g)∘f != h∘g∘f
без намерений сгладить. Это нарушает ассоциативность, и если только процесс сглаживания, вероятно, по закону прав на левый/правый моноиды/монады делает вещи ассоциативными, являются ли эти законы идентичности и ассоциативные отношения каким-то образом не изолированы друг от друга?
Попробуем разобраться в терминологии.
В теории категорий категория C состоит из объектов и морфизмов (также называемых стрелками). Функтор F между двумя категориями C и D, записанный F: C → D, отображает объекты C в объекты D и морфизмы C в морфизмы D.
Вы можете составлять функторы (очевидным образом).
Композиция функторов ассоциативна: если F: C → D, G: D → E и H: E → F, их состав (который является функтором от C до F) не нуждается в скобках.
Вы также можете составить морфизмы (внутри категории). Состав морфизмов также ассоциативен.
Более того, функтор должен учитывать композицию морфизмов (т.е. F (g∘f) = F (g) ∘F (f)). Это полностью отличается от ассоциативности.
Энтофуктор является функтором из некоторой категории в ту же категорию, F: C → C.
В Javascript нет типов, поэтому, чтобы превратить их в категорию, представьте один объект (какой-то универсальный тип) как единственный объект. Морфизмы - это функции с одним аргументом.
Теперь это
const F = [1, 2, 3];
не является функтором: вы не сказали, как объекты отображаются (хотя выбора не так много), вы не сказали, как отображаются морфизмы (функции Javascript).
Тем не менее, вы можете определить функтор Array для Javascript следующим образом:
а) Наш универсальный тип привязывается к самому себе. (Если Javascript имел типы, мы сопоставляли бы тип t с типом "массивы t").
b) Функция f отображается в функцию из массива в массив, применяя ее "поточечно". Это карта на массивах в Javascript: Array (f) = (x => x.map(f)) (используя =>
для функций).
Обратите внимание, что массив не является чем-либо, что вы можете полностью записать в Javascript.
Теперь этот функтор учитывает композицию морфизмов (функции Javascript) и т.д. И т.д., Так как легко получается. И один промежуточный шаг в этом случае будет состоять в том, что действительно x.map(f).map(g) = x.map(y => g (f (y)) (неформально, не имеет значения, если вы сначала примените f к все элементы массива x, а затем примените g ко всем из них или если вы сразу примените g после f ко всем элементам массива).
Это также было бы ассоциативно, если мы скомпоновали бы его с другими функторами (хотя у нас еще нет примеров для других функторов в Javascript).
На данный момент оставь монады из картины.
Это помогает?
С const r3 = v.map(take1)
вы уже const r3 = v.map(take1)
в один слой map-yness, вы входите в функтор, если хотите. Внутри функтора у вас есть чистые, развернутые значения. Но take1
сам пытается снова использовать map
на этих значениях! Это может работать, но только если значения сами представляют собой значения функтора - например, вложенный Identity
.
Чтобы использовать функцию, которая уже использует map
внутри, просто примените ее к значению functor:
const f = n => n + 1;
const g = n => n * 2;
const Identity = value => ({
map: fn => Identity(fn(value)),
valueOf: () => value,
});
const take1 = a => a //any
.map(g)
.map(f);
const v = Identity(100);
const r3 = take1(v);
console.log(r3.valueOf());
trace
а только отображать конечные значения. (Не то, что Haskellers не используютtrace
для отладки или, скорее,traceShowId
, но только как хак, чтобы узнать о некоторых деталях расхождения значений. Сначала всегда идет этап проверки типов.)