web-dev-qa-db-fra.com

Quelle est la différence entre devis et liste?

Je sais que vous pouvez utiliser ' (aka quote) pour créer une liste, et je l'utilise tout le temps, comme ceci:

> (car '(1 2 3))
1

Mais cela ne fonctionne pas toujours comme je m'y attendais. Par exemple, j'ai essayé de créer une liste de fonctions, comme celle-ci, mais cela n'a pas fonctionné:

> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
  expected a procedure that can be applied to arguments
  given: '+

Lorsque j'utilise list, cela fonctionne:

> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)

Pourquoi? J'ai pensé ' n'était qu'un raccourci pratique, alors pourquoi le comportement est-il différent?

39
Alexis King

TL; DR: Ils sont différents; utilisez list en cas de doute.

Une règle d'or: utilisez list chaque fois que vous voulez que les arguments soient évalués; quote "distribue" sur ses arguments, donc '(+ 1 2) est comme (list '+ '1 '2). Vous vous retrouverez avec un symbole dans votre liste, pas une fonction.


Un examen approfondi de list et quote

Dans Scheme et Racket, quote et list sont des choses entièrement différentes , mais puisque les deux peuvent être utilisées pour produire des listes, la confusion est courante et compréhensible. Il y a une différence incroyablement importante entre eux: list est un simple vieux fonction, tandis que quote (même sans le spécial ' syntaxe) est un forme spéciale. Autrement dit, list peut être implémenté dans un schéma simple, mais quote ne peut pas l'être.

La fonction list

La fonction list est en fait de loin la plus simple des deux, alors commençons par là. Il s'agit d'une fonction qui accepte un nombre quelconque d'arguments et collecte les arguments dans une liste.

> (list 1 2 3)
(1 2 3)

Cet exemple ci-dessus peut être déroutant car le résultat est imprimé sous la forme d'une expression s quoteable, et il est vrai, dans ce cas, que les deux syntaxes sont équivalentes. Mais si nous devenons un peu plus compliqués, vous verrez que c'est différent:

> (list 1 (+ 1 1) (+ 1 1 1))
(1 2 3)
> '(1 (+ 1 1) (+ 1 1 1))
(1 (+ 1 1) (+ 1 1 1))

Que se passe-t-il dans l'exemple quote? Eh bien, nous en discuterons dans un instant, mais d'abord, jetez un œil à list. C’est juste une fonction ordinaire, donc elle suit la sémantique d’évaluation de Scheme standard: elle évalue chacun de ses arguments avant de passer à la fonction. Cela signifie que des expressions comme (+ 1 1) sera réduit à 2 avant qu'ils ne soient regroupés dans la liste.

Ce comportement est également visible lors de la fourniture de variables à la fonction de liste:

> (define x 42)
> (list x)
(42)
> '(x)
(x)

Avec list, le x est évalué avant d'être passé à list. Avec quote, les choses sont plus compliquées.

Enfin, comme list n'est qu'une fonction, elle peut être utilisée comme n'importe quelle autre fonction, y compris de manière plus élevée. Par exemple, il peut être transmis à la fonction map et fonctionnera de manière appropriée:

> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))

Le formulaire quote

Contrairement à list, la citation est une partie spéciale de Lisps. La forme quote est spéciale en partie parce qu'elle obtient une abréviation spéciale pour le lecteur, ', mais c'est aussi spécial même sans ça. Contrairement à list, quote n'est pas pas une fonction, et donc elle n'a pas besoin de se comporter comme telle - elle a règles propres.

Une brève discussion du code source LISP

Dans LISP, dont Scheme et Racket sont des dérivés, tout le code est en fait constitué de structures de données ordinaires. Par exemple, considérez l'expression suivante:

(+ 1 2)

Cette expression est en fait une liste , et elle comprend trois éléments:

  • les + symbole
  • le nombre 1
  • le nombre 2

Toutes ces valeurs sont des valeurs normales qui peuvent être créées par le programmeur. Il est vraiment facile de créer le 1 value car il s'évalue: il suffit de taper 1. Mais les symboles et les listes sont plus difficiles: par défaut, un symbole dans le code source fait une recherche de variable! Autrement dit, les symboles ne sont pas auto-évaluables :

> 1
1
> a
a: undefined
  cannot reference undefined identifier

En fin de compte, cependant, les symboles ne sont essentiellement que des chaînes, et en fait, nous pouvons les convertir entre eux:

> (string->symbol "a")
a

Les listes font encore plus que des symboles, car par défaut, une liste dans le code source appelle une fonction! Faire (+ 1 2) examine le premier élément de la liste, le +, recherche la fonction qui lui est associée et l'invoque avec le reste des éléments de la liste.

Parfois, cependant, vous souhaiterez peut-être désactiver ce comportement "spécial". Vous voudrez peut-être simplement obtenir la liste ou obtenir le symbole sans qu'il soit évalué. Pour ce faire, vous pouvez utiliser quote.

Le sens de la citation

Avec tout cela à l'esprit, ce que fait quote est assez évident: il "désactive" simplement le comportement d'évaluation spécial de l'expression qu'il encapsule. Par exemple, considérez quoteing un symbole:

> (quote a)
a

De même, considérez quoteing une liste:

> (quote (a b c))
(a b c)

Peu importe ce que vous donnez quote, il sera toujours, toujours crachez-le sur vous. Ni plus ni moins. Cela signifie que si vous lui donnez une liste, aucune des sous-expressions ne sera évaluée - ne vous attendez pas à ce qu'elles le soient! Si vous avez besoin d'une évaluation quelconque, utilisez list.

Maintenant, on pourrait se demander: que se passe-t-il si vous quote autre chose qu'un symbole ou une liste? Eh bien, la réponse est ... rien! Vous venez de le récupérer.

> (quote 1)
1
> (quote "abcd")
"abcd"

Cela a du sens, puisque quote crache toujours exactement ce que vous lui donnez. C'est pourquoi les "littéraux" comme les nombres et les chaînes sont parfois appelés "auto-citations" dans le langage LISP.

Encore une chose: que se passe-t-il si vous quote une expression contenant quote? Autrement dit, que faire si vous "doublez quote"?

> (quote (quote 3))
'3

Que s'est-il passé là-bas? Eh bien, rappelez-vous que ' n'est en fait qu'une abréviation directe de quote, donc rien de spécial n'est arrivé du tout! En fait, si votre schéma a un moyen de désactiver les abréviations lors de l'impression, il ressemblera à ceci:

> (quote (quote 3))
(quote 3)

Ne vous laissez pas berner par quote étant spécial: tout comme (quote (+ 1)), le résultat ici est juste une ancienne liste. En fait, nous pouvons retirer le premier élément de la liste: pouvez-vous deviner ce que ce sera?

> (car (quote (quote 3)))
quote

Si vous avez deviné 3, Vous avez tort. N'oubliez pas que quote désactive toutes les évaluations , et une expression contenant un symbole quote n'est encore qu'une simple liste. Jouez avec ceci dans le REPL jusqu'à ce que vous soyez à l'aise avec cela.

> (quote (quote (quote 3)))
''3
(quote (1 2 (quote 3)))
(1 2 '3)

La citation est incroyablement simple, mais elle peut sembler très complexe en raison de la façon dont elle tend à défier notre compréhension du modèle d'évaluation traditionnel. En fait, c'est déroutant parce que à quel point c'est simple: il n'y a pas de cas particuliers, il n'y a pas de règles. Il retourne exactement ce que vous lui donnez, exactement comme indiqué (d'où le nom "citation").


Annexe A: Quasiquotation

