3
votes

Quelle est la manière standard de définir les fermetures globales dans le schéma?

Je veux donc savoir s'il existe un moyen standard d'avoir un code comme celui-ci:

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

Je connais:

(let ((x 10))
  (macro x))

mais cela ne fonctionnera pas si je veux définir plusieurs fonctions, j'ai besoin de connaître la méthode standard pour pouvoir écrire une macro qui définira des fonctions. où vous pouvez appeler la macro à l'intérieur let:

(define add10 (let ((x 10))
                 (lambda (a) (+ x a))))

et par exemple la macro créera une liste de fonctions:

(let ((x 10))
  (define (add10 a)
     (+ x a)))

Y a-t-il manière standard de définir les fonctions add1..add3? Dans le schéma que je testais, les fonctions seront locales à l'intérieur de let et non accessibles à l'extérieur.

Si vous affichez le code de macro, je ne suis intéressé que par les macros lisp avec define -macro et quasiquote , s'il vous plaît pas de define-syntax , car c'est principalement pour une utilisation dans mon propre lisp (basé sur un schéma) où je n'ai que des macros lisp .

Si le schéma ne prend pas en charge quelque chose comme ça, est-ce qu'un autre dialecte comme Common Lisp autorise quelque chose comme ça?


0 commentaires

3 Réponses :


3
votes
(let ((x 10))
  ()
  (define (add1) (+ x 1)))

13 commentaires

Le dernier paragraphe de clhs.lisp.se/Body/s_eval_w.htm dit que laissez over eval-when pourrait être utile pour defun / defmacro, alors que la description dit que les effets secondaires à la compilation ne se produisent que lorsque eval-when est un formulaire de premier niveau. Peut-être qu'il y a un moyen d'atteindre ce que OP veut en enveloppant le tout dans eval-when (et je suis d'accord que c'est un mauvais style, btw)


La fonction étendue aura (+ x 1) ou (+ 10 1) ce dont j'ai besoin est (+ x 1) . J'essaie de simplifier le problème réel que j'ai, où j'ai un objet qui doit être en fermeture non évalué et mis dans le code lisp, car il ne peut pas avoir de représentation lèvres comme le numéro 10.


@jcubic: la fonction développée a x .


Ce dernier exemple fonctionne dans certains schémas et pas dans d'autres, je pense que cela fonctionnait dans la guile précédente ou dans Kawa (schéma en Java).


Pouvez-vous expliquer ce que cette liste vide fait dans le schéma? Cela semble fonctionner dans biwascheme mais je ne sais pas pourquoi. où sans liste vide si ne fonctionne pas.


@jcubic: la liste vide correspond à la liste vide. ;-)


mais quelle est la différence si vous l'avez et ne l'avez pas. vérifiez biwascheme.org si vous l'utilisez add1 est défini globalement et sans lui, il est local. C'est bizarre cela fonctionne (let ((x 10)) 1 (define (add2) (+ x 2)))


C'est bizarre, c'est peut-être un bogue dans l'implémentation, est-ce dans la spécification du schéma?


C'est dans la spécification Scheme. «Les définitions peuvent apparaître au début d'un« corps ». Maintenant, ce qui pourrait ne pas être dans la spécification: que faire avec les DEFINE non locaux de niveau supérieur à l'intérieur d'un LET.


Cela ne fonctionne pas par ruse, lancez une erreur que la définition est dans un contexte d'expression où ils ne sont pas autorisés.


C'est pourquoi j'ai écrit «pourrait». Cela pourrait être une chose spécifique à la mise en œuvre. On peut également vérifier si une implémentation a d'autres moyens de créer une variable de niveau supérieur.


() n'est pas une syntaxe de schéma valide et bien sûr define doit être au début d'un corps lambda. Le dernier code est donc en violation des rapports Scheme et est un comportement indéfini et non portable comme la mutation des listes citées dans Scheme et CL. Cela pourrait fonctionner, cela pourrait ne pas fonctionner, cela pourrait signaler une erreur.


@Sylwester ne l'est pas, et c'est le cas. :) Cela ne fonctionne pas, et cela signale une erreur - dans Racket, à la fois sous #lang r5rs et #lang racket que j'ai vérifié.



3
votes

Je pense qu'aucune solution qui encapsule une liaison autour de define ne peut fonctionner du tout de manière portative ou sûre, car les define enveloppés construiront des liaisons locales (formes principales dans le corps) ou être illégale (formes non principales dans le corps), même si je serais heureux qu'une personne aux normes de Scheme indique où je me trompe.

Au lieu de cela, quelque chose comme ce hack légèrement méchant devrait fonctionner Je pense.

> (define-list (inc dec)
    (let ([i 0])
      (list
       (lambda ()
         (set! i (+ i 1))
         i)
       (lambda ()
         (set! i (- i 1))
         i))))
> inc
#<procedure>
> (inc)
1
> (dec)
0
> (dec)
-1
> 

