Maison > interface Web > js tutoriel > Repenser JavaScript. Application partielle, transparence référentielle et opérations paresseuses

Repenser JavaScript. Application partielle, transparence référentielle et opérations paresseuses

Susan Sarandon
Libérer: 2024-12-28 17:34:38
original
410 Les gens l'ont consulté

Rethinking JavaScript. Partial Application, Referential Transparency, and Lazy Operations

Salut les amis ! Il y a quelque temps, en parcourant les dernières propositions du TC39, je suis tombé sur une proposition qui m'a enthousiasmé – et un peu sceptique. Il s'agit d'une syntaxe d'application partielle pour JavaScript. À première vue, cela semble être la solution parfaite à de nombreux problèmes de codage courants, mais en y réfléchissant, j'ai réalisé qu'il y avait à la fois beaucoup de choses à aimer et des possibilités d'amélioration.  

Mieux encore, ces préoccupations ont donné naissance à une toute nouvelle idée qui pourrait rendre JavaScript encore plus puissant. Laissez-moi vous emmener dans ce voyage, complété par des exemples réalistes de la façon dont ces fonctionnalités pourraient changer notre façon de coder chaque jour.

TLDR : l'article provient de mon ancien numéro à la proposition : https://github.com/tc39/proposal-partial-application/issues/53


La proposition

L'application partielle vous permet de « prédéfinir » certains arguments d'une fonction, renvoyant une nouvelle fonction pour une utilisation ultérieure. Notre code actuel ressemble à ceci :

const fetchWithAuth = (path: string) => fetch(
  { headers: { Authorization: "Bearer token" } },
  path,
);
fetchWithAuth("/users");
fetchWithAuth("/posts");
Copier après la connexion
Copier après la connexion

La proposition introduit une syntaxe ~() pour cela :

const fetchWithAuth = fetch~({ headers: { Authorization: "Bearer token" } }, ?);
fetchWithAuth("/users");
fetchWithAuth("/posts");
Copier après la connexion
Copier après la connexion

Vous voyez ce qui se passe ? La fonction fetchWithAuth pré-remplit l'argument des en-têtes, vous n'avez donc qu'à fournir l'URL. C'est comme .bind() mais plus flexible et plus facile à lire.

La proposition vous permet également d'utiliser ? comme espace réservé pour les arguments non remplis et ... pour un paramètre de repos. Par exemple :

const sendEmail = send~(user.email, ?, ...);
sendEmail("Welcome!", "Hello and thanks for signing up!");
sendEmail("Reminder", "Don't forget to confirm your email.");
Copier après la connexion
Copier après la connexion

Ce que je préfère, c'est que je n'ai pas besoin de dupliquer les annotations de type !

Cela semble utile, non ? Mais il y a bien plus à déballer.


Les arguments en faveur de la transparence référentielle

Commençons par un problème pratique : fermetures de fonctions et références de variables obsolètes.

Disons que vous planifiez une notification. Vous pourriez écrire quelque chose comme ceci :

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(() => alert(state.data), 1000)
  }
}
Copier après la connexion
Copier après la connexion

Avez-vous déjà vu le problème ? La propriété « data » peut changer pendant le délai d'attente et l'alerte n'affichera rien ! Pour résoudre ce problème, il faut transmettre explicitement la référence de valeur. Espérons que "setTimeout" accepte des arguments supplémentaires pour la transmettre dans le rappel :

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout((data) => alert(data), 1000, state.data)
  }
}
Copier après la connexion
Copier après la connexion

Pas mal, mais ce n'est pas largement pris en charge par les API. Une application partielle pourrait rendre ce modèle beaucoup plus universel :

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert~(state.data), 1000)
  }
}
Copier après la connexion
Copier après la connexion

En verrouillant state.data au moment de la création de la fonction, nous évitons les bugs inattendus dus à des références obsolètes.


Réduire les calculs répétés

Un autre avantage pratique de l'application partielle est l'élimination du travail redondant lors du traitement de grands ensembles de données.

Par exemple, vous disposez d'une logique de mappage, qui doit calculer des données supplémentaires pour chaque étape d'itération :

const fetchWithAuth = (path: string) => fetch(
  { headers: { Authorization: "Bearer token" } },
  path,
);
fetchWithAuth("/users");
fetchWithAuth("/posts");
Copier après la connexion
Copier après la connexion

Le problème vient de l'accès proxy à this.some.another, c'est assez lourd pour appeler chaque étape d'itération. Il serait préférable de refactoriser ce code comme ceci :

const fetchWithAuth = fetch~({ headers: { Authorization: "Bearer token" } }, ?);
fetchWithAuth("/users");
fetchWithAuth("/posts");
Copier après la connexion
Copier après la connexion

