Подтвердить что ты не робот

Невозможно обернуть голову вокруг "подъема" в Ramda.js

Глядя на источник для Ramda.js, особенно на функцию "лифта".

lift

liftN

Здесь приведен пример:

var madd3 = R.lift(R.curry((a, b, c) => a + b + c));

madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

Итак, первое число результата легко, a, b и c - все первые элементы каждого массива. Второй мне не так легко понять. Являются ли аргументы вторым значением каждого массива (2, 2, undefined) или это второе значение первого массива и первые значения второго и третьего массива?

Даже не учитывая порядок происходящего здесь, я действительно не вижу значения. Если я выполнил это без lift ing, сначала в итоге получим массивы concat, включенные как строки. Кажется, что это похоже на работу flatMap, но я не могу следовать логике, стоящей за ней.

4b9b3361

Ответ 1

Ответ Берги велик. Но еще один способ подумать об этом - это немного подробнее. Рамде действительно нужно включить в свою документацию пример, не являющийся списком, поскольку списки на самом деле не фиксируют это.

Возьмем простую функцию:

var add3 = (a, b, c) => a + b + c;

Это работает на трех числах. Но что, если бы у вас были контейнеры с номерами? Возможно, у нас Maybe. Мы не можем просто добавить их вместе:

const Just = Maybe.Just, Nothing = Maybe.Nothing;
add3(Just(10), Just(15), Just(17)); //=> ERROR!

(Хорошо, это Javascript, на самом деле он не будет вызывать ошибку здесь, просто попробуйте конкатенировать то, чего он не должен... но он определенно не делает то, что вы хотите!)

Если бы мы могли поднять эту функцию до уровня контейнеров, это облегчило бы нашу жизнь. То, что Берги указывал как lift3, было реализовано в Рамде с liftN(3, fn) и блеском lift(fn), который просто использует арность поставленной функции. Итак, мы можем сделать:

const madd3 = R.lift(add3);
madd3(Just(10), Just(15), Just(17)); //=> Just(42)
madd3(Just(10), Nothing(), Just(17)); //=> Nothing()

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

madd3([100, 200], [30, 40], [5, 6, 7]);
//=> [135, 136, 137, 145, 146, 147, 235, 236, 237, 245, 246, 247]

Вот как я думаю о lift. Он выполняет функцию, которая работает на уровне некоторых значений и поднимает ее до функции, которая работает на уровне контейнеров этих значений.

Ответ 2

lift/liftN "поднимает" обычную функцию в аппликативный контекст.

// lift1 :: (a -> b) -> f a -> f b
// lift1 :: (a -> b) -> [a] -> [b]
function lift1(fn) {
    return function(a_x) {
        return R.ap([fn], a_x);
    }
}

Теперь тип ap (f (a->b) -> f a -> f b) тоже нелегко понять, но пример списка должен быть понятным.

Интересно, что вы переходите в список и возвращаете список, поэтому вы можете повторно применять его, если функции в первом списке имеют правильный тип:

// lift2 :: (a -> b -> c) -> f a -> f b -> f c
// lift2 :: (a -> b -> c) -> [a] -> [b] -> [c]
function lift2(fn) {
    return function(a_x, a_y) {
        return R.ap(R.ap([fn], a_x), a_y);
    }
}

И lift3, который вы неявно использовали в вашем примере, работает одинаково - теперь с ap(ap(ap([fn], a_x), a_y), a_z).

Ответ 3

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

Вот пример R.lift, который мы пытаемся понять:

var madd3 = R.lift((a, b, c) => a + b + c);
madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]

Для меня есть три вопроса, на которые нужно ответить, прежде чем понимать его.

  • Fantasy-land Apply spec (Я буду называть его Apply) и что Apply#ap делает
  • Ramda R.ap реализация и что Array имеет отношение к Apply spec
  • Какую роль играет карри в R.lift

Понимание Apply spec

В fantasy-land объект реализует Apply spec, если он имеет метод ap, определенный (этот объект также должен Внесите Functor spec, определив метод map).

Метод ap имеет следующую подпись:

ap :: Apply f => f a ~> f (a -> b) -> f b

В обозначение подписи типа fantasy-land:

  • => объявляет ограничения типа, поэтому f в сигнатуре выше относится к типу Apply
  • ~> объявляет декларацию метода, поэтому ap должна быть функцией, объявленной в Apply, которая обтекает значение, которое мы называем a (мы увидим в примере ниже, некоторые реалии фантазийных земель ap не согласуются с этой сигнатурой, но идея та же)