Ici, je me suis appuyé sur une constante appelée undefined qui signifie «pas encore défini correctement»: Racket fournit cela dans raquette / non défini mais cela peut en général être n'importe quoi: vous pourriez juste, quelque part, dire

(require compatibility/defmacro
         racket/undefined)

(define-macro (define-list names expr)
  `(begin
     ,@(let loop ([ntail names] [defs '()])
         (if (null? ntail)
             (reverse defs)
             (loop (cdr ntail)
                   (cons `(define ,(car ntail) undefined) defs))))
     (let ([results ,expr])
       (unless (= (length results)
                  (length ',names))
         (error "wrong number of values"))
       ,@(let loop ([ntail names] [i 0] [assignments '()])
           (if (null? ntail)
               (reverse assignments)
               (loop (cdr ntail) (+ i 1)
                     (cons `(set! ,(car ntail) (list-ref results ,i))
                           assignments)))))))

par exemple.

L'astuce consiste à définir les choses que vous voulez au plus haut niveau avec des valeurs d'espace réservé, puis à leur attribuer dans le let.

Je suis sûr qu'une macro peut être définie qui se développe en quelque chose comme (c'est pourquoi j'ai le tout dans un begin ): je ne l'ai pas fait parce que c'est fi ddly et j'utilise Racket donc je ne peux pas facilement y écrire des macros Lisp à l'ancienne.


Notez que l'approche évidente dans un schéma moderne est d'utiliser define-values ​​ code>:

(require racket/undefined)

(define-syntax define-list
  (syntax-rules ()
    [(define-list () expr)
     (let ((results expr))
       (unless (zero? (length results))
         (error "need an empty list"))
       (void))]
    [(define-list (name ...) expr)
     (begin
       (define name undefined)
       ...
       (let ([results expr])
         (unless (= (length results)
                    (length '(name ...)))
           (error "wrong number of values"))
         (set! name (begin0
                      (car results)
                      (set! results (cdr results))))
         ...))]))

Fait ce que vous voulez. Comme mentionné dans une autre réponse, vous pouvez implémenter define-values ​​ en tant que macro si vous n'avez que plusieurs valeurs. Mais si vous n'avez pas du tout plusieurs valeurs, vous pouvez utiliser quelque chose qui définit les choses en fonction d'une liste de résultats:

(define-list (x y) (let (...) (list ...)))

Ici sont deux variantes approximatives de cette macro: la première utilise les macros natives de Racket:

(define-values (x y) (let (...) (values ...)))

tandis que la seconde utilise des macros non hygiéniques dans Racket:

(define undefined 'undefined)


2 commentaires

N'est-il pas étonnant que l'on ne puisse pas définir une variable globale depuis l'intérieur d'un LET selon le standard? Est-ce une fonctionnalité inhabituelle?


@RainerJoswig Eh bien, je déteste l'équivalent CL (let (...) (defun ...) ...) , donc je suis partial. Si nous sommes autorisés à Racket, alors (define-values ​​(...) (let (...) ... (values ​​...))) fonctionne bien, et je pense que define-values ​​ peut être implémenté comme une macro, probablement.



3
votes

Dans R7RS, le dernier rapport Scheme, nous avons define-values ​​. Il peut être utilisé de cette manière:

(define-values (add1 add2 add3)
  (let ((x 10))
    (values (lambda () (+ x 1))
            (lambda () (+ x 2))
            (lambda () (+ x 3)))))

Bien sûr, pour des blocs plus grands, on peut vouloir faire une définition locale et la référencer dans valeurs à la place.

Dans le rapport R7RS, vous trouverez une règle de syntaxe pour define-values ​​ qui fonctionnera pour R6RS et R5RS. Il utilise call-with-values ​​ où les valeurs sont passées à list puis définissent à partir de cela. Je parie que cela fonctionne aussi à l'intérieur de lambdas , car l'implémentation du schéma peut en fait transformer cela en un letrec donc même s'il n'est pas très élégant, il fait le sale boulot.


3 commentaires

C'est cool: j'essayais de déterminer si define-values ​​ pouvait être écrit comme une macro, et il s'avère que c'est possible. Merci!


Est-ce que define-values ​​ peut être utilisé dans let, ou est-ce la même chose qu'avec define, let et lambda unique? Si c'est la même chose, alors il est utilisé avec le cas où let est en dehors de la macro qui génère des fonctions.


@jcubic cela fonctionne de la même manière que define . si vous faites (let () (définir le nom ...)) alors nom devient local probablement transformé en un letrec . Ainsi, vous laissez doit entrer dans l'expression comme je l'ai démontré. define-values ​​ est également utile lorsqu'il s'agit de procédures qui renvoient plusieurs valeurs, par exemple. (define-values ​​(q r) (quotient-and-reste 15 7)) et ceci est un cas d'utilisation pour les liaisons locales.