首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >TypeScript:将Container<Maybe<unknown>>转换为Maybe<Container<unknown>>

TypeScript:将Container<Maybe<unknown>>转换为Maybe<Container<unknown>>
EN

Stack Overflow用户
提问于 2021-05-31 10:49:34
回答 2查看 1K关注 0票数 3

使用Typescipt 4.x.x

我编写了一些代码来实现其他语言Elm/Rust/Haskell中常用的可能/选项类型。

我想编写一个泛型函数,它可以采用映射的类型。

代码语言:javascript
复制
type MyMappedType<T> = {
    value: T
    name: string
}

它有一个Maybe<unknown>作为其类型参数之一,例如。MyMappedType<Maybe<unknown>>并返回一个Maybe<MappedType<unknown>>

然而,我找不到一种很好的方法来描述这一点,下面是我的尝试,extract函数是我感兴趣的函数。它目前只对所有字段进行迭代,但理想情况下,我可以使用一些类型记录智能来检查包含Maybe的字段/字段。这能以通用的方式完成吗?

示例可以在TypeScript 游乐场中运行。

代码语言:javascript
复制
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)
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-05-31 16:34:14

它目前只对所有字段进行迭代,但理想情况下,我可以使用一些类型记录智能来检查字段/包含可能字段的字段。这能以通用的方式完成吗?

一般的Container<T>类型应该知道哪些字段可以是T。在您的示例中,Container<T>只是一个空对象形状,那么有一个类型参数,甚至类型有什么意义呢?

此外,我建议使用interfaces而不是types来定义对象形状。这将允许其他形状从它extend,确保扩展的属性具有超级属性。例如:

代码语言:javascript
复制
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中,没有泛型参数化类型参数的意义,因此不能这样做以获得传入的相同参数化类型(以及一些您可能认为有用的代码):

代码语言:javascript
复制
// 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从您的工具箱中删除NonNullableundefined类型。

相反,我会这样定义它们,以便为您的代码打开更多的可能性:

代码语言:javascript
复制
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
票数 2
EN

Stack Overflow用户

发布于 2021-06-01 04:35:29

在您的代码中有一些不太理想的东西。

如果将所有内容都放在一个步骤中,您的Maybe类型如下:

代码语言:javascript
复制
export type Maybe<T> = [NonNullable<T>] | []

您的isJustisNothing函数不需要检查Array.isArray(t),因为t (即Maybe<T> )始终是一个数组。您还可以删除三元结构,然后从比较中返回boolean

代码语言:javascript
复制
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

代码语言:javascript
复制
export function unwrap<T>(t: Just<T>): T {
    return t[0];
}

您通常不希望扩展返回类型。Just<T>也是Maybe<T> (反之亦然)。您的just函数应该返回Just<T>而不是Maybe<T>,这样您就有了最大的信息量。

代码语言:javascript
复制
export function just<T>(t: NonNullable<T>): Just<T> {
    return [t]
}

现在我们开始讨论extractContainer,这似乎还没有完成,因为Container到底是什么?它只是一个与<Inner>无关的空对象。您的MyContainer类型是有意义的。应该是这样的吗?

您有一个具有属性value (即Maybe<string> )和属性name (即string )的对象。如果value是一个Nothing,那么返回一个Nothing。否则,您将返回基本相同的对象,但将Maybe<string>属性解压缩为string

我认为你试图概括这一点,但我不确定“规则”。Maybe属性总是value吗?多个属性可以是Maybe吗?有些属性是Maybe的,而其他的则不是。但我们可以解决这个问题。

让我们使用映射类型将任何Maybe 属性映射到其未包装版本。这里的T是原始容器对象的类型。如果一个属性是Maybe<U>,我们将其替换为U,否则它将保持相同的类型。

代码语言:javascript
复制
type UnwrappedContainer<T> = {
    [K in keyof T]: T[K] extends Maybe<infer U> ? U : T[K]
}

如果您想要更好的自动类型推断,可以考虑展开所有数组。除非被明确告知,否则类型记录将不会将['hello']视为Maybe<string>。这是因为它将类型推断为数组string[]而不是元组[string]

代码语言:javascript
复制
type UnwrappedContainer<T> = {
    [K in keyof T]: T[K] extends Array<infer U> ? U : T[K]
}

函数的签名是,,其中C是描述输入容器的泛型类型参数。

代码语言:javascript
复制
export function extract<C>(t: C): Maybe<UnwrappedContainer<C>>{

我们已经开始实施了。我现在明白了为什么您以前在isNothing中签入了isNothing。这是因为您使用不是Maybe<T>的变量调用函数,并通过断言它们实际上是Maybe<T>来避免类型记录错误。你不应该断言你知道的东西是假的。属性'james'是你正在检查的对象值之一,我们知道它不是Maybe

您可以在函数中执行Array.isArray检查,但是现在使用调用它的方式,您将希望isJustisNothing检查unknown值,而不仅仅是已知为Maybe的值。在T的情况下,isNothing并不重要,因此它很简单。

代码语言:javascript
复制
export function isNothing(t: unknown): t is Nothing {
    return Array.isArray(t) && t.length === 0;
}

使用isJust,我们必须从一个unknown中推断出T。我正在使通用在这里参考整个投入。如果提供了[string],那么T将是[string]而不是string。这件事变得非常复杂,但我认为它是正确的。

代码语言:javascript
复制
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检查,并且只是验证长度,那么就容易多了。

代码语言:javascript
复制
export function isJust<T>(t: T[]): t is Just<T> {
    return t.length === 1;
}

我不认为需要在defaultMapUnwrap中使用extract,因为您没有映射任何东西(inner => inner),而且您已经验证了value不是空的,所以不需要默认设置。

代码语言:javascript
复制
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]会发生什么。你也许应该检查一下,但此时我已经写得够多了,所以我会把这个留给你!

TypeScript游乐场链接

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67772324

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档