Сгенерировать предикат из двух предикатов (задание для моноида, fold?)

#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 .