2
votes

Pourquoi Lisp permet-il le remplacement des opérateurs mathématiques dans let?

Je sais que dans Scheme je peux écrire ceci:

(let [+ *] (+ 2 3)) => 6

De plus, dans Clojure:

(let ((+ *)) (+ 2 3)) => 6

Je sais que cela peut travail, mais c'est tellement bizarre. Je pense que dans n'importe quelle langue, les opérateurs mathématiques sont prédéfinis. C ++ et Scala peuvent surcharger les opérateurs, mais cela ne semble pas être le cas.

Cela ne crée-t-il pas de confusion? Pourquoi Lisp autorise-t-il cela?


3 commentaires

Notez également que dans un Lisp-2, la liaison d'une variable ne change pas la liaison dans l'espace de noms de la fonction.


Notez que dans Scheme, la syntaxe correcte est: (let ((+ *)) (+ 2 3)) => 6


Dans Lisp et ses derivees, + , - , etc. ne sont pas des opérateurs - ce sont des fonctions, et pour mal citer un certain chat: "Une fonction fait tout ce que nous disons qu'elle fait , ni plus ni moins ". Bienvenue dans le miroir. Un grand pouvoir implique de grandes responsabilités. Attention à votre tête ...


8 Réponses :


1
votes

La raison pour laquelle nous autorisons cela dans lisp est que toutes les liaisons sont effectuées avec portée lexicale , qui est un concept issu du calcul lambda .

Le calcul lambda est un système simplifié de gestion de la liaison de variables. Dans le calcul lambda, les règles pour des choses comme

(lambda (x) (lambda (x) x))

et

(lambda (x) (lambda (y) x))

et même

(lambda (x) (lambda (y) y))


1 commentaires

(quand je dis lisp dans ce post, je veux dire en fait `` schéma '')



6
votes

Clause de non-responsabilité: Ceci est du point de vue de Clojure.

+ est juste une autre fonction. Vous pouvez le faire circuler et écrire somme avec lui, avoir une application partielle, lire des documents à ce sujet, ...:

user=> (defn + [s] (re-pattern (str s "+")))
WARNING: + already refers to: #'clojure.core/+ in namespace: user, being replaced by: #'user/+
#'user/+
user=> (+ "\\d")
#"\d+"
user=> (re-find (+ "\\d") "666")
"666"

Donc vous pouvez avoir beaucoup de + dans différents espaces de noms. Par défaut, celui de base est "utilisé" pour vous, mais vous pouvez simplement écrire le vôtre. Vous pouvez écrire votre propre DSL:

