#functional-programming #fp-ts
#функциональное программирование #fp-ts
Вопрос:
У меня есть два предиката
interface Foo {}
interface Bar {}
declare const isFoo: (a:unknown):a is Foo
declare const isBar: (a:unknown):a is Bar
Каков функциональный способ объединения двух предикатов для создания нового предиката (для простоты предположим, что это a => isFoo(a) amp;amp; isBar(a)
?
С fp-ts
помощью, я изначально думал, что смогу fold(monoidAll)([isFoo, isBar])
, но fold
ожидает, что массив будет состоять из логических значений, а не из функций, которые оцениваются как логические.
Это работает
import { monoid as M, function as F, apply as A, identity as I, reader as R } from 'fp-ts'
interface Foo{}
interface Bar{}
declare const isFoo:(a:unknown) => a is Foo
declare const isBar:(a:unknown) => a is Bar
const isFooAndBar = F.pipe(A.sequenceT(R.reader)(isFoo, isBar), R.map(M.fold(M.monoidAll)))
Но, мальчик, как это запутанно. Я думал, что может быть другой способ. В итоге я написал свой собственный моноид, который принимает два предиката и объединяет их, вызывая его monoidPredicateAll
:
const monoidPredicateAll:M.Monoid<Predicate<unknown>> = {
empty: ()=>true,
concat: (x,y) => _ => x(_) amp;amp; y(_)
}
Существует ли канонический FP-способ объединения двух предикатов? Я знаю, что мог бы сделать что-то вроде
xs.filter(x => isFoo(x) amp;amp; isBar(x))
Но это может усложниться с большим количеством предикатов, а повторное использование моноида снижает вероятность того, что я сделаю опечатку, например isFoo(x) || isBar(x) amp;amp; isBaz(x)
, когда я имел в виду все amp;amp;
(и именно здесь a xs.filter(fold(monoidPredicateAll)(isFoo,isBar,isBaz))
помогло бы.
Я нашел обсуждение этого на SO, но оно касалось Java и встроенного Predicate
типа, поэтому напрямую не касалось моего вопроса.
Да, я слишком много думаю об этом 🙂
Комментарии:
1. Да, использование экземпляра monoid — лучшее решение вашей проблемы. И да, ваш экземпляр monoid для предикатов верен. hackage.haskell.org/package/base-4.14.0.0/docs/src /…
2. На всякий случай, если вы этого не знаете, вот статья о контравариантных функторах, которая включает раздел о
Predicate
типе, где автор составляет сложные предикаты из простых. Он включает моноиды и контравариантные функторы, оба из которых очень полезны для такого типа.
Ответ №1:
В итоге я сделал это:
export const monoidPredicateAll:Monoid<Predicate<unknown>> = {
empty: ()=>true,
concat: (x,y) => _ => x(_) amp;amp; y(_)
}
Тогда я мог бы сделать
import {monoid as M} from 'fp-ts'
declare const isFoo: Predicate<number>
declare const isBar: Predicate<number>
const isFooAndBar = M.fold(monoidPredicateAll)([isFoo,isBar])
Комментарии:
1. Ваш пример выдает ошибку компиляции
Type 'Predicate<number>' is not assignable to type 'Predicate<unknown>'.
Не могли бы вы привести пример / обновить свой ответ, чтобы сделать моноид универсальным?2. Это не выдает ошибку компиляции. Я только что вставил весь этот код в свою IDE, и в нем нет ошибок. Я подозреваю, что вы неправильно его используете.
Ответ №2:
Для других, ищущих рабочее решение, на основе ответа @user1713450
import * as P from 'fp-ts/lib/Predicate';
import * as M from 'fp-ts/Monoid';
const createMonoidPredicateAll = <T>(): M.Monoid<P.Predicate<T>> => ({
empty: () => true,
concat: (x, y) => (_) => x(_) amp;amp; y(_),
});
export const combine = <T>(predicates: P.Predicate<T>[]) =>
M.concatAll(createMonoidPredicateAll<T>())(predicates);
Комментарии:
1. Поскольку вы используете только T в возвращаемом типе, в этом нет необходимости.
unknown
работает, и у вас что-то настроено неправильно на вашем конце, если это не работает для вас. Дженерики полезны только в том случае, если вы используете их в нескольких местах, чтобы обеспечить их одинаковый тип. Если вы используете только универсальный в одном месте, вы можете заменить его наunknown
.