代码之家  ›  专栏  ›  技术社区  ›  ThomasReggi

具有已定义类型的函数库

  •  1
  • ThomasReggi  · 技术社区  · 5 年前

    我试图以一种方式组合函数,在这种方式中,我可以定义名称、输入和返回类型,然后从中心函数访问它们。但是,当我这样做时,我会丢失输入信息。

    如果没有硬编码的值类型,如何构建这样的系统?

    import * as fs from 'fs';
    import { promisify } from 'util';
    import * as lodash from 'lodash';
    
    const libs = []
    
    export enum Types {
        number,
        numbers,
        string,
        buffer,
    }
    
    export enum Actions {
        add,
        subtract,
        readFile,
    }
    
    libs.push({
        action: Actions.add,
        from: Types.numbers,
        get: Types.number,
        fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
    })
    
    libs.push({
        action: Actions.subtract,
        from: Types.numbers,
        get: Types.number,
        fn: (...n: number[]): number => n.reduce((a, b) => a - b, 0),
    })
    
    libs.push({
        action: Actions.readFile,
        from: Types.string,
        get: Types.string,
        fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
    })
    
    libs.push({
        action: Actions.readFile,
        from: Types.string,
        get: Types.buffer,
        fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
    })
    
    const library = (a: Actions, from: Types, get: Types, lib) => {
        const found = lodash.find(lib, fn => {
            return (
            lodash.isEqual(fn.from, from) &&
            lodash.isEqual(fn.get, get)
            );
        });
        if (!found) throw new Error('no conversion');
        return found.fn;
    }
    
    const { readFile } = Actions;
    const { string: s } = Types;
    
    const x = library(readFile, s, s, libs)
    
    x('./tres.ts').then(console.log)
    

    我如何保留的输入信息 x

    1 回复  |  直到 5 年前
        1
  •  2
  •   Titian Cernicova-Dragomir    5 年前

    我们需要保护自己 libs 数组中实际项的类型。最简单的方法是借助一个额外的函数来推断 自由基 基于数组中的实际项(包括where的文本类型 Actions Types

    根据这些信息,我们可以键入 library 要从中提取函数类型的函数 自由基 那是一样的 action , get from 作为传入类型:

    import * as fs from 'fs';
    import { promisify } from 'util';
    import * as lodash from 'lodash';
    
    
    export enum Types {
        number,
        numbers,
        string,
        buffer,
    }
    
    export enum Actions {
        add,
        subtract,
        readFile,
    }
    
    function makeLib<T extends Array<{action : A, from: F, get: G, fn: (...a: any[])=> any}>, A extends Actions, F extends Types, G extends Types>(...a:T){
        return a;
    }
    const libs = makeLib({
        action: Actions.add,
        from: Types.numbers,
        get: Types.number,
        fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
    }, {
        action: Actions.subtract,
        from: Types.numbers,
        get: Types.number,
        fn: (...n: number[]): number => n.reduce((a, b) => a - b, 0),
    }, {
        action: Actions.readFile,
        from: Types.string,
        get: Types.string,
        fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
    }, {
        action: Actions.readFile,
        from: Types.string,
        get: Types.buffer,
        fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
    })
    
    const library = <T extends Array<{action : Actions, from: Types, get: Types, fn: (...a: any[])=> any}>, A extends Actions, F extends Types, G extends Types>(a: A, from: F, get: G, lib: T) => {
        const found = lodash.find(lib, fn => {
            return (
            lodash.isEqual(fn.from, from) &&
            lodash.isEqual(fn.get, get)
            );
        });
        if (!found) throw new Error('no conversion');
        return found.fn as Extract<T[number], {action : A, from: F, get: G }>['fn'];
    }
    
    const { readFile } = Actions;
    const { string: s } = Types;
    
    const x = library(readFile, s, s, libs) // x is (s: string) => Promise<string
    
    x('./tres.ts').then(console.log)
    
    const x2 = library(Actions.subtract, Types.string, Types.string, libs)  // never
    const x3 = library(Actions.subtract, Types.numbers, Types.number, libs)  //  (...n: number[]) => number
    

    strings 而不是枚举:

    function makeLib<T extends Array<{action : V, from: V, get: V, fn: (...a: any[])=> any}>, V extends string>(...a:T){
        return a;
    }
    const libs = makeLib({
        action: "add",
        from: "numbers",
        get: "number",
        fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
    }, {
        action: "subtract",
        from: "numbers",
        get: "number",
        fn: (...n: number[]): number | null => n.reduce((a, b) => a - b, 0),
    }, {
        action: "readFile",
        from: "string",
        get: "string",
        fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
    }, {
        action: "readFile",
        from: "string",
        get: "buffer",
        fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
    })
    
    const library = <T extends Array<{action : string, from: string, get: string, fn: (...a: any[])=> any}>, 
        A extends T[number]['action'], F extends T[number]['from'], G extends T[number]['get']>(a: A, from: F, get: G, lib: T) => {
        const found = lodash.find(lib, fn => {
            return (
            lodash.isEqual(fn.from, from) &&
            lodash.isEqual(fn.get, get)
            );
        });
        if (!found) throw new Error('no conversion');
        return found.fn as Extract<T[number], {action : A, from: F, get: G }>['fn'];
    }
    
    const { readFile } = Actions;
    const { string: s } = Types;
    
    const x = library("readFile", "string", "string", libs) // x is (s: string) => Promise<string
    
    x('./tres.ts').then(console.log)
    
    const x2 = library("subtract", "string", "string", libs)  // never
    const x3 = library("subtract", "numbers", "number", libs)  //  (...n: number[]) => number