user=> (apply + [1 2 3])
6
user=> (reduce + [1 2 3])
6
user=> (map (partial + 10) [1 2 3])
(11 12 13)
user=> `+
clojure.core/+
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'

Ce n'est pas une forme spéciale, ce n'est rien de différent de toute autre fonction. Donc, avec cela établi, pourquoi ne devrait-il pas être autorisé à être remplacé?


0 commentaires

7
votes

Il ne s’agit pas d’une fonctionnalité générale de Lisp.

En Common Lisp, les effets de la liaison d’une fonction de langage de base sont undefined . Cela signifie que le développeur ne doit pas s'attendre à ce que cela fonctionne en code portable. Une implémentation peut également signaler un avertissement ou une erreur.

Par exemple, le compilateur SBCL le signalera erreur:

(defpackage "MYLISP"
  (:use "CL")
  (:shadow CL:+))

(in-package "MYLISP")

(defun foo (a b)
  (flet ((+ (x y)
           (* x y)))
    (+ a b)))

Nous pouvons avoir notre propre + en Common Lisp, mais il doit alors être dans un package em différent > (= espace de nom du symbole):

; caught ERROR:
;   Lock on package COMMON-LISP violated when
;   binding + as a local function while
;   in package COMMON-LISP-USER.
;   See also:
;     The SBCL Manual, Node "Package Locks"
;     The ANSI Standard, Section 11.1.2.1.2

;     (DEFUN FOO (X Y)
;       (FLET ((+ (X Y)
;                (* X Y)))
;         (+ X Y)))


4 commentaires

Common Lisp a fait de grands efforts pour s'assurer que l'on ne peut pas redéfinir le langage. Je me demande combien de code il faut à l'implémentation pour y parvenir. (CLISP se plaint des globaux, mais pas de flet )


@Sylvester: non, dit la norme de langage, que les conséquences ne sont pas définies.


Une implémentation n'est donc pas nécessaire pour signaler une erreur comme le font la plupart des implémentations? C'est incroyable de voir combien d'entre eux agissent de la même manière quand cela se produit.


@Sylwester: CLISP a des "verrous de package", donc je ne pense pas qu'il faut autant de code dans CLISP.



1
votes

Ce n'est pas bizarre car dans lisp il n'y a pas d'opérateurs sauf des fonctions et des formes spéciales comme let ou if , qui peuvent être intégrées ou créées sous forme de macros. Donc ici + n'est pas un opérateur, mais une fonction qui est assignée au symbole + qui ajoute ses arguments (dans le schéma et le clojure, vous pouvez dire que c'est juste une variable qui tient fonction pour ajouter des nombres), le même * n'est pas un opérateur de multiplication mais un astérisque qui multiplie ses arguments, c'est donc juste une notation pratique qui utilise le symbole + cela pourrait être add ou sum mais + est plus court et similaire à celui des autres langues.

C'est l'un de ces concepts décevants lorsque vous l'avez trouvé pour la première fois, comme les fonctions en tant qu'arguments et les valeurs de retour d'autres fonctions.

Si vous utilisez des calculs Lisp et lambda très basiques, vous n'avez même pas besoin de nombres et d'opérateurs + dans la langue de base. Vous pouvez créer des nombres à partir de fonctions et de fonctions plus et moins en utilisant la même astuce et les attribuer aux symboles + et - (voir Encodage d'église )


7 commentaires

Je ne comprends pas pourquoi j'ai voté, + n'est pas un opérateur dans lisp c'est juste une fonction, cette réponse est une extension de cela. C'était supposé être un commentaire.


Eh bien, le langage s'appelle Lisp, pas LIPS. En fait, le Lisp original de McCarthy ne supporte pas vraiment la sémantique du calcul lambda, il ne supporte même pas les fermetures lexicales. Cela a pris un peu de temps à comprendre. Le Lisp original de McCarthy avait un support direct pour les opérations numériques et il n'aurait pas été pratique de les remplacer par des fonctions. L'opération d'addition originale pour les nombres (flottants) dans Lisp 1 s'appelait SUM. Dans de nombreux systèmes Lisp, ce ne serait pas une bonne idée de redéfinir globalement + avec votre propre implémentation ...


@RainerJoswig merci pour le commentaire, après avoir créé l'interpréteur lisp appelé lèvres, je continue à faire des fautes de frappe lorsque j'écris lisp.


@RainerJoswig Je ne voulais pas dire qu'il est pratique de remplacer + par votre propre implémentation seulement c'est possible (uniquement avec schéma ou Clojure) et que vous n'avez pas besoin de + du tout pour en créer un, vous pouvez l'obtenir depuis les airs (référence aux vidéos SICP originales et implémentation contre , car et cdr ).


'seulement c'est possible' -> pas en général, car + puis peut planter Lisp ou votre programme de manière intéressante, car une fonction globale peut être appelée de toutes sortes d'endroits (-> liaison tardive)


@RainerJoswig c'est la même chose qu'avec car , cdr et contre des vidéos SICP (elles ont été créées à partir de fermetures) ce n'est pas quelque chose que vous faites normalement votre code, mais quelque chose que vous pouvez pour apprendre comment cela fonctionne. La même chose avec les chiffres d'église, vous pouvez tester cela pour savoir que cela fonctionne comme un exercice, pas quelque chose que vous faites en vrai code.


Oui, vous ne pouvez pas remplacer la fonction CAR par la vôtre - à moins qu'elle ne soit entièrement compatible avec l'original. Peut-être que même alors ce n'est pas possible. Tout comme on ne peut pas remplacer la fonction + par la vôtre. Dans un vrai Lisp pratique. Dans une vidéo sur un tableau blanc, c'est possible. ;-)



2
votes

Dans Scheme, vous créez une liaison locale, en omettant tout ce qui est supérieur, avec let . Étant donné que + et * ne sont que des variables qui se comparent à des procédures, vous donnez simplement aux anciennes procédures d'autres noms de variables.

parseFloat = parseInt;
parseFloat("4.5") 
// ==> 4

Dans Scheme, il n'y a aucun mot réservé . Si vous regardez d'autres langues, la liste des mots réservés est assez élevée. Ainsi, dans Scheme, vous pouvez faire ceci:

#!r6rs
(import (rnrs base)) ; imports both * and +   
(define + *)         ; defines + as an alias to *

(+ 10 20) 
; ==> 200 guaranteed

La chose vraiment intéressante à ce sujet est que si vous choisissez un nom et que la prochaine version de la norme utilise le même nom pour quelque chose de similaire, mais non compatible, votre code ne cassera pas. Dans d'autres langues, j'ai travaillé avec une nouvelle version pourrait introduire des conflits.

R6RS rend les choses encore plus faciles

De R6RS, nous avons des bibliothèques. Cela signifie que nous avons un contrôle total sur les formulaires de haut niveau que nous obtenons de la norme dans nos programmes. Vous avez plusieurs façons de le faire:

#!r6rs
(import (except (rnrs base) +))    
(define + *)

(+ 10 20) 
; ==> 200 guaranteed

C'est également OK.

#!r6rs
(import (rename (except (rnrs base) +) (* +)))

(+ 10 20) 
; ==> 200 

Et enfin:

(define (test v)
  (define let 10)           ; from here you cannot use let in this scope
  (define define (+ let v)) ; from here you cannot use define to define stuff
  define) ; this is the variable, not the special form
;; here let and define goes out of scope and the special forms are OK again
(define define +) ; from here you cannot use top level define
(define 5 6) 
; ==> 11

D'autres langues le font aussi:

JavaScript est peut-être le plus évident:

(let ((+ *))
  +)
; ==> #<procedure:*> (non standard visualization of a procedure)

Mais vous ne pouvez pas toucher leurs opérateurs. Ils sont réservés car la langue doit faire beaucoup de choses pour la priorité des opérateurs. Tout comme Scheme, JS est un langage agréable pour la frappe de canard.


0 commentaires

1
votes

La philosophie de Scheme est d'imposer une restriction minimale afin de donner une puissance maximale au programmeur.

Une raison d'autoriser de telles choses est que dans Scheme vous pouvez intégrer d'autres langages et dans d'autres langages vous voulez utiliser le * avec une sémantique différente.

Par exemple, si vous implémentez un langage pour représenter des expressions régulières, vous voulez donner au * la sémantique du kleene algébrique opérateur et écrivez des programmes comme celui-ci

(* (+ "abc" "def"))

pour représenter une langue contenant des mots comme celui-ci

empty
abc
abcabc
abcdef
def
defdef
defabc
....

À partir de la langue principale, lambda calcul non typé, il est possible de créer une langue en que vous pouvez redéfinir absolument tout sauf le symbole lambda . C'est le modèle du schéma de calcul sur lequel est construit.


0 commentaires

2
votes

Les dialectes Lisp traditionnels n'ont pas de jetons réservés pour les opérations d'infixe. Il n'y a pas de différence catégorique entre + , expt , format ou open-file : ce ne sont que des symboles.

Un proram Lisp qui exécute (let ((+ 3)) ...) est spirituellement très similaire à un programme C qui fait quelque chose comme {int sqrt = 42; ...} . Il y a une fonction sqrt dans la bibliothèque C standard, et comme C a un seul espace de noms (c'est un Lisp-1), ce sqrt est maintenant masqué.

Ce que nous ne pouvons pas faire en C, c'est {int + = 42; ...} car + est un jeton d'opérateur. Un identifiant est demandé, il y a donc une erreur de syntaxe. Nous ne pouvons pas non plus faire {struct interface * if = get_interface (...); } car if est un mot-clé réservé et non un identifiant, même s'il en ressemble à un. Les lisps ont tendance à ne pas avoir de mots-clés réservés, mais certains dialectes ont certains symboles ou catégories de symboles qui ne peuvent pas être liés en tant que variables. Dans ANSI Common Lisp, nous ne pouvons pas utiliser nil ou t comme variables. (Plus précisément, les symboles nil et t qui proviennent du package common-lisp ). Cela ennuie certains programmeurs, car ils aimeraient une variable t pour "time" ou "type". En outre, les symboles du package de mots-clés, apparaissant généralement avec un deux-points, ne peuvent pas être liés en tant que variables. La raison en est que tous ces symboles s'auto-évaluent. nil , t et les symboles de mots-clés s'évaluent d'eux-mêmes, et n'agissent donc pas comme des variables pour désigner une autre valeur.


0 commentaires

0
votes

Pourquoi Lisp permet-il de relier les opérateurs mathématiques?

  • pour la cohérence et
  • car cela peut être utile.

Cela ne crée-t-il pas de confusion?

  • Non.

La plupart des langages de programmation, suivant la notation algébrique traditionnelle, ont une syntaxe spéciale pour les fonctions arithmétiques élémentaires d'addition et de soustraction, etc. Les règles de priorité et d'association rendent certains appels de fonction implicites. Cette syntaxe facilite la lecture des expressions au prix de la cohérence.

Les LISP font basculer la bascule dans l'autre sens, préférant la cohérence à la lisibilité.

Dans Clojure (le Lisp que je connais), les opérateurs mathématiques ( + , - , * , ...) n'ont rien de spécial .

  • Leurs noms ne sont que des symboles ordinaires.
  • Ce sont des fonctions principales comme toutes les autres.

Vous pouvez bien sûr les remplacer.

< finalUtilité

Pourquoi voudriez-vous remplacer les opérateurs arithmétiques de base? Par exemple, la bibliothèque units2 les redéfinit pour accepter des quantités dimensionnées ainsi que nombres simples.


L'algèbre de Clojure est plus difficile à lire.

  • Tous les opérateurs sont des préfixes.
  • Toutes les applications de l'opérateur sont explicites - pas de priorités.

Si vous êtes déterminé à avoir des opérateurs infixes avec des priorités, vous pouvez le faire. Incanter le fait: voici quelques exemples et ici est le code source.


0 commentaires