مرحبا هبر! اسم القائمة هو يوري بوغومولوف، والتي (ربما) يمكن أن تعرفني لعملي على سلسلة من #MonadicMondays بالتغريد على القناة على yutyube أو المواد إلى متوسط أو dev.to . في الجزء الناطق بالروسية من الإنترنت ، هناك القليل جدًا من المعلومات حول البرمجة الوظيفية في TypeScript وواحدة من أفضل النظم البيئية لهذه اللغة - مكتبة fp-ts ، التي ساهمت في نظامها الإيكولوجي بنشاط كبير منذ بعض الوقت. مع هذه المقالة ، أريد أن أبدأ قصة عن FP في TypeScript ، وإذا كان هناك رد إيجابي من مجتمع Habra ، فسأواصل السلسلة.
لا أعتقد أنه سيكون بمثابة اكتشاف لأي شخص أن TypeScript هي واحدة من أكثر المجموعات الفرعية المكتوبة بقوة في JS. بعد تمكين وضع التجميع الصارم وضبط linter على حظر الاستخدام ، anyتصبح هذه اللغة مناسبة للتنمية الصناعية في العديد من المجالات - من CMS إلى البرامج المصرفية والسمسرة. بالنسبة لنظام الكتابة TypeScript ، كانت هناك محاولات غير رسمية لإثبات اكتمال Turing ، مما يسمح بتطبيق تقنيات البرمجة المتقدمة على مستوى النوع لضمان صحة منطق الأعمال من خلال جعل الحالات غير القانونية غير قابلة للتمثيل.
أعطى كل ما سبق دفعة لإنشاء مكتبة رائعة للبرمجة الوظيفية لـ TypeScript - fp-tsبواسطة عالم الرياضيات الإيطالي جوليو كانتي. من أول الأشياء التي يواجهها الشخص الذي يريد إتقانها هي التعريفات المحددة جدًا لأنواع الأنواع Kind<URI, SomeType>أو interface SomeKind<F extends URIS> {}. في هذا المقال أريد أن أقود القارئ إلى فهم كل هذه "التعقيدات" وأظهر أن كل شيء في الواقع بسيط للغاية ومفهوم - عليك فقط أن تبدأ في حل هذا اللغز.
الولادة من رتبة أعلى
عندما يتعلق الأمر بالبرمجة الوظيفية ، يتوقف مطورو JS عادةً عن إنشاء وظائف نقية وكتابة أدوات دمج بسيطة. قليلون ينظرون إلى منطقة البصريات الوظيفية ، ويكاد يكون من المستحيل أن تصادف المغازلة مع واجهات برمجة التطبيقات المجانية أو مخططات العودية. في الواقع ، كل هذه التركيبات ليست معقدة بشكل مفرط ، ونظام الكتابة يسهل التعلم والفهم بشكل كبير. تتمتع TypeScript كلغة بقدرات تعبيرية غنية جدًا ، ومع ذلك ، فإن لها حدودها ، وهو أمر غير مريح - غياب الأجناس / الأنواع / الأنواع. لتوضيح الأمر ، دعنا نلقي نظرة على مثال.
دعونا نلقي المألوفة ومدروسة مجموعة لل جميع . المصفوفة ، مثل القائمة ، هي بنية بيانات تعبر عن فكرة عدم الحتمية: يمكنها تخزين عناصر من 0 إلى N من نوع معين أ. علاوة على ذلك ، إذا كانت لدينا وظيفة في النموذج A -> B، فيمكننا "مطالبة" هذه المصفوفة بتطبيقها عن طريق استدعاء طريقة .map()ينتج عنها مصفوفة بنفس حجم المخرجات ، مع عناصر من النوع B بنفس الترتيب كما في المصفوفة الأصلية:
const as = [1, 2, 3, 4, 5, 6]; // as :: number[]
const f = (a: number): string => a.toString();
const bs = as.map(f); // bs :: string[]
console.log(bs); // => [ '1', '2', '3', '4', '5', '6' ]
لنقم بتجربة عقلية. دعنا ننقل الوظيفة mapمن نموذج المصفوفة إلى واجهة منفصلة. نتيجة لذلك ، نحصل على وظيفة متعددة الأشكال ذات ترتيب أعلى حسب نوع المدخلات وأنواع المخرجات ، والتي سأجعلها على الفور سهلة القراءة:
interface MappableArray {
readonly map: <A, B>(f: (a: A) => B) => (as: A[]) => B[];
}
. , , map (Set), - (Map), , , … , . , map :
type MapForSet = <A, B>(f: (a: A) => B) => (as: Set<A>) => Set<B>;
type MapForMap = <A, B>(f: (a: A) => B) => (as: Map<string, A>) => Map<string, B>;
type MapForTree = <A, B>(f: (a: A) => B) => (as: Tree<A>) => Tree<B>;
type MapForStack = <A, B>(f: (a: A) => B) => (as: Stack<A>) => Stack<B>;
- Map , , , .
, : Mappable. , , . TypeScript, , - -:
interface Mappable<F> {
// Type 'F' is not generic. ts(2315)
readonly map: <A, B>(f: (a: A) => B) => (as: F<A>) => F<B>;
}
, , TypeScript , - F . Scala F<_> - — . , ? , « ».
, TypeScript , , «» — . — , . (pattern-matching) . , , «Definitional interpreters for higher-order programming languages», , .
, : - Mappable, - F, , , - . , :
- -
F— , , :'Array', 'Promise', 'Set', 'Tree'. - -
Kind<IdF, A>,FA:Kind<'F', A> ~ F<A>. -
Kind-, — .
, :
interface URItoKind<A> {
'Array': Array<A>;
} // 1-: Array, Set, Tree, Promise, Maybe, Task...
interface URItoKind2<A, B> {
'Map': Map<A, B>;
} // 2-: Map, Either, Bifunctor...
type URIS = keyof URItoKind<unknown>; // - «» 1-
type URIS2 = keyof URItoKind2<unknown, unknown>; // 2-
// ,
type Kind<F extends URIS, A> = URItoKind<A>[F];
type Kind2<F extends URIS2, A, B> = URItoKind2<A, B>[F];
//
: URItoKindN , , . TypeScript, (module augmentation). , :
type Tree<A> = ...
declare module 'my-lib/path/to/uri-dictionaries' {
interface URItoKind<A> {
'Tree': Tree<A>;
}
}
type Test1 = Kind<'Tree', string> // Tree<string>
Mappable
Mappable - — 1- , :
interface Mappable<F extends URIS> {
readonly map: <A, B>(f: (a: A) => B) => (as: Kind<F, A>) => Kind<F, B>;
}
const mappableArray: Mappable<'Array'> = {
// `as` A[], - `Kind`:
map: f => as => as.map(f)
};
const mappableSet: Mappable<'Set'> = {
// — , ,
// ,
map: f => as => new Set(Array.from(as).map(f))
};
// , Tree — : ,
// , :
const mappableTree: Mappable<'Tree'> = {
map: f => as => {
switch (true) {
case as.tag === 'Leaf': return f(as.value);
case as.tag === 'Node': return node(as.children.map(mappableTree.map(f)));
}
}
};
, Mappable , Functor. T fmap, A => B T<A> T<B>. , A => B T ( , Reader/Writer/State).
fp-ts
, fp-ts. , : https://gcanti.github.io/fp-ts/guides/HKT.html. — fp-ts URItoKind/URItoKind2/URItoKind3, fp-ts/lib/HKT.
- io-ts — - , TS
- parser-ts — ,
parsec - monocle-ts — , monocle TS
- remote-data-ts — RemoteData,
- retry-ts —
- elm-ts — - Elm Architecture TS
- waveguide, matechs-effect — TS, ZIO
:
- circuit-breaker-monad — Circuit Breaker
- kleisli-ts — , ZIO
- fetcher-ts — fetch, io-ts
- alga-ts — alga TS
. , , , . , , . , , Mappable/Chainable .., — , , ? , .