0

I have defined a function in JS that keep taking functions(params => string) until it receive an object then it resolves the final string as a concatenation of functions invocations over the same object that is passed at the end

Here is the function

export function prefixer(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer(fns: any[]) {

        return function next(param: any) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer([func1 || identity])
}

My problem is in defining the right types for its params and return type so the user of this function can pass a generic type(to help the function know what is the type of the final params object) and can help the user re-invoke the output of that function over and over.

Here's the function in a test case:

test('should auto prefix', () => {

    let prefix1: any = prefixer((params: any) => `https://wwww.${params.domain}.com`)
    let prefix2: any = prefix1(() => '/path/to/item')
    let prefix3: any = prefix2((params: any) => `/${params.itemId}`)

    let params = {
        domain: 'google',
        itemId: '5444'
    }

    let resolvedString1 = prefix1(params);
    let resolvedString2 = prefix2(params);
    let resolvedString3 = prefix3(params);

    let trueResult1 = `https://wwww.${params.domain}.com`
    let trueResult2 = `https://wwww.${params.domain}.com/path/to/item`
    let trueResult3 = `https://wwww.${params.domain}.com/path/to/item/${params.itemId}`

    expect(resolvedString1).toEqual(trueResult1);
    expect(resolvedString2).toEqual(trueResult2);
    expect(resolvedString3).toEqual(trueResult3);
});

I've tried some nonsense ideas but didn't get any close and didn't find a helping answer regarding recursive functions in typescript.

Here's something I've tried but doesn't solve the types definition

export function prefixer<T>(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer<M>(fns: any[]) {

        return function next<S>(param: S | M | ((p: S | M) => any)) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer<T>([func1 || identity])
}

// I still have to pass (p: any)...
let prefix1 = prefixer<{ domain: string }>((p: any) => `https://wwww.${p.domain}.com`)
let prefix2 = prefix1<{ itemId: string }>((p: any) => `https://wwww.${p.itemId}.com`)
1
  • a) don't write such a function, it won't work with functions that expect functions as parameters b) you'll need to use function overloading to distinguish between object and function input Commented Oct 30, 2021 at 19:19

1 Answer 1

1

The first step to type this would be to write type signature of this prefixer without writing the implementation. It should look something like this:

// Utility types to make definitions below shorter
type Pojo = Record<string, unknown>
type EmptyPojo = Record<never, unknown>

// TData represents the object that is expected by all the previous 
// prefixer functions that you've already passed to this prefixer
interface CurriedPrefixer<TData extends Pojo> {
  // First overload: when you pass a function to curried prefixer
  // T represents fields that a prefixer function you now pass
  // to curried prefixer expects
  <T extends Pojo = EmptyPojo>(
    // You could change type of `data` to `TData & T` instead of `T`
    // if you want your prefixer function to have access to data that 
    // all previous prefixer function use, but this is less safe, I would 
    // recommend explicitly typing every iteration
    prefixerFn: (data: T) => string
  ): CurriedPrefixer<TData & T>

  // Second overload: when you pass an object
  (data: TData): string
}

You can now verify that this type signature works as expected, again, without thinking about the implementation for the moment: see sandbox

Now the only thing left is to fit the implementation to this type signature. It could look something like this: sandbox

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.