Скажем, у нас есть два объекта v и u (v = f a; u = f (a -> b)), поэтому это выражение действительно v.ap(u), некоторые вещи, которые можно заметить здесь:

  • v и u оба реализуют Apply. v содержит значение, u содержит функцию, но имеет тот же "интерфейс" Apply (это поможет понять следующий раздел ниже, когда дело доходит до R.ap и Array)
  • Значение a и функция a -> b не знают о Apply, функция просто преобразует значение a. Это Apply, который помещает значение и функцию внутри контейнера и ap, который извлекает их, вызывает функцию по значению и помещает их обратно.

Понимание Ramda R.ap

Подпись R.ap имеет два случая:

  • Apply f => f (a → b) → f a → f b: Это очень похоже на подпись Apply#ap в последнем разделе, разница заключается в том, как вызывается ap (Apply#ap vs. R.ap) и порядок параметров.
  • [a → b] → [a] → [b]: Это версия, если мы заменим Apply f на Array, помните, что значение и функция должны быть завернуты в один и тот же контейнер в предыдущем разделе? Поэтому при использовании R.ap с Array s первым аргументом является список функций, даже если вы хотите применить только одну функцию, поместите его в массив.

Посмотрим на один пример: я использую Maybe из ramada-fantasy, который реализует Apply, одно несоответствие здесь заключается в том, что Maybe#ap подпись: ap :: Apply f => f (a -> b) ~> f a -> f b. Кажется, что некоторые другие реализации fantasy-land также следуют за этим, однако это не должно влиять на наше понимание:

const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const a = Maybe.of(2);
const plus3 = Maybe.of(x => x + 3);
const b = plus3.ap(a);  // invoke Apply#ap
const b2 = R.ap(plus3, a);  // invoke R.ap

console.log(b);  // Just { value: 5 }
console.log(b2);  // Just { value: 5 }

Понимание примера R.lift

В R.lift пример с массивами, функция с arity of 3 передается в R.lift: var madd3 = R.lift((a, b, c) => a + b + c);, как это сделать он работает с тремя массивами [1, 2, 3], [1, 2, 3], [1]? Также обратите внимание, что он не нарисован.

На самом деле внутри исходный код R.liftN (который R.lift делегирует), переданная функция автоматически загружается, затем он итерации через значения (в нашем случае, три массива), сводя к результату: на каждой итерации он вызывает ap с помощью функции curried и одного значения (в нашем случае одного массива). Трудно объяснить словами, посмотрим эквивалент в коде:

const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const madd3 = (x, y, z) => x + y + z;

// example from R.lift
const result = R.lift(madd3)([1, 2, 3], [1, 2, 3], [1]);

// this is equivalent of the calculation of 'result' above,
// R.liftN uses reduce, but the idea is the same
const result2 = R.ap(R.ap(R.ap([R.curry(madd3)], [1, 2, 3]), [1, 2, 3]), [1]);

console.log(result);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]
console.log(result2);  // [ 3, 4, 5, 4, 5, 6, 5, 6, 7 ]

Как только выражение вычисления result2 будет понято, пример станет ясным.

Вот еще один пример, используя R.lift on Apply:

const R = require('ramda');
const Maybe = require('ramda-fantasy').Maybe;

const madd3 = (x, y, z) => x + y + z;
const madd3Curried = Maybe.of(R.curry(madd3));
const a = Maybe.of(1);
const b = Maybe.of(2);
const c = Maybe.of(3);
const sumResult = madd3Curried.ap(a).ap(b).ap(c);  // invoke #ap on Apply
const sumResult2 = R.ap(R.ap(R.ap(madd3Curried, a), b), c);  // invoke R.ap
const sumResult3 = R.lift(madd3)(a, b, c);  // invoke R.lift, madd3 is auto-curried

console.log(sumResult);  // Just { value: 6 }
console.log(sumResult2);  // Just { value: 6 }
console.log(sumResult3);  // Just { value: 6 }

Лучший пример, предложенный Скоттом Сауитом в комментариях (он дает довольно много прозрений, я предлагаю вам их прочитать) было бы легче понять, по крайней мере, он указывает читателю на то направление, в котором R.lift вычисляет декартово произведение для Array с.

var madd3 = R.lift((a, b, c) => a + b + c);
madd3([100, 200], [30, 40, 50], [6, 7]); //=> [136, 137, 146, 147, 156, 157, 236, 237, 246, 247, 256, 257]

Надеюсь, что это поможет.