Использование подъема с монадой «Или» или «Может быть»

Когда я прочитал о концепции лифта, она реализована так (на Javascript)

const liftA2 = f => (a, b) => b.ap(a.map(f));

Я понимаю, что есть случай, когда liftA2 вызовет ошибку: когда b — это Right/Just, а a — это Left/Nothing, потому что сопоставление Left/Nothing ничего не сделает со значением, когда нам нужно, чтобы оно стало частично прикладная функция.

Когда a и b оба являются Left, это не взрывается, но, конечно, значение возвращаемого Left будет значением b, что, я полагаю, может быть проблемой в зависимости от того, что вы ожидаете.

Стоит ли поднимать функцию для использования с этими типами? Должен ли я систематически защищаться от таких случаев перед использованием такой функции? Является ли приведенная выше реализация правильной/полной?


Подробнее о проблеме вы найдете ниже

Let us define the function to lift
const add = a => b => a + b;

В случае базового Wrapper, реализующего of, ap и map, мы можем следить за тем, что происходит

class Wrapper {
    constructor(value) {
        this.value = value;
    }
    static of(value) { return new Wrapper(value); }
    map(f) { return Wrapper.of(f(this.value)); }
    ap(a) { return this.map(a.value); }
}

const a = Wrapper.of(1);
const b = Wrapper.of(2);

// liftA2
const tmp = a.map(add); // Wrapper { λ }
b.ap(tmp); // Wrapper { 3 }

Но проблема с Either или Maybe в том, что у них есть случай Left/Nothing, где map и ap предназначены для того, чтобы не делать ничего особенного.

class Left {
    constructor(value) {
        this.value = value;
    }
    static of(value) { return new Left(value); }
    map(f) { return this; }
    ap(a) { return this; }
}

class Right{
    constructor(value) {
        this.value = value;
    }
    static of(value) { return new Right(value); }
    map(f) { return Right.of(f(this.value)); }
    ap(a) { return this.map(a.value); }
}

const a = Left.of(1);
const b = Right.of(2);

// liftA2 
const tmp = a.map(add); // Left { 1 }
b.ap(tmp); // Error because tmp's value is not a function

