Pemrograman Fungsional dalam TypeScript: Polimorfisme Gender Tingkat Tinggi

Halo, Habr! Nama Menu Yuri Bogomolov, dan Anda (mungkin) bisa tahu saya untuk pekerjaan saya pada serangkaian #MonadicMondays tweeted pada saluran di yutyube atau artikel ke Medium atau dev.to . Di segmen Internet berbahasa Rusia, hanya ada sedikit informasi tentang pemrograman fungsional di TypeScript dan salah satu ekosistem terbaik untuk bahasa ini - pustaka fp-ts , ke ekosistem yang saya sumbangkan secara aktif beberapa waktu lalu. Dengan artikel ini, saya ingin memulai cerita tentang FP di TypeScript, dan jika ada respon positif dari komunitas Habra, saya akan melanjutkan serialnya.



Saya tidak berpikir itu akan menjadi wahyu bagi siapa pun bahwa TypeScript adalah salah satu superset dari JS yang diketik sangat populer. Setelah mengaktifkan mode kompilasi ketat dan menyetel linter untuk melarang penggunaan, anybahasa ini menjadi cocok untuk pengembangan industri di banyak area - dari CMS hingga perangkat lunak perbankan dan perantara. Untuk sistem tipe TypeScript, bahkan ada upaya tidak resmi untuk membuktikan kelengkapan Turing, yang memungkinkan teknik pemrograman level-tipe lanjutan diterapkan untuk memastikan kebenaran logika bisnis dengan membuat status ilegal tidak dapat direpresentasikan.



Semua hal di atas memberi dorongan pada pembuatan perpustakaan yang bagus untuk pemrograman fungsional untuk TypeScript - fp-tsoleh ahli matematika Italia Giulio Canti. Salah satu hal pertama yang ditemui seseorang yang ingin menguasainya adalah definisi yang sangat spesifik dari jenis spesies Kind<URI, SomeType>atau interface SomeKind<F extends URIS> {}. Dalam artikel ini, saya ingin mengarahkan pembaca untuk memahami semua "kerumitan" ini dan menunjukkan bahwa sebenarnya semuanya sangat sederhana dan jelas - Anda hanya perlu mulai melepaskan teka-teki ini.



Melahirkan dari tingkat yang lebih tinggi



Ketika datang ke pemrograman fungsional, pengembang JS biasanya berhenti membuat fungsi murni dan menulis kombinator sederhana. Sedikit yang melihat ke wilayah optik fungsional, dan hampir tidak mungkin untuk menemukan API freemonadic atau skema rekursi. Faktanya, semua konstruksi ini tidak terlalu rumit, dan sistem tipe sangat memudahkan pembelajaran dan pemahaman. Sebagai bahasa, TypeScript memiliki kemampuan ekspresif yang cukup kaya, tetapi mereka memiliki batasannya sendiri, yang tidak nyaman - tidak adanya gender / cines / kind. Untuk membuatnya lebih jelas, mari kita lihat contohnya.



. , , β€” , : 0 N A. , 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, , , - . , :



  1. - F β€” , , : 'Array', 'Promise', 'Set', 'Tree' .
  2. - Kind<IdF, A>, F A: Kind<'F', A> ~ F<A>.
  3. 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.



fp-ts :





:








. , , , . , , . , , Mappable/Chainable .., β€” , , ? , .




All Articles