84 votes

LAISSEZ versus LET*, en Common Lisp

Je comprends la différence entre LET et LET* (parallèle rapport séquentiel de liaison), et comme une question théorique, c'est logique. Mais est-il des cas où vous n'avez jamais vraiment besoin de le LAISSER? Dans l'ensemble de mon code Lisp que j'ai regardé récemment, vous pouvez remplacer tous les LAISSER à LAISSER* pas de changement.

Edit: OK, je comprends pourquoi certains gars inventé LET*, vraisemblablement sous forme d'une macro, le chemin du retour quand. Ma question est, étant donné que LET* existe, est-il une raison pour LAISSER à y rester? Avez-vous écrit tout réel du code Lisp où un LET* ne marcherait pas aussi bien comme une plaine LAISSER?

Je n'achète pas l'efficacité d'un tel argument. Tout d'abord, en reconnaissant les cas où LET* peut être compilé dans quelque chose d'aussi efficace que juste ne semble pas si difficile que ça. Deuxièmement, il y a beaucoup de choses dans la CL spec qui tout simplement ne semblent pas comme ils ont été conçus autour de l'efficacité à tous. (Quand la dernière fois vous avez vu une BOUCLE avec les déclarations de type? Celles-ci sont si dur à comprendre, je n'ai jamais vu utilisé.) Avant de Bite Gabriel repères de la fin des années 1980, CL est franchement lent.

Il ressemble à ce qui est un autre cas de rétro-compatibilité: bon escient, personne ne voulait prendre le risque de casser quelque chose d'aussi fondamental que de LAISSER. Ce qui était mon intuition, mais il est réconfortant de constater que personne n'a une stupidement simple cas j'ai été absent où LAISSER fait un tas de choses ridiculement plus facile que de LAISSER*.

90voto

Rainer Joswig Points 62532

LAISSEZ lui-même n'est pas une primitive réel, dans un langage fonctionnel, car il peut remplacer avec LAMBDA. Comme ceci:

(let ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

est similaire à

((lambda (a1 a2 ... an)
   (some-code a1 a2 ... an))
 b1 b2 ... bn)

Mais

(let* ((a1 b1) (a2 b2) ... (an bn))
  (some-code a1 a2 ... an))

est similaire à

((lambda (a1)
    ((lambda (a2)
       ...
       ((lambda (an)
          (some-code a1 a2 ... an))
        bn))
      b2))
   b1)

Vous pouvez imaginer ce qui est le plus simple chose. LAISSER et ne pas LAISSER*.

LAISSEZ rend le code de la compréhension plus facile. On voit un tas de liaisons et on peut lire chaque liaison à l'unité, sans la nécessité de comprendre les haut-bas/gauche-droite des "effets" (rebindings). Avec LET* les signaux pour le programmeur (celui qui lit le code) que les liaisons ne sont pas indépendants, mais il y a une sorte de top-down flow - ce qui complique les choses.

Common Lisp a la règle que les valeurs pour les liaisons en location sont calculés de gauche à droite. Juste comment les valeurs d'un appel de fonction sont évalués de gauche à droite. Alors, allons-est conceptuellement plus simple déclaration, et il doit être utilisé par défaut.

Types dans la BOUCLE? Sont utilisés très souvent. Il y a quelques formes primitives de type de déclaration qui sont faciles à retenir. Exemple:

(LOOP FOR i FIXNUM BELOW (TRUNCATE n 2) do (something i))

Ci-dessus, déclare la variable i pour être un fixnum.

Gabriel a publié son livre sur des points de référence, en 1985, et à l'époque, ces points de référence ont également été utilisés avec des non-CL Lisps. Common Lisp elle-même était toute neuve en 1985 - le CLtL1 livre qui décrit la langue a été publié en 1984. Pas étonnant que les implémentations ne sont pas très optimisé à l'époque. Les optimisations mises en œuvre ont été essentiellement le même (ou moins) que les implémentations avant d'eu (comme MacLisp).

Mais pour LAISSER vs LET* la principale différence est que le code à l'aide de LET est plus facile à comprendre pour les humains, depuis la liaison clauses sont indépendants les uns des autres - surtout depuis qu'il est mauvais style pour tirer parti de la gauche vers la droite de l'évaluation (pas de la définition de variables comme un effet secondaire).

38voto

Mr Fooz Points 21092

Vous n'avez pas besoin de le LAISSER, mais, normalement, vous voulez .

LAISSE penser que vous êtes juste faire des parallèles standard de liaison avec rien de difficile en cours. LAISSEZ - * induit des restrictions sur le compilateur et l'indique à l'utilisateur qu'il ya une raison que séquentiel liaisons sont nécessaires. En termes de style, soit c'est mieux quand vous n'avez pas besoin de ces restrictions supplémentaires imposées par LET*.

Il peut être plus efficace d'utiliser les LAISSEZ que LET* (selon le compilateur, l'optimiseur, etc.):

  • en parallèle liaisons peuvent être exécutées en parallèle (mais je ne sais pas si tout LISP systèmes de réellement le faire, et l'init formulaires doivent encore être exécutées de manière séquentielle)
  • en parallèle liaisons de créer une seule nouvelle de l'environnement (scope) pour toutes les liaisons. Séquentielle liaisons de créer un nouveau imbriqués pour chaque liaison. En parallèle liaisons utilisent moins de mémoire et d'avoir plus rapidement variable de recherche.

(Ci-dessus bullet points s'appliquent à Système, un autre dialecte de LISP. clisp peuvent varier.)

29voto

Logan Capaldo Points 22145

Je viens de roulement des exemples inventées. Comparer le résultat de ceci:

(print (let ((c 1))
         (let ((c 2)
              (a (+ c 1)))
               a)))

avec le résultat de l'exécution de cette:

(print (let ((c 1))
         (let* ((c 2)
               (a (+ c 1)))
                a)))

11voto

David Thornley Points 39051

En LISP, il y a souvent un désir d'utiliser la plus faible possible des constructions. Certains guides de style vous dira d'utiliser = plutôt que d' eql quand vous savez que les éléments comparés sont numériques, par exemple. L'idée est souvent de préciser ce que tu veux dire plutôt que le programme de l'ordinateur de manière efficace.

Cependant, il peut y avoir de réelles améliorations de l'efficacité en disant seulement ce que vous entendez, et ne pas utiliser plus de constructions. Si vous avez des initialisations avec LET, ils peuvent être exécutées en parallèle, tout en LET* d'initialisation doivent être exécutées de manière séquentielle. Je ne sais pas si toutes les implémentations de réellement le faire, mais certains pourraient bien à l'avenir.

8voto

Attila Lendvai Points 1084

- je aller une étape plus loin et d'utiliser bind qui unifie laissez, let*, multiples-lier, déstructuration-lier, etc, et c'est encore extensible.

généralement j'aime bien les utiliser "le plus faible de construire", mais pas avec laisser & amis, parce qu'ils donnent juste le bruit du code (la subjectivité avertissement! inutile d'essayer de me convaincre du contraire...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X