TypeScript, ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾ, ആധുനിക ലൈബ്രറികൾ എന്നിവ ഉപയോഗിച്ച് JavaScript-ൽ ടൈപ്പ്-സുരക്ഷിതവും കംപൈൽ-ടൈം വെരിഫൈഡ് പാറ്റേൺ മാച്ചിംഗ് എങ്ങനെ നേടാമെന്ന് കണ്ടെത്തുക.
JavaScript പാറ്റേൺ മാച്ചിംഗും ടൈപ്പ് സുരക്ഷയും: കംപൈൽ-ടൈം വെരിഫിക്കേഷനിലേക്കുള്ള ഒരു ഗൈഡ്
ആധുനിക പ്രോഗ്രാമിംഗിലെ ഏറ്റവും ശക്തവും എക്സ്പ്രെസ്സീവുമായ സവിശേഷതകളിൽ ഒന്നാണ് പാറ്റേൺ മാച്ചിംഗ്. Haskell, Rust, F# തുടങ്ങിയ ഫങ്ഷണൽ ഭാഷകളിൽ ഇത് പണ്ടുമുതലേ ആഘോഷിക്കപ്പെടുന്നു. ഡാറ്റയെ ഡീകൺസ്ട്രക്ട് ചെയ്യാനും അതിൻ്റെ ഘടനയെ അടിസ്ഥാനമാക്കി കോഡ് എക്സിക്യൂട്ട് ചെയ്യാനും ഇത് ഡെവലപ്പർമാരെ അനുവദിക്കുന്നു, അത് സംക്ഷിപ്തവും അവിശ്വസനീയമാംവിധം വായിക്കാൻ എളുപ്പവുമാണ്. JavaScript വികസിച്ചുകൊണ്ടിരിക്കുമ്പോൾ, ഡെവലപ്പർമാർ ഈ ശക്തമായ രീതികൾ സ്വീകരിക്കാൻ കൂടുതൽ കൂടുതൽ ശ്രമിക്കുന്നു. എന്നിരുന്നാലും, ഒരു പ്രധാന വെല്ലുവിളി നിലനിൽക്കുന്നു: JavaScript-ൻ്റെ ഡൈനാമിക് ലോകത്ത് ഈ ഭാഷകളുടെ ശക്തമായ ടൈപ്പ് സുരക്ഷയും കംപൈൽ-ടൈം ഗ്യാരൻ്റികളും എങ്ങനെ നേടാം?
TypeScript-ൻ്റെ സ്റ്റാറ്റിക് ടൈപ്പ് സിസ്റ്റം ഉപയോഗിക്കുന്നതിലാണ് ഇതിനുള്ള ഉത്തരം. JavaScript തന്നെ നേറ്റീവ് പാറ്റേൺ മാച്ചിംഗിലേക്ക് നീങ്ങുമ്പോൾ, അതിൻ്റെ ഡൈനാമിക് സ്വഭാവം കാരണം ഏതൊരു പരിശോധനയും റൺടൈമിൽ സംഭവിക്കും, ഇത് പ്രൊഡക്ഷനിൽ অপ্রত্যাশিত പിശകുകൾക്ക് കാരണമാകും. ഈ ലേഖനം യഥാർത്ഥ കംപൈൽ-ടൈം പാറ്റേൺ വെരിഫിക്കേഷൻ സാധ്യമാക്കുന്ന സാങ്കേതിക വിദ്യകളിലേക്കും ടൂളുകളിലേക്കും ആഴത്തിൽ ഇറങ്ങിച്ചെല്ലുന്നു, നിങ്ങളുടെ ഉപയോക്താക്കൾ പിശകുകൾ കണ്ടെത്താൻ കാത്തിരിക്കുന്നതിനു പകരം നിങ്ങൾ ടൈപ്പ് ചെയ്യുമ്പോൾ തന്നെ കണ്ടെത്തുന്നുവെന്ന് ഉറപ്പാക്കുന്നു.
TypeScript-ൻ്റെ ശക്തമായ സവിശേഷതകളും പാറ്റേൺ മാച്ചിംഗിൻ്റെ ഭംഗിയും സംയോജിപ്പിച്ച് എങ്ങനെ ശക്തവും സ്വയം വിശദീകരിക്കുന്നതും പിശകുകൾ ഇല്ലാത്തതുമായ സിസ്റ്റങ്ങൾ നിർമ്മിക്കാമെന്ന് നമ്മുക്ക് പര്യവേക്ഷണം ചെയ്യാം. റൺടൈം ബഗുകളുടെ ഒരു വലിയ വിഭാഗത്തെ ഇല്ലാതാക്കാനും സുരക്ഷിതവും പരിപാലിക്കാൻ എളുപ്പമുള്ളതുമായ കോഡ് എഴുതാനും തയ്യാറാകൂ.
എന്താണ് ഈ പാറ്റേൺ മാച്ചിംഗ്?
അടിസ്ഥാനപരമായി, പാറ്റേൺ മാച്ചിംഗ് എന്നത് ഒരു സങ്കീർണ്ണമായ കൺട്രോൾ ഫ്ലോ മെക്കാനിസമാണ്. ഇത് ഒരു സൂപ്പർ-പവർ `switch` സ്റ്റേറ്റ്മെൻ്റ് പോലെയാണ്. ലളിതമായ വാല്യുവികൾക്കെതിരെ (സംഖ്യകൾ അല്ലെങ്കിൽ സ്ട്രിംഗുകൾ പോലെ) ഇക്വാലിറ്റി പരിശോധിക്കുന്നതിനുപകരം, പാറ്റേൺ മാച്ചിംഗ് ഒരു വാല്യുവിനെ സങ്കീർണ്ണമായ 'പാറ്റേണുകൾക്കെതിരെ' പരിശോധിക്കാനും ഒരു പൊരുത്തം കണ്ടെത്തിയാൽ, ആ വാല്യുവിൻ്റെ ഭാഗങ്ങളിലേക്ക് വേരിയബിളുകൾ ബന്ധിപ്പിക്കാനും നിങ്ങളെ അനുവദിക്കുന്നു.
ഇതിനെ പരമ്പരാഗത സമീപനങ്ങളുമായി താരതമ്യം ചെയ്യാം:
പഴയ രീതി: `if-else` ചെയിനുകളും `switch` ഉം
ഒരു ജ്യാമിതീയ രൂപത്തിൻ്റെ ഏരിയ കണക്കാക്കുന്ന ഒരു ഫംഗ്ഷൻ പരിഗണിക്കുക. ഒരു പരമ്പരാഗത സമീപനത്തിൽ, നിങ്ങളുടെ കോഡ് ഇങ്ങനെയായിരിക്കാം:
// Shape is an object with a 'type' property
function calculateArea(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.radius * shape.radius;
} else if (shape.type === 'square') {
return shape.sideLength * shape.sideLength;
} else if (shape.type === 'rectangle') {
return shape.width * shape.height;
} else {
throw new Error('Unsupported shape type');
}
}
ഇത് പ്രവർത്തിക്കും, പക്ഷേ ഇത് വലുതും പിശകുകൾക്ക് സാധ്യതയുള്ളതുമാണ്. നിങ്ങൾ ഒരു പുതിയ ആകൃതി, ഒരു `triangle` ചേർത്താൽ എന്ത് സംഭവിക്കും, പക്ഷേ ഈ ഫംഗ്ഷൻ അപ്ഡേറ്റ് ചെയ്യാൻ മറന്നുപോയാൽ? കോഡ് റൺടൈമിൽ ഒരു പൊതുവായ പിശക് കാണിക്കും, അത് യഥാർത്ഥ ബഗ് അവതരിപ്പിച്ച സ്ഥലത്ത് നിന്ന് വളരെ അകലെയായിരിക്കാം.
പാറ്റേൺ മാച്ചിംഗ് രീതി: ഡിക്ലറേറ്റീവും എക്സ്പ്രെസ്സീവും
പാറ്റേൺ മാച്ചിംഗ് ഈ ലോജിക്കിനെ കൂടുതൽ ഡിക്ലറേറ്റീവ് ആക്കുന്നു. ഒരു പരമ്പര ഇംപറേറ്റീവ് പരിശോധനകൾക്ക് പകരം, നിങ്ങൾ പ്രതീക്ഷിക്കുന്ന പാറ്റേണുകളും എടുക്കേണ്ട പ്രവർത്തനങ്ങളും നിങ്ങൾ വ്യക്തമാക്കുന്നു:
// Pseudocode for a future JavaScript pattern matching feature
function calculateArea(shape) {
match (shape) {
when ({ type: 'circle', radius }): return Math.PI * radius * radius;
when ({ type: 'square', sideLength }): return sideLength * sideLength;
when ({ type: 'rectangle', width, height }): return width * height;
default: throw new Error('Unsupported shape type');
}
}
പ്രധാനപ്പെട്ട ഗുണങ്ങൾ ഉടൻ തന്നെ വ്യക്തമാകും:
- ഡീസ്ട്രക്ചറിംഗ്: `radius`, `width`, `height` പോലുള്ള വാല്യുവികൾ `shape` ഒബ്ജക്റ്റിൽ നിന്ന് സ്വയമേവ എക്സ്ട്രാക്ട് ചെയ്യപ്പെടുന്നു.
- വായിക്കാവുന്ന കോഡ്: കോഡിൻ്റെ ഉദ്ദേശ്യം വ്യക്തമാണ്. ഓരോ `when` ക്ലോസും ഒരു പ്രത്യേക ഡാറ്റാ ഘടനയും അതിൻ്റെ അനുബന്ധ ലോജിക്കും വിവരിക്കുന്നു.
- എക്സ്ഹോസ്റ്റീവ്നെസ്സ്: ടൈപ്പ് സുരക്ഷയ്ക്ക് ഇത് ഏറ്റവും നിർണായകമായ നേട്ടമാണ്. സാധ്യമായ ഒരു കേസ് നിങ്ങൾ കൈകാര്യം ചെയ്യാൻ മറന്നുപോയാൽ, ഒരു ശക്തമായ പാറ്റേൺ മാച്ചിംഗ് സിസ്റ്റത്തിന് കംപൈൽ സമയത്ത് നിങ്ങളെ അറിയിക്കാൻ കഴിയും. ഇതാണ് നമ്മുടെ പ്രധാന ലക്ഷ്യം.
JavaScript വെല്ലുവിളി: ഡൈനാമിസവും സുരക്ഷയും
JavaScript-ൻ്റെ ഏറ്റവും വലിയ ശക്തി - അതിൻ്റെ ഫ്ലെക്സിബിലിറ്റിയും ഡൈനാമിക് സ്വഭാവവും - ടൈപ്പ് സുരക്ഷയുടെ കാര്യത്തിൽ അതിൻ്റെ ഏറ്റവും വലിയ ദൗർബല്യം കൂടിയാണ്. കംപൈൽ സമയത്ത് കോൺട്രാക്റ്റുകൾ നടപ്പിലാക്കുന്ന ഒരു സ്റ്റാറ്റിക് ടൈപ്പ് സിസ്റ്റം ഇല്ലാതെ, സാധാരണ JavaScript-ലെ പാറ്റേൺ മാച്ചിംഗ് റൺടൈം പരിശോധനകളിൽ പരിമിതമാണ്. ഇതിനർത്ഥം:
- കംപൈൽ-ടൈം ഗ്യാരൻ്റികളില്ല: നിങ്ങളുടെ കോഡ് പ്രവർത്തിക്കുകയും ആ പ്രത്യേക പാതയിൽ എത്തുകയും ചെയ്യുന്നതുവരെ നിങ്ങൾ ഒരു കേസ് നഷ്ടപ്പെടുത്തിയെന്ന് അറിയില്ല.
- സൈലൻ്റ് ഫെയില്യർ: നിങ്ങൾ ഒരു ഡിഫോൾട്ട് കേസ് മറന്നുപോയാൽ, പൊരുത്തമില്ലാത്ത ഒരു വാല്യു കേവലം `undefined` ആയി കലാശിക്കുകയും ഡൗൺസ്ട്രീമിൽ ചെറിയ ബഗുകൾ ഉണ്ടാക്കുകയും ചെയ്യും.
- റീഫാക്ടറിംഗ് പേടിസ്വപ്നങ്ങൾ: ഒരു ഡാറ്റാ ഘടനയിലേക്ക് ഒരു പുതിയ വേരിയൻ്റ് ചേർക്കുന്നത് (ഉദാഹരണത്തിന്, ഒരു പുതിയ ഇവൻ്റ് തരം, ഒരു പുതിയ API റെസ്പോൺസ് സ്റ്റാറ്റസ്) അത് കൈകാര്യം ചെയ്യേണ്ട എല്ലാ സ്ഥലങ്ങളും കണ്ടെത്താൻ ഒരു ഗ്ലോബൽ സെർച്ച്-ആൻഡ്-റീപ്ലേസ് ആവശ്യമാണ്. ഒന്ന് നഷ്ടപ്പെട്ടാൽ നിങ്ങളുടെ ആപ്ലിക്കേഷൻ തകരാറിലാകും.
ഇവിടെയാണ് TypeScript ഗെയിം മാറ്റുന്നത്. അതിൻ്റെ സ്റ്റാറ്റിക് ടൈപ്പ് സിസ്റ്റം ഉപയോഗിച്ച്, നമ്മുടെ ഡാറ്റ കൃത്യമായി മോഡൽ ചെയ്യാനും സാധ്യമായ എല്ലാ വ്യതിയാനങ്ങളും കൈകാര്യം ചെയ്യുന്നുവെന്ന് ഉറപ്പാക്കാൻ കമ്പൈലറെ ഉപയോഗിക്കാനും കഴിയും. എങ്ങനെ എന്ന് നോക്കാം.
ടെക്നിക്ക് 1: ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകളുമായുള്ള അടിസ്ഥാനം
ടൈപ്പ്-സുരക്ഷിതമായ പാറ്റേൺ മാച്ചിംഗ് സാധ്യമാക്കുന്നതിനുള്ള ഏറ്റവും പ്രധാനപ്പെട്ട TypeScript ഫീച്ചറാണ് ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ (ഇതിനെ ടാഗ്ഡ് യൂണിയൻ അല്ലെങ്കിൽ ആൾജിബ്രാക്ക് ഡാറ്റാ ടൈപ്പ് എന്നും അറിയപ്പെടുന്നു). വ്യത്യസ്തമായ നിരവധി സാധ്യതകളിൽ ഒന്നാകാൻ കഴിയുന്ന ഒരു ടൈപ്പിനെ മോഡൽ ചെയ്യാനുള്ള ശക്തമായ മാർഗ്ഗമാണിത്.
എന്താണ് ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ?
ഒരു ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ മൂന്ന് ഘടകങ്ങളിൽ നിന്നാണ് നിർമ്മിച്ചിരിക്കുന്നത്:
- വ്യത്യസ്ത ടൈപ്പുകളുടെ ഒരു കൂട്ടം (യൂണിയൻ അംഗങ്ങൾ).
- ഒരു ലിറ്ററൽ ടൈപ്പുള്ള ഒരു പൊതു പ്രോപ്പർട്ടി, ഇതിനെ ഡിസ്ക്രിമിനൻ്റ് അല്ലെങ്കിൽ ടാഗ് എന്ന് വിളിക്കുന്നു. യൂണിയനുള്ളിലെ നിർദ്ദിഷ്ട ടൈപ്പ് ചുരുക്കാൻ ഈ പ്രോപ്പർട്ടി TypeScript-നെ അനുവദിക്കുന്നു.
- എല്ലാ അംഗ ടൈപ്പുകളും സംയോജിപ്പിക്കുന്ന ഒരു യൂണിയൻ ടൈപ്പ്.
ഈ പാറ്റേൺ ഉപയോഗിച്ച് നമ്മുടെ ആകൃതിയുടെ ഉദാഹരണം പുനർനിർമ്മിക്കാം:
// 1. Define the distinct member types
interface Circle {
kind: 'circle'; // The discriminant
radius: number;
}
interface Square {
kind: 'square'; // The discriminant
sideLength: number;
}
interface Rectangle {
kind: 'rectangle'; // The discriminant
width: number;
height: number;
}
// 2. Create the union type
type Shape = Circle | Square | Rectangle;
ഇപ്പോൾ, `Shape` ടൈപ്പിലുള്ള ഒരു വേരിയബിൾ ഈ മൂന്ന് ഇൻ്റർഫേസുകളിൽ ഒന്നായിരിക്കണം. TypeScript-ൻ്റെ ടൈപ്പ് ചുരുക്കാനുള്ള കഴിവുകൾ തുറക്കുന്ന താക്കോലായി `kind` പ്രോപ്പർട്ടി പ്രവർത്തിക്കുന്നു.
കംപൈൽ-ടൈം എക്സ്ഹോസ്റ്റീവ്നെസ്സ് ചെക്കിംഗ് നടപ്പിലാക്കുന്നു
നമ്മുടെ ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ നിലവിൽ ഉള്ളതിനാൽ, സാധ്യമായ എല്ലാ ആകൃതികളും കൈകാര്യം ചെയ്യാൻ കമ്പൈലർ ഉറപ്പുനൽകുന്ന ഒരു ഫംഗ്ഷൻ ഇപ്പോൾ നമുക്ക് എഴുതാൻ കഴിയും. ഇതിലെ മാന്ത്രിക ചേരുവ TypeScript-ൻ്റെ `never` ടൈപ്പാണ്, ഇത് ഒരിക്കലും സംഭവിക്കാൻ പാടില്ലാത്ത ഒരു വാല്യുവിനെ പ്രതിനിധീകരിക്കുന്നു.
ഇത് നടപ്പിലാക്കാൻ നമുക്ക് ഒരു ലളിതമായ ഹെൽപ്പർ ഫംഗ്ഷൻ എഴുതാം:
function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}
ഇനി, ഒരു സാധാരണ `switch` സ്റ്റേറ്റ്മെൻ്റ് ഉപയോഗിച്ച് നമ്മുടെ `calculateArea` ഫംഗ്ഷൻ വീണ്ടും എഴുതാം. `default` കേസിൽ എന്ത് സംഭവിക്കുന്നുവെന്ന് ശ്രദ്ധിക്കുക:
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
// TypeScript knows `shape` is a Circle here!
return Math.PI * shape.radius ** 2;
case 'square':
// TypeScript knows `shape` is a Square here!
return shape.sideLength ** 2;
case 'rectangle':
// TypeScript knows `shape` is a Rectangle here!
return shape.width * shape.height;
default:
// If we've handled all cases, `shape` will be of type `never`
return assertUnreachable(shape);
}
}
ഈ കോഡ് തികച്ചും കംപൈൽ ചെയ്യുന്നു. ഓരോ `case` ബ്ലോക്കിനുള്ളിലും, TypeScript `shape`-ൻ്റെ ടൈപ്പിനെ `Circle`, `Square`, അല്ലെങ്കിൽ `Rectangle` ആയി ചുരുക്കിയിട്ടുണ്ട്, ഇത് `radius` പോലുള്ള പ്രോപ്പർട്ടികൾ സുരക്ഷിതമായി ആക്സസ് ചെയ്യാൻ ഞങ്ങളെ അനുവദിക്കുന്നു.
ഇനി മാന്ത്രിക നിമിഷം. നമ്മുടെ സിസ്റ്റത്തിലേക്ക് ഒരു പുതിയ ആകൃതി അവതരിപ്പിക്കാം:
interface Triangle {
kind: 'triangle';
base: number;
height: number;
}
type Shape = Circle | Square | Rectangle | Triangle; // Add it to the union
`Triangle`-നെ `Shape` യൂണിയനിലേക്ക് ചേർത്ത ഉടൻ തന്നെ, നമ്മുടെ `calculateArea` ഫംഗ്ഷൻ ഒരു കംപൈൽ-ടൈം പിശക് ഉടനടി നൽകും:
// In the `default` block of `calculateArea`:
return assertUnreachable(shape);
// ~~~~~
// Argument of type 'Triangle' is not assignable to parameter of type 'never'.
ഈ പിശക് അവിശ്വസനീയമാംവിധം വിലപ്പെട്ടതാണ്. TypeScript കമ്പൈലർ നമ്മളോട് പറയുന്നത് ഇതാണ്, "സാധ്യമായ എല്ലാ `Shape`-കളും കൈകാര്യം ചെയ്യാമെന്ന് നിങ്ങൾ വാഗ്ദാനം ചെയ്തു, പക്ഷേ നിങ്ങൾ `Triangle`-നെക്കുറിച്ച് മറന്നു. `shape` വേരിയബിൾ ഡിഫോൾട്ട് കേസിൽ ഇപ്പോഴും ഒരു `Triangle` ആകാം, അത് `never`-ലേക്ക് അസൈൻ ചെയ്യാൻ കഴിയില്ല."
പിശക് പരിഹരിക്കാൻ, കാണാത്ത കേസ് ചേർക്കുക. കമ്പൈലർ നമ്മുടെ സുരക്ഷാ വലയമായി മാറുന്നു, നമ്മുടെ ലോജിക് നമ്മുടെ ഡാറ്റാ മോഡലുമായി സമന്വയിപ്പിച്ച് നിലനിർത്തുമെന്ന് ഉറപ്പാക്കുന്നു.
// ... inside the switch
case 'triangle':
return 0.5 * shape.base * shape.height;
default:
return assertUnreachable(shape);
// ... now the code compiles again!
ഈ സമീപനത്തിൻ്റെ ഗുണങ്ങളും ദോഷങ്ങളും
- ഗുണങ്ങൾ:
- Dependency ഇല്ല: ഇത് പ്രധാന TypeScript ഫീച്ചറുകൾ മാത്രം ഉപയോഗിക്കുന്നു.
- പരമാവധി ടൈപ്പ് സുരക്ഷ: ഇത് ശക്തമായ കംപൈൽ-ടൈം ഗ്യാരൻ്റികൾ നൽകുന്നു.
- മികച്ച പ്രകടനം: ഇത് ഉയർന്ന രീതിയിൽ ഒപ്റ്റിമൈസ് ചെയ്ത സാധാരണ JavaScript `switch` സ്റ്റേറ്റ്മെൻ്റിലേക്ക് കംപൈൽ ചെയ്യുന്നു.
- ദോഷങ്ങൾ:
- വാചാലത: `switch`, `case`, `break`/`return`, കൂടാതെ `default` എന്നിവയുടെ ബോയിലർപ്ലേറ്റ് വളരെ വലുതായി തോന്നാം.
- ഒരു എക്സ്പ്രെഷൻ അല്ല: ഒരു `switch` സ്റ്റേറ്റ്മെൻ്റ് നേരിട്ട് റിട്ടേൺ ചെയ്യാനോ വേരിയബിളിലേക്ക് അസൈൻ ചെയ്യാനോ കഴിയില്ല, ഇത് കൂടുതൽ ഇംപറേറ്റീവ് കോഡ് ശൈലികളിലേക്ക് നയിക്കുന്നു.
ടെക്നിക്ക് 2: ആധുനിക ലൈബ്രറികളുള്ള എർഗണോമിക് API-കൾ
`switch` സ്റ്റേറ്റ്മെൻ്റുള്ള ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ അടിസ്ഥാനമാണെങ്കിലും, അതിൻ്റെ ബോയിലർപ്ലേറ്റ് മടുപ്പിക്കുന്നതാണ്. ഇത് ടൈപ്പ് സുരക്ഷയ്ക്കായി TypeScript-ൻ്റെ കമ്പൈലറെ ഉപയോഗിക്കുമ്പോൾ തന്നെ പാറ്റേൺ മാച്ചിംഗിനായി കൂടുതൽ ഫങ്ഷണൽ, എക്സ്പ്രെസ്സീവ്, എർഗണോമിക് API നൽകുന്ന മികച്ച ഓപ്പൺ സോഴ്സ് ലൈബ്രറികളുടെ വളർച്ചയിലേക്ക് നയിച്ചു.
`ts-pattern` അവതരിപ്പിക്കുന്നു
ഈ രംഗത്തെ ഏറ്റവും പ്രചാരമുള്ളതും ശക്തവുമായ ലൈബ്രറികളിൽ ഒന്നാണ് `ts-pattern`. ഇത് `switch` സ്റ്റേറ്റ്മെൻ്റുകളെ ഒരു എക്സ്പ്രെഷനായി പ്രവർത്തിക്കുന്ന ഫ്ലൂയിൻ്റ്, ചെയിൻ ചെയ്യാവുന്ന API ഉപയോഗിച്ച് മാറ്റിസ്ഥാപിക്കാൻ നിങ്ങളെ അനുവദിക്കുന്നു.
`ts-pattern` ഉപയോഗിച്ച് നമ്മുടെ `calculateArea` ഫംഗ്ഷൻ വീണ്ടും എഴുതാം:
import { match } from 'ts-pattern';
function calculateAreaWithTsPattern(shape: Shape): number {
return match(shape)
.with({ kind: 'circle' }, (s) => Math.PI * s.radius ** 2)
.with({ kind: 'square' }, (s) => s.sideLength ** 2)
.with({ kind: 'rectangle' }, (s) => s.width * s.height)
.with({ kind: 'triangle' }, (s) => 0.5 * s.base * s.height)
.exhaustive(); // This is the key to compile-time safety
}
എന്താണ് സംഭവിക്കുന്നതെന്ന് നോക്കാം:
- `match(shape)`: ഇത് പൊരുത്തപ്പെടുത്തേണ്ട വാല്യു എടുത്ത് പാറ്റേൺ മാച്ചിംഗ് എക്സ്പ്രെഷൻ ആരംഭിക്കുന്നു.
- `.with({ kind: '...' }, handler)`: ഓരോ `.with()` കോളും ഒരു പാറ്റേൺ നിർവചിക്കുന്നു. രണ്ടാമത്തെ ആർഗ്യുമെൻ്റിൻ്റെ (handler ഫംഗ്ഷൻ) തരം അനുമാനിക്കാൻ `ts-pattern` যথেষ্ট സ്മാർട്ടാണ്. `{ kind: 'circle' }` പാറ്റേണിനായി, handler-ലേക്കുള്ള ഇൻപുട്ട് `s` `Circle` തരത്തിലുള്ളതായിരിക്കുമെന്ന് ഇതിന് അറിയാം.
- `.exhaustive()`: ഈ രീതി നമ്മുടെ `assertUnreachable` ട്രിക്കിന് തുല്യമാണ്. സാധ്യമായ എല്ലാ കേസുകളും കൈകാര്യം ചെയ്യണമെന്ന് ഇത് `ts-pattern`-നോട് പറയുന്നു. ഞങ്ങൾ `.with({ kind: 'triangle' }, ...)` ലൈൻ നീക്കം ചെയ്താൽ, പൊരുത്തം പൂർണമല്ലെന്ന് `ts-pattern` `.exhaustive()` കോളിൽ ഒരു കംപൈൽ-ടൈം പിശക് ട്രിഗർ ചെയ്യും.
`ts-pattern`-ൻ്റെ വിപുലമായ സവിശേഷതകൾ
`ts-pattern` ലളിതമായ പ്രോപ്പർട്ടി മാച്ചിംഗിനപ്പുറം പോകുന്നു:
- `.when()` ഉപയോഗിച്ച് പ്രെഡിക്കേറ്റ് മാച്ചിംഗ്: ഒരു നിബന്ധനയെ അടിസ്ഥാനമാക്കി പൊരുത്തപ്പെടുത്തുക.
match(input) .when(isString, (str) => `It's a string: ${str}`) .when(isNumber, (num) => `It's a number: ${num}`) .otherwise(() => 'It is something else'); - ആഴത്തിലുള്ള നെസ്റ്റഡ് പാറ്റേണുകൾ: സങ്കീർണ്ണമായ ഒബ്ജക്റ്റ് ഘടനകളിൽ പൊരുത്തപ്പെടുത്തുക.
match(user) .with({ address: { city: 'Paris' } }, () => 'User is in Paris') .otherwise(() => 'User is elsewhere'); - Wildcards-ഉം പ്രത്യേക സെലക്ടറുകളും: ഒരു പാറ്റേണിനുള്ളിൽ ഒരു വാല്യു ക്യാപ്ചർ ചെയ്യാൻ `P.select()` ഉപയോഗിക്കുക, അല്ലെങ്കിൽ ഒരു നിശ്ചിത തരത്തിലുള്ള ഏതെങ്കിലും വാല്യുവുമായി പൊരുത്തപ്പെടുത്താൻ `P.string`, `P.number` എന്നിവ ഉപയോഗിക്കുക.
import { match, P } from 'ts-pattern'; match(event) .with({ type: 'USER_LOGIN', user: { name: P.select() } }, (name) => { console.log(`${name} logged in.`); }) .otherwise(() => {});
`ts-pattern` പോലുള്ള ഒരു ലൈബ്രറി ഉപയോഗിക്കുന്നതിലൂടെ, നിങ്ങൾക്ക് രണ്ട് ലോകത്തിലെയും മികച്ചത് ലഭിക്കും: TypeScript-ൻ്റെ `never` ചെക്കിംഗിൻ്റെ ശക്തമായ കംപൈൽ-ടൈം സുരക്ഷയും വൃത്തിയുള്ളതും ഡിക്ലറേറ്റീവും വളരെ എക്സ്പ്രെസ്സീവുമായ API-യും.
ഭാവി: TC39 പാറ്റേൺ മാച്ചിംഗ് പ്രൊപ്പോസൽ
JavaScript ഭാഷ തന്നെ നേറ്റീവ് പാറ്റേൺ മാച്ചിംഗ് നേടുന്നതിനുള്ള പാതയിലാണ്. ഭാഷയിലേക്ക് ഒരു `match` എക്സ്പ്രെഷൻ ചേർക്കാൻ TC39-ൽ (JavaScript സ്റ്റാൻഡേർഡ് ചെയ്യുന്ന കമ്മിറ്റി) ഒരു പ്രൊപ്പോസൽ ഉണ്ട്.
നിർദ്ദിഷ്ട സിൻ്റാക്സ്
സിൻ്റാക്സ് ഇതുപോലെയായിരിക്കും:
// This is proposed JavaScript syntax and might change
const getMessage = (response) => {
return match (response) {
when ({ status: 200, body: b }) { return `Success with body: ${b}`; }
when ({ status: 404 }) { return 'Not Found'; }
when ({ status: s if s >= 500 }) { return `Server Error: ${s}`; }
default { return 'Unknown response'; }
}
};
ടൈപ്പ് സുരക്ഷയെക്കുറിച്ച് എന്ത്?
ഇതാണ് നമ്മുടെ ചർച്ചയിലെ നിർണായക ചോദ്യം. നേറ്റീവ് JavaScript പാറ്റേൺ മാച്ചിംഗ് ഫീച്ചർ അതിൻ്റെ പരിശോധനകൾ റൺടൈമിൽ നടത്തും. നിങ്ങളുടെ TypeScript ടൈപ്പുകളെക്കുറിച്ച് ഇതിന് അറിയില്ല.
എന്നിരുന്നാലും, ഈ പുതിയ സിൻ്റാക്സിന് മുകളിൽ TypeScript ടീം സ്റ്റാറ്റിക് അനാലിസിസ് നിർമ്മിക്കുമെന്ന് ഉറപ്പാണ്. ടൈപ്പ് ചുരുക്കാൻ TypeScript `if` സ്റ്റേറ്റ്മെൻ്റുകളും `switch` ബ്ലോക്കുകളും വിശകലനം ചെയ്യുന്നതുപോലെ, അത് `match` എക്സ്പ്രെഷനുകളും വിശകലനം ചെയ്യും. ഇതിനർത്ഥം നമുക്ക് ഏറ്റവും മികച്ച ഫലം ലഭിക്കും:
- നേറ്റീവ്, പെർഫോമൻ്റ് സിൻ്റാക്സ്: ലൈബ്രറികളുടെയോ ട്രാൻസ്പിലേഷൻ ട്രിക്കുകളുടെയോ ആവശ്യമില്ല.
- പൂർണ്ണമായ കംപൈൽ-ടൈം സുരക്ഷ: TypeScript ഒരു ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനെതിരെ `switch`-ൽ ചെയ്യുന്നതുപോലെ `match` എക്സ്പ്രെഷൻ പൂർണ്ണമാണോയെന്ന് പരിശോധിക്കും.
ഈ ഫീച്ചർ പ്രൊപ്പോസൽ ഘട്ടങ്ങളിലൂടെയും ബ്രൗസറുകളിലേക്കും റൺടൈമുകളിലേക്കും എത്തുന്നതുവരെ, ഇന്ന് നമ്മൾ ചർച്ച ചെയ്ത ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകളും ലൈബ്രറികളും ഉപയോഗിച്ചുള്ള സാങ്കേതിക വിദ്യകൾ പ്രൊഡക്ഷന് തയ്യാറായതും അത്യാധുനികവുമായ പരിഹാരമാണ്.
പ്രായോഗിക ആപ്ലിക്കേഷനുകളും മികച്ച രീതികളും
ഈ പാറ്റേണുകൾ സാധാരണവും യഥാർത്ഥവുമായ ഡെവലപ്മെൻ്റ് സാഹചര്യങ്ങളിൽ എങ്ങനെ പ്രയോഗിക്കാമെന്ന് നോക്കാം.
സ്റ്റേറ്റ് മാനേജ്മെൻ്റ് (Redux, Zustand, തുടങ്ങിയവ)
ആക്ഷനുകൾ ഉപയോഗിച്ച് സ്റ്റേറ്റ് മാനേജ് ചെയ്യുന്നത് ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾക്കുള്ള മികച്ച ഉപയോഗമാണ്. ആക്ഷൻ തരങ്ങൾക്കായി സ്ട്രിംഗ് കോൺസ്റ്റൻ്റുകൾ ഉപയോഗിക്കുന്നതിനുപകരം, സാധ്യമായ എല്ലാ ആക്ഷനുകൾക്കുമായി ഒരു ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ നിർവചിക്കുക.
// Define actions
interface IncrementAction { type: 'counter/increment'; payload: number; }
interface DecrementAction { type: 'counter/decrement'; payload: number; }
interface ResetAction { type: 'counter/reset'; }
type CounterAction = IncrementAction | DecrementAction | ResetAction;
// A type-safe reducer
function counterReducer(state: number, action: CounterAction): number {
return match(action)
.with({ type: 'counter/increment' }, (act) => state + act.payload)
.with({ type: 'counter/decrement' }, (act) => state - act.payload)
.with({ type: 'counter/reset' }, () => 0)
.exhaustive();
}
ഇപ്പോൾ, നിങ്ങൾ `CounterAction` യൂണിയനിലേക്ക് ഒരു പുതിയ ആക്ഷൻ ചേർത്താൽ, റിഡ്യൂസർ അപ്ഡേറ്റ് ചെയ്യാൻ TypeScript നിങ്ങളെ നിർബന്ധിക്കും. മറന്നുപോയ ആക്ഷൻ ഹാൻഡിലറുകൾ ഉണ്ടാകില്ല!
API റെസ്പോൺസുകൾ കൈകാര്യം ചെയ്യുന്നു
ഒരു API-യിൽ നിന്ന് ഡാറ്റ ഫെച്ച് ചെയ്യുന്നതിൽ ഒന്നിലധികം സ്റ്റേറ്റുകൾ ഉൾപ്പെടുന്നു: ലോഡിംഗ്, വിജയം, പിശക്. ഒരു ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ ഉപയോഗിച്ച് ഇത് മോഡൽ ചെയ്യുന്നത് നിങ്ങളുടെ UI ലോജിക്കിനെ കൂടുതൽ ശക്തമാക്കുന്നു.
// Model the async data state
type RemoteData =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: E };
// In your UI component (e.g., React)
function UserProfile({ userId }: { userId: string }) {
const [userState, setUserState] = useState>({ status: 'idle' });
// ... useEffect to fetch data and update state ...
return match(userState)
.with({ status: 'idle' }, () => Click a button to load the user.
)
.with({ status: 'loading' }, () => )
.with({ status: 'success' }, (state) => )
.with({ status: 'error' }, (state) => )
.exhaustive();
}
നിങ്ങളുടെ ഡാറ്റ ഫെച്ചിൻ്റെ സാധ്യമായ എല്ലാ സ്റ്റേറ്റുകൾക്കും നിങ്ങൾ ഒരു UI നടപ്പിലാക്കിയിട്ടുണ്ടെന്ന് ഈ സമീപനം ഉറപ്പ് നൽകുന്നു. ലോഡിംഗ് അല്ലെങ്കിൽ പിശക് കേസ് കൈകാര്യം ചെയ്യാൻ നിങ്ങൾ അറിയാതെ മറന്നുപോകാൻ കഴിയില്ല.
മികച്ച രീതികളുടെ സംഗ്രഹം
- ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾ ഉപയോഗിച്ച് മോഡൽ ചെയ്യുക: ഒന്നിലധികം വ്യത്യസ്ത രൂപങ്ങളിലുള്ള ഒരു വാല്യു എപ്പോഴൊക്കെ ഉണ്ടാകുന്നുവോ, അപ്പോഴെല്ലാം ഒരു ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയൻ ഉപയോഗിക്കുക. TypeScript-ലെ ടൈപ്പ്-സുരക്ഷിതമായ പാറ്റേണുകളുടെ അടിസ്ഥാനമാണിത്.
- എല്ലായ്പ്പോഴും എക്സ്ഹോസ്റ്റീവ്നെസ്സ് നടപ്പിലാക്കുക: നിങ്ങൾ ഒരു `switch` സ്റ്റേറ്റ്മെൻ്റുള്ള `never` ട്രിക്ക് ഉപയോഗിച്ചാലും അല്ലെങ്കിൽ ഒരു ലൈബ്രറിയുടെ `.exhaustive()` രീതി ഉപയോഗിച്ചാലും, ഒരു പാറ്റേൺ മാച്ചിനെ ഒരിക്കലും തുറന്ന രീതിയിൽ വിടരുത്. ഇവിടെ നിന്നാണ് സുരക്ഷ വരുന്നത്.
- ശരിയായ ടൂൾ തിരഞ്ഞെടുക്കുക: ലളിതമായ കേസുകൾക്ക്, ഒരു `switch` സ്റ്റേറ്റ്മെൻ്റ് മതി. സങ്കീർണ്ണമായ ലോജിക്കിന്, നെസ്റ്റഡ് മാച്ചിംഗിന് അല്ലെങ്കിൽ കൂടുതൽ ഫങ്ഷണൽ ശൈലിക്ക്, `ts-pattern` പോലുള്ള ഒരു ലൈബ്രറി റീഡബിലിറ്റി ഗണ്യമായി മെച്ചപ്പെടുത്തുകയും ബോയിലർപ്ലേറ്റ് കുറയ്ക്കുകയും ചെയ്യും.
- പാറ്റേണുകൾ വായിക്കാൻ എളുപ്പമുള്ളതാക്കുക: വ്യക്തതയാണ് ലക്ഷ്യം. ഒറ്റനോട്ടത്തിൽ മനസ്സിലാക്കാൻ പ്രയാസമുള്ള സങ്കീർണ്ണമായ നെസ്റ്റഡ് പാറ്റേണുകൾ ഒഴിവാക്കുക. ചില സമയങ്ങളിൽ, ഒരു മാച്ചിനെ ചെറിയ ഫംഗ്ഷനുകളായി വിഭജിക്കുന്നത് ഒരു മികച്ച സമീപനമാണ്.
ഉപസംഹാരം: സുരക്ഷിതമായ JavaScript-ൻ്റെ ഭാവി എഴുതുന്നു
പാറ്റേൺ മാച്ചിംഗ് എന്നത് സിൻ്റാക്റ്റിക് ഷുഗറിനേക്കാൾ കൂടുതലാണ്; ഇത് കൂടുതൽ ഡിക്ലറേറ്റീവ്, റീഡബിൾ, ഏറ്റവും പ്രധാനമായി കൂടുതൽ ശക്തമായ കോഡിലേക്ക് നയിക്കുന്ന ഒരു രീതിയാണ്. JavaScript-ൽ ഇതിൻ്റെ നേറ്റീവ് വരവിനായി ഞങ്ങൾ ആകാംഷയോടെ കാത്തിരിക്കുമ്പോൾ തന്നെ, അതിൻ്റെ ഗുണങ്ങൾ അനുഭവിക്കാൻ നമ്മുക്ക് കാത്തിരിക്കേണ്ടതില്ല.
TypeScript-ൻ്റെ സ്റ്റാറ്റിക് ടൈപ്പ് സിസ്റ്റത്തിൻ്റെ ശക്തി ഉപയോഗിച്ച്, പ്രത്യേകിച്ചും ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾ ഉപയോഗിച്ച്, കംപൈൽ സമയത്ത് സ്ഥിരീകരിക്കാൻ കഴിയുന്ന സിസ്റ്റങ്ങൾ നമ്മുക്ക് നിർമ്മിക്കാൻ കഴിയും. ഈ സമീപനം അടിസ്ഥാനപരമായി ബഗ് കണ്ടെത്തലിനെ റൺടൈമിൽ നിന്ന് ഡെവലപ്മെൻ്റ് സമയത്തേക്ക് മാറ്റുന്നു, ഇത് എണ്ണമറ്റ മണിക്കൂറുകൾ ലാഭിക്കുകയും പ്രൊഡക്ഷൻ അപകടങ്ങൾ തടയുകയും ചെയ്യുന്നു. `ts-pattern` പോലുള്ള ലൈബ്രറികൾ ഈ ശക്തമായ അടിത്തറയിൽ നിർമ്മിക്കുകയും ടൈപ്പ്-സുരക്ഷിതമായ കോഡ് എഴുതുന്നത് ആനന്ദകരമാക്കുകയും ചെയ്യുന്നു.
കംപൈൽ-ടൈം പാറ്റേൺ വെരിഫിക്കേഷൻ സ്വീകരിക്കുന്നത് കൂടുതൽ പ്രതിരോധശേഷിയുള്ളതും പരിപാലിക്കാൻ എളുപ്പമുള്ളതുമായ ആപ്ലിക്കേഷനുകൾ എഴുതുന്നതിനുള്ള ഒരു ചുവടുവയ്പ്പാണ്. നിങ്ങളുടെ ഡാറ്റയ്ക്ക് ഉണ്ടാകാൻ സാധ്യതയുള്ള എല്ലാ സ്റ്റേറ്റുകളെക്കുറിച്ചും വ്യക്തമായി ചിന്തിക്കാൻ ഇത് നിങ്ങളെ പ്രോത്സാഹിപ്പിക്കുന്നു, അവ്യക്തത ഇല്ലാതാക്കുകയും നിങ്ങളുടെ കോഡിൻ്റെ ലോജിക് വ്യക്തമാക്കുകയും ചെയ്യുന്നു. ഇന്ന് തന്നെ ഡിസ്ക്രിമിനേറ്റഡ് യൂണിയനുകൾ ഉപയോഗിച്ച് നിങ്ങളുടെ ഡൊമെയ്ൻ മോഡൽ ചെയ്യാൻ ആരംഭിക്കുക, ബഗ്ഗുകളില്ലാത്ത സോഫ്റ്റ്വെയർ നിർമ്മിക്കുന്നതിൽ TypeScript കമ്പൈലർ നിങ്ങളുടെ അക്ഷീണനായ പങ്കാളിയാകട്ടെ.