Typescript: السماح للفصول بأن تكون حدوديًا في الفئات البارامترية الأخرى

تم إنشاؤها على ١٩ نوفمبر ٢٠١٤  ·  140تعليقات  ·  مصدر: microsoft/TypeScript

هذا اقتراح للسماح للأدوية الجنيسة كمعلمات للنوع. من الممكن حاليًا كتابة أمثلة محددة من monads ، ولكن من أجل كتابة الواجهة التي ترضي جميع monads ، أقترح الكتابة

interface Monad<T<~>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

وبالمثل ، من الممكن كتابة أمثلة محددة من الممنوعات الديكارتية ، ولكن من أجل كتابة الواجهة التي ترضي جميع الممثلين الديكارتين ، أقترح الكتابة

interface Cartesian<T<~>> {
  all<A>(a: Array<T<A>>): T<Array<A>>;
}

يمكن لمعلمات النوع البارامترية أن تأخذ أي عدد من الوسائط:

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

أي ، عندما يتبع معلمة النوع علامة تلدة وحرف طبيعي ، يجب السماح باستخدام معلمة النوع كنوع عام مع وجود حرف معين في بقية الإعلان.

تمامًا كما هو الحال الآن ، عند تنفيذ مثل هذه الواجهة ، يجب ملء معلمات النوع العام:

class ArrayMonad<A> implements Monad<Array> {
  map<A, B>(f: (a:A) => B): Array<A> => Array<B> {
    return (arr: Array<A>) =>  arr.map(f);
  }
  lift<A>(a: A): Array<A> { return [a]; }
  join<A>(tta: Array<Array<A>>): Array<A> {
    return tta.reduce((prev, cur) => prev.concat(cur));
  }
}

بالإضافة إلى السماح مباشرة بتركيبات الأنواع العامة في الحجج ، أقترح أن تكون أنواع الحروف تدعم أيضًا تعريف الأدوية الجنيسة بهذه الطريقة (انظر العدد 308 ):

typedef Maybe<Array<~>> Composite<~> ;
class Foo implements Monad<Composite<~>> { ... }

يجب أن تتطابق مجالات التعريف والاسم المستعار مع تعريف typedef.

Suggestion help wanted

التعليق الأكثر فائدة

مع عقليات HKT يمكن تغييرها ، وكسر العادات ، وإعادة الأجيال الضائعة إلى الحياة ، وسيكون ذلك أكبر شيء منذ الأدوية الجنيسة والأرقام الفارغة وغير المحددة الصريحة ، يمكن أن تغير كل شيء

يرجى اعتبارها ميزة كبيرة تالية ، توقف عن الاستماع إلى الأشخاص الذين يستمرون في مطالبتك بحصان أفضل ، ومنحهم f * g ferrari

ال 140 كومينتر

عدم القيام بأي افتراضات متهورة ، لكنني أعتقد أنك تكتبها بشكل غير صحيح. تتطلب جميع أنواع المعلمات أسماء معلمات ، لذا ربما كنت تقصد الكتابة

map<A, B>(f: (x: A) => B): T<A> => T<B>;

بينما الخريطة الآن هي دالة تأخذ مصمم الخرائط من النوع any (حيث يكون اسم المعلمة هو A ) إلى B .

حاول استخدام علامة --noImplicitAny للحصول على نتائج أفضل.

شكرا تصحيح.

لقد قمت بتحديث تعليقي في اقتراح.

: +1: النوع الأعلى سيكون بمثابة مكافأة كبيرة لبناء البرمجة الوظيفية ، ولكن قبل ذلك أفضل أن يكون لدي دعم صحيح لوظيفة الترتيب الأعلى والعامة: p

شبه معتمد.

نحن نحب هذه الفكرة كثيرًا ، لكننا نحتاج إلى تنفيذ عملي لمحاولة فهم جميع الآثار والحالات الحافة المحتملة. إن الحصول على نموذج للعلاقات العامة يعالج على الأقل 80٪ من حالات استخدام هذا سيكون خطوة تالية مفيدة حقًا.

ما هي آراء الناس في بناء جملة التلدة؟ بديل لـ T~2 سيكون شيئًا مثل

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

التي تسمح بالتكوين المباشر للأدوية بدلاً من الحاجة إلى أسماء مستعارة:

interface Foo<T<~,~,~>, U<~>, V<~, ~>> {
  bar<A, B, C, D>(a: A, f: (b: B) => C, d: D): T<U<A>, V<B, C>, D>;
}

من الغريب أن يكون لدينا سبب واضح لأننا لا نفعل ذلك في أي مكان آخر ، لذا

interface Foo<T<~,~>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

أوضح قليلاً ، على الرغم من أنني أعرف أن اللغات الأخرى تستخدم * في سياقات مماثلة بدلاً من ~ :

interface Foo<T<*,*>> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

على الرغم من التطرف في هذه النقطة ، فقد تحصل على:

interface Foo<T: (*,*) => *> {
  bar<A, B>(f: (a: A) => B): T<A, B>;
}

أعتقد أن T<~,~> أوضح من T~2 أيضًا. سوف أقوم بتعديل الاقتراح أعلاه. لا يهمني ما إذا كنا نستخدم ~ أو * ؛ لا يمكن أن يكون معرف JS ، لذلك لا يمكننا استخدام ، على سبيل المثال ، _ . لا أرى الفائدة التي يوفرها التدوين => ؛ تأخذ جميع الأدوية الجنسية بعض أنواع الإدخال وتعيد نوع الإخراج الفردي.

فالصيغة الأخف وزنًا ستتجاهل أصل الأدوية الجنيسة تمامًا ؛ سيقوم المحلل اللغوي باستنتاجه من الاستخدام الأول وإلقاء خطأ إذا لم يكن الباقي متسقًا معه.

يسعدني بدء العمل على تنفيذ هذه الميزة. ما هو المنتدى الموصى به للمطورين حول تفاصيل تنفيذ Transpiler؟

يمكنك تسجيل العديد من المشكلات الجديدة لأسئلة أكبر باستخدام المزيد من نماذج التعليمات البرمجية المتضمنة ، أو إجراء مشكلة طويلة الأمد بسلسلة من الأسئلة أثناء التنقل. بدلاً من ذلك ، يمكنك الانضمام إلى غرفة الدردشة هنا https://gitter.im/Microsoft/TypeScript ويمكننا التحدث هناك.

metaweta أي أخبار؟ إذا كنت بحاجة إلى أي مساعدة / مناقشة ، فسيسعدني طرح الأفكار حول هذه المشكلة. أنا حقا أريد هذه الميزة.

لا ، استحوذت الأمور في العمل على وقت الفراغ الذي كان عليّ العمل عليه.

bump: هل هناك فرصة لرؤية هذه الميزة في الاعتبار؟

https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288 لا يزال هو حالته الحالية. لا أرى أي شيء هنا من شأنه أن يجعلنا نغير أولوية الميزة.

يبدو لي أن هذا مفيد في مواقف أكثر بكثير من مجرد استيراد تجريدات نظرية التصنيف. على سبيل المثال ، قد يكون من المفيد أن تكون قادرًا على كتابة مصانع الوحدات التي تأخذ تنفيذًا (مُنشئ) Promise كوسيطة ، على سبيل المثال قاعدة بيانات مع تنفيذ وعد قابل للتوصيل:

interface Database<P<~> extends PromiseLike<~>> {   
    query<T>(s:string, args:any[]): P<T> 
}

: +1:

مع عقليات HKT يمكن تغييرها ، وكسر العادات ، وإعادة الأجيال الضائعة إلى الحياة ، وسيكون ذلك أكبر شيء منذ الأدوية الجنيسة والأرقام الفارغة وغير المحددة الصريحة ، يمكن أن تغير كل شيء

يرجى اعتبارها ميزة كبيرة تالية ، توقف عن الاستماع إلى الأشخاص الذين يستمرون في مطالبتك بحصان أفضل ، ومنحهم f * g ferrari

نعم ، لقد اصطدمت بهذا في أول 15 دقيقة بعد محاولة إضافة أنواع إلى قاعدة كود JS الحالية. أنا لا أقوم بالتبديل إلى TS حتى أراه.

هل يمكنني المساعدة ، في الواقع؟

أتساءل كيف سيتعلق هذا بالرقم 7848؟ إنها متشابهة جدًا ، على الرغم من أنها تتعلق بالوجه الآخر للأنواع ذات الترتيب الأعلى.

رد @ boris-marinov ريان كافانو يقول أنه يمكنك:

إن الحصول على نموذج للعلاقات العامة يعالج على الأقل 80٪ من حالات استخدام هذا سيكون خطوة تالية مفيدة حقًا.

الآن لدي الوقت لتنفيذ مثل هذه العلاقات العامة البسيطة ، آمل أن أحصل على بعض التلميحات من المطورين الأساسيين ، لكن لا توجد أسئلة حتى الآن - كل شيء يبدو جيدًا ومفهومًا. سوف تتبع التقدم هنا.

Artazor هل ترغب في إلقاء نظرة على تكسير # 7848 أيضًا؟ هذا يعتني بالجانب الآخر من هذه المشكلة ، بما في ذلك الأدوية الجنيسة ، وسيشعر IMHO بأن هذا غير مكتمل بدونه (ستعمل المعلمات العامة على تبسيط الكثير من التعليمات البرمجية على مستوى النوع).

أعتقد أن هذا الاقتراح رائع للغاية. إن وجود أنواع أعلى من النوع في TypeScript سيأخذها إلى مستوى جديد حيث يمكننا وصف تجريدات أكثر قوة مما هو ممكن حاليًا.

ومع ذلك ، ألا يوجد خطأ في الأمثلة الواردة في OP؟ A في السطر

class ArrayMonad<A> implements Monad<Array> {

لا يتم استخدامه في أي من الطرق ، نظرًا لأن لديهم جميعًا A الخاص بهم.

أيضًا ، إذا تم تنفيذ functor باستخدام map كطريقة تستخدم this كيف ستبدو؟ ربما مثل هذا؟

interface Functor<T, A> {
  map<B>(f: (a: A) => B): T<A> => T<B>;
}

class Maybe<A> implements Functor<Maybe, A> {
  ...
}

paldepind تحقق من #

متى ستهبط هذه الأشياء؟ هذا يبدو وكأنه نوع من الضروري.

كما ستجعل من الممكن مثل:

interface SomeX<X, T> {
   ...// some complex definition
  some: X<T>
}

interface SomeA<T> extends SomeX<A, T> {
}

؟

whitecolor أعتقد أن هناك سمكة أكبر للقلي في الوقت الحالي ، والتي تستحق أولوية أعلى:

  1. تم إصدار TypeScript 2.0 RC منذ أقل من أسبوعين بقليل. سيستغرق ذلك الكثير من الوقت في حد ذاته.
  2. bind ، call ، و apply ، وظائف JS الأصلية ، غير مطبوعة. هذا يعتمد في الواقع على اقتراح الأدوية المتغيرة . يحتاج Object.assign أيضًا إلى حل مشابه ، لكن الأدوية البديلة وحدها لن تحل ذلك.
  3. وظائف مثل طرق Lodash _.pluck وطرازات Backbone get و set وما إلى ذلك حاليًا غير مطبعية ، وإصلاح هذا يجعل العمود الفقري قابلاً للاستخدام مع TypeScript بطريقة أكثر أمانًا. قد يكون لها أيضًا

لا يعني ذلك أنني لا أريد هذه الميزة (سأحب هذه الميزة) ، فأنا لا أراها على الأرجح قريبًا.

تضمين التغريدة
شكرا لشرح. نعم ، العنصر الثالث في القائمة مهم جدًا ، في انتظار https://github.com/Microsoft/TypeScript/issues/1295 أيضًا.

لكنني آمل في الإصدار الحالي ربما في 2.1dev بطريقة ما.

أنا موافق. آمل أن تتمكن من تحقيق ذلك.

(تعدد الأشكال من المرتبة 2 ، الذي تريده هذه القضية ، هو أيضًا ضرورة
مستخدمو Fantasy Land ، لكتابة أدوات ADT المختلفة بشكل صحيح ضمن تلك المواصفات.
Ramda هي مثال جيد للمكتبة التي تحتاج إلى هذا بشدة.)

في الثلاثاء ، 6 سبتمبر 2016 ، 11:00 كتب Alex [email protected] :

isiahmeadows https://github.com/isiahmeadows
شكرا لشرح. نعم ، العنصر الثالث في القائمة مهم جدًا ،
في انتظار # 1295 https://github.com/Microsoft/TypeScript/issues/1295
جدا.

لكنني آمل في الإصدار الحالي ربما في 2.1dev بطريقة ما.

-
أنت تتلقى هذا لأنه تم ذكرك.

قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -244978475 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AERrBMvxBALBe0aaLOp03vEvEyokvxpyks5qnX_8gaJpZM4C99VY
.

يبدو أن هذه الميزة ستساعدنا كثيرًا في تحديد أشكال التفاعل. على سبيل المثال ، لديك هيكل:

interface Model {
  field1: string,
  field2: number,
  field3?: Model
}

لدي معالج ، والذي يعرف بأنه:

interface Handler<T> {
  readonly value: T;
  onChange: (newValue: T) => void;
}

تم تمرير هذا المعالج كدعامات لمكونات React. لدي أيضًا وظيفة ، والتي تأخذ البنية وتعيد نفس البنية ، ولكن باستخدام المعالجات بدلاً من القيم:

function makeForm(value: Model): {
  field1: Handler<string>,
  field2: Handler<number>,
  field3: Handler<Model>,
}

بالنسبة إلى الآن ، لا يمكنني كتابة هذه الوظيفة بشكل صحيح ، لأن TS لا يمكنها إنتاج كتابة بناءً على بنية من نوع آخر.

بقرة يمكنني كتابة makeForm باستخدام HKT؟

جلالة مثير للاهتمام.

ربما يكون شيء من هذا القبيل ممكنًا:

//Just a container
interface Id <A> {
  value: A
}

interface Model <T> {
  field1: T<string>,
  field2: T<number>,
  field3?: T<Model>
}

makeForm (Model<Id>): Model<Handler>

@ boris-marinov النقطة الأكثر إثارة للاهتمام هي هذا الخط:

interface Model<T> {
  //...
  field3?: T<Model> // <- Model itself is generic.
                    // Normally typescript will error here, requiring generic type parameter.
}

قد يكون من الجدير بالذكر أن HKT كان يمكن أن يكون إجابة لما يسمى الأنواع الجزئية (https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155):

type MyDataProto<K<~>> = {
    one: K<number>;
    another: K<string>;
    yetAnother: K<boolean>;
}
type Identical<a> = a;
type Optional<a> = a?; // or should i say: a | undefined;
type GettableAndSettable<a> = { get(): a; set(value: a): void }

type MyData = MyDataProto<Identical>; // the basic type itself
type MyDataPartial = MyDataProto<Optional>; // "partial" type or whatever you call it
type MyDataProxy = MyDataProto<GettableAndSettable>; // a proxy type over MyData
// ... etc

ليس تماما. {x: number?} غير قابل للتخصيص لـ {x?: number} ، لأنه واحد
مضمون في الوجود ، بينما الآخر ليس كذلك.

في الثلاثاء ، 11 أكتوبر 2016 ، 09:16 كتب أليكسي بيكوف إخطارات github.com:

قد يكون من الجدير بالذكر أن HKT كان يمكن أن يكون الرد على ما يسمى
أنواع جزئية (# 4889 (تعليق)
https://github.com/Microsoft/TypeScript/issues/4889#issuecomment-247721155
):

اكتب MyDataProto واحد: ك؛
آخر: ك؛
بعد آخر: ك؛
} type متطابق = a ؛ اكتب اختياري = a ؟؛
= {get (): a ؛

؛
؛
؛

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -252913109 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AERrBNFYFfiW01MT99xv7UE2skQ3qiPMks5qy4wRgaJpZM4C99VY
.

isiahmeadows أنت على حق ، في الوقت الحالي لا توجد طريقة / بناء جملة لجعل الخاصية اختيارية حقًا بناءً على نوعها فقط ، وهذا عار

واحد آخر: سيكون من الجيد إذا كان من الممكن صنع العقار readonly . يبدو أن بعض ميزات وحدات الماكرو مطلوبة.

مجرد طرح هذا هناك ... أفضل بناء جملة * على بناء جملة ~ . شيء ما حول ~ يبدو بعيدًا جدًا عن منظور تخطيط لوحة المفاتيح. أيضًا ، لست متأكدًا من السبب ، لكنني أعتقد أن * يبدو أكثر قابلية للقراءة / للتمييز مع جميع الأقواس الزاوية الموجودة في المزيج. ناهيك عن أن الأشخاص المطلعين على لغات أخرى مثل Haskell قد يربطون على الفور بناء الجملة بـ HKT. يبدو أكثر طبيعية قليلاً.

يجب أن أوافق على بناء الجملة * . أولاً ، إنه أكثر تميزًا ،
وثانيًا ، يمثل بشكل أفضل نوع "أي نوع يعمل".


إيشيا ميدوز
[email protected]

يوم الأحد ، 6 نوفمبر ، 2016 الساعة 12:10 صباحًا ، Landon Poch [email protected]
كتب:

مجرد رمي هذا هناك ... أنا أفضل * بناء الجملة على ~ ~.
شيء ما حول ~ يبدو بعيدًا جدًا عن تخطيط لوحة المفاتيح
إنطباع. أيضًا ، لست متأكدًا من السبب ، لكنني أعتقد * يبدو أكثر قليلاً
يمكن قراءته / تمييزه بكل أقواس الزاوية الموجودة في المزيج.
ناهيك عن أن الأشخاص المطلعين على لغات أخرى مثل هاسكل قد يفعلون ذلك
قم بربط بناء الجملة على الفور بـ HKT. يبدو أكثر طبيعية قليلاً.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -258659277 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AERrBHQ4SYeIiptB8lhxEAJGOYaxwCkiks5q7VMvgaJpZM4C99VY
.

معلم: community ؟ ما هو الوضع الحالي لهذه المشكلة / الميزة؟

whiteecolor الحالة هي DIY (افعلها بنفسك)

المشكلة لها تصنيف Accepting PRs . هذا يعني أن طلبات السحب لتنفيذ هذه الميزة مرحب بها. راجع https://github.com/Microsoft/TypeScript/wiki/FAQ#what -do-the-labels-on-these-issues-mean لمزيد من التفاصيل.

يرجى أيضًا الاطلاع على https://github.com/Microsoft/TypeScript/issues/1213#issuecomment -96854288

حسنًا ، أرى الملصقات ، لدي شكوك فقط في أنه إذا كان الفريق غير TS قادرًا على تحقيق ذلك.

الآن لدي الوقت لتنفيذ مثل هذه العلاقات العامة البسيطة ، آمل أن أحصل على بعض التلميحات من المطورين الأساسيين ، لكن لا توجد أسئلة حتى الآن - كل شيء يبدو جيدًا ومفهومًا. سوف تتبع التقدم هنا.

Artazor هل لديك أي حظ في هذا؟

raveclassic - أصبح الأمر أكثر صعوبة مما يبدو ، ولكن ما زلت آمل في المضي قدمًا. من الواضح من الناحية النحوية ، لكن قواعد / مراحل التحقق من الكتابة ليست واضحة بالنسبة لي كما أريد -)

