使用Typescipt 4.x.x
我编写了一些代码来实现其他语言Elm/Rust/Haskell中常用的可能/选项类型。
我想编写一个泛型函数,它可以采用映射的类型。
type MyMappedType<T> = {
value: T
name: string
}它有一个Maybe<unknown>作为其类型参数之一,例如。MyMappedType<Maybe<unknown>>并返回一个Maybe<MappedType<unknown>>。
然而,我找不到一种很好的方法来描述这一点,下面是我的尝试,extract函数是我感兴趣的函数。它目前只对所有字段进行迭代,但理想情况下,我可以使用一些类型记录智能来检查包含Maybe的字段/字段。这能以通用的方式完成吗?
示例可以在TypeScript 游乐场中运行。
export type Just<T> = [NonNullable<T>]
export const nothing = []
export type Nothing = typeof nothing
export type Maybe<T> = Just<T> | Nothing
export function just<T>(t: NonNullable<T>): Maybe<T> {
return [t]
}
export function map<T, R>(t: Maybe<T>, f: (t: T, index?: number) => R): Maybe<R> {
return t.map(f) as Maybe<R>
}
export function isJust<T>(t: Maybe<T>): t is Just<T> {
// Type guards are runtime checked, so TS tuples are actually Arrays
return Array.isArray(t) && t.length == 1 ? true : false
}
export function isNothing<T>(t: Maybe<T>): t is Nothing {
return Array.isArray(t) && t.length == 0? true : false
}
export function unwrap<T>(t: Just<T>) {
return t[0] as T
}
export function filter<T>(a: Maybe<T>[]): T[] {
return a.filter(isJust).map(unwrap)
}
export function defaultMapUnwrap<T, D>(t: Maybe<T>, f: (t: T, index?: number) => D, d: D): D {
return isJust(t) ? f(unwrap(t)) : d
}
type Container<Inner> = {} // <- this is bad
// How do I make this function better?
export function extract<Inner>(t: Container<Maybe<Inner>>): Maybe<Container<Inner>>{
let result = {}
for (let [key,value] of Object.entries(t)){
if (isNothing(value as Maybe<any>)){
return nothing
}
Object.assign(result, {[key]: defaultMapUnwrap(value as Maybe<any>, inner=> inner, value)})
}
return just(result)
}发布于 2021-05-31 16:34:14
它目前只对所有字段进行迭代,但理想情况下,我可以使用一些类型记录智能来检查字段/包含可能字段的字段。这能以通用的方式完成吗?
一般的Container<T>类型应该知道哪些字段可以是T。在您的示例中,Container<T>只是一个空对象形状,那么有一个类型参数,甚至类型有什么意义呢?
此外,我建议使用interfaces而不是types来定义对象形状。这将允许其他形状从它extend,确保扩展的属性具有超级属性。例如:
interface Container<T> {
value: T;
}
interface MyContainer<T> extends Container<T> {
name: 'bob';
}
const myContainer: MyContainer<'builds'> = {
name: 'bob',
value: 'builds',
};现在,您知道对于任何一般的Maybe<T>,哪些字段可能是Container<Maybe<T>>。如果您还想知道对于更特定的Maybe<T>扩展,哪些字段可以是Container,则需要对该特定类型进行单独处理。考虑到类型安全性:在您的示例中,您只需遍历所有字段并将其与nothing进行比较,但是解释器如何知道一个值将被解释为nothing = []而不是一个空的数据数组?
如果您想在Container的键上迭代以找到Maybe<T>值,创建一个class Maybe<T>可能会很有帮助。这样,您就可以检查一个值是否为instanceof Maybe。
不幸的是,在TypeScript 4.3.2中,没有泛型参数化类型参数的意义,因此不能这样做以获得传入的相同参数化类型(以及一些您可能认为有用的代码):
// NOT A VALID SIGNATURE!!!
function extract<T, A extends Container>(t: A<Maybe<T>>): Maybe<A<T>> {
return t.value.length === 0
? nothing
: [
{
...t,
value: t.value[0],
},
];
}撇开
我认为您对Just<T>和Nothing的定义有点奇怪。您可以将它们定义为一个单值数组元组和一个空数组元组,但是使用null从您的工具箱中删除NonNullable和undefined类型。
相反,我会这样定义它们,以便为您的代码打开更多的可能性:
type Just<T> = [T];
type Nothing = [];
type Maybe<T> = Just<T> | Nothing;
const x: Maybe<string | undefined> & Nothing = [];
const y: Maybe<string | undefined> & Just<string | undefined> = [undefined];
const z: Maybe<string | undefined> & Just<string | undefined> = ['something else'];
console.log(x.length); // 0
console.log(y.length); // 1
console.log(z.length); // 1发布于 2021-06-01 04:35:29
在您的代码中有一些不太理想的东西。
如果将所有内容都放在一个步骤中,您的Maybe类型如下:
export type Maybe<T> = [NonNullable<T>] | []您的isJust和isNothing函数不需要检查Array.isArray(t),因为t (即Maybe<T> )始终是一个数组。您还可以删除三元结构,然后从比较中返回boolean。
export function isJust<T>(t: Maybe<T>): t is Just<T> {
return t.length === 1;
}
export function isNothing<T>(t: Maybe<T>): t is Nothing {
return t.length === 0;
}unwrap不需要断言as T,因为t[0]总是T。
export function unwrap<T>(t: Just<T>): T {
return t[0];
}您通常不希望扩展返回类型。Just<T>也是Maybe<T> (反之亦然)。您的just函数应该返回Just<T>而不是Maybe<T>,这样您就有了最大的信息量。
export function just<T>(t: NonNullable<T>): Just<T> {
return [t]
}现在我们开始讨论extract和Container,这似乎还没有完成,因为Container到底是什么?它只是一个与<Inner>无关的空对象。您的MyContainer类型是有意义的。应该是这样的吗?
您有一个具有属性value (即Maybe<string> )和属性name (即string )的对象。如果value是一个Nothing,那么返回一个Nothing。否则,您将返回基本相同的对象,但将Maybe<string>属性解压缩为string。
我认为你试图概括这一点,但我不确定“规则”。Maybe属性总是value吗?多个属性可以是Maybe吗?有些属性是Maybe的,而其他的则不是。但我们可以解决这个问题。
让我们使用映射类型将任何Maybe 属性映射到其未包装版本。这里的T是原始容器对象的类型。如果一个属性是Maybe<U>,我们将其替换为U,否则它将保持相同的类型。
type UnwrappedContainer<T> = {
[K in keyof T]: T[K] extends Maybe<infer U> ? U : T[K]
}如果您想要更好的自动类型推断,可以考虑展开所有数组。除非被明确告知,否则类型记录将不会将['hello']视为Maybe<string>。这是因为它将类型推断为数组string[]而不是元组[string]。
type UnwrappedContainer<T> = {
[K in keyof T]: T[K] extends Array<infer U> ? U : T[K]
}函数的签名是,,其中C是描述输入容器的泛型类型参数。
export function extract<C>(t: C): Maybe<UnwrappedContainer<C>>{我们已经开始实施了。我现在明白了为什么您以前在isNothing中签入了isNothing。这是因为您使用不是Maybe<T>的变量调用函数,并通过断言它们实际上是Maybe<T>来避免类型记录错误。你不应该断言你知道的东西是假的。属性'james'是你正在检查的对象值之一,我们知道它不是Maybe。
您可以在函数中执行Array.isArray检查,但是现在使用调用它的方式,您将希望isJust和isNothing检查unknown值,而不仅仅是已知为Maybe的值。在T的情况下,isNothing并不重要,因此它很简单。
export function isNothing(t: unknown): t is Nothing {
return Array.isArray(t) && t.length === 0;
}使用isJust,我们必须从一个unknown中推断出T。我正在使通用在这里参考整个投入。如果提供了[string],那么T将是[string]而不是string。这件事变得非常复杂,但我认为它是正确的。
export function isJust<T>(t: T): t is T & Just<T extends Array<infer U> ? U : never> {
return Array.isArray(t) && t.length === 1;
}如果在此函数之外执行Array.isArray检查,并且只是验证长度,那么就容易多了。
export function isJust<T>(t: T[]): t is Just<T> {
return t.length === 1;
}我不认为需要在defaultMapUnwrap中使用extract,因为您没有映射任何东西(inner => inner),而且您已经验证了value不是空的,所以不需要默认设置。
export function extract<C>(t: C): Maybe<UnwrappedContainer<C>>{
const result = {} as UnwrappedContainer<C>;
for (const [key, value] of Object.entries(t)) {
if ( Array.isArray(value) ) {
// If there are any Nothing properties then the whole function returns []
if ( isNothing(value) ) {
return nothing;
}
// Unwrap non-empty arrays
result[key as keyof C] = value[0];
}
// properties which are not arrays just get returned as-is
else {
result[key as keyof C] = value;
}
}
return just(result);
}我的实现实际上并不是检查任何地方的value.length === 1,只是它比0更好。我们映射一个对象,如果它有任何array属性,那么就用它的第一个元素替换数组。如果这些数组中的任何一个为空,则整个函数返回[]。
我想我没有讨论的唯一问题是NonNullable问题,以及如果数组是[null]会发生什么。你也许应该检查一下,但此时我已经写得够多了,所以我会把这个留给你!
https://stackoverflow.com/questions/67772324
复制相似问题