Quels sont les pièges potentiels de l'activation de la liaison lexicale pour un tampon?

12

Cela a été inspiré par la discussion sur lexical-binding vs lexical-let dans cette question . Comme la liaison lexicale vous donne la possibilité d'avoir des fermetures utiles auxquelles les gens peuvent être habitués dans d'autres langues comme JavaScript, pourquoi ne pas l'activer tout le temps?

En supposant que la rétrocompatibilité avec les anciens Emacsen ne pose pas de problème, quels pièges devriez-vous rechercher si vous l'activez dans les tampons de code hérités?

stsquad
la source

Réponses:

13

Un écueil majeur est que la sémantique de liaison pour les variables non définies - c'est-à-dire les variables non définies avec defvaret amis - change avec lexical-binding: Sans elle, letlie tout dynamiquement, mais avec les lexical-bindingvariables non définies activées sont liées lexicalement , et même élides complètement si inutilisées dans la portée lexicale actuelle .

L'ancien code repose parfois sur cela. Pour éviter les dépendances matérielles pour les fonctionnalités facultatives, il lierait les variables dynamiques sans nécessiter la bibliothèque correspondante ou déclarer la variable elle-même:

(let ((cook-eggs-enabled t))
  (cook-my-meal))

Si la fonction de cuisson est facultative, nous ne voulons pas forcer des dépendances inutiles sur l'utilisateur, nous n'utilisons donc pas (require 'cook)et nous nous appuyons plutôt sur le chargement automatique de la cook-my-mealfonction.

Il est évident pour le lecteur humain qu'il cook-eggs-enabledne s'agit pas d'une variable locale, mais se réfère toujours à une variable dynamique globale de la cookbibliothèque ici. Sans lexical-bindingce code fonctionne comme prévu: cook-eggs-enabledest lié dynamiquement, qu'il soit défini ou non.

Avec lexical-bindingcependant, il se casse: cook-eggs-enabledest maintenant lié lexicalement (puis optimisé loin, parce qu'il est pas utilisé), de sorte que la variable globale dynamique cook-eggs-enabledest pas toujours touché du tout et encore nilpar le temps cook-my-mealest appelé, donc nous étonnamment pas des œufs dans notre repas.

Heureusement, ces problèmes sont très faciles à repérer : le compilateur d'octets vous avertit naturellement d'une liaison lexicale inutilisée ici.

La solution est simple: ajoutez un (require 'cook)(pour les fonctionnalités qui ne sont pas vraiment facultatives de toute façon) ou, pour éviter les dépendances matérielles, déclarez la variable comme variable dynamique dans votre propre code . Il existe un defvarformulaire spécial pour cela:

(defvar cook-eggs-enabled)

Cela définit cook-eggs-enabledcomme variable dynamique, mais n'affecte pas la docstring, le load-history(et donc find-variableet amis) ou quoi que ce soit d'autre, sauf la nature contraignante de la variable.

lunaryorn
la source
Dans le cas dynamique, cet extrait de code ne provoquerait-il cook-eggs-enabledpas une dissociation à la letfin? Je suis presque sûr d'avoir rencontré un bug comme celui-ci auparavant. Le defvar se produisait à l'intérieur du let, et le letdernier a restauré la variable à son état initial (vide).
Malabarba
1
@Malababa Non, c'est une situation différente. Voir le dernier paragraphe de Définition des variables pour la raison.
lunaryorn