لنحاول إحياء نشاطي -)

مجرد تتبع التقدم ومسار تطور الفكرة. لقد فكرت في ثلاثة خيارات حول كيفية تنفيذ هذه الميزة.

لقد خططت لإثراء TypeParameterDeclaration higherShape بخاصية

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        higherShape?: HigherShape // For Higher-Kinded Types <--- this one 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

ودرسوا ثلاثة خيارات لكيفية تنفيذ HigherShape

1. Arity بسيط للمجال

type HigherShape = number

يتوافق مع الاستخدام التالي:

class Demo<Wrap<*>, WrapTwo<*,*>> {   // 1 and 2
    str: Wrap<string>;
    num: Wrap<number>;
    both: WrapTwo<number, string>;
}

في هذه الحالة الأبسط ، يبدو أن النوع number سيكون كافيًا. ومع ذلك ، يجب أن نكون قادرين على تحديد شكل أعلى فعليًا لكل نوع معين للتأكد من أنه يمكننا استخدامه كوسيطة نوع لمتطلبات الشكل المحددة. وهنا نواجه مشكلة: الشكل الأعلى للفئة Demo نفسها لا يمكن التعبير عنها كرقم. إذا كان الأمر كذلك ، فيجب تمثيله كـ 2 - لأنه يحتوي على معلمتين من النوع ،
وسيكون من الممكن الكتابة

var x: Demo<Array, Demo>

ثم مواجهة مشكلة التحقق من النوع المؤجل مع الخاصية .both . وبالتالي فإن النوع number غير كافٍ (على ما أعتقد) ؛

في الواقع ، اكتب Demo له شكل الترتيب العالي التالي:

(* => *, (*,*) => *) => *

2. مجال منظم بالكامل ومجال مشترك

ثم قمت بالتحقيق في العكس ، التمثيل الكامل للأشكال الأعلى ، والذي من شأنه أن يسمح بتمثيل مثل هذه الأشكال مثل الشكل المذكور أعلاه ، بل والأسوأ من ذلك:

(* => (*,*)) => ((*,*) => *)

بنية البيانات الخاصة بذلك واضحة ومباشرة ، لكنها لا تتفاعل بشكل جيد مع نظام كتابة TypeScript. إذا سمحنا بمثل هذه الأنواع ذات الترتيب الأعلى ، فلن نعرف أبدًا ما إذا كان * يعني نوع الأرض ، والذي يمكن استخدامه لكتابة القيم. إلى جانب ذلك ، لم أتمكن حتى من العثور على بناء جملة مناسب حول كيفية التعبير عن قيود الترتيب الأعلى الوحشية هذه.

3. المجال المهيكل / المجال المشترك البسيط الضمني

الفكرة الرئيسية - تعبير النوع (حتى مع وسيطات النوع الفعلي) ينتج دائمًا نوعًا أساسيًا - يمكن استخدامه لكتابة متغير. من ناحية أخرى ، يمكن أن يكون لكل معلمة نوع معلمات النوع التفصيلية الخاصة بها بنفس التنسيق المستخدم في مكان آخر.

كان هذا قراري الأخير الذي سأحاول الدفاع عنه.

type HigherShape = NodeArray<TypeParameterDeclaration>;

مثال:

class A {x: number}
class A2 extends A { y: number }
class Z<T> { z: T; }

class SomeClass<T1<M extends A> extends Z<M>, T2<*,*<*>>, T3<* extends string>> {
        var a: T1<A2>; // checked early
        var b: T2<string, T1>; // second argument of T2 should be generic with one type parameter  
        var c: T3<"A"|"B">; // not very clever but it is checked
        // ...
        test() {
             this.a.z.y = 123 // OK
             // nothing meaningful can be done with this.b and this.c
        }
}

أريد هنا أن أشير إلى أن M محلي مقابل T1<M extends A> extends Z<M> وهو موجود في نطاق رؤية أعمق من T1. وبالتالي فإن M غير متوفر في الجسم SomeClass .
ويعني * ببساطة معرفًا جديدًا (نوع مجهول) لا يتعارض أبدًا مع أي شيء (ويمكن تنفيذه في مرحلة لاحقة)


وبالتالي التوقيع النهائي لإعلان TypeParameter

    export interface TypeParameterDeclaration extends Declaration {
        kind: SyntaxKind.TypeParameter;
        name: Identifier;
        typeParameters?: NodeArray<TypeParameterDeclaration> // !!! 
        constraint?: TypeNode;

        // For error recovery purposes.
        expression?: Expression;
    }

هل تريد سماع أي رأي لـ DanielRosenwasser و @ aleksey-bykov و isiahmeadows وغيرهم -)

يبدو الأمر جيدًا بالنسبة لي ، لكنني أعرف القليل جدًا عن البنية الداخلية لقاعدة شفرة TypeScript.

أود أن أضيف صوتي للجوقة تطلب ذلك وأهتف لك يا أرتازور! :)

ستكون هذه الميزة مفيدة بالنسبة لي في تطبيقي لجعل نوع Redux آمنًا.

michaeltontchev ما هي المشكلات التي تواجهها في إنشاء Redux منضدة آمنة؟

إذا كنت مهتمًا ، فقد قمت مؤخرًا بنشر https://github.com/bcherny/tdux و https://github.com/bcherny/typed-rx-emitter ، والتي تعتمد على أفكار من Redux و EventEmitter.

الآن يبدو، الحاجة إلى rebase إلى فرعrbuckton # 13487 مع المعلمات العامة الافتراضية. في حالة أخرى سوف نتعارض إلى حد كبير.

bcherny - شكرًا على الروابط ،

كنت أبحث في كيفية جعل المجمعات من النوع الآمن من خلال التأكد من أنها تحتوي على مخفض من النوع المناسب لكل خاصية للدولة (بدون إضافات). تمكنت من القيام بذلك في هذه الحالة بالذات بدون الأدوية الجنيسة المتداخلة ، لكن الحل المتداخل كان سيكون أجمل. لدي ما يلي:

import { combineReducers, Reducer } from 'redux';

interface IState {
    // my global state definition
}

type StatePropertyNameAndTypeAwareReducer\<S> = {
    [P in keyof S]: Reducer<S[P]>;
};

let statePropertyToReducerMap : StatePropertyNameAndTypeAwareReducer<IState> = {
    navBarSelection: navBarReducer,
};

let combinedReducers = combineReducers<IState>(statePropertyToReducerMap);

بشكل أساسي ، يضمن النوع الذي قدمته أعلاه أن تعيينات المخفض التي تمررها لتجميع المخفضات تغطي كل خاصية من ولايتك ولديها أنواع الإرجاع المناسبة. لم أتمكن من العثور على أي حل من هذا القبيل أثناء البحث عبر الإنترنت - يبدو لي أنه لا يمكن القيام بذلك بدون ميزة keyof ، التي ظهرت منذ شهرين فقط :)

أتوقع أن تكون ميزة keyof في متناول اليد لـ ImmutableJs أيضًا لجعل أدوات الضبط والحاصل آمنة من النوع ، على الرغم من أنك قد لا تزال بحاجة إلى بعض الأدوات الإضافية حول ذلك.

تحرير: للتوضيح ، كان من الممكن أن تسمح لي الأدوية العامة المتداخلة بعدم الاضطرار إلى ترميز نوع Reducer في نوع StatePropertyNameAndTypeAwareReducer ، ولكن بدلاً من ذلك ، يجب أن تكون عامة متداخلة ، وهو أمر غير ممكن في اللحظة.

Edit2: أنشأ مشكلة لـ Redux هنا: https://github.com/reactjs/redux/issues/2238 يبدو أنهم لا يفعلون الكثير من TypeScript ، لذا فهم يبحثون عن أشخاص من TypeScript يعرفون Redux ليأخذوا في الاعتبار.

كيف الحال أو كيف تسير الأمور؟

ربما يكون سؤالًا ساذجًا ، ولكن لماذا ~ أو * بدلاً من المعلمة العامة العادية؟ هل هو إشارة إلى أنه غير مستخدم؟ بمعنى آخر. لما لا:

type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

أو:

kind Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

او حتى:

abstract type Functor<A<T>> = {
  map(f: (value: T) => U): A<U>
}

bcherny أعتقد أن هذا يسبب غموضًا في البنية ، نظرًا لأن Functor<A<T>> كان يعني سابقًا " A من T " ، حيث T نوع ما في اللغة المحلية نطاق. هذا غير مرجح ، ولكن قد ينتهي الأمر بهذا التركيب إلى تغيير جذري لبعض قواعد الرموز ، للسبب نفسه.

masaeedu فهمت. تعني الصيغة الجديدة "ربط T lazily" ، بدلاً من "ربط T بشكل صارم في النطاق الحالي".

ومع ذلك ، أعتقد أن اقتراح DanielRosenwasser لـ T: * => * يبدو منطقيًا للغاية هنا ، نظرًا لوجود "حالة فنية سابقة" لذلك.

في Haskell ، عامل التشغيل -> هو في الواقع نوع محدد (ربما يكون من الأسهل تصور Func<TArg, TRet> ). يقبل مُنشئ النوع -> نوعين تعسفيين T و U وينتج نوع مُنشئ القيمة (أي دالة) الذي يعيّن قيمًا من النوع T إلى قيم من النوع U .

الشيء المثير للاهتمام هو أنه مُنشئ لطيف أيضًا ! يقبل المُنشئ النوع -> نوعين تعسفيين T* و U* (علامة النجمة للتمييز المرئي فقط) ، وينتج نوع مُنشئ النوع الذي يعيّن أنواعًا من النوع T* لأنواع من النوع U* .

قد تلاحظ وجود نمط في هذه المرحلة. يتم ببساطة إعادة استخدام بناء الجملة والدلالات المستخدمة لتحديد الأنواع والإشارة إليها لتحديد الأنواع والإشارة إليها. في الواقع ، لم يتم إعادة استخدامه حتى ، بل هو مجرد تعريف ضمني للأشياء في عالمين مختلفين في وقت واحد. (حقيقة أنه متماثل في الواقع تعني أن هذا قادر على تعريف الأشياء في مستويات وقيم لا نهائية -> أنواع -> أنواع -> أنواع -> ... ، باستثناء * المؤسف ، ولكن هذا موضوع لوقت مختلف)

في الواقع ، هذا النمط منطقي للغاية لدرجة أن بعض الأشخاص قاموا بتطبيق امتداد GHCi مستخدم على نطاق واسع والذي يعممه على جميع منشئي النوع ، وليس فقط -> . يُطلق على الامتداد اسم "أنواع البيانات" ، وهذه هي الطريقة التي تأتي بها Haskell من خلال قوائمها غير المتجانسة ( [] هو نوع قوائم القيم ونوع قوائم الأنواع) ، مجموعات غير متجانسة ، "الطول الذكي المتجهات والعديد من الميزات الأخرى بجانب.

ربما لا نرغب في الوصول إلى ما يصل إلى DataKinds حتى الآن ، لذلك سنلتزم فقط بالمنشئات اللطيفة * و -> ، لكن إذا اتبعنا بناء الجملة الذي اقترحه دانيال ، أو بشكل عام جعل التعريفات اللطيفة متشابهة لتعريفات النوع ، فإننا نفتح أنفسنا للاستفادة من التطوير المستقبلي في هذا المجال.

متابعة من منشوراتي السابقة ، أود أن أوصي باستخدام any بدلاً من * ؛ هذا يمثل نوع كل قيمة ونوع كل نوع. إذا كانت الصيغة تبدو مربكة ، فيمكننا إخراج صفحة من كتاب هاسكل واستخدام بادئة ' لإزالة الغموض عن الأنواع والأنواع.

سيتم بعد ذلك كتابة مثال OP كما يلي:

interface Monad<(T: 'any => 'any)> {
    // ...
}

Nitpick: أجد أن any مربكًا بشكل عام بمعنى أنه يفعل شيئين مختلفين.
إنه نوع ممتاز لجميع الأنواع الأخرى ، حيث لا يعتبر أبدًا نوعًا فرعيًا لجميع الأنواع الأخرى ، لذلك إذا طلبت إحدى الوظائف معلمة any ، فيمكنك وضع أي شيء. حتى الان جيدة جدا.
الجزء الذي يصبح مضحكًا هو عندما تطلب إحدى الوظائف شيئًا محددًا ، وتقدم any . هذا النوع من عمليات التحقق ، في حين أن كل نوع آخر أوسع مما تم طلبه سيجعله خطأ بدلاً من ذلك.
لكن نعم ، أيا كان.

في ملاحظة أخرى ، سيكون ' مربكًا لأنه يُستخدم أيضًا في السلاسل الحرفية.