Avec une application partielle, nous pouvons le faire de manière moins verbeuse :

const sendEmail = send~(user.email, ?, ...);
sendEmail("Welcome!", "Hello and thanks for signing up!");
sendEmail("Reminder", "Don't forget to confirm your email.");
Copier après la connexion
Copier après la connexion

En intégrant des calculs partagés, vous rendez le code plus concis et plus facile à suivre, sans sacrifier les performances.


Pourquoi ajouter une nouvelle syntaxe ?

Maintenant, c’est ici que j’ai commencé à me gratter la tête. Si la syntaxe proposée est élégante, JavaScript possède déjà de nombreux opérateurs. Surtout les opérateurs de point d'interrogation ?. L'ajout de ~() pourrait rendre le langage plus difficile à apprendre et à analyser.

Et si nous pouvions obtenir la même fonctionnalité sans introduire de nouvelle syntaxe ?


Une alternative basée sur la méthode

Imaginez étendre Function.prototype avec une méthode tie :

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(() => alert(state.data), 1000)
  }
}
Copier après la connexion
Copier après la connexion

C'est un peu plus verbeux mais évite d'introduire un tout nouvel opérateur. En utilisant un symbole spécial supplémentaire pour les espaces réservés, nous pouvons remplacer le point d'interrogation.

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout((data) => alert(data), 1000, state.data)
  }
}
Copier après la connexion
Copier après la connexion

Il polypile parfaitement sans complexité supplémentaire de construction !

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert~(state.data), 1000)
  }
}
Copier après la connexion
Copier après la connexion

Mais ce n’est que la pointe de l’iceberg. cela rend le concept d'espace réservé réutilisable dans différentes API.


Opérations paresseuses : aller plus loin

C’est ici que les choses deviennent vraiment intéressantes. Et si nous élargissions le concept des symboles pour permettre des opérations paresseuses ?

Exemple 1 : combiner .filter() et .map()

Supposons que vous traitiez une liste de produits pour un site de commerce électronique. Vous souhaitez afficher uniquement les articles en promotion, avec leurs prix arrondis. Normalement, vous écririez ceci :

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    return this.list.map((el) => computeElement(el, this.some.another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
Copier après la connexion

Mais cela nécessite de parcourir le tableau deux fois. Avec des opérations paresseuses, nous pourrions combiner les deux étapes en une seule passe :

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    const { another } = this.some
    return this.list.map((el) => computeElement(el, another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
Copier après la connexion

Le Symbol.skip indique au moteur d'exclure les éléments du tableau final, ce qui rend l'opération à la fois efficace et expressive !

Exemple 2 : résiliation anticipée dans .reduce()

Imaginez calculer le revenu total des cinq premières ventes. Normalement, vous utiliseriez un conditionnel à l'intérieur de .reduce() :

class Store  {
  data: { list: [], some: { another: 42 } }
  get computedList() {
    return this.list.map(computeElement~(?, this.some.another))
  }
  contructor() {
    makeAutoObservable(this)
  }
}
Copier après la connexion

Cela fonctionne, mais il traite toujours chaque élément du tableau. Avec des réductions paresseuses, nous pourrions signaler une résiliation anticipée :

function notify(state: { data?: Data }) {
  if (state.data) {
      setTimeout(alert.tie(state.data), 1000)
  }
}
Copier après la connexion

La présence de Symbol.skip pourrait indiquer au moteur d'arrêter d'itérer dès que la condition est remplie, économisant ainsi de précieux cycles.


Pourquoi c'est important

Ces idées – application partielle, transparence référentielle et opérations paresseuses – ne sont pas que des concepts académiques. Ils résolvent des problèmes du monde réel :

  • Utilisation de l'API plus propre : Verrouillez les arguments dès le départ et évitez les références obsolètes.
  • Performances améliorées : Éliminez les calculs redondants et activez des itérations plus efficaces.
  • Une plus grande expressivité : Écrivez un code concis et déclaratif, plus facile à lire et à maintenir.

Que nous nous en tenions à ~() ou que nous explorions des alternatives comme tie et Symbol.skip, les principes sous-jacents ont un énorme potentiel pour améliorer la façon dont nous écrivons JavaScript.

Je vote pour l'approche symbolique car elle est facile à polyfill et a diverses utilisations.


Quelle est la prochaine étape ?

Je suis curieux : qu’en pensez-vous ? Est-ce que ~() est la bonne direction ou devrions-nous explorer des approches basées sur des méthodes ? Et quel serait l'impact des opérations paresseuses sur votre flux de travail ? Discutons-en dans les commentaires !

La beauté de JavaScript réside dans son évolution pilotée par la communauté. En partageant et en débattant d’idées, nous pouvons façonner un langage qui fonctionne mieux pour tout le monde. Poursuivons la conversation !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal