Я пытался реализовать тип Apply / Applicative на основе Javascript Functor, Applicative, монад в изображениях и серии блогов Fantas, Eel и Specification .
Я думаю, что у меня хороший прогресс, но я столкнулся с делом, которое не смог найти ни в одной из статей.
TL; DR Вопрос
Если lift2
это
f -> A.of(x) -> A.of(y) -> A.of(f (x) (y)) -or- A(y).ap(A(x).map(f))
что такое теория / имя / тип позади
A.of(f) -> A.of(x) -> A.of(y) -> A(f (x) (y)) -or- A(y).ap(A(x).ap(A(f)))
Введение
Я работаюс knockout.js , что дает мне наблюдаемые значения.Я пытаюсь использовать их неким функциональным образом, расширяя их по мере необходимости.
Сначала я реализовал map
, чтобы сделать себя функтором :
ko.subscribable.fn.map = function(f) {
// note: calling without arguments is knockout's
// way of "unwrapping"
return ko.pureComputed(
() => f(this())
);
}
Это позволяет мне делать такие вещи, как:
// "Pure" part describing my app
const myValue = ko.observable(2);
const doubleThat = myValue.map(x => x * 2);
// In- and output (IO?)
doubleThat.subscribe(console.log);
myValue(3); // Logs 6
Затем я столкнулся с проблемой работы с функциями, которые принимают несколько аргументов.Например:
const filter = (pred, xs) => xs.filter(pred);
Я решил свои проблемы, реализовав ap
и выделив свои функции:
ko.subscribable.fn.ap = function(sf) {
return ko.pureComputed(
() => sf () (this())
);
};
const filter = pred => xs => xs.filter(pred);
С этими изменениями я могу сделать:
const odd = x => x % 2 === 1;
const myPred = ko.observable(odd);
const myValues = ko.observable([ 1, 2, 3 ]);
const myFilter = myPred.map(filter);
const myResult = myValues.ap(filter); // S([ 1, 3 ])
Определение lift2
дает мне другой способ написать то же самое.
const myResult = lift2 (filter) (myPred) (myResult)
Пока все хорошо.Я могу использовать точечные вызовы, если промежуточный результат можно использовать повторно, и вызов liftN
, если я забочусь только о конечном результате.
Проблема
Способ цепочки liftN
один map
с N - 1
вызовами ap
работает, только если я использую простые функции.Однако в моем приложении я часто имею дело с функциями, которые сами по себе обернуты в подписку!Например:
const sum = x => y => x + y;
const mathStrategy = ko.observable(sum);
const v1 = ko.observable(2);
const v2 = ko.observable(3);
Мои попытки
Цепочка работает, но быстро становится очень трудно понять.
// Ugly...
const myResult = v2.ap(v1.ap(mathStrategy)); // S(5)
Я могу использовать liftN
, но только еслиЯ уверен, что моя первая функция - id
.
// Also ugly...
const id = x => x;
const myResultL = lift3 (id) (mathStrategy) (v1) (v2); // S(5)
Мои вопросы
- Если
lift2
обрабатывает f -> A.of(x) -> A.of(y) -> A.of(f (x) (y))
, что за теория / имя / тип позади A.of(f) -> A.of(x) -> A.of(y) -> A(f (x) (y))
- Если такая вещь на самом деле не «существует», будет ли хорошо написать реализацию
ap
, которая разворачивает A(f)
на ходу?(т.е. f => ko.unwrap (f) (x)
)
Пример кода
Object.assign(ko.subscribable, {
of: function(x) {
return ko.pureComputed(() => x)
}
});
Object.assign(ko.subscribable.fn, {
map: function(f) {
return ko.pureComputed(() => f(this()));
},
ap: function(sf) {
return ko.pureComputed(() => sf () (this()));
},
toString: function() {
return `S(${JSON.stringify(this())})`;
}
});
// Example code:
const sum = x => y => x + y;
const mult = x => y => x * y;
const mathStrategy = ko.observable(sum);
const v1 = ko.observable(1);
const v2 = ko.observable(3);
const result = v2.ap(v1.ap(mathStrategy));
console.log(result); // S(4)
v1(2);
mathStrategy(mult);
console.log(result); // S(6)
.as-console-wrapper { min-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Примечание. Это как бы связано с вопросом, который я задавал ранее .Тогда я сосредоточился на ленивой распаковке A(x)
и A(y)
, от которой я уже отказался ?.