1
votes

L'intégration interne `try` dans` fixed-point`

Je lis le point fixe de SICP:

#+begin_src emacs-lisp :session sicp :lexical t
(defvar tolerance 0.00001)
(defun fixed-point(f guess)
  (defun close-enoughp(v1 v2) ;
    (< (abs (- v1 v2)) tolerance))

  (let ((next (funcall f guess)))
    (if (close-enoughp guess next)
        next
      (fixed-point f next)))
  )
;;(trace-function #'fixed-point)
(fixed-point #'cos 1.0)
#+end_src

#+RESULTS:
: 0.7390822985224024

Dans le cas ci-dessus, j'ai appris qu'une nature de tandis que est le concept abstrait "essayer"

#+begin_src ipython :session sicp :results output :tangle pySrc/sicp_fixedpoint.py
import math
tolerance = 0.00001

def fixed_point(f, guess):
    def good_enoughp(a, b):
        return abs(a-b) < tolerance

    nex = f(guess)
    if good_enoughp(guess, nex):
        return nex
    else:
        return fixed_point(f, nex)    

print(fixed_point(math.cos, 1))
#+end_src

#+RESULTS:
: 0.7390822985224024

Donc je pourrais écrire une itération en python juste avec la pensée d'abstraction fonctionnelle efficace.

Quand réfléchir sur try , plus que "try is a while in iteration", qu'est-ce que ça m'apprend?

Il pourrait être recadré sans try , mais renvoie return fixed_point (f, nex) directement.

#+begin_src ipython :session sicp :results output pySrc/sicp_fixedpoint2.py
import math

def fixed_point(f, guess):
    while True:
        nex = f(guess)
        if abs(guess-nex) < 0.0001:
            return nex
        else:
            guess = nex #local assignment is nature of lambda
print(fixed_point(math.cos, 1))
#+end_src

#+RESULTS:
: 0.7390547907469174

Alors pourquoi SICP a introduit try ici, je suppose que l'efficacité n'est peut-être pas la principale considération de l'auteur .

Test avec elisp

#+begin_src emacs-lisp :session sicp :lexical t
(defvar tolerance 0.00001)
(defun fixed-point(f first-guess)
  (defun close-enoughp(v1 v2) 
    (< (abs (- v1 v2)) tolerance))
  (defun try(guess) ;;
    (let ((next (funcall f guess)))
      (if (close-enoughp guess next)
          next
          (try next))))
  (try first-guess))
(fixed-point #'cos 1.0)
#+end_src

#+RESULTS:
: 0.7390822985224024

Cela fonctionne comme prévu.

Il semble que return point fixe f suivant soit un peu plus propre qu'une itération interne avec try.

Quelle est la considération de SICP ici, qu'est-ce qui était destiné à enseigner?


0 commentaires

3 Réponses :


1
votes

C'est le contraire: c'est plus propre et plus efficace avec try car il n'a pas besoin de redéfinir le good-assez-p .

(également, vous n'êtes pas censé utiliser la récursivité en Python).


La version avec try est meilleure que la version qui appelle la fonction principale, point fixe , car point fixe contient des définitions internes , des fonctions good-assez-p et try . Un compilateur simple d'esprit le compilerait de sorte qu'à chaque appel, il répète ces définitions, encore et encore, à chaque appel. Avec try il n'y a pas de problème car il est déjà à l'intérieur de l'environnement interne du point fixe good-assez-p < / code> est déjà défini, et donc try peut simplement s'exécuter.

(correction / clarification: ce qui précède traite votre code comme s'il s'agissait de Scheme, avec des define internes au lieu du Common Lisp avec des defun comme vous show. SICP est Scheme, après tout. En Common Lisp / ELisp, il n'y a même pas de question - les defun internes seront toujours exécutés, à chaque appel à la fonction englobante, juste (re) définissant les mêmes fonctions au plus haut niveau encore et encore.)

Soit dit en passant, j'aime bien votre traduction de boucle Python, elle est une traduction textuelle de la boucle récursive de queue du Scheme, un à un.

Votre traduction while est exactement ce qu'un compilateur Scheme est censé faire étant donné le premier code Scheme récursif de queue dans votre question. Les deux sont exactement les mêmes, jusqu'à "horrible while True ... avec un échappement" que, personnellement, j'apprécie assez pour son immédiateté et sa clarté. Cela signifie que je n'ai pas besoin de savoir quelle valeur est attribuée à quelle variable et quelle variable est renvoyée à la fin - au lieu de cela, une valeur est simplement renvoyée, comme dans Scheme.


2 commentaires

Oui, pas pris en charge par python, cela pourrait donc prendre du temps pour essayer de compenser le manque d'esprit. Je ne comprends pas votre idée "pas besoin de redéfinir assez bien"


Je préfère "while True ...", car les branches de condition suivantes sont équilibrées avec "if and else" plutôt qu'une branche singulière. Lorsqu'il s'agit d'une branche singulière, je ne suis pas sûr de son exhaustivité lors de la deuxième lecture.



1
votes

La manière naturelle d'écrire quelque chose comme ça en Python est quelque chose comme ça, je pense:

> (fixed-point cos 0)
0.7390822985224023
> (fixed-point cos 0 #:tolerance 0.1)
0.7013687736227565

Ceci:

  • utilise une boucle while plutôt qu'une récursivité comme cela est naturel en Python;
  • n'utilise pas d'horrible tandis que True ... avec un échappement qui est juste déroutant.

(En fait, puisque l'appel de fonction en Python est généralement très lent, il est probablement plus naturel d'ouvrir-coder l'appel à close_enough et de supprimer complètement la fonction locale.)

Mais c'est du code impératif: il est plein d'affectations (les deux premières 'affectations' sont en réalité des liaisons de variables car Python ne distingue pas les deux syntaxiquement, mais les affectations ultérieures sont vraiment des affectations). Nous voulons exprimer cela d'une manière qui n'a pas de mission. Nous voulons également le remplacer par quelque chose qui n'utilise aucune construction en boucle ou qui exprime ces constructions en boucle en termes d'appels de fonction.

Nous pouvons le faire de deux manières:

  • nous pouvons traiter la fonction de niveau supérieur comme ce que nous appelons récursivement;
  • nous pouvons définir une fonction locale à travers laquelle nous récurons.

Laquelle de ces choses que nous faisons est vraiment un choix, et dans ce cas, cela fait probablement peu de différence. Cependant, la deuxième approche présente souvent des avantages significatifs: en général, la fonction de niveau supérieur (la fonction qui se trouve dans une interface que nous pourrions exposer à des personnes) peut avoir toutes sortes d'arguments supplémentaires, dont certains peuvent avoir des valeurs par défaut, etc. on, que nous ne voulons vraiment pas avoir à continuer de passer par les appels ultérieurs; la fonction de niveau supérieur peut également ne pas avoir du tout une signature d'argument appropriée car les étapes itératives peuvent être itératives sur un ensemble de valeurs dérivées des arguments de la fonction de niveau supérieur.

Donc, il est généralement préférable d'exprimer l'itération en termes de fonction locale bien que ce ne soit pas toujours le cas.

Voici une version récursive en Python qui en profite pour faire également la signature de la fonction de premier niveau visiblement plus riche. Notez que cette approche serait un style terrible en Python puisque Python ne fait rien de spécial avec les appels tail. Le code est également jonché de return car Python n'est pas un langage d'expression (ne croyez pas les gens qui disent 'Python est comme Lisp': ce n'est pas le cas):

(define (fixed-point f initial-guess #:tolerance (tolerance default-tolerance))
  (define (close-enough? v1 v2)
    (< (abs (- v1 v2)) tolerance))
  (let try ((guess initial-guess) (next (f initial-guess)))
    (if (close-enough? guess next)
        next
        (try next (f next)))))


8 commentaires

Parce que j'essaye généralement d'écrire des extensions en elisp pour mes Emacs. Scheme ne semble pas avoir d'applications pratiques à l'exclusion des experts des tutoriels.


@Algebra: eh bien, dans ce cas, je recommanderais d'apprendre elisp, pas Scheme. Ce que vous faites, c'est comme apprendre Pascal quand vous voulez apprendre C.


Non, je n'apprends pas un langage avec SICP, mais je perfectionne mes compétences en programmation et je construis des bases solides.


@Algebra: le problème est que si vous écrivez elisp (ou d'ailleurs Common Lisp) comme vous le feriez pour Scheme, vous finissez par écrire du code catastrophiquement unidiomatique et souvent tout simplement faux . Dans elisp, vous avez dans la question les defun 'locaux' ne sont pas, en fait, du tout locaux, par exemple. Scheme et elisp n'ont pas d'ancêtre commun de ce côté de 1970.


Vous avez votre idée, se concentrera sur Scheme du chapitre deux, merci.


Je j'aime le "horrible while True ... with escape". :)


Ouais. :) :) J'aime même prog aussi. (ne veut pas dire que je l'utiliserais dans un code de production à moins que cela ne soit absolument nécessaire. Je l'aime juste) --- btw j'ai expliqué mes raisons dans ma réponse. et je me fiche vraiment de savoir si une langue a une construction prête pour moi, ou je dois utiliser un petit extrait de code pour obtenir exactement le même effet.


s / s'en fiche / ne me dérange pas. (votre commentaire a disparu; si quelqu'un l'a signalé, ce n'était pas moi. :) En fait, je l'ai trouvé très drôle, de bonne humeur.)



1
votes

Le schéma permet de redéfinir les symboles de niveau supérieur, tels que point fixe ; même la fonction f pourrait le redéfinir! Les compilateurs (et les interprètes) doivent prendre cela en considération, et vérifier une redéfinition de chaque appel de point fixe . En revanche, try n'est pas visible en dehors de la définition de fixed-point , donc f ne peut pas le redéfinir. Ainsi, le compilateur (ou l'interpréteur) peut transformer cette fonction récursive de queue en une boucle.


0 commentaires