有没有一种毫无意义的方法来做 O.alt?



给定一个值,我想通过两个函数传递它,每个函数都会返回一个Option。我想使用返回的第一个Some

为此,我目前使用如下O.alt

稍微做作的例子:

import { constFalse, pipe } from "fp-ts/function";
import * as O from "fp-ts/Option";
const normalParams = new URLSearchParams("normal=yes");
const otherParams = new URLSearchParams("otherNormal=yes");
const getFromNormal = (params: URLSearchParams): O.Option<string> =>
O.fromNullable(params.get("normal"));
const getFromOther = (params: URLSearchParams): O.Option<string> =>
O.fromNullable(params.get("otherNormal"));
const isNormal = (params?: URLSearchParams): boolean =>
pipe(
params,
O.fromNullable,
O.chain<URLSearchParams, string>((p) =>
pipe(
getFromNormal(p),
O.alt(() => getFromOther(p))
)
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);
console.assert(isNormal(normalParams) === true);
console.assert(isNormal(otherParams) === true);
console.assert(isNormal(undefined) === false);

我希望能够将O.chain部分替换为以下内容:

O.chain<URLSearchParams, string>(
O.alt(getFromNormal, getFromOther)
),

但显然O.alt不能以这种方式工作。但是,我可以使用另一种类型的函数来实现无意义的方法吗?

我最初试图回答这个问题,但有人指出我最初的答案实际上并不是毫无意义的。我又一次尝试让它毫无意义,这就是我最终得到的:

import { constFalse, flow } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/Reader";
import * as M from "fp-ts/lib/Monoid";
import { fanOut } from "fp-ts/lib/Strong";
import { first } from "fp-ts/lib/Semigroup";
// Get an instance of fanOut for Reader
const fanOutImpl = fanOut(R.Strong, R.Category);
// Get an instance of a monoid that has the same behavior as `alt`
const firstMonoid = O.getMonoid<string>(first());
// A different _alternative_ would be to use the helpers from the
// Alternative module. I believe altAll(O.Alternative) is equivalent to
// the above code.
const normalParams = new URLSearchParams("normal=yes");
const otherParams = new URLSearchParams("otherNormal=yes");
const getFromNormal = (params: URLSearchParams): O.Option<string> =>
O.fromNullable(params.get("normal"));
const getFromOther = (params: URLSearchParams): O.Option<string> =>
O.fromNullable(params.get("otherNormal"));
// Used `flow` to get fully pointfree style
const isNormal: (params?: URLSearchParams) => boolean = flow(
O.fromNullable,
O.chain<URLSearchParams, string>(
flow(fanOutImpl(getFromNormal, getFromOther), M.concatAll(firstMonoid))
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);
console.assert(isNormal(normalParams) === true);
console.assert(isNormal(otherParams) === true);
console.assert(isNormal(undefined) === false);

我正在使用带有fanOutReader类型类来获取使用单个输入调用多个函数的行为(在本例中为params)。然后,该输出将传递到MonoidconcatAll帮助程序,该帮助程序将定义如何将该结果中的值收集到单个值中。在这里,我指定了与alt具有相同行为的first(将返回第一个Some值)

此外,在这种情况下,fanOut仅适用于两个可能无法缩放的函数。一种选择是根据您的具体情况制作一个助手,例如:

// Add a helper for fanning out over an array
const fanAll = <A, B>(arr: Array<(a: A) => B>) => (a: A): B[] => pipe(
arr,
A.map((f) => f(a))
);
const isNormal2: (params?: URLSearchParams) => boolean = flow(
O.fromNullable,
O.chain<URLSearchParams, string>(
flow(fanAll([getFromNormal, getFromOther, getFromThird]), M.concatAll(firstMonoid))
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);

这和编写的原始代码之间有一个区别,即fanOut会急切地调用每个getFrom*函数以获得每个Option的结果,然后使用 Monoid 逻辑将它们压缩为单个值。O.alt只有在上面的代码None时才会运行后续代码。此行为不会影响运行时复杂性,但可能仍不是最佳的。

要实现相同的懒惰行为,您必须执行以下操作:

const altMonoid: M.Monoid<() => O.Option<string>> = {
empty: constant(O.none),
concat: (a, b) => flow(a, O.alt(b))
};
function apply<R>(f: () => R) {
return f();
}
function apply1<A, R>(arg: A) {
return (f: (a: A) => R) => f(arg);
}
function alt(
ins: Array<(params: URLSearchParams) => () => O.Option<string>>
): (p: URLSearchParams) => O.Option<string> {
return (p) => pipe(ins, A.map(apply1(p)), M.concatAll(altMonoid), apply);
}
function lazy<Args extends any[], R>(f: (...args: Args) => R) {
return (...args: Args) => () => f(...args);
}
const isNormal3: (params?: URLSearchParams) => boolean = flow(
O.fromNullable,
O.chain<URLSearchParams, string>(
pipe(
[getFromNormal, getFromOther, getFromThird],
A.map(lazy),
alt
)
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);
console.assert(isNormal3(normalParams) === true);
console.assert(isNormal3(otherParams) === true);
console.assert(isNormal3(undefined) === false);

但这变得有点复杂,所以我想我会推荐前两个选项之一,除非您真的需要代码是无意义的并且具有与带有O.alt的版本相同的懒惰配置文件。

但是我可以使用另一种类型的函数来实现无意义的方法吗?

您可以使用fp-ts/function中的constant以无意义的方式编写代码,但它的行为会略有不同:

const isNormal = (params?: URLSearchParams): boolean =>
pipe(
params,
O.fromNullable,
O.chain<URLSearchParams, string>((p) =>
pipe(getFromNormal(p), O.alt(constant(getFromOther(p))))
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);

这将具有不同行为的原因是constant将获取一个值并将其包装在返回该值的函数中。要使其具有值,它必须急切地评估getFromOther(p)。只要没问题,那么这种方法就可以了,但是不会有一种毫无意义的方法来编写这种代码,该代码是懒惰计算的,而匿名函数会滑入某个地方。

另一种选择是定义帮助程序:

function lazy<T, Args extends any[]>(f: (...args: Args) => T) {
return (...args: Args) => () => f(...args);
}
// Or this if you prefer to write code in a less "curried" way.
function lazy2<T, Args extends any[]>(f: (...args: Args) => T, ...args: Args) {
return () => f(...args);
}

然后,您可以毫无意义地使用它,并且仍然可以避免急于评估:

const isNormal = (params?: URLSearchParams): boolean =>
pipe(
params,
O.fromNullable,
O.chain<URLSearchParams, string>((p) =>
pipe(getFromNormal(p), O.alt(lazy(getFromOther)(p)))
),
O.map((s) => s === "yes"),
O.getOrElse(constFalse)
);

相关内容

最新更新