Artazor أي أخبار بهذا؟ آخر مرة ذكرت فيها أنك بحاجة إلى إعادة التأسيس على المعلمات العامة الافتراضية. ويبدو لي أنك الشخص الوحيد المقرب بدرجة كافية من POC العامل.

يجدر أيضًا التفكير في كيفية تفاعل هذا مع التصنيف الفرعي. لا يكفي استخدام * حد ذاته ؛ في اللغات التي تستخدم تعدد الأشكال المخصص بدلاً من تعدد الأشكال المحدود ، لديك أنواع مقيدة للحد من وسيطات النوع المقبول. على سبيل المثال ، نوع Monad T هو في الواقع Constraint ، وليس * .

في TypeScript ، نستخدم التصنيف الفرعي الهيكلي بدلاً من ذلك ، لذلك تحتاج أنواعنا إلى عكس العلاقات بين الأنواع الفرعية. قد تقدم ورقة Scala حول هذا الموضوع بعض الأفكار الجيدة حول كيفية تمثيل علاقات التباين والنوع الفرعي في نظام عيني: " نحو حقوق متساوية للأنواع الأعلى من نوعها ".

أي تقدم في هذا؟

نهج بديل بواسطة gcanti https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

تكمن مشكلة الأسلوب الذي تتبعه fp-ts في أنه يجعلك تعيد تنفيذ مكتبات مثبتة بطريقة أخرى. بالنسبة لي ، فإن فكرة الطباعة هي القدرة على كتابة ما يُعتقد حاليًا على أنه أفضل الممارسات في JavaScript بشكل صحيح ، وليس إجبارك على إعادة تنفيذه بطريقة ts.

هناك الكثير من الأمثلة هنا التي تُظهر أن HKT ضرورية لوصف العقود التي نستخدمها حاليًا في js libs ، إما فانتازي لاند أو رامدا أو رد فعل.

سيكون من اللطيف حقًا أن نرى هذا معاقًا :)

~ هل هناك من يرغب / قادر على العمل على هذا مدفوع الأجر؟ لا تتردد في الاتصال بي للمناقشة. أو أي شخص قادر على تدريب شخص ما قد نجده للعمل على هذا ، يرجى إعلامي أيضًا. ~ [تحرير: ربما قررت] (https://github.com/keean/zenscript/issues/35#issuecomment -357567767) للتخلي عن هذا النظام البيئي وتعليقي اللاحق في هذا الموضوع جعلني أدرك أنه من المحتمل أن يكون مهمة ضخمة]

نهج بديل بواسطة gcanti https://medium.com/@gcanti/higher -kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe

لم أكلف نفسي عناء القلق من ذلك ، لأنني لاحظت أن الناتج map لا يزال يحدد صراحة نوع الحاوية Option وبالتالي فهو ليس عامًا تمامًا بالطريقة التي تستخدمها الأنواع الأعلى (HKT) يمكن أن توفر:

function map<A, B>(f: (a: A) => B, fa: HKTOption<A>): Option<B> {
  return (fa as Option<A>).map(f)
}

كما لاحظ spion في 26 أغسطس 2016 ، تعد HKT ضرورية لإنشاء أي وظيفة عامة تحتاج إلى مصنع ويكون فيها نوع الحاوية ذات المعلمات النوعية هو نفسه عامًا. لقد اكتشفنا هذا في مناقشاتنا حول تصميم لغة البرمجة.

ملاحظة: إذا كنت مهتمًا بالفضول ، فإن هذه الميزة تؤثر بشكل كبير في keean ) لمشهد لغة البرمجة .

@ shelby3 FWIW Option 's map (في Option.ts ) ليس عامًا لأنه يمثل المثيل بينما Functor ' s map (في Functor.ts ) عام لأنه يمثل فئة النوع . ثم يمكنك تحديد دالة lift يمكنها العمل مع أي مثيل functor.

سيكون من اللطيف حقًا أن نرى هذا معاقًا :)

أوافق بشدة :)

@ shelby3 : للحصول على ميزة مثل هذه مدمجة ، قد يكون أفضل رهان لك هو الحصول عليها
عليهم تحديد أولوياتها في خارطة طريق TS ؛ كان لدي عدد قليل من العلاقات العامة التي حصلت عليها في المقام الأول
التعليقات / الدمج إما عند إصلاحات صغيرة أو إذا كانوا يبحثون بالفعل
بداخلهم. لا أريد أن أكون سلبيًا ولكن هذا اعتبار إذا كنت
على وشك استثمار الموارد في هذا.

في 8 كانون الثاني (يناير) 2018 الساعة 4:05 مساءً ، كتب "shelby3" [email protected] :

هل هناك أي شخص مستعد / قادر على العمل بأجر؟ لا تتردد في الاتصال
لي [email protected] للمناقشة. أو أي شخص قادر على تدريب شخص ما
قد نجد للعمل على هذا ، يرجى إعلامي أيضًا.

نهج بديل من خلال gcanti https://github.com/gcanti
https://medium.com/@gcanti/higher -kinded-type-in-
طباعة نصية ثابتة وخيال أرض d41c361d0dbe

لم أكلف نفسي عناء التفكير في ذلك تمامًا ، لأنني لاحظت الخريطة الناتجة
لا يزال يحدد بشكل صريح نوع حاوية الخيار وبالتالي ليس بالكامل
عامة بالطريقة التي يمكن أن توفرها الأنواع الأعلى (HKT):

خريطة الوظيفة (f: (a: A) => B، fa: HKTOption ): الخيار { عودة (fa كخيار ). خريطة (و) }

HKT ضرورية لعمل أي وظيفة عامة تحتاج إلى مصنع و
حيث يكون نوع الحاوية ذات المعلمات هو نفسه عامًا. كان لدينا
استكشف هذا https://github.com/keean/zenscript/issues/10 في ملف
مناقشات حول تصميم لغة البرمجة.