Donc, si le devis désactive complètement l'évaluation, à quoi sert-il? Eh bien, à part faire des listes de chaînes, de symboles ou de nombres qui sont tous connus à l'avance, pas grand-chose. Heureusement, le concept de quasiquotation fournit un moyen de sortir de la citation et de revenir à l'évaluation ordinaire.

Les bases sont super simples: au lieu d'utiliser quote, utilisez quasiquote. Normalement, cela fonctionne exactement comme quote dans tous les sens:

> (quasiquote 3)
3
> (quasiquote x)
x
> (quasiquote ((a b) (c d)))
((a b) (c d))

Ce qui rend quasiquote spécial, c'est qu'il reconnaît un symbole spécial, unquote. Partout où unquote apparaît dans la liste, il est alors remplacé par l'expression arbitraire qu'il contient:

> (quasiquote (1 2 (+ 1 2)))
(1 2 (+ 1 2))
> (quasiquote (1 2 (unquote (+ 1 2))))
(1 2 3)

Cela vous permet d'utiliser quasiquote pour construire des modèles de sortes qui ont des "trous" à remplir avec unquote. Cela signifie qu'il est possible d'inclure réellement les valeurs des variables à l'intérieur des listes citées:

> (define x 42)
> (quasiquote (x is: (unquote x)))
(x is: 42)

Bien sûr, utiliser quasiquote et unquote est assez verbeux, donc ils ont leurs propres abréviations, tout comme '. Plus précisément, quasiquote est ` (backtick) et unquote est , (virgule). Avec ces abréviations, l'exemple ci-dessus est beaucoup plus agréable au goût.

> `(x is: ,x)
(x is: 42)

Un dernier point: la quasiquote en fait peut être implémentée dans Racket en utilisant une macro plutôt velue, et elle l'est. Il s'étend aux utilisations de list, cons, et bien sûr, quote.


Annexe B: Implémentation de list et quote dans le schéma

L'implémentation de list est super simple à cause du fonctionnement de la syntaxe "reste argument". C'est tout ce dont vous avez besoin:

(define (list . args)
  args)

C'est tout!

En revanche, quote est beaucoup plus difficile - en fait, c'est impossible! Cela semble tout à fait réalisable, car l'idée de désactiver l'évaluation ressemble beaucoup à des macros. Pourtant, une tentative naïve révèle le problème:

(define fake-quote
  (syntax-rules ()
    ((_ arg) arg)))

Nous prenons simplement arg et le recrachons ... mais cela ne fonctionne pas. Pourquoi pas? Eh bien, le résultat de notre macro sera évalué, donc tout est pour rien. Nous pourrions être en mesure de développer quelque chose comme quote en développant à (list ...) et en citant récursivement les éléments, comme ceci:

(define impostor-quote
  (syntax-rules ()
    ((_ (a . b)) (cons (impostor-quote a) (impostor-quote b)))
    ((_ (e ...)) (list (impostor-quote e) ...))
    ((_ x)       x)))

Malheureusement, sans macros procédurales, nous ne pouvons pas gérer les symboles sans quote. Nous pourrions nous rapprocher en utilisant syntax-case, mais même dans ce cas, nous n'émulerions que le comportement de quote, pas de le répliquer.


Annexe C: Conventions d'impression en rack

Lorsque vous essayez les exemples de cette réponse dans Racket, vous pouvez constater qu'ils n'impriment pas comme on pourrait s'y attendre. Souvent, ils peuvent imprimer avec un _ ', comme dans cet exemple:

> (list 1 2 3)
'(1 2 3)

En effet, Racket, par défaut, imprime les résultats sous forme d'expressions lorsque cela est possible. Autrement dit, vous devriez être en mesure de taper le résultat dans le REPL et obtenir la même valeur. Je trouve personnellement ce comportement agréable, mais cela peut être déroutant lorsque vous essayez de comprendre la citation, donc si vous voulez le désactiver, appelez (print-as-expression #f), ou changez le style d'impression pour "écrire" dans le menu de langue DrRacket.

81
Alexis King