Как использовать lift, если функция и аргументы «обернуты» - PullRequest
0 голосов
/ 26 сентября 2019

Я пытался реализовать тип 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), от которой я уже отказался ?.

1 Ответ

1 голос
/ 27 сентября 2019

Я думаю, что вопрос, который вы хотели задать, с надлежащими обозначениями для типов:

Если lift2 обрабатывает Apply f => (x -> y -> z) -> f x -> f y -> f z, что за теория / имя стоит за Apply f => f (x -> y -> z) -> f x -> f y -> f z?

Такая функция встречается редко, потому что все просто используют ap или соответствующий инфиксный оператор, как в Haskell

let az = af <*> ax <*> ay

Однако я видел некоторые две ссылки в интернете на то, что он называется ap2 [1] [2] , что имеет смысл, поскольку аналогично lift становится lift2.

Вы можете написать эту вспомогательную функцию как

const ap2 = af => ax => ay => ap(ap(af, ax), ay)

или как вы уже сделали

const ap2 = lift3(id)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...