ملاحظة: إذا كنت فضوليًا ، فإن هذه الميزة تؤثر بشكل كبير في ملفي
(بما في ذلك تحليلkeean https://github.com/keean) لملف
لغة البرمجة المشهد
https://github.com/keean/zenscript/issues/35#issuecomment-355850515 . أنا
ندرك أن وجهات نظرنا لا ترتبط تمامًا بأولويات التنضيد
بهدف أساسي هو أن تكون مجموعة شاملة من Javascript / ECMAScript ودعمها
هذا النظام البيئي.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/Microsoft/TypeScript/issues/1213#issuecomment-355990644 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AC6uxYOZ0a8G86rUjxvDaO5qIWiq55-Fks5tIi7GgaJpZM4C99VY
.

gcaniti يعتذر عن الضوضاء وشكراً على الشرح الإضافي. كان يجب أن أدرس أكثر قبل التعليق. بالطبع ، هذا هو خطئي في التصور لأن (أعرف بالفعل) يتطلب الممول تنفيذ مثيل.

يتيح برنامج Afaics ، "الاختراق" الذكي الخاص بك ، الإشارة إلى مصنع بشكل عام (على سبيل المثال lift ) ، ولكنه يتطلب النموذج المعياري الإضافي لزيادة الوحدة النمطية (تحديث و) Option . ألن يكون هذا النموذج المعياري مطلوبًا لكل استخدام عام لمصنع عام ، على سبيل المثال sort examplekeean وأنا ناقشته؟ ربما تكون هناك حالات زاوية أخرى يمكن اكتشافها أيضًا؟

هل نسخ Kotlin

لا أريد أن أكون سلبيًا ، لكن هذا اعتبار إذا كنت على وشك استثمار الموارد في هذا الأمر.

نعم ، خطرت لي هذه الفكرة أيضًا. شكرا لتوضيح ذلك. أظن أن أحد الاعتبارات سيكون التأثيرات عمومًا على نظام النوع وأي حالات ركنية قد ينتج عنها ، كما أشارmasaeedu. ربما ستكون هناك مقاومة ما لم يتم التفكير في ذلك بدقة وإثباته.

ملاحظة إنني أتطلع أيضًا إلى Ceylon للتحقق بشكل أفضل من مستوى استثمارهم في هدف تجميع EMCAScript. (ولست بحاجة لمزيد من الدراسة).

لقد تعرضت للعض من هذا القيد. أرغب في أن يتم الاستدلال تلقائيًا على I في المثال التالي:


interface IdType<T> {
  id: T;
}

interface User {
  id: number;
  name: string;
}

function doStuff<T extends IdType<I>>() {
  const recs = new Map<I, T>();
  return {
    upsert(rec: T) {
      recs.set(rec.id, rec);
    },
    find(id: I) {
      return recs.get(id);
    },
  };
}

(function () {
  const stuff = doStuff<User>();
  stuff.upsert({id: 2, name: "greg"});
  console.log(stuff.find(2));
})();

بقدر ما أستطيع أن أقول ، هذا يتطلب نوعًا أعلى أو تحديد معامل عام مكرر (على سبيل المثال doStuff<User, number>() ) والذي يبدو زائدًا عن الحاجة.

لقد صدمت مؤخرًا بهذا القيد أيضًا.

كنت أعمل في مكتبة للوعود . يوفر وظائف مفيدة متنوعة للعمل معهم.

الميزة المركزية للمكتبة هي أنها تعرض نفس نوع الوعد الذي وضعته بداخلها. لذلك إذا كنت تستخدم وعد Bluebird واستدعت إحدى الوظائف ، فستعيد وعد Bluebird بكل الوظائف الإضافية التي توفرها.

أردت ترميز هذا في نظام النوع ، لكنني أدركت بسرعة أن هذا يتطلب العمل مع نوع P من النوع * -> * مثل P<T> extends Promise<T> .

فيما يلي مثال على إحدى هذه الوظائف:

/**
* Returns a promise that waits for `this` to finish for an amount of time depending on the type of `deadline`.
* If `this` does not finish on time, `onTimeout` will be called. The returned promise will then behave like the promise returned by `onTimeout`.
* If `onTimeout` is not provided, the returned promise will reject with an Error.
*
* Note that any code already waiting on `this` to finish will still be waiting. The timeout only affects the returned promise.
* <strong i="14">@param</strong> deadline If a number, the time to wait in milliseconds. If a date, the date to wait for.
* <strong i="15">@param</strong> {() => Promise<*>} onTimeout Called to produce an alternative promise once `this` expires. Can be an async function.
*/
timeout(deadline : number | Date, onTimeout ?: () => PromiseLike<T>) : this;

في الحالة المذكورة أعلاه ، تمكنت من تجنب الحاجة إلى أنواع أعلى باستخدام النوع this المبتكر إلى حد ما.

ومع ذلك ، لا يمكن حل الحالة التالية:

/**
* Returns a promise that will await `this` and all the promises in `others` to resolve and yield their results in an array.
* If a promise rejects, the returned promise will rejection with the reason of the first rejection.
* <strong i="21">@param</strong> {Promise<*>} others The other promises that must be resolved with this one.
* <strong i="22">@returns</strong> {Promise<*[]>} The return type is meant to be `Self<T[]>`, where `Self` is the promise type.
*/
and(...others : PromiseLike<T>[]) : ExtendedPromise<T[]>;

لأنه لا يوجد اختراق يسمح لي بعمل this<T[]> أو شيء من هذا القبيل.

لاحظ اعتذاري الصغير في الوثائق.

حصلت على سيناريو آخر حيث أعتقد أن هذه الميزة ستكون مفيدة (بافتراض أنني فسرت الاقتراح الصحيح) ، كما هو موضح في المرجع أعلاه.

في الحزمة المعنية ، من الضروري وجود فئة أو وظيفة عامة غير نوعية تُستخدم كنوع ، حيث يتم إنشاء الأنواع العامة عادةً بواسطة المستخدم.

عند تطبيق الاقتراح على السيناريو الخاص بي ، أعتقد أنه سيبدو كما يلي:

import { Component, FunctionalComponent } from 'preact';

interface IAsyncRouteProps {
    component?: Component<~,~> | FunctionalComponent<~>;
    getComponent?: (
        this: AsyncRoute,
        url: string,
        callback: (component: Component<~,~> | FunctionalComponent<~>) => void,
        props: any
    ) => Promise<any> | void;
    loading?: () => JSX.Element;
}

export default class AsyncRoute extends Component<IAsyncRouteProps, {}> {
    public render(): JSX.Element | null;
}

نظرًا لعدم وجود طريقة للإشارة إلى الأنواع العامة بشكل موثوق في تطبيقي ، فأنا متأكد من أنني فاتني شيء ما.

@ Silic0nS0ldier في الواقع يمكن حل هذه القضية الآن. يمكنك استخدام نوع مُنشئ هيكلي ، مثل هذا:

type ComponentConstructor = {
    new<A, B>() : Component<A, B>;
}

ثم قل ، component ?: ComponentConstructor .

بشكل عام ، يمكن أن يكون لديك نوع وظيفة عام:

let f : <T>(x : T) => T

وهذا ما يسمى تعدد الأشكال البارامترية للرتبة n وهو في الواقع ميزة نادرة جدًا في اللغات. لذلك من المحير أكثر لماذا لا تحتوي TypeScript على أنواع من النوع الأعلى ، وهي ميزة أكثر شيوعًا.

سيظهر القيد الذي تمت مناقشته هنا إذا كنت بحاجة إلى الإشارة إلى TComponent<T, S> . لكن في حالتك يبدو هذا غير ضروري.


يمكنك أيضًا استخدام typeof Component والذي سيوفر لك نوع المُنشئ Component ولكن هذا سيتسبب في العديد من المشكلات مع الأنواع الفرعية.

GregRos بدا الحل المقترح واعدًا (يقبل الأنواع الموجودة داخل ملف التعريف) ، ولكن يتم رفض الأنواع المتوافقة. https://gist.github.com/Silic0nS0ldier/3c379367b5e6b1abd76e4a41d1be8217

@ Silic0nS0ldier انظر تعليقاتي على

chrisdavies هل هذا العمل؟

interface IdType<T> {
    id: T;
}

interface User {
    id: number;
    name: string;
}

function doStuff<T extends IdType<any>>() {
    type I = T['id']; // <==== Infer I
    const recs = new Map<I, T>();
    return {
        upsert(rec: T) {
            recs.set(rec.id, rec);
        },
        find(id: I) {
            return recs.get(id);
        },
    };
}

(function() {
    const stuff = doStuff<User>();
    stuff.upsert({ id: 2, name: "greg" });
    console.log(stuff.find(2));
})();

@ جاك ويليامز نعم. هذا يعمل من أجل السيناريو الخاص بي. لم أجد هذا المثال في المستندات (على الرغم من أنني معروف بفقدان بعض الأشياء!). شكرا لك!

لقد كنت أفكر في هذه الميزة كثيرًا ، ولدي بعض الأفكار حول هذا الموضوع ، أميل نحو نوع من المواصفات ، لكن لا يزال بإمكاني رؤية الكثير من المشكلات. اقتراحاتي مختلفة قليلاً عما تم اقتراحه حتى الآن.


بادئ ذي بدء ، أعتقد أن استخدام أي نوع من بناء جملة T<*, *> لمنشئي النوع فكرة سيئة لأنها لا تتناسب بشكل جيد مع تعقيد مُنشئ النوع. لست متأكدًا أيضًا مما إذا كانت هناك نقطة في تحديد نوع مُنشئ النوع كلما تمت الإشارة إليه ، نظرًا لأننا لا نقوم بذلك للوظائف ، حتى للوظائف ذات معلمات النوع.

أعتقد أن أفضل طريقة لتنفيذ ذلك هي التعامل مع الأنواع الأعلى نوعًا مثل الأنواع الأخرى ، بأسماء عادية ، وتحديد علاقة نوع فرعي جيدة على منشئي النوع أنفسهم التي يمكن استخدامها لفرض قيود.

أعتقد أننا يجب أن نستخدم نوعًا من البادئة أو postfix لتمييزها عن الأنواع الأخرى ، وذلك أساسًا لحماية المستخدمين من رسائل الخطأ غير المفهومة التي تتضمن منشئي النوع عندما يرغبون فقط في كتابة تعليمات برمجية عادية. يعجبني نوعًا ما شكل: ~Type, ^Type, &Type أو شيء من هذا القبيل.

على سبيل المثال ، قد يكون توقيع الوظيفة:

interface List<T> {
    push(x : T);
}

function mapList<~L extends ~List, A, B>(list : L<A>, f : (x : A) => B) : L<B>;

(أنا لا أستخدم البادئة ~ للأنواع المنشأة عن قصد)

باستخدام extends هنا ، قلت شيئين أساسيين:

** 1. إذا لزم الأمر: ~L هو مُنشئ نوع له نفس النوع مثل ~List ، أي النوع * -> * (أو ربما * => * ، منذ => هو سهم TypeScript).

  1. ~L هو نوع فرعي من ~List . **

استخدام extends للإشارة إلى نوع مقاييس مُنشئ النوع لمنشئات الأنواع المعقدة بشكل تعسفي ، بما في ذلك أشياء مثل ((* => *) => (* => *)) => * .

لا يمكنك حقًا رؤية هذا النوع في معرف النوع ، لكنني لا أعتقد أنك بحاجة إلى ذلك. لست متأكدًا حتى من أن علاقة النوع الفرعي بين منشئي النوع يجب أن تحافظ على الأنواع ، لذلك (1) قد لا تكون ضرورية.

لا بناء أنواع غير مكتملة

أعتقد أننا لا ينبغي أن ندعم بناء أنواع غير مكتملة. وهذا هو شيء من هذا القبيل:

(*, *) => * => *

أعتقد أنه سيخلق مشاكل أكثر مما يستحق. على سبيل المثال ، يجب على كل مُنشئ للنوع إنشاء نوع معين من الخرسانة ، ويجب تحديد هذا النوع الملموس في السياق حيث يتم تعريف TC.

الطريقة الهيكلية لتحديد نوع الصانعين

أعتقد أيضًا أنه يجب أن تكون هناك طريقة هيكلية لتحديد منشئي النوع ، بنفس الطريقة التي يمكن بها تحديد أي نوع آخر هيكليًا ، بما في ذلك أنواع الوظائف العامة عالية الترتيب جدًا. كنت أفكر في بناء الجملة مثل:

~<A, B> { 
    a : A,
    b : B
}

وهو مشابه للصيغة الحالية لأنواع الوظائف ذات معلمات النوع:

<A, B>() => { a : A, b : B};

يمكن الجمع بين الاثنين للحصول على هذا:

~<A><B, C> => [A, B, C]

وهو مُنشئ نوع يقوم بإنشاء نوع دالة عامة.

وتتمثل الفائدة في أنه يمكن استخدام هذه الأنواع الهيكلية عند تحديد أنواع هيكلية أخرى ، عند تحديد قيود النوع ، وما إلى ذلك. يعني هذا أحيانًا أنه يمكنهم استخدام الرموز المحلية المرجعية التي لا يمكن الرجوع إليها من أي مكان آخر.

هنا مثال:

type List<A, B> = ...;

type AdvancedType<~L extends ~<A>List<A, B>, B> = ...;

في المثال أعلاه ، يشير مُنشئ النوع الهيكلي ~<A>List<A, B> إلى معلمة النوع B . لا يمكن تحديد هذه العلاقة بأي طريقة أخرى ، على الأقل بدون تشفير النوع المركب جزئيًا List<A, *> . هناك أمثلة أخرى أيضًا.

علاقة النوع الفرعي

يبدو أن علاقة النوع الفرعي منطقية ، لكنني واجهت عددًا من الصعوبات في محاولة وصفها.

كانت فكرتي الأولى هي التالية. لكي يكون ~A نوعًا فرعيًا من ~B :

  1. (أ) يجب أن يكون لديهم نفس النوع (من حيث السبب وليس القيود).
  2. (ب) لكل معلمة قانونية T₁, T₂, ... من ~A ، يجب أن يكون A<T₁, T₂, ...> نوعًا فرعيًا من B<T₁, T₂, ...> .

ومع ذلك ، هذا له العديد من القيود.

  1. فئة MySpecialPromise تنفذ PromiseLike {} في هذه الحالة ، ~MySpecialPromise ليس نوعًا فرعيًا من ~PromiseLike لأن لهما أنواعًا مختلفة.

  2. فئة MyArrayPromiseتنفذ PromiseLike

    لا يتم حفظ علاقة النوع الفرعي في هذه الحالة أيضًا.

نسخة أكثر عمومية من (ب) هي التالية:

(ب) لكل معلمة قانونية T₁, T₂, ... من ~A ، توجد معلمة S₁, S₂, ... من ~B مثل أن A<T₁, T₂, ...> هو نوع فرعي من B<S₁, S₂, ...> .

بمعنى آخر ، هناك تعيين F (T₁ ، T₂ ، ...) = S₁ ، S₂ ، ... مع الخصائص المذكورة أعلاه. يجب استخدام هذا التعيين من أجل إنشاء المعلمة B<...> من معلمة A<...> . قد يسمح لنا بالقيام بذلك حتى لو كان لمنشئ النوع أنواعًا مختلفة.

تكمن مشكلة هذه العلاقة في أنني لست متأكدًا من كيفية العثور على التعيين الصحيح. في اللغات ذات الكتابة الاسمية ، كل عبارة على غرار:

A<...> extends B<...>

يحدد تعيينًا بين معلمات النوع ~A ومعلمات النوع ~B ، لذلك هذه هي الطريقة التي يمكن بها استرداد التعيين. ومع ذلك ، في نظام TypeScript للكتابة الهيكلية ، لا يمكننا الاعتماد على عبارات صريحة من هذا النوع.

تتمثل إحدى الطرق في دعم منشئي النوع فقط للأنواع ذات النوع الصحيح informaiton ، مثل عبارات implements أو نوع من أعضاء النوع المجرد مشابه لـ Scala. لست متأكدًا مما إذا كان هذا هو الطريق إلى الأمام.

GregRos - ملاحظات مثيرة للاهتمام! بعض الأسئلة.


ماذا تقصد بنوع الخرسانة؟ هل تقصد شيئًا من النوع * ، أو نوع ليس به معلمات نوع ملزمة؟


لا بناء أنواع غير مكتملة
أعتقد أننا لا ينبغي أن ندعم بناء أنواع غير مكتملة. وهذا هو شيء من هذا القبيل:
(* ، *) => * => *

ماذا تقصد ببناء أنواع غير مكتملة؟ هل تقصد أن كل تطبيق مثل L<A> يجب أن يكون نوع * . هل مُنشئ نوع الزوج خاص في مثالك ، على سبيل المثال ، هل سيكون (* => *) => * => * جيدًا؟


الطريقة الهيكلية لتحديد نوع الصانعين

~<A, B> { 
    a : A,
    b : B
}
inferface TyCon<A, B> { 
    a : A,
    b : B
}

هل هذه الأمثلة مختلفة ، بخلاف أن تكون الأولى مجهولة؟


علاقة النوع الفرعي

لا يشير ~A و ~B إلى الأنواع ، فهل من المنطقي أن يكون لديهم علاقة بالنوع الفرعي؟ متى تحتاج بالفعل إلى التحقق من أن المنشئ هو "نوع فرعي" لآخر؟ هل من الممكن الانتظار حتى يتم تطبيق البناة والتحقق من الأنواع الناتجة؟

@ جاك ويليامز شكرا على ردود الفعل!

ماذا تقصد ببناء أنواع غير مكتملة؟ هل تقصد أن كل تطبيق مثل L<A> يجب أن يكون نوعًا *. هل مُنشئ نوع الزوج خاص في مثالك ، على سبيل المثال ، هل سيكون (* => *) => * => * جيدًا؟

نعم بالضبط. يجب أن يحتوي كل تطبيق مثل L<A> على النوع * . لست متأكدًا من مدى بيعي لذلك.


هل هذه الأمثلة مختلفة ، بخلاف أن تكون الأولى مجهولة؟

الأول عبارة عن تعبير نوع ، بينما الثاني هو تصريح. إنها متطابقة في معظم النواحي ، بنفس الطريقة التي تتطابق بها هذه الأنواع في معظم النواحي:

{
     a : number;
     b : string;
}

interface Blah {
    a : number;
    b : string;
}

للصيغة عدة دوافع:

  1. تمامًا مثل أي شيء آخر في TypeScript ، فإنه يتيح تحديد منشئي الكتابة هيكليًا ومجهول الهوية.
  2. يمكن استخدام تعبير نوع (مثل الكائن المجهول المكتوب أعلاه) في سياقات معينة حيث لا يمكن استخدام عبارات التصريح ، مثل تواقيع النوع للوظائف. هذا يسمح لهم بالتقاط المعرفات المحلية والتعبير عن الأشياء التي لا يمكن التعبير عنها بطريقة أخرى.

لا يشير ~A و ~B إلى الأنواع ، فهل من المنطقي أن يكون لديهم علاقة بالنوع الفرعي؟ متى تحتاج بالفعل إلى التحقق من أن المنشئ هو "نوع فرعي" لآخر؟ هل من الممكن الانتظار حتى يتم تطبيق البناة والتحقق من الأنواع الناتجة؟

قد يتم اعتبار منشئي النوع أنواعًا أو لا. أقترح اعتبارها أنواعًا ، فقط أنواع غير مكتملة لا تحتوي على قيم ولا يمكن أن تظهر في أي سياق يتطلب نوع القيمة. هذه هي نفس الفلسفة التي اتبعها سكالا ، في هذه الوثيقة

من خلال علاقة النوع الفرعي ، أعني بشكل أساسي نوعًا من علاقة "المطابقة" التي يمكن استخدامها لتقييد منشئي النوع. على سبيل المثال ، إذا كنت أرغب في كتابة دالة تعمل على جميع أنواع الوعود من أنواع مختلفة ، مثل Promise<T> ، Bluebird<T> ، وما إلى ذلك ، فأنا بحاجة إلى القدرة على تقييد معلمات TC باستخدام واجهة PromiseLike<T> بطريقة ما.

الكلمة الطبيعية لهذا النوع من العلاقات هي علاقة من نوع فرعي.

لنلقي نظرة على مثال. بافتراض أننا توصلنا إلى علاقة نوع فرعي بين منشئي النوع ، يمكنني كتابة دالة مثل هذه:

function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B>;

ومن المفترض أن يضمن القيد ~P extends ~PromiseLike هذه وظيفة تعمل على الوعود والوعود فقط. سيضمن القيد أيضًا أنه داخل جسم الوظيفة ، يُعرف promise بتنفيذ PromiseLike<A> ، وهكذا. بعد كل شيء ، فإن الأعضاء المعترف بهم بواسطة TypeScript في جسم الوظيفة هم على وجه التحديد تلك التي يمكن إثبات وجودها من خلال القيود.

بنفس الطريقة Promise<T> extends PromiseLike<T> ، لأنها متوافقة من الناحية الهيكلية ويمكن استبدالها ببعضها البعض ، ~Promise extends ~PromiseLike لأنها تنشئ أنواعًا متوافقة هيكليًا وبالتالي يمكن استبدالها ببعضها البعض.


لتأكيد المشكلة في مشكلة النوع الفرعي ، ضع في اعتبارك مرة أخرى:

interface MyPromise<T> extends Promise<T[]> {}

هل يمكننا تجريد أكثر من ~MyPromise بنفس الطريقة التي نجرد أكثر من ~Promise ؟ كيف نلتقط العلاقة بينهما؟

التعيين الذي تحدثت عنه سابقًا هو التعيين الذي ، نظرًا للمعلمات ~MyPromise ، سينتج معلمة ~Promise بحيث يكون النوع الذي تم إنشاؤه بواسطة ~MyPromise نوعًا فرعيًا من واحد تم إنشاؤه بواسطة ~Promise .

في هذه الحالة ، يكون التعيين كما يلي:

T => T[]

تضمين التغريدة

في هذه الحالة ، ~MySpecialPromise ليس نوعًا فرعيًا من ~PromiseLike لأن لهما أنواعًا مختلفة.

في Haskell ، يتم حل هذا النوع من المشكلات عن طريق السماح بالتطبيق الجزئي للأنواع ، وتحديد الأنواع بحيث يتطابق معلمة النوع النهائي مع معلمة النوع لأي واجهة تقوم بتنفيذها.

في المثال الخاص بك ، سيتم تعريف MySpecialPromise على أنه MySpecialPromise<TSpecial, TPromiseVal> ، و ~MySpecialPromise<SpecialType> سيكون له نوع مماثل لـ ~Promise .

تضمين التغريدة

من خلال علاقة النوع الفرعي ، أعني بشكل أساسي نوعًا من علاقة "المطابقة" التي يمكن استخدامها لتقييد منشئي النوع. على سبيل المثال ، إذا كنت أرغب في كتابة وظيفة تعمل على جميع أنواع الوعود من مختلف الأنواع ، مثل Promise، طائر أزرقوما إلى ذلك ، فأنا بحاجة إلى القدرة على تقييد معلمات TC بواجهة PromiseLikeبطريقة ما.
function mapPromise<~P extends ~PromiseLike, A, B>(promise : P<A>, func : (x : A) => B) : P<B> ؛

أعتقد أنه عندما يتعلق الأمر بفحص هذه الوظيفة ، فإنك ستحاول توحيد BlueBird<T> و PromiseLike<T> للمختارة T ، فهذه مجرد أنواع محددة وتقع تحت التصنيف الفرعي. لا أرى سبب احتياجك إلى علاقة خاصة للمُنشئين ~BlueBird و ~PromiseLike .

أعتقد أنه سيتم استخدامه في شيء مثل هذا؟


let x: <P extends ~PromiseLike>(input : P<A>, func : (x : A) => B) : P<B>;
let y: <P extends ~BlueBird>(input : P<A>, func : (x : A) => B) : P<B>;
x = y;

هنا قد ترغب في التحقق من أن قيود y تشير ضمنًا إلى قيود x ، لكن هل TypeScript لا يحتوي بالفعل على آلات للتحقق من أن BlueBird<T> يمتد PromiseLike<T> الذي يمكن استخدامه؟

@ jack-williams يتعلق الأمر بكيفية تحديد القيد التالي:

~ P عبارة عن مُنشئ نوع بحيث يكون ، بالنسبة لجميع A ، P<A> نوعًا فرعيًا من PromiseLike<A> .

ما نوع بناء الجملة الذي ستستخدمه؟ ما نوع المفهوم الذي ستستخدمه؟ يمكنك كتابة شيء مثل هذا:

function mapPromise<~P, A, B where P<A> extends PromiseLike<A>>

لكن هذه الصيغة لها حدود. على سبيل المثال ، لا يمكنك التعبير عن هذه الفئة على الإطلاق ، لأنه لا يمكننا إنشاء النوع P<A> عند النقطة التي تم الإعلان عنها من أجل تقييدها:

class PromiseCreator<~P extends ~PromiseLike> {
    create<A>() : P<A>;
}

لكن أعتقد أنه يمكنك استخدام أنواع وجودية لذلك ، مثل هذا:

//Here A is not a captured type parameter
//It's an existential type we introduce to constrain ~P
class PromiseCreator<~P with some A where P<A> extends PromiseLike<A>> {
    create<A>() : P<A>;
}

بعد ذلك يمكنك أن تطلب من جميع منشئي النوع أن يتم تقييدهم من خلال الأنواع المركبة الخاصة بهم داخل توقيع الوظيفة أو النوع ، اختياريًا باستخدام الأنواع الوجودية.

مع الأنواع الوجودية ، سيكون لهذا نفس القوة التعبيرية لعلاقة النوع الفرعي مع التعيين.

ومع ذلك ، قد يكون لهذا عدة مشكلات:

  1. يتطلب تحديد منشئي النوع بأنواع مثل ((* => *) => *) => * تقديم العديد من الأنواع الوجودية ، والتي يجب أن يكون بعضها من رتبة أعلى. سيتعين عليهم جميعًا الظهور في توقيع الوظيفة أو الفصل.
  2. لست متأكدًا تمامًا من أنه سيكون من الأسهل العثور على الأنواع الوجودية المعنية بدلاً من العثور على الخريطة.
  3. أعتقد أنها أقل أناقة من علاقة النوع الفرعي.
  4. يُحتمل أن يقدم شكلاً آخر من النوع الذي يجب أن تتعامل معه.

تضمين التغريدة

ما نوع بناء الجملة الذي ستستخدمه؟ ما نوع المفهوم الذي ستستخدمه؟

_ شخصيًا_ لن أستخدم أي بنية خاصة وأستخدم فقط:

function mapPromise<P extends PromiseLike, A, B>(p: P<A>, f: (x: A) => B): P<B>

class PromiseCreator<P extends PromiseLike> {
    create<A>() : P<A>;
}

ولكن هذا رأيي فقط لأنني أرى أشياء مثل number مُنشئ null-ary: لذلك لا داعي لأن يكون هناك تمييز.

إن وجهة نظري في التصنيف الفرعي لوظائف المُنشئ هي إبقائها بسيطة قدر الإمكان. يجب أن يكون لديهم نفس النوع ويجب أن تكون المعلمات أنواع فرعية من بعضها البعض ، مع مراعاة التناقض والتباين إلى حد كبير مثل ورقة Scala.

يمكن للتطبيق الجزئي الالتفاف على الحالات التي يكون فيها اختلافًا مختلفًا (لا أمانع في إجراء عملية التحليل التلقائي لمنشئي النوع بحيث يمكنك فقط كتابة MySpecialPromise<SpecialType> ).

في المثال interface MyPromise<T> extends Promise<T[]> {} يجب أن أكون صادقًا وأقول إنني لست مقتنعًا بأن الأمر يستحق التعقيد للتعامل مع هذه الحالة - أعتقد أنها ستكون ميزة مفيدة بدرجة كافية بدونها.

معالجة هذه الحالة تعادل (على ما أظن) ، بالقول: ~MyPromise extends ~(Promise . []) حيث [] هو مُنشئ القائمة و . هو تكوين المُنشئ. يبدو أن الأمور تزداد صعوبة حيث لا يكفي الآن فحص هيكل المُنشئين ، ولكن عليك التفكير في التركيب أيضًا!

@ jack-williams هذا لا يعمل مع معلمات النوع الافتراضية. إذا كتبت P extends Foo ، حيث يحتوي Foo على معلمة نوع افتراضية ، مثل type Foo<T = {}> = ... ، فما هو نوع P ؟

أود فقط أن أقول إنني أوافق على الأنواع ذات الترتيب الأعلى (كانت لدي مواقف في مشاريع TypeScript حقيقية حيث ستكون مفيدة).

ومع ذلك ، لا أعتقد أنه يجب عليهم دعم الكاري. أنا أحب Haskell ، لكن هذا لا يتناسب مع TypeScript.

تعد الأنواع ذات الترتيب الأعلى مفيدة حتى بدون التقليل أو التطبيق الجزئي ، ولكن إذا كانت هناك حاجة إلى تطبيق جزئي ، فإنني أفضل أن أرى صياغة واضحة لها. شيء من هذا القبيل:

Foo<number, _>  // equivalent to `type Foo1<A> = Foo<number, A>`

@ كاميرون مارتن

تحرير: آسف ، لا أعتقد أن تعليقاتي _not_ كانت واضحة جدًا. بواسطة P له نوعه الخاص أعني أن له نوعًا مفروضًا عليه باستخدامه. يُفترض دائمًا أن قيود قل هي النوع الأعلى ، لذلك يُفترض أن Foo هو ~Foo . فقط إذا فرضنا أن يكون P من النوع الأدنى ، نتحقق مما إذا كان Foo يحتوي على معلمة افتراضية. اهتمامي بهذا هو الاستدلال اللطيف ، ولكن في هذه الحالة لن يساعد ~ وأعتقد أننا بحاجة إلى تعليقات توضيحية كاملة.

P له نوعه الخاص ، أليس كذلك؟ ألن يكون السؤال هو هل نتعامل مع Foo أنه ~Foo ، أو Foo<{}> : سأجادل في أن ذلك سيكون مدفوعًا بنوع P. لذا إذا P هو نوع نفرض المعلمة الافتراضية ، وإذا كان P مُنشئ * => * ، فإننا نتعامل مع Foo بنفس الطريقة.

Pauan أوافق على اقتراحاتكم.

@ jack-williams لقد فكرت في فكرة التصنيف الفرعي ، كما ذكرت سابقًا:

كانت فكرتي الأولى هي التالية. لكي يكون ~A نوعًا فرعيًا من ~B :

  1. (أ) يجب أن يكون لديهم نفس النوع (من حيث السبب وليس القيود).
  2. (ب) لكل معلمة قانونية T₁, T₂, ... من ~A ، يجب أن يكون A<T₁, T₂, ...> نوعًا فرعيًا من B<T₁, T₂, ...> .

تكمن المشكلة في أننا إذا أبقينا الأشياء بسيطة قدر الإمكان ، فسننتهي بعلاقة من النوع الفرعي متناقضة ولا تتناسب مع اللغة.

إذا كان MyPromise<T> extends Promise<T[]> هذا يعني أن MyPromise<T> يجب أن يكون قابلاً للاستخدام في أي مكان يكون Promise<T[]> قابلاً للاستخدام ، لكن هذا لن يكون كذلك.

إذا استخدمت as لتحويل a : MyPromise<T> إلى Promise<T[]> ، فستكون مفاجئًا ، لكن من المفارقة أن هذا سيجعل a أكثر قابلية للتخصيص.

يمكن أيضًا استخدام القيود العامة الحالية ، والتي تتبع علاقة النوع الفرعي الحالية ، لتحقيق تأثير مماثل والتسبب في سلوك غريب:

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

ستصبح الكتابة على الأقل اسمية جزئيًا كأثر جانبي. ستكون هذه الأنواع مختلفة فجأة ، حيث تكون هي نفسها حاليًا:

type GenericNumber<T> = number;

type RegularNumber = number;

لست متأكدًا حتى من تأثير ذلك على أنواع الاتحاد / التقاطع المعقدة مع معلمات النوع ، والأنواع الهيكلية البحتة ، والأنواع مع فهم الأعضاء ، وما شابه.

شعوري الشخصي هو أن: علاقة النوع الفرعي على منشئي النوع تحتاج إلى احترام العلاقة الموجودة ، وليس معارضتها . للأسف ، هذا يتطلب أن تكون الأمور أكثر تعقيدًا.


أكبر سبب لاستخدام نوع من تدوين خاص لنوع الصانعين هو أن 99٪ من المطورين لا نعرف ما هو نوع منشئ ولن تريد أن يكون لوابل من رسائل الخطأ عنهم.

هذا يختلف تمامًا عن Haskell ، حيث يُطلب من كل مطور بموجب القانون أن يأخذ دورة متقدمة في نظرية الفئات.

الأسباب الثانوية هي أنه في بعض الحالات (مثل حالة المعلمات الافتراضية المذكورة أعلاه) ، قد يكون بناء الجملة غامضًا أو لن يكون من الممكن تجريد مُنشئ نوع معين على الإطلاق.

تحرير: آسف GregRos لم أر تعليقاتك الأخيرة!

يجب أن تحترم علاقة النوع الفرعي عبر مُنشئات النوع الموجودة ، وليس معارضتها.

إذا كان من الممكن تحقيق ذلك فأنا أوافق. أنا فقط لم أفهم كل التفاصيل ومدى سهولة ذلك.

الأسباب الثانوية هي أنه في بعض الحالات (مثل حالة المعلمات الافتراضية المذكورة أعلاه) ، قد يكون بناء الجملة غامضًا أو لن يكون من الممكن تجريد مُنشئ نوع معين على الإطلاق.

لست متأكدًا من أنني أوافق على أنه سيكون من الغموض أن تفترض دائمًا أعلى نوع من القيد حتى تحتاج إلى أن يكون أقل. هذا ليس تأكيدًا ، وإذا كانت هناك أمثلة أخرى تظهر خلاف ذلك ، فهذا عادل بما فيه الكفاية.


تكمن المشكلة في أننا إذا أبقينا الأشياء بسيطة قدر الإمكان ، فسننتهي بعلاقة من النوع الفرعي متناقضة ولا تتناسب مع اللغة.

قد يكون هذا صحيحًا ، أعتقد أنني قلق فقط بشأن ما إذا كان البديل ممكن التنفيذ بالفعل. مقابل ما يستحق ، إذا كان الحل الأكثر تعقيدًا يعمل فسيكون ذلك رائعًا!

يبدو من الصعب تنفيذ فكرة عامة عن التصنيف الفرعي والتي تُظهر وجود وظيفة تعيين بشكل عام. هل المثال التالي يفسر قواعدك بشكل صحيح؟

(ب) لكل معلمة قانونية T₁ ، T₂ ، ... لـ ~ A ، يوجد معلمة S₁ ، S₂ ، ... لـ ~ B مثل أن A

هل سيكون X نوعًا فرعيًا من Y في الحالة التالية ، بالنظر إلى تعيين F (A ، B) = (رقم ، B).

type X = ~<A,B> = {x : B};
type Y = ~<A,B> = A extends number ? {x: B} : never;

ومع ذلك ، لن يكون X<string,number> نوعًا فرعيًا من Y<string,number> .

أعتقد أنني لست واضحًا فيما إذا كان _وجود _ التعيين كافيًا. إذا أخذنا ~ A و ~ B ليكونا دالتين ، وأردنا أن نظهر أن ~ B تقارب ~ A ، أو ~ A هي نوع فرعي من ~ B ، ثم نوضح أن هناك بعض الوظائف ~ C ، بحيث تكون ~ A النوع الفرعي من (~ B. ~ C) ، لا يكفي على ما أعتقد (C هو رسام الخرائط). يجب أن يكون الحال بالنسبة لتعيينات _ all_.

function id1<A, ~P extends ~PromiseLike>(p : P<A>) : P<A>;

function id2<A, P extends Promise<A[]>>(p : P) : P {
    //ERROR - P does not extend PromiseLike<A>
    return id1(p);
}

لا أتبع هذا المثال تمامًا ، ألا يجب أن يحدث الخطأ هنا؟ قراءتي لهذه هي أن id1 يجب أن يحتوي على مُدخل تم إنشاؤه بواسطة الوظيفة P التي تعطي PromiseLike لكل _inputs_. بينما يتحدث id2 عن قيمة يجب أن تكون نوعًا فرعيًا لتطبيق Promise على A []. لست متأكدًا مما إذا كان من الممكن استرداد المعلومات اللازمة لـ id1 ، من النوع id2 . أعتقد أنني قد أساء فهم وجهة نظرك.

ستكون هذه الأنواع مختلفة فجأة ، حيث تكون هي نفسها حاليًا

مرة أخرى ، أخشى أنني قد أفتقد وجهة نظرك ولكن لا أعرف كيف هما نفس الشيء. لا يمكنني استبدال RegularNumber بـ GenericNumber في أحد الأنواع ، سأضطر إلى إعطاء الوسيطة الأخيرة.

أعتقد أنني لست واضحًا فيما إذا كان وجود الخرائط كافيًا. إذا أخذنا ~ A و ~ B ليكونا دالتين ، وأردنا أن نظهر أن ~ B تقارب ~ A ، أو ~ A هي نوع فرعي من ~ B ، ثم نوضح أن هناك بعض الوظائف ~ C ، بحيث تكون ~ A النوع الفرعي من (~ B. ~ C) ، لا يكفي على ما أعتقد (C هو رسام الخرائط). يجب أن يكون الحال بالنسبة لجميع التعيينات.

نعم ، أنت على حق ، وكذلك المثال المضاد الذي قدمته. لقد وجدت أمثلة أخرى مضادة. لا يعمل إطلاقا.

لقد أعدت قراءة هذا الموضوع والكثير من ردودك. أعتقد أنك محق في الكثير من الأشياء وكنت أنظر إلى المشكلة بطريقة خاطئة. سأصل إلى ما أعنيه.

لست متأكدًا من أنني أوافق على أنه سيكون من الغموض أن تفترض دائمًا أعلى نوع من القيد حتى تحتاج إلى أن يكون أقل. هذا ليس تأكيدًا ، وإذا كانت هناك أمثلة أخرى تظهر خلاف ذلك ، فهذا عادل بما فيه الكفاية.

إنه إما غامض أو يصبح من المستحيل الإشارة إليه. كما في المثال أعلاه ، يصبح من المستحيل الإشارة إلى مُنشئ النوع Foo لأنه مخفي بالنوع نفسه. إذا كتبت ~Foo أو لهذا الأمر Foo<*> أو ~<A>Foo<A> أو أي شيء آخر لا يتعارض مع أشياء أخرى ، فلن تواجه هذا النوع من المشاكل.

نعم ، يمكنك تجاوز ذلك من خلال تحديد اسم مستعار ، رغم أنه ليس جميلًا جدًا:

type Foo2<T> = Foo<T>

كما قلت ، لا أعتقد أن هذا هو الشاغل الأكثر أهمية.

لا أتبع هذا المثال تمامًا ، ألا يجب أن يحدث الخطأ هنا؟ قراءتي لهذه هي أن id1 يجب أن يحتوي على مدخلات تم إنشاؤها بواسطة الوظيفة P التي تعطي PromiseLike لجميع المدخلات. بينما يتحدث id2 عن قيمة يجب أن تكون نوعًا فرعيًا لتطبيق الوعد على A []. لست متأكدًا مما إذا كان من الممكن استعادة المعلومات اللازمة لـ id1 ، من نوع id2. أعتقد أنني قد أساء فهم وجهة نظرك.

هذه هي القراءة الصحيحة ، نعم. ولكن إذا كان P extends Promise<A[]> يجب أن يكون قابلاً للتخصيص إلى أي مكان يقبل Promise<A[]> ، مثل id1 . هذا هو الحال الآن ، وما يعنيه التصنيف الفرعي.

لا أعتقد حقًا أنه يمكن تجنب ذلك بعد الآن.

مرة أخرى ، أخشى أنني قد أفتقد وجهة نظرك ولكن لا أعرف كيف هما نفس الشيء. لا يمكنني استبدال رقم RegularNumber بـ GenericNumber في نوع ما ، وسأضطر إلى إعطاء الوسيطة الأخيرة.

ما قصدته هو هذا: النوع GenericNumber<T> ، لكل T ، والنوع RegularNumber متطابقان وقابلان للتبادل. لا يوجد سياق يكتب فيه أحدهم check والآخر لا. الآن على الأقل.

ما كنا نتحدث عنه سيجعلهم مختلفين. نظرًا لأن GenericNumber<T> يأتي من أحد المساهمين الأساسيين ، فسيكون من السهل تجميعه في الأماكن التي لا يمكن أن يكون فيها RegularNumber . لذلك لم يعد قابلاً للتبادل.

لقد فكرت في هذا ، وأعتقد أنه قد يكون لا مفر منه ، وليس بالضرورة سيئًا. مجرد سلوك جديد مختلف.

طريقة واحدة للتفكير في الأمر هي أن تصبح معلمة النوع جزءًا من "بنية" النوع.

أعتقد أن أبرز المساهمين سوف يقودون إلى سلوكيات مختلفة أكثر.

اتجاه جديد

بادئ ذي بدء ، أعتقد أنك محق في أن علاقة النوع الفرعي الصحيحة هي تلك التي لا تحتوي على التعيين:

كانت فكرتي الأولى هي التالية. بالنسبة إلى ~A ليكون نوعًا فرعيًا من ~B :

  1. (أ) يجب أن يكون لديهم نفس النوع (من حيث السبب وليس القيود).
  2. (ب) لكل معلمة قانونية T₁, T₂, ... من ~A ، يجب أن يكون A<T₁, T₂, ...> نوعًا فرعيًا من B<T₁, T₂, ...> .

شيء رسم الخرائط ... بصراحة ، إنه غبي جدًا. لا أعتقد أن هناك أي طريقة لتوحيد MyPromise<T> extends Promise<T[]> و ~Promise بعد الآن. أود أن أعرف ما إذا كان هناك من يعتقد خلاف ذلك.

أود أيضًا أن أعرف ما إذا كان هناك مثال أفتقده حيث حتى هذه القاعدة لا تعمل.

إذا اتفقنا على ضرورة التعبير عن قيود مُنشئ النوع باستخدام علاقة نوع فرعي ، والتي يبدو أنها تعمل جيدًا ، يمكننا الانتقال إلى أشياء أخرى.

حول بناء الجملة

يبدو أننا لسنا متفقين حقًا على هذا الموضوع. أفضل صيغة بادئة مشابهة لـ ~Promise . من الناحية المفاهيمية ، يمكن النظر إلى ~ على أنه "مرجع إلى TC لـ" عامل التشغيل أو شيء من هذا القبيل.

أعتقد أنني قدمت عدة أسباب تجعله أفضل من البدائل:

  1. لا لبس فيه تماما.

    1. الأخطاء التي تنطوي على هذا النحو هي أيضا لا لبس فيها. إذا نسي شخص ما وضع معلمات لنوع ما ، فلن يحصل على أخطاء حول منشئي النوع عندما لا يعرفون ما هي.

    2. كأثر جانبي ، لن يكون من الضروري تغيير نصوص الأخطاء الموجودة ومنطقها. إذا كتب أحد الأشخاص Promise فستكون رسالة الخطأ كما هي الآن تمامًا. لن تضطر إلى التغيير للتحدث عن أبرز المساهمين.

  2. يمتد بشكل جيد إلى بناء جملة هيكلي.
  3. أعتقد أنه من السهل تحليلها. أي ~\w يظهر حيث من المتوقع أن يشير النوع إلى مرجع إلى TC.
  4. سهل الكتابة.

آمل أن يتمكن الآخرون من إبداء آرائهم.

حول الاتحادات والتقاطعات ومعلمات النوع الافتراضية

هل الأنواع المثقلة / المختلطة من النماذج * & (* => *) ، * | (* => *) ، وهكذا ، تعتبر قانونية؟ هل لديهم استخدامات مثيرة للاهتمام؟

أعتقد أنها فكرة سيئة ويصعب التفكير فيها. لست متأكدًا أيضًا من نوع التعليق التوضيحي الذي ستحتاجه لإزالة الغموض عن * | (* => *) حتى تتمكن من إنشاء نوع منه.

إحدى الطرق التي يمكن أن يقال عنها وجود مثل هذه الأنواع في الوقت الحالي ، هي الأنواع ذات معلمات النوع الافتراضية:

type Example<A = number> = {}

يمكن القول بأن هذا النوع من النوع * & (* => *) لأنه يمكن أن يقبل معلمة نوع لبناء نوع ، لكن ليس من الضروري.

أعتقد أن معلمات النوع الافتراضية يجب أن تكون شكلاً من أشكال الاختزال ، وليس طريقة لوصف الأنواع. لذلك أعتقد أنه يجب فقط تجاهل معلمات النوع الافتراضية عند تحديد نوع النوع.

ومع ذلك ، قد يكون من المنطقي التحدث عن أنواع مثل ~Promise | ~Array . لديهم نفس النوع ، لذا فهما ليسا غير متوافقين. أعتقد أنه ينبغي دعم هذا.

الأشياء التي يجب التعامل معها

يجب التعامل مع المواقف ذات الصلة ، مثل هذا الموقف:

type Example = (<~P extends ~Promise>() => P<number>) | (<~M extends ~Map>() => Map<string, number>);

لكن هذا لا يتضمن حقًا النوع (* => *) | (*, *) => * ، لكن شيئًا مختلفًا

إنشاء أبرز المساهمين الآخرين؟

كما ذكرت من قبل ، لا أعتقد أنها فكرة جيدة أن يكون لديك أبرز المساهمين الذين ينشئون أبرز المساهمين الآخرين ، مثل * => (* => *) . إنها القاعدة في اللغات التي تدعم الكاري وما شابه ، ولكن ليس في TypeScript.

لا توجد طريقة يمكنني من خلالها تحديد مثل هذه الأنواع على الإطلاق باستخدام بناء الجملة ~ وعلاقة النوع الفرعي ، لذلك لن يتطلب الأمر أي قواعد خاصة لمنعهم. سوف يتطلب قواعد خاصة لجعلها تعمل.

أعتقد أنه يمكنك تحديدها هيكليًا كما يلي:

~<A>~<B>{a : A, b : B}

هذا إلى حد كبير الطريقة الوحيدة التي أعتقد.

التفاعل مع وظائف الرتبة الأعلى؟

يوجد تفاعل طبيعي ولكن معقد مع أنواع الوظائف التي تأخذ معلمات النوع:

type Example<T> = <~P extends ~Promise>(p : P<T>) : P<T>;

هل يجب إيقاف هذا التفاعل بطريقة ما؟ أستطيع أن أرى أنواعًا مثل هذه تصبح معقدة للغاية.

بشكل عام ، هل هناك أماكن لا يجب أن تظهر فيها معلمات TC؟

هل تركيبتي البنيوية فكرة جيدة؟

لا أعتقد أنه يجب تنفيذه على الفور ، لكنني ما زلت أعتقد أن التركيب الهيكلي الخاص بي فكرة جيدة. يتيح لك:

  1. استخدم المعرفات المحلية مثل معلمات النوع الأخرى في قيودك.
  2. تطبيق مُنشئ النوع جزئيًا ، بطريقة واضحة ومرنة للغاية: ~<A>Map<string, A> ، ~<A, B>Map<B, A> ، وهكذا.
  3. كل جانب آخر من النوع له بناء جملة هيكلي ، لذلك يجب أن يكون لدى المساهمين الأساسيين مثل هذا النحو.

ومع ذلك ، يمكن للمساهمين الأساسيين العمل تمامًا بدون هذا ، وربما لن يشملهم أول علاقات عامة.

التفاعل مع الأنواع الشرطية

كيف ستعمل الميزة مع الأنواع الشرطية؟ هل يجب أن تكون قادرًا على فعل هذا؟

type Example<~P extends ~PromiseLike> = ~P extends ~Promise ? 0 : 1

لست متأكدًا تمامًا من نفسي. لم يتم استيعاب الأنواع الشرطية بالكامل بعد.

قرار الزائد

لدي شعور بأن هذا سيكون من الصعب القيام به. هذا في الواقع مهم جدًا لأن الأشكال المختلفة من دقة التحميل الزائد ستنتهي ببناء أنواع مختلفة.

بعد قولي هذا ، لا يمكنني التوصل إلى أي أمثلة جيدة في الوقت الحالي.

كما تعلم ، كان الكثير من هذا سيكون نقطة خلافية إذا تم استخدام لغة وسيطة محددة جيدًا لوصف TypeScript كنقطة بداية. على سبيل المثال: النظام F <: أو أحد الأنظمة التي تعتمد على الصوت مثل Simplified Dependent ML .

سأكون مندهشا بصدق إذا تم حل هذا من قبل
https://github.com/Microsoft/TypeScript/issues/14833

أشعر أن # 17961 من المحتمل أن يحل هذا بشكل غير مباشر. انظر إلى هذا الجوهر لمزيد من التفاصيل.

لاحظ أن النوعين Bifunctor و Profunctor معقدان بعض الشيء عند مستوى القيد - سيكون الأمر أبسط بكثير إذا كان لدي أنواع عالمية واضحة للعمل معها ، بدلاً من infer T الذي يقتصر فقط على الأنواع الشرطية. أيضًا ، سيكون من الرائع استخدام this كنوع "إرجاع" (وهذا هو مستوى النوع المحض) - كان ذلك سيجعل تعريف معظم واجهاتي أسهل.

(أنا لست مستخدمًا ثقيلًا لـ TS ، لذلك ربما أرتكب أخطاء. أخفقت في أي مكان في فوضى النوع؟ العلاقات العامة أعلاه ، وقد رأيت بعض تجاربك والمرافق الأخرى.)

MustafaHosny اللهم امين ...

أنت على حق. إذا فهمتها بشكل صحيح ، فستحصل على نفس النتائج إلى حد كبير.

هناك عدد قليل من الاختلافات. لكن من الناحية الوظيفية ، هما متطابقان إلى حد كبير ، وأعتقد أنه يمكن حل هذه الاختلافات.

لا يمكن استنتاج وظيفة النوع الصحيح

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;

هنا يمكنك استنتاج ~Promise و ~Bluebird من p . ومع ذلك ، إذا قمت بذلك على النحو التالي:

function example<F extends <T>(t: T) => PromiseLike<T>>(p : F(number)) : F(string)

أشك بشدة في أن هذا سيعمل:

example(null as Promise<number>)

لا توجد طريقة لاستنتاج أن F يُقصد به:

<T>(t : T) => Promise<T>

لأن هذه الوظيفة لا تعتبر خاصة بأي شكل من الأشكال. بينما مع المساهمين الأساسيين ، تحتوي بعض الأنواع أساسًا على وظيفة على مستوى النوع "ضمنيًا": TC الخاصة بهم.

لا يمكن الإشارة بسهولة إلى أبرز المساهمين الحاليين

لا يمكنك أن تفعل مثل ~Promise في اقتراحي. يجب عليك ترميز النوع مباشرة باستخدام تنسيق هيكلي:

type PromiseTC = <T>() => Promise<T>

صحيح ، وهذا مصدر قلق. هذا هو أكثر من قضية الاستدلال النوع حيث تحتاج إلى القدرة على استنتاج الوظيفة العامة نفسها من نوع وسيطة معروف (عكس ما يحدث عادة). إنه قابل للحل بطريقة عامة بما يكفي للعمل في معظم الحالات ، لكنه يتطلب حالة خاصة جديدة غير تافهة.

قد يكون قابلاً للحل جزئيًا عن طريق الاستخدام الاستراتيجي لـ NoInfer<T> ، لكنني لست متأكدًا بنسبة 100٪ من كيفية القيام بذلك ، ومقدار ما يمكن أن يعالج حتى الحالة الشائعة.

تضمين التغريدة

أنا لا أؤيد بشدة أي بناء جملة ، إنه أكثر ما أفضّله ، فهناك الكثير من المزايا لـ ~ . أعتقد أن الشيء الرئيسي الذي يجب مراعاته هو ما إذا كان بناء الجملة للتعليقات التوضيحية الطيبة الصريحة مطلوبًا لأن الاستدلال ليس ممكنًا دائمًا.


شيء رسم الخرائط ... بصراحة ، إنه غبي جدًا. لا أعتقد أن هناك أي طريقة لتوحيد MyPromiseيمتد الوعد

أعتقد أنه لا يزال من الممكن أن يكون رسم الخرائط فكرة مفيدة ، ولكن في الحالة المذكورة أعلاه لا أعتقد أننا يجب أن نحاول أبدًا توحيد ~MyPromise مع ~Promise ، الشيء الذي يجب توحيده هو ~MyPromise و ~<T>Promise<T[]> ، ويمكننا أيضًا كتابة ~(Promise . []) . أعتقد أن الشيء المفقود هو أن التعيين يجب أن يكون جزءًا من علاقة النوع الفرعي: فهو جزء من المُنشئ مثل Promise . في هذا المثال ، التعيين هو مجرد مُنشئ القائمة.

interface A<T> {
    x: T;
} 

interface B<T> {
    x: T[];
}

هل ~<T>B<T> يمتد ~<T>A<T[]> ؟ نعم. هل ~<T>B<T> يمتد ~<T>A<T> ؟ لا. لكن في النهاية هما سؤالان غير مرتبطين.

إذا اتفقنا على أنه يجب التعبير عن قيود مُنشئ النوع باستخدام علاقة نوع فرعي ، والتي يبدو أنها تعمل جيدًا ، يمكننا الانتقال إلى أشياء أخرى.

نعم أعتقد أنها طريقة لطيفة لوصف الأشياء.


لا يمكن استنتاج وظيفة النوع الصحيح

function example<~P extends ~PromiseLike>(p : P<number>) : P<string>;
هنا يمكنك استنتاج ~Promise و ~Bluebird من p.

هذا ليس تأكيدًا ، بل هو سؤال مفتوح أكثر لأنني لست متأكدًا تمامًا من كيفية عمل فحص النوع. اعتقدت أنه باستخدام الواجهة A أعلاه كمثال ، لا يمكن تمييز الأنواع A<number> و {x: number} وبالتالي لست متأكدًا من أنه سيكون من الممكن استنتاج المنشئ من النوع الذي تم إرجاعه من تطبيق المنشئ. هل من الممكن استرداد P من P<number> ؟ أنا متأكد من أنه يمكن تغيير الأشياء لدعم هذا ، أنا فقط أتساءل عما يفعله الآن.

رد متقاطع من # 17961 ، لكني لست متأكدًا من كيفية جعل نهج isiahmeadows يعمل ، للأسف. أخشى أن الاستدلال العكسي على مكالمات النوع غير تافه.

لذا يبدو أنه استنادًا إلى الإدخال Promise<number> أو Bluebird<number> نريد أن نكون قادرين على استنتاج الإصدارات غير المطبقة من هذه الأنواع بحيث يمكننا إعادة تطبيقها على سبيل المثال string . هذا يبدو صعبًا.

حتى لو كانت أنواع المدخلات مثل هذا بدلاً من بعض المعادلات الهيكلية (نحن لغة مكتوبة هيكليًا ، أليس كذلك؟) ، فإن هذا المنطق يصبح أيضًا غامضًا إذا كان على سبيل المثال Bluebird نوعان من المعلمات بدلاً من ذلك ، وعند هذه النقطة لدينا <string> قد لا يكون تطبيق معلمات النوع
لست متأكدًا من وجود حل جيد هناك. (إخلاء المسؤولية: لقد تأخرت قليلاً في المناقشة هنا.)

@ tycho01 هل T ؟

أعتقد أن هذا معقول ، نظرًا لأنني أشك في إمكانية حل الاستدلال لجميع الحالات على أي حال.

@ jack-williams: ليس مع # 17961 حتى الآن ، لكني أعتقد أن استخدامه للإرسال قد يساعد:

let arr = [1, 2, 3];
let inc = (n: number) => n + 1;
let c = arr.map(inc); // number[]
let map = <Functor extends { map: Function }, Fn extends Function>(x: Functor, f: Fn) => x['map'](f); // any on 2.7 :(
let e = map(arr, inc);

@ tycho01 نعم أدركت أن اقتراحي كان سيئًا لأن T لا يتم تمثيله عند استدعاءات الأسلوب.

هل شيء مثل العمل التالي؟

interface TyCon<A> {
    C: <A>(x: A) => TyCon<A>
}

interface Functor<A> extends TyCon<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(this: this["C"](A), f: (x: A) => B): this["C"](B);
}

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

