Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curry cannot infer generic type parameters correctly #207

Closed
enricopolanski opened this issue Mar 1, 2021 · 8 comments
Closed

Curry cannot infer generic type parameters correctly #207

enricopolanski opened this issue Mar 1, 2021 · 8 comments
Labels
limitation We're limited here

Comments

@enricopolanski
Copy link

enricopolanski commented Mar 1, 2021

馃悶 Bug Report

Describe the bug

Currying a generic function loses the previous types.

Reproduce the bug

import {F} from 'ts-toolbelt'

declare function curry<Fn extends F.Function>(fn: Fn): F.Curry<Fn>

declare const tuple: <A>(a: A) => <B>(b: B) => [A, B]

// const f: <B>(b: B) => [unknown, B]
const f = curry(tuple)('a')

Expected behavior

f should infer <B>(b: B) => ['a', B]

Or

<B>(b: B) => [string, B]

Additional context

The previous test would also fail for a binary rather than unary function, the return type would be lost anyway.

declare const tuple: <A>(a: A, alsoA: A) => <B>(b: B) => [A, A, B]

// f: F.Curry<(alsoA: unknown) => <B>(b: B) => [unknown, unknown, B]>
const f = curry(tuple)('a')
@millsp
Copy link
Owner

millsp commented Mar 1, 2021

It's a ts limitation that we cannot overcome yet. It comes from the simple fact that:

type test0 = Parameters<<A, B>(a: A, b: B) => A> // [a: unknown, b: unknown]

@millsp millsp closed this as completed Mar 1, 2021
@millsp millsp added the limitation We're limited here label Mar 1, 2021
@millsp
Copy link
Owner

millsp commented Mar 1, 2021

I will reopen this case as soon as it's fixable

@millsp
Copy link
Owner

millsp commented Mar 1, 2021

However, it could be possible to write a more type safe curry, but we'll have to say goodbye to the placeholder feature:

declare function curry<R, A, B>(fn: (a: A, b: B) => R):
    (a: A) => (b: B) => R

@millsp
Copy link
Owner

millsp commented Mar 1, 2021

Unfortunately, things like this don't work either. As soon as you have more than one overload, ts makes the generic unknown:

interface CurriedFunction1<T1, R> {
    (): CurriedFunction1<T1, R>;
    (t1: T1): R;
}

interface CurriedFunction2<T1, T2, R> {
    (): CurriedFunction2<T1, T2, R>;
    (t1: T1): CurriedFunction1<T2, R>;
    (t1: T1, t2: T2): R;
}

interface CurriedFunction3<T1, T2, T3, R> {
    (): CurriedFunction3<T1, T2, T3, R>;
    (t1: T1): CurriedFunction2<T2, T3, R>;
    (t1: T1, t2: T2): CurriedFunction1<T3, R>;
    (t1: T1, t2: T2, t3: T3): R;
}

interface CurriedFunction4<T1, T2, T3, T4, R> {
    (): CurriedFunction4<T1, T2, T3, T4, R>;
    (t1: T1): CurriedFunction3<T2, T3, T4, R>;
    (t1: T1, t2: T2): CurriedFunction2<T3, T4, R>;
    (t1: T1, t2: T2, t3: T3): CurriedFunction1<T4, R>;
    (t1: T1, t2: T2, t3: T3, t4: T4): R;
}

interface CurriedFunction5<T1, T2, T3, T4, T5, R> {
    (): CurriedFunction5<T1, T2, T3, T4, T5, R>;
    (t1: T1): CurriedFunction4<T2, T3, T4, T5, R>;
    (t1: T1, t2: T2): CurriedFunction3<T3, T4, T5, R>;
    (t1: T1, t2: T2, t3: T3): CurriedFunction2<T4, T5, R>;
    (t1: T1, t2: T2, t3: T3, t4: T4): CurriedFunction1<T5, R>;
    (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): R;
}

declare function curry<T1, R>(func: (t1: T1) => R):
CurriedFunction1<T1, R>;

declare function curry<T1, T2, R>(func: (t1: T1, t2: T2) => R):
CurriedFunction2<T1, T2, R>;

declare function curry<T1, T2, T3, R>(func: (t1: T1, t2: T2, t3: T3) => R):
CurriedFunction3<T1, T2, T3, R>;

declare function curry<T1, T2, T3, T4, R>(func: (t1: T1, t2: T2, t3: T3, t4: T4) => R):
CurriedFunction4<T1, T2, T3, T4, R>;

declare function curry<T1, T2, T3, T4, T5, R>(func: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R):
CurriedFunction5<T1, T2, T3, T4, T5, R>;

@millsp
Copy link
Owner

millsp commented Mar 1, 2021

@enricopolanski
Copy link
Author

enricopolanski commented Mar 2, 2021

Shouldn't a failing case be added to the test suite tho?

It could be skipped from the build/test process as long as ts limitations remain in place, but it would live here for future work.

Don't really see the value otherwise of only including tests that pass.

@millsp
Copy link
Owner

millsp commented Mar 2, 2021

Sure, PRs are welcome

@millsp
Copy link
Owner

millsp commented Mar 2, 2021

Tests that pass prevent regressions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
limitation We're limited here
Projects
None yet
Development

No branches or pull requests

2 participants