Aller au contenu

Arguments et Variables


Fraid

Messages recommandés

Bonjour,

 

defun truc (argument / variable)

Quelque temps que je cherche quelque explication

sur ce qu'il se passe entre les parentheses se trouvant apres le defun.

 

l'argument est il une variable déclaré dans une autre fonction? qui n'est pas remis a nil ?

 

Quand est on obligé de déclarer les variables dés le depart ?

(j'ai un certain nombre de lisp qui fonctionne tres bien avec les parentheses vides)

 

Merci de m'eclairer.

je sais c'est vraiment une question de débutant, mais comme on dit y a pas de question bete...

 

  • Upvote 1
  • Downvote 1
Lien vers le commentaire
Partager sur d’autres sites

coucou

 

merci à Toi d'avoir posé la question,

je vais aussi guetter les réponses

à force de faire des lisp, il devient urgent d'avoir de la théorie

 

nul doute que (giles), l'excellent ;)

va venir nous apporter la substantifique explication

 

amicalement

  • Upvote 1
Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

Je vais essayer de répondre en 2 parties : les variables puis les arguments

 

Les variables

 

On appelle variable un symbole auquel est liée une valeur.

En LISP, on définit les variables avec la fonction setq.

setq requiert une ou plusieurs paires d'arguments : le symbole et une expression LISP dont l'évaluation donnera la valeur à la variable.

(setq a 12)

affecte la valeur 12 à la variable a.

On redéfinit la valeur de la variable a de la même façon:

(setq a 42)

 

On distingue les variables locales et globales selon qu'elles sont ou non déclarées dans la définition d'une fonction.

 

Si on fait :

(setq a 12)

en ligne de commande (ou dans la console Visual LISP) en dehors de tout code, on affecte au symbole a la valeur 12 et ce pour toute la session dans le dessin courant. On dit que a est globale. Elle conservera sa valeur tant qu'elle n'est pas redéfinie et sera accessible depuis la ligne de commande en entrant : !a.

Ceci peut être pratique mais n'est pas sans danger si une fonction LISP dans laquelle la variable a est redéfinie sans être déclarée localement :

 

(defun foo () 
 (setq a (sslength (ssget)))
 (princ (strcat "\n" (itoa a) " objets sélectionnés"))
 (princ)
)

À chaque fois que foo sera exécutée la valeur de a prendra le nombre d'entités sélectionnées.

Pour éviter cela, il suffit de déclarer localement la variable a dans la fonction foo :

 

(defun foo (/ a) 
 (setq a (sslength (ssget)))
 (princ (strcat "\n" (itoa a) " objets sélectionnés"))
 (princ)
)

Ainsi la valeur que pouvait avoir a à l'extérieur de la fonction n'affectera pas sa valeur dans la fonction et réciproquement.

 

En résumé, à part quelques rares exceptions, il est toujours préférable de déclarer localement les variables utilisées dans une fonction. Lorsque on veut utiliser des variables globales, une convention veut qu'on mette une astérisque au début et à la fin du symbole et la prudence recommande qu'on utilise des noms ayant peu de chance d'être utilisés par d'autres fonctions : *variableGlobale*.

  • Upvote 1

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Les arguments

 

Dans les fonctions prédéfinies

On appelle arguments (ou paramètres) d'une fonction les données passées à cette fonction pour permettre son exécution. Ces données doivent correspondre au(x) type(s) requis par la fonction.

 

Les fonctions LISP natives (prédéfinies) requièrent, suivant les cas, 0, 1 ou plusieurs arguments. Pour certaines fonctions ces arguments peuvent être optionnels ou de nombre indéfini.

 

Exemples :

 

layoutlist ne requiert aucun argument :

(layoutlist) => retourne la liste des onglets de présentation

 

sqrt requiert un seul argument de type nombre :

(sqrt 4) retourne 2.0 qui est la la racine carrée de l'argument de la fonction : 4

 

strcase requiert au moins un argument de type chaîne et, optionnellement, un second argument (T) :

(strcase "Toto") retourne "TOTO" l'argument "Toto" mis en majuscules

(strcase "Toto" T) retourne "toto" l'argument "Toto" mis en minuscules

 

l'opérateur + requiert un ou plusieurs arguments de type nombre :

(+ 1 5 8) retourne 14 qui est la somme des arguments 1, 5 et 8 passés à la fonction.

 

Dans les fonctions définies par l'utilisateur

La programmation AutoLISP (langage fonctionnel) consiste essentiellement en la définition de fonctions (avec la fonction defun). On peut classer ces fonctions en deux catégories :

- les commandes définies en LISP, dont le nom est préfixé avec c: qui peuvent être appelées directement en ligne de commande, et, de ce fait, elles ne peuvent pas utiliser d'argument.

- les fonctions définies en LISP (souvent appelées sous-routines) qui sont de nouvelles fonctions LISP utilisables exactement comme les fonctions prédéfinies. Ces 'sous-routines' peuvent donc utiliser des arguments mais ceux-ci ne peuvent pas être optionnels et leur nombre est défini dans le defun.

Il est donc possible de se constituer une (ou des) bibliothèque de routines pour compléter les fonctions prédéfinies (avant l'arrivée de Visual LISP, AutoLISP ne fournissait qu'environ 250 fonctions avec lesquelles on pouvait en définir une infinité*).

 

Un premier exemple simple pour illustrer ce propos.

AutoLISP ne fournit pas de fonction qui retourne le carré d'un nombre, il est (très) facile de définir une sous-routine pour ce faire. Il faudra passer à cette sous-routine un argument : le nombre dont on veut avoir le carré en retour.

(defun sqr (num) ; num est l'argument de la fonction sqr
 (* num num) ; l'évaluation de cette expression est le retour de la fonction sqr
)

On peut, si la fonction est chargée, l'appeler depuis une autre fonction ou commande définie en LISP :

(defun c:test5 (/ n)
 (if (setq n (getint "\nEntrez un nombre entier: "))
   (princ (strcat (itoa (sqr n)) " est le carré de " (itoa n))
   )
 )
 (princ)
)

 

La sous routine peut, comme toute fonction avoir une ou des variables locales. Un autre exemple (un peu plus avancé et peut-être plus utile) : une fonction qui serait la complémentaire de la fonction prédéfinie member.

member requiert comme argument une expression et une liste et retourne la liste à partir de la première occurrence de l'expression.

(member 4 '(1 2 3 4 5 6)) retourne (4 5 6)

Pour définir une fonction qui retourne la liste complémentaire (1 2 3), il faudra lui passer les mêmes arguments : l'expression 4 et la liste '(1 2 3 4 5 6) :

(defun trunc (expr lst / result) ; expr et lst sont les arguments, result est une variable locale
 (while (and lst (not (equal expr (car lst))))
   (setq result (cons (car lst) result)
  lst	 (cdr lst)
   )
 )
 (reverse result) ; le retour de la fonction
)

Il est a noter que les arguments se comportent comme des variables locales. Dans la routine ci dessus, la liste lst est tronquée de son premier élément à chaque itération dans la boucle while, ces modifications n'affectent en rien la liste originelle.

(defun test5 (/ lst)
 (setq lst '(1 2 3 4 5 6))
 (princ (trunc 4 lst))
 (terpri)
 (princ lst)
 (princ)
)

$ (test5)

(1 2 3)

(1 2 3 4 5 6)

 

Factoriser le code

La factorisation d'un code ressemble un peu à la factorisation d'une expression mathématique. Il peut être intéressant d'extraire du code d'une fonction principale un groupe d'expressions soit parce qu'il revient plusieurs fois dans le code, soit parce qu'on pense réutiliser la fonction dans d'autres codes, soit pour des raisons de lisibilité (le tutoriel Visual LISP fournit avec l'aide d'AutoCAD, que je ne recommande absolument pas aux débutants, abuse de ce procédé et finit par rendre le code plus difficilement lisible).

Un exemple avec une commande qui met les entités sélectionnées sur le calque "0" :

(defun c:ssTo0 (/ ss n)
 (if (setq ss (ssget))
   (progn
     (setq n 0)
     (while (setq ent (ssname ss n))
(setq elst (entget ent)
      n	   (1+ n)
)
(entmod (subst (cons 8 "0") (assoc 8 elst) elst))
     )
   )
 )
 (princ)
)

Dans ce cas, il peut être intéressant de factoriser la partie du code qui modifie le calque d'une entité pour en faire un fonction plus générique qui permettrait au programmeur de l'utiliser en spécifiant un autre calque que le calque "0". La fonction devra alors avoir 2 arguments : le nom d'entité et le nom du calque à attribuer. Dans la commande ssTo0, lors de l'appel de la sous-routine ChangeLayer, il faudra spécifier le calque "0":

(defun ChangeLayer (ent lay / elst) ; ent et lay = arguments, elst = variable locale
 (setq elst (entget ent))
 (entmod (subst (cons 8 lay) (assoc 8 elst) elst))
)
(defun c:ssTo0 (/ ss n)
 (if (setq ss (ssget))
   (progn
     (setq n 0)
     (while (setq ent (ssname ss n))
(setq n	   (1+ n))
(ChangeLayer ent "0") ; appel de ChangeLayer avec 2 arguments
     )
   )
 )
 (princ)
)

 

Un autre exemple de factorisation ici

 

Voilà, j'espère avoir été clair. N'hésitez pas à poster vos remarques, questions, etc.

 

* Les pionniers (Reini Urban, Vladimir Nesterovsky, etc.) avaient essayé de constituer une gigantesque bibliothèque de routines complémentaires définies en LISP (StdLib) dans laquelle on trouvais les équivalents de la plupart des fonctions vl-* arrivées plus tard avec Visual LISP (exemples ici).

On peu trouver ici trois bibliothèques de routines : Dialog.lsp, Vecteurs&Matrices.lsp, Listes.lsp

  • Upvote 2

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

coucou

 

je ne viendrais pas crtitiquer la clarté des propos

car je suis tellement aveuglé par mes mauvaises habitudes

que j'en perds la vue.

 

Je ne me sers pas, actuellement, de ce mode opératoire,

mais je viens te remercier grandement

car je fais des boucles et des boucles dans mes programmes

qui seraient bien simplifiés en utilisant tes explications

 

encore M E R C I

 

amicalement

Lien vers le commentaire
Partager sur d’autres sites

Merci Didier, mais ne te laisse pas aveugler, je ne suis pas du tout sûr d'avoir été très clair.

 

Encore une fois, ne pas hésiter à demander des précisions.

 

On pousse un peu le bouchon ?

 

Les fonctions passées comme argument

 

Certaines fonctions LISP natives requièrent une fonction comme argument (appelées "fonctions d'ordre supérieur", ces fonctions sont caractéristiques de la programmation fonctionnelle) : les essentielles mapcar et apply, mais aussi vl-sort, vl-member-if, vl-remove-if, vl-catch-all-apply, etc.

 

Attardons nous un peu sur quelques unes de ces fonctions avant de voir comment définir de telles fonctions.

 

apply est une fonction essentielle en LISP (on va s'en servir prochainement), elle requiert comme argument une fonction et une liste et passe la liste comme arguments de la fonction pour retourner le résultat de l'évaluation.

 

Un petit exemple pour illustrer ça :

 

(apply '+ '(1 2 3))

le premier argument : '+ est la fonction (elle doit être 'quotée' pour ne pas être évaluée dans cette expression) le second : '(1 2 3) est une liste de nombres.

 

Cette expression est équivalente à :

 

(eval (cons '+ '(1 2 3))) qui s'évalue pas à pas : (eval '(+ 1 2 3)) => (+ 1 2 3) => 6

 

L'intérêt de cette fonction peut ne pas sembler évident dans cet exemple simple où la liste est explicite (on aurait pu écrire simplement (+ 1 2 3)) mais on est souvent amené en LISP à manipuler des listes dont on ne connais pas la valeur des éléments.

 

 

mapcar autre fonction essentielle requiert comme arguments une fonction et une ou plusieurs listes. Elle retourne une liste dont chaque élément est le résultat de l'application de la fonction à chacun des éléments de la (ou des) liste(s).

 

Quelques exemples simples pour illustrer ça :

 

(mapcar '1+ '(1 2 3))

la fonction 1+ est le premier argument, la liste le second.

 

mapcar applique la fonction à chacun des éléments et retourne la liste des résultats :

 

(2 3 4) soit le résultat de : (list (1+ 1) (1+ 2) (1+ 3))

 

Dans la même veine,

 

(mapcar 'strcase '("a" "b" "c"))

retourne ("A" "B" "C")

 

Avec plusieurs listes passées comme arguments, mapcar passe chacun des éléments correspondants de chaque liste comme argument à la fonction (premier argument de mapcar) :

 

(mapcar '+ '(1 2 3) '(4 5 6))

est équivalent à (list (+ 1 4) (+ 2 5) (+ 3 6)) et retourne (5 7 9)

 

 

Dans les exemples ci dessus la fonction passée comme argument est une fonction native, mais on peut aussi utiliser des fonctions définies par l'utilisateur ou des fonctions anonymes (fonction lambda)

 

 

Par exemple, avec la fonction sqr définie plus haut :

 

(mapcar 'sqr '(1 2 3))

retourne (1 4 9)

 

 

Mais il n'est pas toujours intéressant ou judicieux de définir une fonction avec defun pour l'utiliser avec ces fonctions. On peut aussi définir localement (dans l'expression mapcar, apply ou autre) une fonction anonyme.

 

 

La fonction LISP lambda sert à définir ces fonctions anonymes qui sont exécutée à l'endroit même où elle sont définies et qu'on ne peut appeler depuis ailleurs (puisqu'elle n'ont pas de nom).

 

Par exemple, si on n'avait pas défini de fonction nommée sqr (qui ne présente que peu d'intérêt vue la simplicité du code) on pourrait très bien définir une fonction anonyme pour avoir le même résultat que ci-dessus.

 

La syntaxe avec la fonction lambda est exactement la même qu'avec defun à ceci près que l'argument 'nom de la fonction' est omis.

 

(mapcar '(lambda (num) (* num num)) '(1 2 3))

retourne aussi (1 4 9)

 

'(lambda (num) (* num num)) est la fonction anonyme premier argument pour mapcar

 

'(1 2 3) est la liste second argument pour mapcar

 

Dans la fonction anonyme num est l'argument de la fonction (comme dans sqr) qui prend tour à tour les valeurs de chaque élément de la liste.

 

 

Il est aussi tout à fait possible de définir des fonctions LISP dont un des argument est une fonction.

 

En prenant comme exemple la fonction ssmap (extraite de la stdlib de Reini Urban) on va pousser la factorisation de c:ssTo0 commencée plus haut.

 

ssmap permet d'appliquer une fonction à un jeu de sélection, les arguments requis sont : la fonction à appliquer à chaque entité et le jeu de sélection (noter l'utilisation de apply dans ssmap).

 

(defun ssmap (fun ss / n)
 (if (= 'PICKSET (type ss)) ; si ss est bien un jeu de sélection,
   (repeat (setq n (sslength ss)) ; répéter autant de fois qu'il y a d'entité,
     (apply fun (list (ssname ss (setq n (1- n))))) ; appliquer la fonction a chaque entité
   )
 )
)

 

 

La fonction à appliquer à chaque entité du jeu de sélection est la fonction ChangeLayer, or, de la façon dont nous l'avons définie cette fonction requiert 2 arguments : le nom d'entité et le nom du calque. Il n'est donc pas possible de l'utiliser en l'état avec ssmap dont le premier argument doit être une fonction qui ne requiert qu'un argument de type ENAME (nom d'entité).

 

Qu'à cela ne tienne, on va inclure la fonction ChangeLayer dans une fonction anonyme pour lui passer le second argument (le nom du calque).

 

c:ssTo0 peut donc s'écrire (avec ChangeLayer et ssmap chargés) :

 

(defun c:ssTo0 (/ ss n)
 (if (setq ss (ssget))
   (ssmap '(lambda (x) (ChangeLayer x "0")) ss)
 )
 (princ)
)

 

'(lambda (x) (ChangeLayer x "0")) est le premier argument de ssmap où x est l'argument de la fonction anonyme qui prendra tour à tour la valeur de chaque entité contenue dans ss, le second argument pour ssmap

 

 

On peut voir ici comment implémenter des fonction dont l'argument est une fonction en utilisant apply ou eval.

 

 

Je sais que cette partie est plus dure à assimiler, mais je suis convaincu que la compréhension des fonctions apply, mapcar et lambda est une étape majeure dans l'apprentissage du LISP (une autre étant la compréhension de la récursivité). Une fois ceci acquis tout le reste n'est plus que du "vocabulaire"...

  • Upvote 1

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Une petite dernière pour boucler sur la déclaration de variables et après, au lit !

 

 

Si les fonctions définies en LISP (ou sous-routines) généralistes et souvent utiles ont tout intérêt à être définies à l'extérieur des fonction principales pour constituer des bibliothèques (ou librairies pour faire un anglicisme) il est parfois intéressant de définir une sous-routine à l'intérieur de la fonction ou commande principale et il est alors possible (et conseillé) de la déclarer localement pour éviter tout conflit avec une autre routine de même nom.

 

Dans ce cas, la fonction doit être définie avant son premier appel.

 

 

C'est ce qui se fait habituellement avec la fonction *error* (noter les astérisque pour signifier une variable globale) qui est fournie pour être redéfinie localement suivant des besoins particuliers (en général pour restaurer l'environnement initial en cas d'Echap).

 

 

La fonction*error* prédéfinie affiche un message qui est une description de l'erreur AutoLISP qui a provoqué son lancement.

 

Ce message est l'argument fournit par AutoCAD à la fonction *error* :

 

 

Commande: (/ 2 0)

 

; erreur: division par zéro

 

 

On peut redéfinir localement cette fonction et la déclarer pour que la portée de cette définition soit limitée à la fonction principale.

 

Par exemple dans la fonction div qui requiert deux arguments de type nombre (entiers ou réels) et retourne toujours un nombre réel (contrairement à l'opérateur natif : /)

 

 

(defun div (x y / *error*) ; 'localisation' de la fonction *error*

 ;; redéfinition de la fonction*error*  (modification du message)
 (defun *error* (msg)
   (princ (strcat "\nErreur dans la fonction div: " msg))
   (princ)
 )

 ;; expressions de la fonction principale
 (/ x (float y))
)

 

 

Commande: (div 2 3)

 

0.666667

 

Commande: (div 2 0)

 

Erreur dans la fonction div: division par zéro

 

 

Voir un exemple ici avec deux fonctions locales (*error* et MakeBlock) définies dans la commande c:txt2blk

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

Je ne vois pas ce que je pourrais dire de plus que l'aide.

defun-q existe essentiellement pour conserver la compatibilité avec les premières versions d'AutoLISP dans les quelles la structure des fonctions étaient de type liste (depuis elles sont compilées en objets de type SUBR ou USUBR).

On n'utilise plus defun-q que dans les rares cas où on a besoin d'avoir des fonctions définies sous forme de listes, comme avec S::STARTUP (on peut trouver aussi une utilisation de defun-q dans la routine ai_sysvar dans acad20XXdoc.lsp ou ai_utils.lsp pour les plus anciennes versions).

defun-q-list-get permet d'accéder à la liste d'une fonction définie avec defun-q.

defun-q-list-set permet de modifier cette liste.

 

Une recherche dans les forums avec defun-q t'aurais fait trouver, entre autres, les sujets suivants :

(defun-q S::STARTUP ...)

Une fonction qui "apprend"

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

Un autre exemple de factorisation et d'utilisation de sous-routines ici.

Cet exemple est peut-être un peu plus complexe mais montre bien l'utilisation des sous-routines (parfois appelée par d'autres sous-routines) ainsi que l'utilisation de apply, mapcar, vl-sort avec des fonctions lambda.

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

  • 1 mois après...

Une petite synthèse avec un exemple.

 

 

Portée des arguments et variables dans les fonctions imbriquées

 

 

Soient quatre symboles a, b, c et d, peu importe qu'ils soient initialisés dans le dessin (qu'ils aient un valeur affectée) ou nil et trois fonctions main et sub et princ_abcd.

 

 

main déclare trois variable a, b et c

 

- affecte respectivement les valeurs 1, 2, 3, et 4 à a, b, c et d

 

- appelle princ_abcd

 

- appelle sub en lui passant a comme argument.

 

- appelle princ_abcd

 

 

sub requiert un argument : a et déclare une variable : b

 

- appelle princ_abcd

 

- affecte respectivement les valeurs 10, 20, 30, et 40 à a, b, c et d

 

- appelle princ_abcd

 

 

princ_abcd requiert comme argument un message et affiche le message et les valeurs de a, b, c et d

 

 

 

(defun main (/ a b c)
 (setq	a 1
b 2
c 3
d 4
 )
 (princ_abcd "\n\nValeurs après affectation dans 'main'")
 (sub a)
 (princ_abcd "\n\nValeurs dans 'main' après exécution de 'sub'")
)

(defun sub (a / B)
 (princ_abcd "\n\nExécution de 'sub'\nValeurs initiales dans 'sub'")
 (setq	a 10
b 20
c 30
d 40
 )
 (princ_abcd "\nValeurs après affectation dans 'sub'")
)

(defun princ_abcd (msg)
 (princ
   (strcat msg
    "\n\ta = "
    (vl-princ-to-string a)
    "\n\tb = "
    (vl-princ-to-string B)
    "\n\tc = "
    (vl-princ-to-string c)
    "\n\td = "
    (vl-princ-to-string d)
   )
 )
 (princ)
)

 

 

À la ligne commande ou dans la console de l'éditeur Visual LISP, on lance les expressions suivantes :

 

 

(princ_abcd "Valeurs avant exécution de 'main'")
(main)
(princ_abcd "\n\nValeurs après exécution de 'main'")

 

On lit les résultats dans la fenêtre de texte ou la console.

 

Avant exécution de main les valeurs sont nil ou autre au cas où ces symboles aient déjà des valeurs (variables globales).

 

Dans main, les valeurs 1, 2, 3 et 4 sont affectées et sub est appelée.

 

Dans sub, avant les nouvelles affectations de valeurs :

 

- l'argument a a la valeur 1 qui lui a été passée par main lors de l'appel

 

- la variable b est nil puisqu'elle est locale à sub.

 

- c a la valeur qui lui a été affectée dans main, on pourrait dire que c est "globale dans sub".

 

- d a la valeur qui lui a été affectée dans main, ce qui est normal pour une variable globale.

 

Les nouvelle valeurs (10, 20, 30 et 40) sont affectées aux symboles.

 

 

De retour dans main,

 

- la valeur de a est toujours 1, a était un argument de sub qui a donc eu le même comportement qu'une variable locale de sub.

 

- b est toujours 2, normal, c'était une variable locale de sub.

 

- c a conservé la valeur qui lui a été affectée dans sub, c'est ce qu'on appelle un "effet de bord".

 

- d a conservé la valeur qui lui a été affectée dans sub, normal c'est une variable globale.

 

 

Après l'exécution de main tous les symboles ont retrouvé leurs valeurs initiale (ou nil) sauf d bien sûr (variable globale).

 

 

On peut noter que princ_abcd affiche les valeurs qu'ont a, b et c à l'endroit où elle est appelée.

 

 

La connaissance et la compréhension de ces comportements est essentielle pour éviter des déboires dans les programmes où les imbrications de fonctions peuvent être nombreuses.

 

Les "effets de bord" qu'ils soient dus à l'utilisation de variables globales ou de variable ds même nom dans des fonctions imbriquées peuvent, lorsqu'ils ne sont pas maitrisés avoir des effets inattendus qui ne sont pas toujours facile à déboguer.

 

 

En conclusion, en LISP la "portée" d'une variable locale dans une fonction s'étend à toutes les fonctions appelée par elle.

 

L'utilisation des "effets de bord" (ou des variables globales) en LISP* peut être utile dans certains cas mais doit être utilisé avec prudence et circonspection.

 

 

* d'autres langages de programmation sont implémentés de manière à proscrire (ou éviter au maximum) les effets de bords. par exemple, en VB(A) ou .NET pour pouvoir modifier la valeur de variables déclarése dans une fonction (ou Sub ou méthode) depuis une autre fonction appelée par celle-ci (le même comportement qu'avec c dans main et sub) il faut utiliser des arguments (paramètres) de type référence (ByRef au lieu de ByVal en VB(A)).

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

  • 6 mois après...

Intéressant de connaître cet "effet de bord".

Récemment je me suis aperçu qu'une variable V1 définie dans une fonction F1 en variable Locale, était "vue" dans la fonction F2 quand j'appelais F2 de F1.

Alors que je croyais jusqu'ici que V1 serait de valeur Nil.

Je ne suis peut être pas très clair dans mon explication, je m'explique:

 

 
(defun F1 ( / V1 V2 result)
(setq V1 25 V2 3)
(setq result (F2 V2))
(princ (strcat (itoa V1) " * " (itoa V2) " = " (itoa result)))
(princ)
)
(defun F2 (Val)
(* Val V1)
)

 

Si je lance de la ligne de commande:

(F1)

> 25 * 3 = 75

La variable V1 de la fonction F2 est la variable locale de la fonction F1.

Alors que si je tape:

(F2 3)

> ; erreur: type d'argument incorrect: numberp: nil

La variable V1 n'existe plus hors de la fonction F1.

Peut être que ça parait logique aux pros du lisp comme (gile) mais je pensais que V1 ne serait pas vu dans F2.

...

 

Lien vers le commentaire
Partager sur d’autres sites

Salut,

La variable V1 n'existe plus hors de la fonction F1.

C'est logique : Tu as déclarer V1 en locale dans F1, V1 n'existe donc QUE pour F1.

Ne la déclare pas, et tu pourras la voir partout.....

 

En fait, ce que tu écris est faux :

une variable V1 définie dans une fonction F1 en variable Locale, était "vue" dans la fonction F2 quand j'appelais F2 de F1.

F2 ne vois pas V1, mais F1 renseigne V1 qu'il passe dans F2 lancé dans F1.

Donc, c'est normal.

 

[Edité le 26/4/2011 par Bred]

Si vous êtes persuadés de tout savoir sur un sujet, c''est que vous en ignorez quelque chose...

Lien vers le commentaire
Partager sur d’autres sites

F2 ne vois pas V1, mais F1 renseigne V1 qu'il passe dans F2 lancé dans F1.

 

Non, non...

Charge le code et essaye de lancer (F1) qui renseigne V1 et appelle F2 en lui passant V2, tout se passe bien.

Puis essaye de lancer (F2 12), par exemple, tout seul, et tu auras un erreur parce que F2 utilise V1 qui est redevenue nil.

 

Donc, F2 voit bien V1 quand elle est appelée depuis F1, mais pas si elle est appelée depuis ailleurs.

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

La variable V1 n'existe plus hors de la fonction F1.

...

 

BRED:

C'est logique : Tu as déclarer V1 en locale dans F1, V1 n'existe donc QUE pour F1.

Ne la déclare pas, et tu pourras la voir partout.....

 

oui Bred ça je sais que c'est logique, ce qui me paraissait moins logique c'est ça:

 

une variable V1 définie dans une fonction F1 en variable Locale, était "vue" dans la fonction F2 quand j'appelais F2 de F1.

 

Lien vers le commentaire
Partager sur d’autres sites

Charge le code et essaye de lancer (F1) qui renseigne V1 et appelle F2 en lui passant V2, tout se passe bien.

Puis essaye de lancer (F2 12), par exemple, tout seul, et tu auras un erreur parce que F2 utilise V1 qui est redevenue nil.

 

Donc, F2 voit bien V1 quand elle est appelée depuis F1, mais pas si elle est appelée depuis ailleurs.

 

Ben oui, c'est ce que j'essaye de dire dans

F2 ne vois pas V1, mais F1 renseigne V1 qu'il passe dans F2 lancé dans F1.

 

Dis autrement :

F1 renseigne V1.

Tant que la fin de la fonction F1 n'est pas atteinte, V1 est renseigné (puisque V1 es déclaré en locale DANS F1)

Donc, si l'on fait appel à F2 dans F1, obligatoirement ça fonctionne : V1 est déclaré et F1 n'est pas fini...

 

une variable V1 définie dans une fonction F1 en variable Locale, était "vue" dans la fonction F2 quand j'appelais F2 de F1.

Donc, pour moi, quand une variable locale est déclarée, elle est mise à nil au lancement, et à la fin.

Au milieu, on peut la renseigner tant que l'on veut (et heureusement !)

 

J'avoue que soit je ne comprends rien, soit pour moi c'est vraiment plus que logique...

 

Par contre, là où pour moi ce n'est pas logique (et c'est peut-être ce que vous voulez dire), c'est que la déclaration d'une variable d'un même nom dans 2 fonction différente sont en fait pas la même variable, et c'est peut-être cela que vous voulez dire :

 

Dans mon exemple, (princ V1) dans F1 ne devrais pas fonctionner car je remet à nil V1 dans (F2).

Mais non, V1 de F1 n'est pas considéré comme le V1 de F2, car F1 m'écrit les deux V1 différent, dans la même fonction (donc V1 n'est pas remis à nil) :

 

(defun F1 ( / V1)
(setq V1 "TOTO !!!")
(F2)
(princ V1)
(princ)
)

(defun F2 (/ V1)
(setq V1 "NON !")
(princ v1)
)

 

Si vous êtes persuadés de tout savoir sur un sujet, c''est que vous en ignorez quelque chose...

Lien vers le commentaire
Partager sur d’autres sites

Je crois qu'on s'est juste mal compris.

F2 ne vois pas V1, mais F1 renseigne V1 qu'il passe dans F2 lancé dans F1.

Pour moi F1 ne passe pas V1 dans F2 puisque l'argument passé à F2 est V2.

 

Pour résumer (et pour répondre à la dernière interrogation de Bred) : LISP n'est qu'imbrications.

De la même manière qu'une variable globale dans le document garde sa valeur dans une fonction si elle n'est pas déclarée ou est temporairement "écrasée" si elle est déclarée, une variable déclarée dans une fonction conserve sa valeur dans une sous-fonction appelée par celle-ci si elle n'est pas déclarée dans la sous fonction ou est temporairement écrasée si elle est déclarée dans la sous fonction.

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Re,

 

Quand je relie ma remarque, il est vrias que ce n'est pas très clair.

pour donner un contre exemple à celui de mattoti, pour que F2 ne puisse pas lire V1, il faut la déclarer dans F2.

Il est donc logique que ça ne fonctionne pas

(defun F2 (Val / V1)
(* Val V1)
)

 

, comme il est logique que V1s'il n'est pas déclaré (donc remis à nil) pendant le passage de F2, F2 "l'accepte".

 

C'est ça qui me chagrine :

 

une variable (...) est temporairement écrasée si elle est déclarée dans la sous fonction.

Le "temporairement" est pour moi bien bizarre.... une valeur de variable change puis reviens à sa valeur initiale sans qu'on lui dise de le faire....

Ce doit être l'une des bases des réactions de la lecture automatique du code lisp.

Si vous êtes persuadés de tout savoir sur un sujet, c''est que vous en ignorez quelque chose...

Lien vers le commentaire
Partager sur d’autres sites

 

Pour résumer (et pour répondre à la dernière interrogation de Bred) : LISP n'est qu'imbrications.

De la même manière qu'une variable globale dans le document garde sa valeur dans une fonction si elle n'est pas déclarée ou est temporairement "écrasée" si elle est déclarée, une variable déclarée dans une fonction conserve sa valeur dans une sous-fonction appelée par celle-ci si elle n'est pas déclarée dans la sous fonction ou est temporairement écrasée si elle est déclarée dans la sous fonction.

 

J'ai lu environ un dizaine de fois ce passage pour le comprendre.

D'accord (gile) j'ai compris, mais comme Bred ce qui me gêne c'est le "temporairement écrasée".

Je pense plutôt que si une variable globale est déclarée dans une fonction, bien que les 2 variables aient le même nom, pour le LISP ce sont 2 entités complètement différentes, et il interprète la variable en fonction de la situation.

 

Lien vers le commentaire
Partager sur d’autres sites

  • 6 mois après...

Bonjour (gile)

 

J’ai revisité l’intégral de ce post (que j’affectionne beaucoup et qui m’a été d’un grand secours à mes débuts), dommage que les liens ne soient plus actif depuis la migration du site..

 

En espérant qu’ils puissent être réactivés un jour en attendant voici ceux que j’ai pu identifier :

 

* Les pionniers (Reini Urban, Vladimir Nesterovsky, etc.) avaient essayé de constituer une gigantesque bibliothèque de routines complémentaires définies en LISP (StdLib) dans laquelle on trouvais les équivalents de la plupart des fonctions vl-* arrivées plus tard avec Visual LISP (exemples ici).

Équivalences à vl-*

http://cadxp.com/index.php?/topic/8252-equivalences-a-vl/

 

On peu trouver ici trois bibliothèques de routines : Dialog.lsp, Vecteurs&Matrices.lsp, Listes.lsp

Les LISP de gile

http://cadxp.com/index.php?/topic/14427-les-lisp-de-gile/

 

Une recherche dans les forums avec defun-q t'aurais fait trouver, entre autres, les sujets suivants :

(defun-q S::STARTUP ...)

Une fonction qui "apprend"

 

(defun-q S::STARTUP ...)

http://cadxp.com/index.php?/topic/23751-defun-q-sstartup/

Une fonction qui "apprend" cadxp.com

http://cadxp.com/index.php?/topic/24489-une-fonction-qui-apprend/

 

 

Nota : A la relecture du sujet dans Les fonctions passées comme argument, j’aurais plutôt vu l’écriture de ssTo0 comme ceci :

(defun c:ssTo0 ()
 (ssmap '(lambda (x) (ChangeLayer x "0")) (ssget))
 (princ)
)

;) :rolleyes:

 

Merci Bruno,

Apprendre => Prendre => Rendre

Lien vers le commentaire
Partager sur d’autres sites

  • 3 mois après...

Bonjour à tous,

 

Je relance le sujet car j'ai 2 questions plutôt simple pour vous mais très flou pour moi débutant j'ai eu beau chercher, je n'ai rien trouver de concret

 

1°)

J'ai 2 fonctions que voici:

(defun c:Fonction1 (/ Fonction2  PINS T1)
(initget (+ 1 2))
(setq PINS (getpoint "\nChoisissez le point d'insertion:"))
(setq T1 (getstring "\nText: "))		
)
(Fonction2 INSERTIONC X)

 

(defun Fonction2 (P1 T2/)
(command "_circle" P1 6.7)
(command "TEXTE" "J" "MC" P1 "8" "0" T2)
)

 

La fonction1 fait appel à la fonction2 mais je ne sais pas comment l'imbriquer ou la déclarer de manière a ce que je n'ai pas:

; erreur: no function definition: Fonction2 

et surtout que la fonction1 fonctionne correctement.

 

2°)

après avoir lu le message explicatif de (gile) sur les arguments je me demande si le nombre d'argument d'une fonction est obligatoirement fixe?

 

JeanV.

je comprend vite mais il faut m'expliquer longtemps ;)

Lien vers le commentaire
Partager sur d’autres sites

Salut,

 

Il semble que tu n'aies pas bien compris la notion de variable locale (que ce soit un valeur ou une fonction).

 

Une variable déclarée localement limite la portée de la variable à la fonction dans la quelle elle est déclarée ce qui veut dire que la valeur de la variable ou la définition de la fonction n'existera telle qu'elle est définie dans la fonction que dans celle-ci mais aussi que toute autre variable ou fonction du même nom définie à l'extérieur de la fonction sera inaccessible dans cette fonction.

 

Il faut donc écrire, si tu veux que Fonction2 n'existe telle quelle que dans Fonction1 :

(defun c:Fonction1 (/ Fonction2  PINS T1)

 (defun Fonction2 (P1 T2/)
   (command "_circle" P1 6.7)
   (command "TEXTE" "J" "MC" P1 "8" "0" T2)
 )

 (initget (+ 1 2))
 (setq PINS (getpoint "\nChoisissez le point d'insertion:"))
 (setq T1 (getstring "\nText: "))  
 (Fonction2 PINS T1)              
)

 

Ou, si tu veux que Fonction2 soit accessible pour d'autres fonction, il ne faut pas la déclarer dans Fonction1

 

(defun c:Fonction1 (/ PINS T1)
 (initget (+ 1 2))
 (setq PINS (getpoint "\nChoisissez le point d'insertion:"))
 (setq T1 (getstring "\nText: ")) 
 (Fonction2 PINS T1)               
)

(defun Fonction2 (P1 T2/)
 (command "_circle" P1 6.7)
 (command "TEXTE" "J" "MC" P1 "8" "0" T2)
)

 

Dans les deux cas, il faut que l'appel à Fonction2 soit à l'intérieur de Fonction1 et que les arguments qui lui sont passés soient connus dans Fonction1 : INSERTIONC et X n'existent pas dans le code que tu a posté.

Gilles Chanteau - gileCAD -
Développements sur mesure pour AutoCAD
ADSK_Expert_Elite_Icon_S_Color_Blk_125.png

Lien vers le commentaire
Partager sur d’autres sites

Merci pour la réponse (gile),

 

J'aurais dû mieux me relire dans un premier temps car effectivement les arguments "INSERTIONC et X" que je donne a la fonction2 dans la fonction1 ne sont pas déclaré pour cause j'ai modifié les noms des variable lors de mon poste pour essayer d'être plus claire et j'ai oublier de les changer. De plus dans la fonction1 ma parenthèse de fin de fonction est placée une ligne trop tôt. Promis je ferais mieux la prochaine fois ! :mellow:

 

Concernant le sujet en lui même, si j'ai bien compris

  • Si je fait de ma fonction, une fonction local, je doit la "déclarer" avant de l'utilisée comme dans ton 1er code en prenant soin de la marquer dans les variables local entre les () de defun.
  • Si je fait un fonction globale, je les sépare comme dans ton 2ème code

 

 

Je t’avouerais qu'avant de poster j'ai tourné dans tous les sens les 2 fonctions et je me suis retrouvé dans le même cas que là (quand je n'avais pas le problème de la fonction manquante):

 

Commande: FONCTION1

Choisissez le point d'insertion:
Text: F
_circle Spécifiez le centre du cercle ou [3P/2P/Ttr (tangente tangente rayon)]:
Spécifiez le rayon du cercle ou [Diamètre] <6.7000>: 6.700000000000001
Commande: TEXTE
Style de texte courant:  "Standard"  Hauteur de texte:  2.5000  Annotatif:  Non
Spécifiez le point de départ du texte ou [Justifier/Style]: J Entrez une option 
[Aligne/Fixé/Centre/Milieu/Droite/HG/HC/HD/MG/MC/MD/BG/BC/BD]: MC
Spécifiez le milieu du texte:
Spécifiez la hauteur <2.5000>: 8
Spécifiez l'angle de rotation du texte <0>: 0
Entrez le texte:
Commande: nil

 

Le cercle bien tracé mais pas de texte. Cela provient-il de mon code de débutant ? Ai-je oublié quelque chose?

je comprend vite mais il faut m'expliquer longtemps ;)

Lien vers le commentaire
Partager sur d’autres sites

Créer un compte ou se connecter pour commenter

Vous devez être membre afin de pouvoir déposer un commentaire

Créer un compte

Créez un compte sur notre communauté. C’est facile !

Créer un nouveau compte

Se connecter

Vous avez déjà un compte ? Connectez-vous ici.

Connectez-vous maintenant
×
×
  • Créer...

Information importante

Nous avons placé des cookies sur votre appareil pour aider à améliorer ce site. Vous pouvez choisir d’ajuster vos paramètres de cookie, sinon nous supposerons que vous êtes d’accord pour continuer. Politique de confidentialité