@ jack-williams أعتقد أن السؤال يجب أن يكون كيف يمكن مقارنته في السلوك بتطبيق ADT في fp-ts ، لكن يبدو أن هذا قد ينجح ، نعم. ربما أيضًا بدون TyCon .

@ جاك ويليامز isiahmeadows :

لقد جربت الفكرة عند محاولة التدفق ، حيث يتوفر بالفعل $Call . بالنسبة لي يبدو أنها أصبحت لا تستجيب بطريقة ما على الرغم من ...

interface Functor<A> {
    C: <A>(x: A) => Functor<A>;
    fmap<B>(f: (x: A) => B): $Call<$ElementType<this, "C">, B>;
}
// this: $Call<$ElementType<this, "C">, A>, 
// ^ flow doesn't seem to do `this` params

interface Option<A> extends Functor<A> {
    C: <A>(x: A) => Option<A>;
}

let o: Option<string>;
let f: (s: string) => number;
let b = o.fmap(f);

@ tycho01 أعتقد أنه لا يمكنك فقط الحصول على عقارات $ElementType من this في التدفق

@ tycho01 لا يمكنك في الواقع
الملعب: https://goo.gl/tMBKyJ

goodmind : Maybe<number> بدلاً من Functor<number> بعد نسخ أكثر من fmap من Functor إلى Maybe .
باستخدام مكالمات النوع ، أعتقد أن ذلك من شأنه أن يتحسن إلى وجود النوع فقط بدلاً من الحاجة إلى تنفيذ وقت التشغيل للنوع.
الآن ، سيحتاج المنفذون بالفعل إلى تطبيقاتهم الخاصة بـ fmap . هذا من شأنه أن يمتص للأساليب المشتقة رغم ذلك.
العودة إلى المربع الأول. : /

