ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • typescript - exercises 4일차
    Typescript 2023. 7. 13. 16:36

    🔔 서론

    원래 남은 4문제를 이틀에 걸쳐 두 문제씩 풀기로 계획했었는데, 알고보니 16번 문제가 없어서 3문제를 이틀에 걸쳐 풀기엔 낭비인 것 같아서 남은 문제를 모두 풀기로 했다.. 파이팅..

    https://typescript-exercises.github.io/

     

    TypeScript Exercises

    A set of interactive TypeScript exercises

    typescript-exercises.github.io

     

    이번 포스트는 위 사이트의 13~15번 문제를 풀며 각 문제 풀이에 해당하는 핵심 키워드 개념을 정리하는 글이다.


    🥁 13번

    interface merging을 사용해 index.d.ts 파일을 완성시키는 문제이다.

    🌈 Interface Merging

    interface merging은 같은 이름의 interface에 대해 type 선언을 병합하는 것이다. 단, 같은 속성에 대해 type 재선언을 하면 에러를 발생시킨다.

    // ex)
    interface Person {
        name: string,
        age: number
    }
    
    interface Person {
        height: number,
        phone: number
    }
    
    const testPerson: Person = {
      name: "jack",
      age: 23,
      height: 180,
      phone: 1234567
    }

     

    요구사항 : date-wizard 모듈 구현 코드를 확인 후 index.d.ts를 완성시켜야 한다.

    /* 요구사항
    Exercise:
        Check date-wizard module implementation at:
        node_modules/date-wizard/index.js
        node_modules/date-wizard/index.d.ts
    
        Extend type declaration of that module in:
        module-augmentations/date-wizard/index.ts
    */

     

    test.ts에서 dateWizard.DateDetails가 받은 date 매개변수에 대한 모든 속성 타입 체크를 하고있다.

    // test.ts
    import {IsTypeEqual, typeAssert} from 'type-assertions';
    import {dateWizard} from './index';
    
    typeAssert<
        IsTypeEqual<
            dateWizard.DateDetails,
            {
                year: number;
                month: number;
                date: number;
                hours: number;
                minutes: number;
                seconds: number;
            }
        >
    >();
    
    typeAssert<
        IsTypeEqual<
            typeof dateWizard.pad,
            (level: number) => string
        >
    >();

     

    date-wizard 폴더의 index.d.ts를 보면 이미 DateDetails interface를 선언해둔 상태이다. 하지만 year, month, date 속성의 타입만 선언해둔 상태이다. 또한, pad 함수에 대한 call signature도 선언되지 않았다.

    // date-wizard > index.d.ts
    declare function dateWizard(date: Date, format: string): string;
    
    declare namespace dateWizard {
        interface DateDetails {
            year: number;
            month: number;
            date: number;
        }
    
        function dateDetails(date: Date): DateDetails;
        function utcDateDetails(date: Date): DateDetails;
    }
    
    export = dateWizard;

     

    따라서, 이미 DateDetails interface에 타입이 선언된 속성을 제외한 나머지 속성들에 대한 타입과 pad 함수의 call signature를 선언 해주면 된다.

    // index.d.ts
    import 'date-wizard';
    
    declare module 'date-wizard' {
        interface DateDetails {
            hours: number;
            minutes: number;
            seconds: number;
        }
    
        function pad(s: number): string;
    }

    🥁 14번 (포기)

    주어진 함수의 타입정의를 하는 문제이다.

    /* index.ts
    Exercise:
        Provide proper typing for the specified functions.
    
    Bonus:
        Could you please also refactor the code to reduce
        code duplication?
        You might need some excessive type casting to make
        it really short.
    */
    
    /**
     * 2 arguments passed: returns a new array
     * which is a result of input being mapped using
     * the specified mapper.
     *
     * 1 argument passed: returns a function which accepts
     * an input and returns a new array which is a result
     * of input being mapped using original mapper.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Function} mapper
     * @param {Array} input
     * @return {Array | Function}
     */
    export function map(mapper, input) {
        if (arguments.length === 0) {
            return map;
        }
        if (arguments.length === 1) {
            return function subFunction(subInput) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return subInput.map(mapper);
            };
        }
        return input.map(mapper);
    }
    
    /**
     * 2 arguments passed: returns a new array
     * which is a result of input being filtered using
     * the specified filter function.
     *
     * 1 argument passed: returns a function which accepts
     * an input and returns a new array which is a result
     * of input being filtered using original filter
     * function.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Function} filterer
     * @param {Array} input
     * @return {Array | Function}
     */
    export function filter(filterer, input) {
        if (arguments.length === 0) {
            return filter;
        }
        if (arguments.length === 1) {
            return function subFunction(subInput) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return subInput.filter(filterer);
            };
        }
        return input.filter(filterer);
    }
    
    /**
     * 3 arguments passed: reduces input array it using the
     * specified reducer and initial value and returns
     * the result.
     *
     * 2 arguments passed: returns a function which accepts
     * input array and reduces it using previously specified
     * reducer and initial value and returns the result.
     *
     * 1 argument passed: returns a function which:
     *   * when 2 arguments is passed to the subfunction, it
     *     reduces the input array using specified initial
     *     value and previously specified reducer and returns
     *     the result.
     *   * when 1 argument is passed to the subfunction, it
     *     returns a function which expects the input array
     *     and reduces the specified input array using
     *     previously specified reducer and inital value.
     *   * when 0 argument is passed to the subfunction, it
     *     returns itself.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Function} reducer
     * @param {*} initialValue
     * @param {Array} input
     * @return {* | Function}
     */
    export function reduce(reducer, initialValue, input) {
        if (arguments.length === 0) {
            return reduce;
        }
        if (arguments.length === 1) {
            return function subFunction(subInitialValue, subInput) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                if (arguments.length === 1) {
                    return function subSubFunction(subSubInput) {
                        if (arguments.length === 0) {
                            return subSubFunction;
                        }
                        return subSubInput.reduce(reducer, subInitialValue);
                    };
                }
                return subInput.reduce(reducer,subInitialValue);
            }
        }
        if (arguments.length === 2) {
            return function subFunction(subInput) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return subInput.reduce(reducer, initialValue);
            };
        }
        return input.reduce(reducer, initialValue);
    }
    
    /**
     * 2 arguments passed: returns sum of a and b.
     *
     * 1 argument passed: returns a function which expects
     * b and returns sum of a and b.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Number} a
     * @param {Number} b
     * @return {Number | Function}
     */
    export function add(a, b) {
        if (arguments.length === 0) {
            return add;
        }
        if (arguments.length === 1) {
            return function subFunction(subB) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return a + subB;
            };
        }
        return a + b;
    }
    
    /**
     * 2 arguments passed: subtracts b from a and
     * returns the result.
     *
     * 1 argument passed: returns a function which expects
     * b and subtracts b from a and returns the result.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Number} a
     * @param {Number} b
     * @return {Number | Function}
     */
    export function subtract(a, b) {
        if (arguments.length === 0) {
            return subtract;
        }
        if (arguments.length === 1) {
            return function subFunction(subB) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return a - subB;
            };
        }
        return a - b;
    }
    
    /**
     * 2 arguments passed: returns value of property
     * propName of the specified object.
     *
     * 1 argument passed: returns a function which expects
     * propName and returns value of property propName
     * of the specified object.
     *
     * 0 arguments passed: returns itself.
     *
     * @param {Object} obj
     * @param {String} propName
     * @return {* | Function}
     */
    export function prop(obj, propName) {
        if (arguments.length === 0) {
            return prop;
        }
        if (arguments.length === 1) {
            return function subFunction(subPropName) {
                if (arguments.length === 0) {
                    return subFunction;
                }
                return obj[subPropName];
            };
        }
        return obj[propName];
    }
    
    /**
     * >0 arguments passed: expects each argument to be
     * a function. Returns a function which accepts the
     * same arguments as the first function. Passes these
     * arguments to the first function, the result of
     * the first function passes to the second function,
     * the result of the second function to the third
     * function... and so on. Returns the result of the
     * last function execution.
     *
     * 0 arguments passed: returns itself.
     *
     * TODO TypeScript
     *   * Should properly handle at least 5 arguments.
     *   * Should also make sure argument of the next
     *     function matches the return type of the previous
     *     function.
     *
     * @param {Function[]} functions
     * @return {*}
     */
    export function pipe(...functions) {
        if (arguments.length === 0) {
            return pipe;
        }
        return function subFunction() {
            let nextArguments = Array.from(arguments);
            let result;
            for (const func of functions) {
                result = func(...nextArguments);
                nextArguments = [result];
            }
            return result;
        };
    }

     

    타입은 고사하고 test.ts조차 이해할 수 없는 코드이다.

    // test.ts
    import {typeAssert, IsTypeEqual} from 'type-assertions/index';
    import {map, reduce, filter, add, subtract, prop, pipe} from './index';
    
    const mapResult1 = map()(String)()([1, 2, 3]);
    typeAssert<IsTypeEqual<typeof mapResult1, string[]>>();
    
    const mapResult2 = map(Boolean, [1, 0, 1]);
    typeAssert<IsTypeEqual<typeof mapResult2, boolean[]>>();
    
    const reduceResult1 = reduce()((a: number, b: number) => a + b)()(0)()([1, 2, 3]);
    typeAssert<IsTypeEqual<typeof reduceResult1, number>>();
    
    const reduceResult2 = reduce(add, 0, [1, 2, 3]);
    typeAssert<IsTypeEqual<typeof reduceResult2, number>>();
    
    const reduceResult3 = reduce(subtract, 0, [1, 2, 3]);
    typeAssert<IsTypeEqual<typeof reduceResult3, number>>();
    
    const reduceResult4 = reduce((a: string, b: string) => a + b, '', ['1', '2', '3']);
    typeAssert<IsTypeEqual<typeof reduceResult4, string>>();
    
    const filterResult1 = filter()((n: number) => n !== 0)()([0, 1, 2]);
    typeAssert<IsTypeEqual<typeof filterResult1, number[]>>();
    
    const filterResult2 = filter(Boolean, [0, 1, 2]);
    typeAssert<IsTypeEqual<typeof filterResult2, number[]>>();
    
    const addResult1 = add()(1)()(2);
    typeAssert<IsTypeEqual<typeof addResult1, number>>();
    
    const addResult2 = add(1, 2);
    typeAssert<IsTypeEqual<typeof addResult2, number>>();
    
    const subtractResult1 = subtract()(2)()(1);
    typeAssert<IsTypeEqual<typeof subtractResult1, number>>();
    
    const subtractResult2 = subtract(2, 1);
    typeAssert<IsTypeEqual<typeof subtractResult2, number>>();
    
    const propResult1 = prop()('x')()({x: 1, y: 'Hello'});
    typeAssert<IsTypeEqual<typeof propResult1, number>>();
    
    const propResult2 = prop('y', {x: 1, y: 'Hello'});
    typeAssert<IsTypeEqual<typeof propResult2, string>>();
    
    const pipeResult1 = pipe(filter(Boolean), map(String), reduce((a: string, b: string) => a + b, ''))([0, 1, 2, 3]);
    typeAssert<IsTypeEqual<typeof pipeResult1, string>>();
    
    const pipeResult2 = pipe()()(filter(Boolean), map(String))([0, 1, 2, 3]);
    typeAssert<IsTypeEqual<typeof pipeResult2, string[]>>();

     

    풀어올 예정

    // 못풀었음 ㅜ

    ..... 쉽지않다


    🥁 15번

    Generic Type에 대한 심화 문제이다.

     

    요구사항이 이젠 뻔뻔해졌다. 그냥 도와달라니..

    /*
    Exercise:
        Here is a library which helps manipulating objects.
        We tried to write type annotations and we failed.
        Please help!
    */
    
    export class ObjectManipulator<T> {
    
        constructor(protected obj: T) {}
    
        public set<K extends string, V>(key: K, value: V): ObjectManipulator<T & {[key in K]: V}> {
            return new ObjectManipulator({...this.obj, [key]: value} as T & {[key in K]: V});
        }
    
        public get<K extends keyof T>(key: K): T[K] {
            return this.obj[key];
        }
    
        public delete<K extends keyof T>(key: K): ObjectManipulator<Omit<T, K>> {
            const newObj = {...this.obj};
            delete newObj[key];
            return new ObjectManipulator(newObj);
        }
    
        public getObject(): T {
            return this.obj;
        }
    }

    1. set 함수의 key값과 value는 어떤 값이 들어올지 모르기 때문에 generic type 사용 단, 객체의 key는 string 형태이기 때문에 key에 대한 generic type은 string에 제한되어야 한다.

    2. set 함수와 달리 get, delete 함수의 key값은 이미 obj의 key값이기 때문에 string에 제한되는 것이 아닌 obj의 키에 제한되어야한다.

    3. delete 함수의 return type은 obj이지만 key값을 제외한 obj를 반환하기에 Omit을 통해 key에 해당하는 속성은 제외시키고 return 한다.


    💊 4일차 후기

    마지막 날에 걸맞게 정말 어려운 문제였다 특히 14번...은 나중에 시간의 여유가 생기면 그 때 제대로 다시 풀어볼 예정이다. 아무튼 이렇게 typescript - exercises 스터디가 마무리 되었지만... 오히려 문제를 풀면 풀어낼 수록 typescript에 대한 자신감이 떨어지는 듯한 기분이 드는 건 착각.. 이고싶다.. 그래도 어느정도 잊고 있던 개념들을 다시 복기할 수 있는 기회였기에 좋은 경험이었고, 추후에 또 이런 컨텐츠가 있다면 참여할 의사는 무조건 있다! 

Designed by Tistory.