person geoffrey    schedule 26.07.2020    source источник
comment
Я не понимаю, что вы подразумеваете под вызовет ошибку - вы говорите, что он возвращает значение Left/Nothing, которое было передано для a или b, или вы имеете в виду, что он генерирует исключение ( взорвется) и программа не работает что ли?   -  person Bergi    schedule 26.07.2020
comment
Я полагаю, что может быть проблема в зависимости от того, что вы ожидаете. - вы имеете в виду, что люди ожидают значение a, когда оба Left? Это зависит от реализации ap и должно быть задокументировано там. Вы все еще можете перевернуть свою функцию, чтобы поменять местами результаты.   -  person Bergi    schedule 26.07.2020
comment
Нет ошибки. ap не ожидает частично примененной функции. Он ожидает частично примененную функцию в контексте. С Maybe контекст представляет собой вычисление, которое может не дать результата. Не только a/b может быть Nothing, но и результатом map.   -  person Iven Marquardt    schedule 26.07.2020
comment
Я обновил свой вопрос с более подробной информацией. Когда я говорю о value, я имею в виду значение внутри контекста: this.value в реализации, которую я только что добавил.   -  person geoffrey    schedule 26.07.2020
comment
Проблема с вашим образцом кода в том, что Right.prototype.ap не работает. Ему нужно различать аргумент a на Left и Right, он не может просто получить доступ к своему .value.   -  person Bergi    schedule 26.07.2020
comment
@Bergi Берги Я действительно имею в виду исключение в случае, когда b является Right, а a - Left. Что касается случая, когда a и b оба являются Left, эта проблема не так велика, как первая, которую я выразил, и я не могу указать пальцем на то, что было бы проблематичным именно с этим результатом, кроме потери контекста, который произвел первый Left (а). Я чувствую, что, вообще говоря, если Left является ошибкой или чем-то еще, вы, вероятно, захотите проверить, можно ли ее восстановить, прежде чем игнорировать ее, или вы можете что-то сделать с ошибкой, чтобы приготовить сообщение об ошибке или зарегистрировать его где-нибудь.   -  person geoffrey    schedule 26.07.2020
comment
@Bergi Берги Я понимаю, что это не работает, но есть ли спецификация того, что ap должен делать в этом случае? должен ли Right.ap возвращать Left, если аргумент a является Left? Должен ли вообще Right знать о типе Left? У меня такое ощущение, что это несовместимо (по крайней мере, эстетически) с этой двуглавой реализацией.   -  person geoffrey    schedule 26.07.2020
comment
@geoffrey Ну, он не может вернуть Right ожидаемого типа вывода, поскольку нет значения для его создания, поэтому он должен вернуть значение Left. В данном конкретном случае это в основном определяется интерфейсом монады.   -  person Bergi    schedule 26.07.2020
comment
@geoffrey Должны ли вообще правые знать о типе левых? - они не отдельные, разные типы, они вообще не типы. Это конструкторы значений для одного и того же типа: Either. Да, любой метод Either должен знать как Left, так и Right. Ваша реализация с двумя classes немного странная в этом отношении. Конечно, вы можете эмулировать сопоставление с образцом с помощью динамической диспетчеризации, но ap нужно сделать это дважды.   -  person Bergi    schedule 26.07.2020
comment
Я думаю, вы указали именно то, что было неправильным в моих рассуждениях. Я путал полиморфизм с типами.   -  person geoffrey    schedule 26.07.2020
comment
Типы могут быть полиморфными. Я думаю, вы скорее перепутали типы, такие как Either<string, number[]>, с конструкторами типов, такими как Either<A, B>, с конструкторами значений, такими как Left/Right. Это может быть довольно запутанным! Последние создают значения, помеченные тегами, чтобы среда выполнения могла их различать. Теги не являются частью уровня типа, но являются частью уровня значения.   -  person Iven Marquardt    schedule 26.07.2020
comment
@scriptum не могли бы вы порекомендовать какую-нибудь литературу по этой теме или ключевые слова для ввода в поисковике? Кажется, мне не хватает серьезных академических основ   -  person geoffrey    schedule 26.07.2020
comment
@geoffrey learnyouahaskell.com/chapters   -  person Bergi    schedule 26.07.2020


Ответы (1)


Мой Javascript немного прост (я его не использую). Но я думаю, как было указано в комментариях, ваша реализация ap не делает то, что вы хотите.

Во-первых, взгляните на этот ответ на аналогичный вопрос о том, что такое подъем. Предполагается взять функцию n параметров и поместить ее в контекст данного функтора/монады, где каждый параметр содержится в этом функторе/монаде.

Другими словами, если f: 'a -> 'b -> 'c (функция, которая принимает два параметра типов 'a и 'b и возвращает результат типа 'c), то мы могли бы использовать lift2 где:

lift2:: ('a -> 'b -> 'c) -> (M['a] -> M['b] -> M['c])

что превратит f в функцию, которая принимает M[a] и M[b] и возвращает M[c] (где M в данном случае — это ваши Either или Maybe).

Поскольку я лучше знаком с F#, я буду использовать его здесь в качестве примера. Если бы мне нужно было реализовать lift2 вместо Option в F# (эквивалентно Maybe), я бы сделал это примерно так:

let lift2 (f: 'a -> 'b -> 'c) : ('a option -> 'b option -> 'c option) =
  let f2 ao bo =
    match ao, bo with
    | Some a, Some b -> Some(f a b)
    | _, _ -> None
  f2

Здесь вы видите, что я сопоставляю оба типа ввода. Оба должны быть Some, чтобы вернуть значение Some. В противном случае я просто возвращаю None. В случае Result (эквивалентно Either) мне нужно было бы определить, каким образом сместить совпадение. Это может быть в любом случае с точки зрения того, какое значение Left/Error нужно вернуть.

Используя приведенный выше код в FSI, я получаю следующее:

> let f' = lift2 f;;
val f' : (int option -> int option -> int option)

> f' (Some(2)) (Some(3));;
val it : int option = Some 5

> f' (Some(2)) None;;
val it : int option = None

> f' None (Some(3));;
val it : int option = None
person melston    schedule 27.07.2020