بعض الأفكار ذات الصلة على https://github.com/SimonMeskens/TypeProps/issues/1.

أخطط لإصدار نسخة ألفا في أسرع وقت ممكن ، ولكن يمكنك أن تتابع معي كتابة الأمثلة في هذه المسألة لتتعرف عليها بالفعل.

هذه المشكلة بالتحديد طويلة بعض الشيء ، لكن ما أبحث عنه بسيط ، لكن الأمثلة الحقيقية للرموز التي لا يمكنك كتابتها بسبب نقص الأنواع العامة ذات المعلمات. أعتقد أنه يمكنني كتابة معظمهم (بشرط ألا يعتمدوا على منشئي النوع الرفع المجرد). لا تتردد في فتح المشكلات في الريبو أعلاه مع الكود وسأكتبها لك إذا استطعت (أو يمكنك نشرها هنا أيضًا).

تنبيه لقد بدأت محاولة تنفيذ هذا في # 23809. لا يزال غير مكتمل للغاية ولكن تحقق من ذلك إذا كنت مهتمًا.

لقد وعدتكم يا رفاق بمثال بسيط ، ها هو. يستخدم هذا بعض الحيل التي تعلمتها من كتابة مكتبتي.

type unknown = {} | null | undefined;

// Functor
interface StaticFunctor<G> {
    map<F extends Generic<G>, U>(
        transform: (a: Parameters<F>[0]) => U,
        mappable: F
    ): Generic<F, [U]>;
}

