如果您想留在“fp-ts 内”,您可以执行以下操作:
import { flow, pipe, tuple } from "fp-ts/function"
import * as Mn from "fp-ts/Monoid"
import * as N from "fp-ts/number"
import * as O from "fp-ts/Option"
import * as Ord from "fp-ts/Ord"
import * as RM from "fp-ts/ReadonlyMap"
import * as RT from "fp-ts/ReadonlyTuple"
import * as Sg from "fp-ts/Semigroup"
import * as S from "fp-ts/string"
type Person = string
type Age = number
type PersonAge = readonly [Person, Age]
const ordByAge: Ord.Ord<PersonAge> = pipe(N.Ord, Ord.contramap(RT.snd))
const monoidMaxByAge: Mn.Monoid<O.Option<PersonAge>> = pipe(
ordByAge,
Sg.max,
O.getMonoid
)
const oldestPerson = (ps: ReadonlyMap<Person, Age>) =>
pipe(
ps,
RM.foldMapWithIndex(S.Ord)(monoidMaxByAge)(flow(tuple, O.some)),
O.map(RT.fst)
)
这非常冗长,并没有解决您对性能的担忧。
我觉得烦人的一件事是它需要一个 ORD。也许这是使函数更具确定性所必需的,但排序对于许多折叠/归约任务并不重要并且会降低性能。
你说得对,它是关于决定论的。原生 JS Map 按插入顺序存储条目,因此如果没有 Ord 实例作为键,这个 oldestPerson 函数可能会为两个给定的 Maps 产生不同的结果,除了插入顺序之外它们是等效的。我会认为这是出乎意料的行为。
这是我期望持有的:
import * as assert from "assert"
const aliceBob: ReadonlyMap<Person, Age> = new Map([
["Alice", 25],
["Bob", 25],
])
const bobAlice: ReadonlyMap<Person, Age> = new Map([
["Bob", 25],
["Alice", 25],
])
const emptyMap: ReadonlyMap<Person, Age> = new Map([])
assert.deepStrictEqual(oldestPerson(aliceBob), oldestPerson(bobAlice))
assert.deepStrictEqual(oldestPerson(emptyMap), O.none)
我同意fp-ts/ReadonlyMap 中的许多函数效率不高,我认为它们只是围绕原生数据结构的最小“函数式 API”包装器,不能很好地发挥不变性和决定论。如果性能更受关注,我可能会像您一样使用本机方法。
叫 getFoldableWithIndex 的东西,但我不知道如何使用它或让它与 Typescript 一起编译。
getFoldableWithIndex 将返回一个FoldableWithIndex 类型的类实例,它在这里对你没有用处。这是一个值,您可以将其作为参数传递给一个需要FoldableWithIndex 实例的函数,我什至不确定是否有一个很好的例子?。在fp-ts/Foldable 中有一个traverse_,它采用Foldable 实例,但没有对应的traverseWithIndex_,尽管该假设函数将采用FoldableWithIndex 实例。 some functions 在 fp-ts/FoldableWithIndex 中有 functions,但它们是关于组合两个 FoldableWithIndex 实例到另一个实例(这在这里没有用)。
你可以直接使用FoldableWithIndex:类型类实例是一个带有uncurried foldMapWithIndex 方法的对象,所以如果你想制作原始的sn-p 更详细的,你可以这样做:
const oldestPerson = (ps: ReadonlyMap<Person, Age>) =>
pipe(
RM.getFoldableWithIndex(S.Ord).foldMapWithIndex(monoidMaxByAge)(
ps,
flow(tuple, O.some)
),
O.map(RT.fst)
)
但这些实例并不是真的要直接在pipe 中使用,这就是我使用顶级RM.foldMapWithIndex 的原因。还有更多关于fp-ts 中的类型类如何工作here。