以类型安全的方式在管道中使用 Ramda 的克隆
Use Ramda's clone in pipe in a type-safe way
我想使用 Ramda 以类型安全的方式克隆和更新对象(受 this idiom 启发),但我无法使其以类型安全的方式工作。
以类型安全的方式更新嵌套对象非常好:
interface Person {
name: string
department: {
name: string
budget: number
manager?: string
}
}
const personX: Person = {
name: 'Foo Bar',
department: {
name: 'x',
budget: 2000,
},
}
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const x = addManager('Michael Scott')(personX) // x is of type `Person`
我也可以使用 pipe
或 compose
成功组合函数:
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const increaseBudget = (budget: number): (input: Person) => Person => assocPath([
'department',
'budget',
], budget)
const addManagerAndUpdateBudget = pipe(addManager('MichaelScott'), increaseBudget(10000))
const x = addManagerAndUpdateBudget(personX) // x is still of type Person
然而,一旦我使用 clone
它就失败了:
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const increaseBudget = (budget: number): (input: Person) => Person => assocPath([
'department',
'budget',
], budget)
const addManagerAndUpdateBudget = pipe(clone, addManager('MichaelScott'), increaseBudget(10000))
const x = addManagerAndUpdateBudget(personX) // Person is not assignable to readonly unknown[]
这可能是类型问题?或者我在这里遗漏了什么?
(免责声明:我是 Ramda 的核心团队之一。)
Ramda 团队在 TypeScript 方面的专业知识不多。我添加了 definitelytyped
标签,因为该项目保留了通常的 Ramda 类型。
不太了解 TypeScript 类型,我不明白为什么这不起作用,就像我阅读 clone
definition:
export function clone<T>(value: T): T;
export function clone<T>(value: readonly T[]): T[];
和相关的pipe
definition
export function pipe<TArgs extends any[], R1, R2, R3>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
): (...args: TArgs) => R3;
我觉得一切都很好。我想知道您是否需要将 addManager
和 increaseBudget
的结果声明为 Person
。但这只是一个 non-TS 人的猜测。
我回答主要是因为我想指出,对于许多用途,您不需要使用 clone
,因为 assocPath
已经对它正在改变的任何数据做了等效的处理。
const person2 = increaseBudget (10000) (person1)
person2 == person1 //=> false
person2 .department == person1 .department //=> false
当然 assocPath
在可以和不进行完整克隆的地方使用结构共享:
const person2 = assocPath ('startDate', '2014-07-12') (person1)
person2 .department == person1 .department //=> true
但对于许多用途,特别是如果使用 Ramda 或其他不可变技术进行进一步修改,clone
根本就没有必要。
-- 斯科特
当使用R.pipe
(或R.compose
)与其他Ramda泛型函数(例如R.clone
)时,TS有时无法推断出正确的类型,而创建的实际签名功能。
注意:我正在使用 Ramda - 0.28.0 和@types/ramda - 0.28.8.
在你的例子中,我们希望 Ramda 使用这个签名——一个参数列表传递给创建的函数 (TArgs
),然后是 3 return 类型的管道函数 (R1
, R2
, R3
):
export function pipe<TArgs extends any[], R1, R2, R3>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
): (...args: TArgs) => R3;
由于 Ramda 不推断它们,我们需要显式添加它们 (sandbox):
const addManagerAndUpdateBudget = pipe<[Person], Person, Person, Person>(
clone,
addManager('MichaelScott'),
increaseBudget(10000)
);
参数 - 具有单个 Person
的元组,每个 return 值也是一个 Person
。我们需要声明所有这些,以便 TS 将使用我们需要的特定签名。
另一个选项是在管道中显式键入第一个函数,因此 TS 可以使用它来推断其他类型 (sandbox):
const addManagerAndUpdateBudget = pipe(
clone as (person: Person) => Person,
addManager('MichaelScott'),
increaseBudget(10000)
);
我想使用 Ramda 以类型安全的方式克隆和更新对象(受 this idiom 启发),但我无法使其以类型安全的方式工作。
以类型安全的方式更新嵌套对象非常好:
interface Person {
name: string
department: {
name: string
budget: number
manager?: string
}
}
const personX: Person = {
name: 'Foo Bar',
department: {
name: 'x',
budget: 2000,
},
}
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const x = addManager('Michael Scott')(personX) // x is of type `Person`
我也可以使用 pipe
或 compose
成功组合函数:
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const increaseBudget = (budget: number): (input: Person) => Person => assocPath([
'department',
'budget',
], budget)
const addManagerAndUpdateBudget = pipe(addManager('MichaelScott'), increaseBudget(10000))
const x = addManagerAndUpdateBudget(personX) // x is still of type Person
然而,一旦我使用 clone
它就失败了:
const addManager = (name: string): (input: Person) => Person => assocPath([
'department',
'manager',
], name)
const increaseBudget = (budget: number): (input: Person) => Person => assocPath([
'department',
'budget',
], budget)
const addManagerAndUpdateBudget = pipe(clone, addManager('MichaelScott'), increaseBudget(10000))
const x = addManagerAndUpdateBudget(personX) // Person is not assignable to readonly unknown[]
这可能是类型问题?或者我在这里遗漏了什么?
(免责声明:我是 Ramda 的核心团队之一。)
Ramda 团队在 TypeScript 方面的专业知识不多。我添加了 definitelytyped
标签,因为该项目保留了通常的 Ramda 类型。
不太了解 TypeScript 类型,我不明白为什么这不起作用,就像我阅读 clone
definition:
export function clone<T>(value: T): T;
export function clone<T>(value: readonly T[]): T[];
和相关的pipe
definition
export function pipe<TArgs extends any[], R1, R2, R3>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
): (...args: TArgs) => R3;
我觉得一切都很好。我想知道您是否需要将 addManager
和 increaseBudget
的结果声明为 Person
。但这只是一个 non-TS 人的猜测。
我回答主要是因为我想指出,对于许多用途,您不需要使用 clone
,因为 assocPath
已经对它正在改变的任何数据做了等效的处理。
const person2 = increaseBudget (10000) (person1)
person2 == person1 //=> false
person2 .department == person1 .department //=> false
当然 assocPath
在可以和不进行完整克隆的地方使用结构共享:
const person2 = assocPath ('startDate', '2014-07-12') (person1)
person2 .department == person1 .department //=> true
但对于许多用途,特别是如果使用 Ramda 或其他不可变技术进行进一步修改,clone
根本就没有必要。
-- 斯科特
当使用R.pipe
(或R.compose
)与其他Ramda泛型函数(例如R.clone
)时,TS有时无法推断出正确的类型,而创建的实际签名功能。
注意:我正在使用 Ramda - 0.28.0 和@types/ramda - 0.28.8.
在你的例子中,我们希望 Ramda 使用这个签名——一个参数列表传递给创建的函数 (TArgs
),然后是 3 return 类型的管道函数 (R1
, R2
, R3
):
export function pipe<TArgs extends any[], R1, R2, R3>(
f1: (...args: TArgs) => R1,
f2: (a: R1) => R2,
f3: (a: R2) => R3,
): (...args: TArgs) => R3;
由于 Ramda 不推断它们,我们需要显式添加它们 (sandbox):
const addManagerAndUpdateBudget = pipe<[Person], Person, Person, Person>(
clone,
addManager('MichaelScott'),
increaseBudget(10000)
);
参数 - 具有单个 Person
的元组,每个 return 值也是一个 Person
。我们需要声明所有这些,以便 TS 将使用我们需要的特定签名。
另一个选项是在管道中显式键入第一个函数,因此 TS 可以使用它来推断其他类型 (sandbox):
const addManagerAndUpdateBudget = pipe(
clone as (person: Person) => Person,
addManager('MichaelScott'),
increaseBudget(10000)
);