جنریکها - چگونه یک تاپل آرگومان یک تابع را در تایپاسکریپت پارامترگذاری کنیم


استفاده از Typescript 1.4، فرض کنید من یک تابع مرتبه بالاتر با امضای زیر دارم:

interface F<A,B> {
    (a: (...x:any[]) => A): (...x:any[]) => B
}

تابع 'F' تابع 'a' را میگیرد که مجموعهای از پارامترها 'x' دارد. تابع 'F' یک تابع جدید برمیگرداند که دقیقاً همان مجموعه آرگومانها را مانند تابع 'a' دارد.

من سعی کردهام راهی برای بیان این موضوع در تایپاسکریپت پیدا کنم، اما موفق نشدهام. به عنوان مثال:

interface F<X extends Array<any>,A,B> {
    (a: (...x:X) => A): (...x:X) => B
}

کامپایلر فقط با این خطا مواجه میشود: error TS2370: یک پارامتر باقیمانده باید از نوع آرایه باشد.

گرچه به هر حال درست به نظر نمیرسد، حتی اگر کامپایل هم میشد. حدس میزنم واقعاً چیزی شبیه به این نیاز دارم:

interface F<X extends Tuple,A,B> {
    (a: (...x:X) => A): (...x:X) => B
}

کسی میداند که آیا چنین چیزی حتی با تایپاسکریپت در حال حاضر (نسخه ۱.۴ در زمان نوشتن) امکانپذیر است؟ یا پیشنهادی دارید؟

مثال:

(توجه: این مورد استفاده واقعی من نیست، من فقط از ثبت لاگ به عنوان یک مثال ساده استفاده میکنم - لطفاً روی این جنبه تمرکز نکنید)

// a higher-order function that takes any function and
// returns a new function which takes the same args
// passing them to the original fn and logging the args along with the result

function f(a:(...x:any[]) => any): (...x:any[]) => void {
    return (...x:any[]) => {
        console.log('('+x.join(',')+') => ', a.apply(undefined, x));
    }
}

function a(j:string, k:number): boolean {
    return j === String(k);
}

var b = f(a);

b("1", 1);
b("a", 2);

خروجی کنسول:

(1,1) =>  true
(a,2) =>  false

پس این کار میکند، اما تابع مشتق شده 'b' دارای امضای ضمنی زیر است:

(...x:any[]) => void

به طور ایدهآل، دوست دارم که همان آرگومانها را مانند تابع 'a' داشته باشد، یعنی:

(j: string, k: number) => void

میدانم که میتوانم آن را به طور صریح تعریف کنم، اما خیلی طولانی است و اصلاً ایدهآل نیست، کمی خلاف هدف داشتن نوعبندی قوی در وهله اول است:

var b: (j:string, k:number) => void = f(a);

پاسخ شماره ۱

برای پوشاندن یک تابع با تابع دیگری که نوع بازگشتی متفاوتی دارد، یک روش تقلبی ممکن است با استفاده از اورلودینگ وجود داشته باشد:

function wrap<R>(fn0: ()=> any, p:(f,a)=>R): () => R;
function wrap<R,T>(fn1: (t:T)=> any, p:(f,a)=>R): (t:T) => R;
function wrap<R, T, U>(fn2: (t:T, u:U)=> any, p:(f,a)=>R): (t:T,u:U) => R;
function wrap<R, T, U, V>(fn3: (t:T, u:U, v:V)=> any, p:(f,a)=>R): (t:T,u:U, v:V) => R;
function wrap<R, T, U, V, X>(fn4: (t:T, u:U, v:V, x:X)=> any, p:(f,a)=>R): (t:T,u:U, v:V, x:X) => R;
// ...
function wrap<R>(fn: Function, proc:Function):Function {
	return (...args) => proc(fn, args);
}

// wrap is called with two functions fn and proc
// result is a function with argument types from fn and return type of proc 

function serialize(fn, args):string {
  return JSON.stringify(fn(...args))
}

function foo(a:number,b:string) {
  return true;
}

var wrapped = wrap(foo,serialize)
// type is (a:number,b:string) => string

مواظب باشید، این فقط برای توابعی با تعداد محدودی آرگومان عمل خواهد کرد.

پاسخ شماره ۲

تابع 'F' تابع 'a' را میگیرد که مجموعهای از پارامترها 'x' دارد. تابع 'F' یک تابع جدید برمیگرداند که دقیقاً همان مجموعه آرگومانها را مانند تابع 'a' دارد.

مشخصات کمی نامشخص است، اما بر اساس درک من از منظور شما، این یک نمونه است

// Function 'F' takes function 'a' which has a set of parameters 'x'
// and returns a new function which has exactly the same set of args as function 'a'
function F<A extends Function>(a:A):A{
    return a;
}

var foo = F(function(a:number,b:number):void{});

foo (1,3); // okay 
foo(1); // ERROR

بهروزرسانی

یک مثال برای مورد استفاده شما:

function f<A extends Function>(a:A):A {
    var newFunc = (...x:any[]) => {
        console.log('('+x.join(',')+') => ', a.apply(undefined, x));
        return null;
    }
    return <any>newFunc;
}

function a(j:string, k:number): boolean {
    return j === String(k);
}

var b = f(a);

b("1", 1);
b("a", 2);
b('123','123'); // ERROR

توجه: ما هم آرگومانها و هم نوع بازگشتی را ضبط کردهایم. راهی برای ضبط فقط آرگومانها نمیبینم.