// Examples
const arrayFunctor: StaticFunctor<any[]> = {
    map: <A, B>(fn: (a: A) => B, fa: A[]): B[] => {
        return fa.map(fn);
    }
};
const objectFunctor: StaticFunctor<object> = {
    map: <A, B>(fn: (a: A) => B, fa: A): B => {
        return fn(fa);
    }
};
const nullableFunctor: StaticFunctor<object | null | undefined> = {
    map: <A, B>(
        fn: (a: A) => B,
        fa: A | null | undefined
    ): B | null | undefined => {
        return fa != undefined ? fn(fa) : fa;
    }
};

const doubler = (x: number) => x * 2;

const xs = arrayFunctor.map(doubler, [4, 2]); // xs: number[]
const x = objectFunctor.map(doubler, 42); // x: number
const xNull = nullableFunctor.map(doubler, null); // xNull: null
const xSome = nullableFunctor.map(doubler, 4 as number | undefined); // xSome: number | undefined

const functor: StaticFunctor<unknown | any[]> = {
    map(fn, fa) {
        return Array.isArray(fa)
            ? arrayFunctor.map(fn, fa)
            : fa != undefined
                ? objectFunctor.map(fn, fa)
                : nullableFunctor.map(fn, fa);
    }
};

const ys = functor.map(doubler, [4, 2]); // ys: number[]
const y = functor.map(doubler, 42); // y: number
const yNull = functor.map(doubler, null); // yNull: null
const ySome = functor.map(doubler, 42 as number | undefined); // ySome: number | undefined

// Plumbing
interface TypeProps<T = {}, Params extends ArrayLike<any> = never> {
    array: {
        infer: T extends Array<infer A> ? [A] : never;
        construct: Params[0][];
    };
    null: {
        infer: null extends T ? [never] : never;
        construct: null;
    };
    undefined: {
        infer: undefined extends T ? [never] : never;
        construct: undefined;
    };
    unfound: {
        infer: [NonNullable<T>];
        construct: Params[0];
    };
}

type Match<T> = T extends infer U
    ? ({} extends U ? any
        : TypeProps<U>[Exclude<keyof TypeProps, "unfound">]["infer"]) extends never
    ? "unfound"
    : {
        [Key in Exclude<keyof TypeProps, "unfound">]:
        TypeProps<T>[Key]["infer"] extends never
        ? never : Key
    }[Exclude<keyof TypeProps, "unfound">] : never;


type Parameters<T> = TypeProps<T>[Match<T>]["infer"];

type Generic<
    T,
    Params extends ArrayLike<any> = ArrayLike<any>,
    > = TypeProps<T, Params>[Match<T>]["construct"];

لقد قمت بتحديث العينة وتبسيطها ، وإليك رابط الملعب أيضًا:
ملعب

أضفت مكتبة NPM لما ورد أعلاه ، حتى تتمكن من العمل معها بسهولة. حاليًا في ألفا حتى أجري الاختبار المناسب ، لكن ينبغي أن تساعدكم يا رفاق في محاولة كتابة HKTs.

جيثب لينك

لقد كنت ألعب بطريقة بسيطة لمحاكاة HKTs باستخدام الأنواع الشرطية لاستبدال متغيرات النوع الظاهري داخل نوع مشبع:

declare const index: unique symbol;

// A type for representing type variables
type _<N extends number = 0> = { [index]: N };

// Type application (substitutes type variables with types)
type $<T, S, N extends number = 0> =
  T extends _<N> ? S :
  T extends undefined | null | boolean | string | number ? T :
  T extends Array<infer A> ? $Array<A, S, N> :
  T extends (x: infer I) => infer O ? (x: $<I, S, N>) => $<O, S, N> :
  T extends object ? { [K in keyof T]: $<T[K], S, N> } :
  T;

interface $Array<T, S, N extends number> extends Array<$<T, S, N>> {}

// Let's declare some familiar type classes...

interface Functor<F> {
  map: <A, B>(fa: $<F, A>, f: (a: A) => B) => $<F, B>;
}

interface Monad<M> {
  pure: <A>(a: A) => $<M, A>;
  bind: <A, B>(ma: $<M, A>, f: (a: A) => $<M, B>) => $<M, B>;
}

interface MonadLib<M> extends Monad<M>, Functor<M> {
  join: <A>(mma: $<M, $<M, A>>) => $<M, A>;
  // sequence, etc...
}

const Monad = <M>({ pure, bind }: Monad<M>): MonadLib<M> => ({
  pure,
  bind,
  map: (ma, f) => bind(ma, a => pure(f(a))),
  join: mma => bind(mma, ma => ma),
});

// ... and an instance

type Maybe<A> = { tag: 'none' } | { tag: 'some'; value: A };
const none: Maybe<never> = { tag: 'none' };
const some = <A>(value: A): Maybe<A> => ({ tag: 'some', value });

const { map, join } = Monad<Maybe<_>>({
  pure: some,
  bind: (ma, f) => ma.tag === 'some' ? f(ma.value) : ma,
});

// Not sure why the `<number>` annotation is required here...
const result = map(join<number>(some(some(42))), n => n + 1);
expect(result).toEqual(some(43));

المشروع هنا: https://github.com/pelotom/hkts

نرحب بالتعليقات!

pelotom أنا أحب خفة بناء الجملة من

  1. وصف برتراند ماير طريقة لمحاكاة الأنواع العامة في كتابه عام 1988 "بناء البرمجيات الموجهة للكائنات".

الأمثلة موجودة في Eiffel ، لكن الترجمة التقريبية إلى TypeScript تبدو كما يلي:

https://gist.github.com/mlhaufe/089004abd14ad8e7171e2a122198637f

ستلاحظ أنها يمكن أن تصبح ثقيلة جدًا بسبب الحاجة إلى تمثيلات فئة وسيطة ، ولكن مع نموذج مصنع فئة أو باستخدام نهج TypeScript Mixin ، يمكن تقليل هذا بشكل كبير.

قد يكون هناك بعض الانطباق على # 17588

  1. يتم استخدام الطريقة الثانية عند محاكاة الجبر الكائني (والمصانع المجردة):

يتم تمثيل C<T> بـ App<t,T> حيث يمثل T الفصل ، و t علامة فريدة مرتبطة بـ C

interface App<C,T> {}

عينة:

interface IApp<C,T> {}

interface IList<C> {
    Nil<T>(): IApp<C,T>
    Cons<T>(head: T, tail: IList<C>): IApp<C,T>
}

// defining data
abstract class List<T> implements IApp<typeof List, T> {
    // type-safe down-cast
    static prj<U>(app: IApp<typeof List, U>): List<U> { return app as List<U> }
}
class Nil<T> extends List<T> { }
class Cons<T> extends List<T> {
    constructor(readonly head: T, readonly tail: List<T>) {
        super()
    }
}

// The abstract factory where the HKT is needed
class ListFactory<T> implements IList<typeof List> {
    Nil<T>(): IApp<typeof List, T> { return new Nil() }
    Cons<T>(head: T, tail: IApp<typeof List, T>): IApp<typeof List, T> {
        return new Cons(head, tail)
    }
}

يمكنك الاطلاع على مزيد من التفاصيل والمبررات في الورقة التالية ضمن القسم 3.5 "محاكاة تعدد الأشكال منشئ النوع":

https://blog.acolyer.org/2015/08/13/streams-a-la-carte-extensible-pipelines-with-object-algebras/

metaweta ، هل يمكنك إعادة تسمية هذه المشكلة إلى Higher kinded types in TypeScript حتى تحصل على رؤية أفضل من بحث Google من فضلك؟

ربما يستطيع مشرفونا الحكيمون والخيرون على المستودعات (على سبيل المثال ، RyanCavanaugh ، DanielRosenwasser ) تعديل العنوان ، إذا كان هذا التغيير يستحق تدخلهم؟

أشعر بالفضول لمعرفة ما يعنيه نقل هذا من المجتمع إلى الأعمال المتأخرة. هل هذا شيء يفكر فيه الفريق الأساسي بجدية أكبر الآن أم أنه يعني ببساطة أن الفريق قرر أن هذا ليس مرشحًا جيدًا للمجتمع؟

تم العثور عليها: يبدو أنه تم إهمال معلم "المجتمع" لصالح "Backlog" ، وبالتالي ربما تم ترحيل هذه المشكلة عينيًا.

ليس عضوًا في TS ، فقط شخص قرر النقر فوق الرابط حيث تم إعادة ضبطه.

+1

هذا شيء كنت أحاول بناءه ويبدو أنه حالة عملية حقًا للأنواع الأعلى.

أريد إنشاء تجريد لقاعدة بيانات يمكن تشغيلها بشكل متزامن أو غير متزامن. بدلاً من استخدام عمليات الاسترجاعات والاختراق حول ذلك ، أردت استخدام عام. هذا ما أود فعله:

type Identity<T> = T

interface DatabaseStorage<Wrap<T> extends Promise<T> | Identity<T>> {
    get(key: string): Wrap<any>
    set(key: string, value: any): Wrap<void>
}

سيكون هذا قويا حقا!

ccorcos هذا يسمى نمط MTL. يمكنك إلقاء نظرة على https://github.com/gcanti/fp-ts/blob/master/tutorials/mtl.ts للحصول على مثال وظيفي خالص باستخدام fp-ts .

mlegenhausen أنا آسف ، لكني أجد صعوبة في اتباع هذا المثال.

كلما بحثت في fp-ts ، أشعر بالقلق من أن الأمور تزداد تعقيدًا لدرجة أنها ستكون هشة. من السهل متابعة مثال

أي سبب لعدم اعتماد هذا في TypeScript؟

ccorcos IMHO حتى عندما أوصيت بالمثال من fp-ts لن أوصي بأسلوب MTL / tagless على الإطلاق. أنت تضيف طبقة إضافية من التجريد إلى كل وحدة أحادية فعالة تحتاج إلى إدارتها يدويًا لأن الكتابة المطبوعة غير قادرة على اكتشاف الموناد الذي تريد استخدامه وهذا هو المكان الذي تتعقد فيه الأمور. ما أراه من مجتمع fp-ts هو استخدام وحدة أحادية غير متزامنة (أوصي بـ TaskEither ) والتمسك بها. حتى في اختبار فوائد MTL لا تستحق المتاعب التي تحصل عليها في الكود الذي لا يخضع للاختبار. Hyper-ts استنادًا إلى fp-ts هو مثال على مكتبة أسقطت دعم MTL مؤخرًا.

مثير للاهتمام ... hyper-ts يبدو رائعًا حقًا ...

لقد توصلت إلى ترميز خفيف الوزن من النوع الأعلى بناءً على تعدد الأشكال الذي يحده F: https://github.com/strax/tshkt

تكمن فائدة هذا الأسلوب في أنك لا تحتاج إلى جدول بحث (نوع شرطي واحد أو كائن به مفاتيح سلسلة) لربط مُنشئ النوع بالأنواع. يمكن أيضًا استخدام هذه التقنية لتشفير تطبيق الوظائف العامة على مستوى النوع (فكر في ReturnType<<T>(value: T) => Array<T>> ).

لا يزال هذا دليلًا على المفهوم ، لذا فإن التعليقات حول جدوى هذا النهج موضع تقدير كبير!

سألقي نظرة على

في غضون ذلك ، إليك مثال سخيف لما يمكننا فعله الآن:

type Test1 = λ<Not, [True]>;        // False
type Test2 = λ<And, [True, False]>; // False
type Test3 = λ<And, [True, True]>;  // True

// Boolean

interface True extends Func {
    expression: Var<this, 0>;
}

interface False extends Func {
    expression: Var<this, 1>;
}

interface Not extends Func {
    expression: λ<Var<this, 0>, [False, True]>
}

interface And extends Func {
    expression: λ<Var<this, 0>, [Var<this, 1>, Var<this, 0>]>
}

// Plumbing

type Func = {
    variables: Func[];
    expression: unknown;
}

type Var<F extends Func, X extends number> = F["variables"][X];

type λ<Exp extends Func, Vars extends unknown[]> = (Exp & {
    variables: Vars;
})["expression"];

أرغب في إضافة مؤشرات De Bruijn ، لأن هذا يعني أننا لم نعد بحاجة إلى الواجهات بعد الآن ، ولكنها تتطلب بعض حسابات tuple على ما أعتقد وأنا أحاول تجنب ذلك.

اقتراح

رتبة عليا ، لامدا ، أنواع مرجعية

قم بتمرير النوع كمرجع

ببساطة ، سيسمح نوع المرجع أو الترتيب الأعلى للمرء بتأجيل المعلمات المأخوذة بواسطة النوع لوقت لاحق ، أو حتى استنتاج معلمات النوع (العوامل العامة) بعد ذلك. لكن لماذا يجب أن نهتم؟

إذا تمكنا من تمرير نوع ما كمرجع ، فهذا يعني أنه يمكننا تأخير TypeScript من تقييم النوع حتى نقرر القيام بذلك. دعنا نلقي نظرة على مثال من العالم الحقيقي:

معاينة مع الأنابيب

تخيل أنك تطور نوعًا عامًا مقابل pipe . يدور معظم العمل حول التحقق من أن الوظائف التي سيتم توصيلها بالأنابيب هي بالفعل قادرة على الأنابيب ، وإلا فإننا سنرفع خطأ إلى المستخدم. للقيام بذلك ، سنستخدم نوعًا معيّنًا لنقل أنواع الوظائف مثل pipe(...) :

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
    ?  Fns[K]
    // For all the other functions, we link input<-output
    : (arg:  Return<Fns[Pos<Prev<IterationOf<K & string>>>]>) =>
        Return<Fns[Pos<IterationOf<K & string>>]>;

الآن ، علينا فقط تكرار هذا على الوظائف بنوع معين:

type  Piper<Fns  extends  Function[]> = {
    [K  in  keyof  Fns]:  PipeSync<Fns, K>
}

( انظر التنفيذ الكامل )

نحن الآن قادرون على توصيل الوظائف معًا ويمكن أن تقدم لنا TypeScript تحذيرات:

declare  function  pipe<Fns  extends  F.Function[]>(...args:  F.Piper<Fns>):  F.Pipe<Fns>

const  piped  =  pipe(
    (name:  string, age:  number) => ({name, age}),
    (info: {name:  string, age:  number}) =>  `Welcome, ${info.name}`,
    (message:  object) =>  false, // /!\ ERROR
)

إنها تعمل! لقد حصلنا على خطأ مناسب:

وسيطة من النوع '(message: object) => boolean' غير قابلة للتخصيص لمعامل من النوع '(arg: string) => منطقي

المشكلة

لكن هناك مشكلة. بينما يعمل هذا بشكل جيد جدًا للعمليات البسيطة ، ستجد أنه فشل تمامًا عند بدء استخدام الأدوية (القوالب) في الوظائف التي تمررها إليها:

const  piped  =  pipe(
    (a:  string) =>  a,
    <B>(b:  B) =>  b, // any
    <C>(c:  C) =>  c, // any
)

type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B,
    <C>(c:  C) =>  C,
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  unknown,
//     (c:  unknown) => unknown
// ]

في كلتا الحالتين ، فقد TypeScript مسار أنواع الوظائف.
> هذا هو المكان الذي تلعب فيه أنواع الرتب الأعلى <

بناء الجملة

type  PipeSync<Fns  extends  Function[], K  extends  keyof  Fns> = 
    K  extends  '0'
    // If it's the first function, we leave it unchanged
+   ?  *(Fns[K]) // this will preserve the generics
    // For all the other functions, we link input<-output
+   :  *( // <- Any type can be made a reference
+       <T>(arg:  T) => Return<*(Fns[Pos<IterationOf<K  &  string>>])>
+       // vvv It is now a reference, we can assign generics
+       )<Return<*(Fns[Pos<Prev<IterationOf<K  &  string>>>])>>
+       // ^^^ We also tell TS not to evaluate the previous return
+       // and this could be achieved by making it a reference too

باختصار ، استنتجنا الأدوية الجنيسة يدويًا وديناميكيًا بـ * . في الواقع ، أدى استخدام * تأجيل تقييم الأدوية. لذا فإن * له سلوكيات مختلفة ، اعتمادًا على السياق. إذا كان * على نوع:

  • يمكن أن تتلقى الأدوية الجنيسة : تتولى الأدوية الجنيسة / تحصل على مرجعها
  • هو عام بحد ذاته : يؤجل التقييم حتى يتم معرفة المرجع. لهذا ، يتم إنشاء شجرة مرجعية من الأصل (الوالدين) إلى الأسفل. بمعنى آخر ، يمكن أن تتداخل المراجع. هذا هو بالضبط ما حدث أعلاه مع Return<*(Fns[Pos<Prev<IterationOf<K & string>>>])> الذي تم تعيينه لـ T . في هذا السياق ، يمكننا القول أن * "يحمي" من التقييم الفوري.
  • ليس أيًا مما سبق : لا يفعل شيئًا ، وينتقل إلى نفس النوع
type  piped  =  Piper<[
    (a:  string) =>  string,
    <B>(b:  B) =>  B
    <C>(c:  C) =>  C
]>
// [
//     (a:  string) =>  string,
//     (b:  string) =>  string,
//     (c:  string) =>  string
// ]

لذلك يجب أن تبدأ TypeScript / تستمر في التقييم فقط إذا تم توفير العام ، وتحظر التقييم عند الحاجة (عام غير مكتمل). في الوقت الحالي ، يقيّم TS في لقطة واحدة عن طريق تحويل الأدوية الجنيسة إلى أنواع unknown . باستخدام هذا الاقتراح ، عندما يتعذر حل شيء ما:

type  piped  =  Piper<[
    <A>(a:  A) =>  A, // ?
    <B>(b:  B) =>  B, // ?
    <C>(c:  C) =>  C, // ?
]>
// [
//     <A>(a:  A) =>  A,
//     (b:  A) =>  A,
//     (c:  A) =>  A
// ]

تفاصيل

يسترجع * إشارة إلى نوع ، مما يتيح التلاعب في الأدوية الخاصة به. لذا فإن وضع حرف البدل أمام نوع يسترجع إشارة إليه:

*[type]

يتيح استرداد مرجع لنوع ما معالجة الأدوية الجنسية تلقائيًا:

*[type]<T0, T1, T2...>

يتم استهلاك / تعيين الأدوية الجنيسة حسب النوع المستهدف فقط إذا أمكن ذلك. إذن القيام بهذا:

*string<object, null> // Will resolve to `string`

ولكن يمكن أيضًا التحقق منه بواسطة TypeScript نفسها ، سواء كان يجب أن يعرض تحذيرًا أم لا. لكن داخليًا ، لا ينبغي لـ TS أن تفعل شيئًا من هذا.

اعتقدت أيضًا أنه من الجيد استخدام * حيث يمكن أن يرمز إلى مؤشر لشيء ما (مثل لغات C / C ++) ، ولا يتم استعارته بواسطة TypeScript.

أنواع الطلبات الأعلى

الآن وقد رأينا كيف يعمل في أبسط أشكاله ، أود أن أقدم المفهوم الأساسي: أنواع لامدا . سيكون من الجيد أن يكون لديك أنواع مجهولة ، على غرار عمليات الاسترجاعات ، lambdas ، المراجع في JavaScript .

يوضح المثال أعلاه كيفية تولي الأمر العام للوظيفة. ولكن نظرًا لأننا نتحدث عن المراجع ، يمكن استخدام أي نوع مع * . ببساطة ، مرجع النوع هو نوع يمكننا تمريره ولكن لم يتم استلامه بعد:

type  A<T  extends  string> = {0:  T}
type  B<T  extends  string> = [T]
type  C<T  extends  number> = 42

// Here's our lamda
type  Referer<*Ref<T  extends  string>, T  extends  string> =  Ref<T>
// Notice that `T` & `T` are not in conflict
// Because they're bound to their own scopes

type  testA  =  Referer<A, 'hi'> // {0: 'hi'}
type  testB  =  Referer<B, 'hi'> // ['hi']
type  testC  =  Referer<C, 'hi'> // ERROR

أنواع أعلى

interface Monad<*T<X extends any>> {
  map<A, B>(f: (a: A) => B): T<A> => T<B>;
  lift<A>(a: A): T<A>;
  join<A>(tta: T<T<A>>): T<A>;
}

شروط البحث

high #order #type #references #lambda #HKTs

@ pirix-gh إذا قرأت بضع رسائل فقط ، يمكنك أن ترى أن الكثير مما تطلبه إما ممكن بالفعل أو تم طلبه بالفعل.

لقد قرأتها بالفعل ، واعتقدت أنه يمكنني تلخيص أفكاري كما فعل أي شخص آخر (لحل الكل في واحد) ، في الغالب حول بناء الجملة.

لقد قمت بتحرير الاقتراح أعلاه للحصول على شرح أفضل لكيفية عمل سلسلة من المراجع ، وقمت بإصلاح الطريقة التي سيعمل بها نوع مثل Pipe (كانت هناك بعض الأخطاء فيما يتعلق بمنطق ذلك).

أي تحديث؟

لا يزال هناك تحديث؟ في رأيي ، تصنف هذه المشكلة على أنها العقبة الأولى التي تحول دون تحقيق TypeScript لإمكانياتها الكاملة. هناك العديد من الحالات التي أحاول فيها كتابة مكتباتي بشكل صحيح ، فقط لأستسلم بعد صراع طويل ، مدركًا أنني واجهت هذا القيد مرة أخرى. إنه منتشر ، ويظهر حتى في السيناريوهات التي تبدو بسيطة للغاية. نأمل حقًا أن يتم معالجتها قريبًا.

interface Monad<T<X>> {
    map1<A, B>(f: (a: A) => B): (something: A) => B;

    map<A, B>(f: (a: A) => B): (something: T<A>) => T<B>;

    lift<A>(a: A): T<A>;
    join<A>(tta: T<T<A>>): T<A>;
}

type sn = (tmp: string) => number

function MONAD(m: Monad<Set>,f:sn) {
    var w = m.map1(f);    // (method) Monad<Set>.map1<string, number>(f: (a: string) => number): (something: string) => number
    var w2 = m.map(f);    // (method) Monad<Set>.map<string, number>(f: (a: string) => number): (something: Set<string>) => Set<number>
    var q = m.lift(1);    // (method) Monad<Set>.lift<number>(a: number): Set<number>
    var a = new Set<Set<number>>();
    var w = m.join(q);    // (method) Monad<Set>.join<unknown>(tta: Set<Set<unknown>>): Set<unknown>.  You could see that typeParameter infer does not work for now.
    var w1 = m.join<number>(q);    // (method) Monad<Set>.join<number>(tta: Set<Set<number>>): Set<number>
}

لا يزال هناك الكثير من العمل الذي يتعين القيام به ، مثل: إصلاح المعلومات السريعة ، اكتب معلمة استنتاج ، إضافة رسالة خطأ ، تسليط الضوء على نفس النوع منشئ .....
لكنها بدأت في العمل ، وهذا ما يمكنني الحصول عليه الآن.
واجهة المثال من millsp https://github.com/microsoft/TypeScript/issues/1213#issuecomment -523245130 ، الاستنتاج مفيد حقًا ، شكرًا عظيمًا لذلك.

آمل أن يوفر التواصل المزيد من حالات المستخدم من هذا القبيل ، للتحقق مما إذا كانت الطريقة الحالية تعمل في معظم المواقف.

سيكون من الجيد أيضًا تقديم بعض المعلومات حول HKT / برمجة الوظائف / lambda (عندما أقول lambda ، أعني الرياضيات ، يمكنني فقط العثور على مثال مكتوب بلغة ما ، بدون رياضيات)

فيما يلي بعض الأشياء التي تساعدني كثيرًا:

ShuiRuTian فيما يتعلق m.join(q) Set<unknown> ، أفترض أن --noImplicitAny تسبب في إرسال تحذير أيضًا؟

آمل أن يتمكن المجتمع من توفير المزيد من حالات المستخدمين من هذا القبيل ، للتحقق مما إذا كانت الطريقة الحالية تعمل في معظم المواقف.

سيكون من الجيد أيضًا تقديم بعض المعلومات حول HKT / برمجة الوظائف / lambda (عندما أقول lambda ، أعني الرياضيات ، يمكنني فقط العثور على مثال مكتوب بلغة ما ، بدون رياضيات)

دون أن أذهب إلى أبعد من ذلك ، حاولت مؤخرًا إنشاء وظيفة عامة من نوع filter ، وأردتها أن تفعل شيئًا كالتالي:

const filterNumbers = filter(
    (item: number | string): item is number => typeof item === "number"
);

const array = ["foo", 1, 2, "bar"]; // (number | string)[]
const customObject = new CustomObject(); // CustomObject<number | string>

filterNumbers(array); // number[] inferred
filterNumbers(customObjectWithFilterFunction); // CustomObject<number> inferred

وليس لدي طريقة فعلاً للقيام بذلك ، لأنني بحاجة إلى طريقة لإخبار TypeScript "بإرجاع نفس النوع الذي تلقيته ، ولكن باستخدام هذا المعامل الآخر". شيء من هذا القبيل:

const filter = <Item, FilteredItem>(predicate: (item: Item) => item is FilteredItem) =>
  <Filterable<~>>(source: Filterable<Item>): Filterable<FilteredItem> => source.filter(predicate);

lukeshiru نعم ، هذا هو أساسًا https://pursuit.purescript.org/packages/purescript-filterable/2.0.1/docs/Data.Filterable#v : filter
هناك الكثير من حالات الاستخدام المماثلة الأخرى لـ HKT في TypeScript.

isiahmeadows حاولت ذلك. أنت محق.
lukeshiru و raveclassic شكرا! تبدو هذه الميزة معقولة جدًا. أود إلقاء نظرة على هذا بعد قراءة https://gcanti.github.io/fp-ts/learning-resources/

أنا عالق ولا أعرف ما هو " الحل البديل" الحالي ...
أحاول تنفيذ مواصفات السلسلة :

m['fantasy-land/chain'](f)

يجب أن تقوم القيمة التي تطبق مواصفات السلسلة أيضًا بتطبيق المواصفات.

a['fantasy-land/ap'](b)

لقد قمت بعمل FunctorSimplex والذي تم تمديده بعد ذلك بواسطة FunctorComplex ثم تم تمديده بواسطة Apply ولكن الآن أريد تمديد Apply كـ Chain ينكسر ...

لذلك أحتاج ذلك (الصورة أدناه والرابط إلى الكود):

(أحتاج إلى تمرير نوع إلى ApType بحيث لا يكون السطر 12 Apply "مشفرًا بشكل ثابت" ولكنه عام ... لأخذ أي أنواع ممتدة من IApply )

Screenshot

رابط ثابت لأسطر مقتطف الشفرة من

تحميل
نوع التصدير ApType = ( ap: تطبيق <(val: A) => B> ، ) => تطبيق ؛

/ * [...] * /

واجهة التصدير تقوم IApply بتوسيع FunctorComplex {
/ ** Fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b * /
أب: أبتايب ؛
}
""

تتم الإشارة إلى سؤال Stack Overflow: مشكلة الأنواع العامة في TypeScript: الحل البديل مطلوب

Luxcium حتى

إلى أن يدعم TS الأنواع الأعلى من النوع ، يكون مضاهاة فقط ممكنة. قد ترغب في إلقاء نظرة هناك لترى كيف يمكن تحقيق ذلك

هناك الكثير من الدبابات kapke ، من المحتمل أن أكون أكثر من اللازم في أيام أطروحات FP ، وبما أنه في Javascript يمكن للمرء إرجاع دالة من وظيفة يمكننا كتابة pseudoFnAdd(15)(27) // 42 أود أن أكون قادرًا ، باستخدام TypeScript ، على كتابة pseudoType<someClassOrConstructor><number> // unknown لكنني طفل سيناريو ولست _ ° نوعًا من الأشخاص الذين درسوا لفترة طويلة في الجامعة أو شيء ما ° _

هذه المعلومات والمحاضرات (قراءات) محل تقدير كبير ...

ملحوظة: أنا أتحدث الفرنسية ، في الفرنسية كلمة (جلسات) _ لها معنى _ قراءات_ وليست "حديث غاضب أو جاد موجه إلى شخص ما لانتقاد سلوكه" ...

ربما لا يعمل ما يلي الذي توصلت إليه كحل بسيط بدون العلاقات العامة لجميع الحالات ، لكنني أعتقد أنه من الجدير بالذكر:

type AGenericType<T> = T[];

type Placeholder = {'aUniqueKey': unknown};
type Replace<T, X, Y> = {
  [k in keyof T]: T[k] extends X ? Y : T[k];
};

interface Monad<T> {
  map<A, B>(f: (a: A) => B): (v: Replace<T, Placeholder, A>) => Replace<T, Placeholder, B>;
  lift<A>(a: A): Replace<T, Placeholder, A>;
  join<A>(tta: Replace<T, Placeholder, Replace<T, Placeholder, A>>): Replace<T, Placeholder, A>;
}

function MONAD(m: Monad<AGenericType<Placeholder>>, f: (s: string) => number) {
  var a = m.map(f); // (v: string[]) => number[]
  var b = m.lift(1); // number[]
  var c = m.join([[2], [3]]); // number[]
}
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات
يستخدم موقع bleepcoder.com معلومات GitHub المرخصة بشكل عام لتزويد المطورين حول العالم بحلول لمشاكلهم. نحن لسنا تابعين لشركة GitHub، Inc. أو مع أي مطورين يستخدمون GitHub لمشاريعهم. نحن لا نستضيف أيًا من مقاطع الفيديو أو الصور على خوادمنا. جميع الحقوق تنتمي إلى أصحابها.
مصدر هذه الصفحة: مصدر

لغات البرمجة الشعبية
مشاريع GitHub الشعبية
المزيد من مشاريع GitHub

© 2024 bleepcoder.com - Contact
Made with in the Dominican Republic.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.