Aller au contenu

Un Defun dans un Defun, mais qu'en est-il en fait ?


Messages recommandés

Posté(e)

Bonjour à la communauté.
Je tente de comprendre l'intérêt de placer un (defun dans un autre...
Je pense que ça a été maintes fois discuté (je n'ai rien trouvé de probant), j'en reviens donc au plus simple, avec un petit teste (je teste de plus en plus mes maigres connaissances)...
Car je pense que ça doit être très pratique pour la gestion des variables (il me semble).
Les variables déclarées dans le defun principal restent "vivantes" dans les sous-defun, d'après ce que j'ai compris...
Jai donc fais un bout de Lisp "<SansNom-0>" que je créer à la volée, juste histoire de comprendre 

(defun c:MacroA (/ Var1)
  (setq Var1 "Pouette")
  (princ (strcat "\nVar1=" Var1))
  (MacroB)
  (princ (strcat "\nVar1=" Var1))
  (princ (strcat "\nVar2=" Var2))
  (defun MacroB (/ Var2)
    (setq Var2 "Prout")
    ;; Je sais, mais que faire ? ? ?
    (princ (strcat "\nVar2=" Var2))
    (setq Var1 "EtRePouette")
  ) ;_ Fin de defun
) ;_ Fin de defun

C'est juste un petit teste pour comprendre l'étendu des valeurs des variables du truc du machin, voir plus...
Mais, ma console me répond 

; 1 feuille chargé à partir de #<editor "Chargement de <SansNom-0>...">
_$ MacroA
nil
_$ Var1
nil
_$ MacroB
nil

_$ MacroA   ->   nil    Je n'ai pas compris
_$ MacroB   ->   nil    Je m'en doutais.

Mais pourquoi rien ne fonctionne ?

Windows 11 / AutoCAD 2024

Sur terre, il y a 10 types de personnes, celles qui comptent en binaire et les autres (developpez.net).
Davantage d'avantages, avantagent davantage (Bobby Lapointe).
La connaissance s'accroît quand on la partage (Socrate).
Tant va la cruche à l'eau que l'habit n'amasse pas mousse avant de l'avoir tué. (Moi)

Posté(e)

Salut,

Je ne suis pas certain de comprendre ce que tu cherches à faire mais le symbole 'MacroA' est nil parce qu'il n'y a ni (setq MacroA ..) ni (defun MacroA ...) dans ton code.

Le nom de la fonction est : 'c:MacroA' et si tu veux lancer la fonction dans la console il faut le mettre entre parenthèses : (c:MacroA)

Gilles Chanteau - gileCAD - GitHub
Développements sur mesure pour AutoCAD

Posté(e)

Salut @(gile), et merci pour ta réponse.
Effectivement, j'avais mal tapé dans la console (gros oublis de ma part, mais je la découvre, et de plus en plus).
Mais j'ai maintenant

_$ (c:MacroA)
Var1=Pouette
; erreur: no function definition: MACROB
_$ 

Mon but est surtout de lancer les "sous-macros", juste pour éviter de répéter une séquence de code "commun" (MacroB) entre les différentes parties d'un code "général" (MacroA).
Je pense que c'est un peu comme la gestion des erreurs (mais j'en suis très loin d'être certain), dans mon code "MacroA" (par exemple), je fais souvent appel à la même séquence de code, le même traitement de données, qui peut être regroupée (mutualisé ?) dans un seul "sous-defun").
C'est un peu le "GoSub" de l'ancien temps (comme dans Turbo Basic par exemple), on va traiter une donnée ailleurs, et cet algorithme de traitement peu servir plusieurs fois, dans la même "Macro"...
Je ne suis pas certain d'avoir été plus clair...

Windows 11 / AutoCAD 2024

Sur terre, il y a 10 types de personnes, celles qui comptent en binaire et les autres (developpez.net).
Davantage d'avantages, avantagent davantage (Bobby Lapointe).
La connaissance s'accroît quand on la partage (Socrate).
Tant va la cruche à l'eau que l'habit n'amasse pas mousse avant de l'avoir tué. (Moi)

Posté(e)

Bonjour,

En fait, quand tu défini une fonction dans une autre, c'est pour qu'elle soit exécuté dans celle-ci.

C'est pour cela qu'il faut la déclarer comme une variable, dans ton cas (defun c:MacroA (/ MacroB Var1).

Si tu veux qu'elle soit accessible ailleurs tu l'écris à coté, pas dedans.

Je ne suis pas doué pour les explications....

Posté(e)

Bonjour @Fraid, et merci pour ton explication, je n'avais pas vu une "sous-macro" comme une variable...

Windows 11 / AutoCAD 2024

Sur terre, il y a 10 types de personnes, celles qui comptent en binaire et les autres (developpez.net).
Davantage d'avantages, avantagent davantage (Bobby Lapointe).
La connaissance s'accroît quand on la partage (Socrate).
Tant va la cruche à l'eau que l'habit n'amasse pas mousse avant de l'avoir tué. (Moi)

Posté(e)

Le grand principe est que tout est liste en Autolisp.

Et, comme une variable tu peux faire (type ma_fonction) et te retourne SUBR.

D'ailleurs, si tu as des problèmes de portée de variables,

c'est essentiellement dû au fait que tu oublie le rôle des arguments et du retour qui sont fait pour cela.. 

Posté(e)

Coucou @DenisHen,

Considère les fonctions/commandes comme une maison ou des boîtes avec des portes permettant d'inviter certaines variables ou de retenir prisonnières d'autres variables. Les parenthèses situées à la suite du nom de ta fonction permet de définir tes variables/fonctions localement. Dans le cas ci-dessous, on a pas déclarer de variables locales donc on a une première boîte (nommée fun_1) qui donne naissance à la variable A et celle-ci est libre de sortir de la boîte (fun_1).

(defun fun_1 ()
  (setq a 0)
)

Ainsi

command: (fun_1)
0
command: !a
0

Désormais si je déclare B localement, celle-ci sera créée dans la boîte (fun_2) mais en sera prisonnière (sa valeur retournera à nil une fois l'exécution de (fun_2) terminée).

(defun fun_2 (/ b)
  (setq b 10)
)

Donc

command: (fun_2)
10
command: !b
nil
command: !a
0

Ici A est toujours présent car il n'a jamais été remis à nil ou bien modifié. Si désormais je déclare une variable A localement dans une fonction (fun_3), alors celle-ci sera initialisée à nil au lancement de la fonction mais sa valeur est enregistrée et une fois l'exécution de la fonction terminée, elle retrouve sa valeur initiale sans prendre en compte les modifications opérées au cours de la fonction (fun_3). Par exemple

(defun fun_3 (/ a)
  (setq a (1+ a))
)

on aurait

command: !a
0
command: (fun_3)
; erreur: type d'argument incorrect: numberp: nil

mais si on écrit (fun_3) ainsi

(defun fun_3 (/ a)
  (setq a 1)
)

alors

command: !a
0
command: (fun_3)
1
command: !a
0

Si dans le cas contraire, je veux avoir une variable capable de se déplacer entre certaines fonctions, alors on ne la déclare pas localement UNIQUEMENT dans les fonctions supposées communiquer entre-elles.

(defun fun_4 ()
  (setq a (1+ a))
)

ici on pourra désormais avoir

command: !a
0
command: (fun_4)
1
command: (fun_4)
2
command: !a
2

et si je repasse avec (fun_1)

command: (fun_1)
0
command: (fun_4)
1
command: !a
1

Bien donc chat c'est la logique avec les variables déclarées ou non localement. Donc si on reprends la métaphore des petites boîtes :
 - On a 4 boîtes nommées (fun_1), (fun_2), (fun_3) et (fun_4).
 - La boîte (fun_1) permet de créer une variable A et lui affecte la valeur 0. Cette variable est libre de sortir de cette boîte et de faire sa vie, mais à chaque fois qu'on rentrera dans cette boîte, A aura toujours la valeur 0 à la fin.
 - La boîte (fun_2) permet de créer
une variable B et lui affecte la valeur 10. Cette variable est prisonnière de cette boîte donc à chaque fois qu'on rentre dans cette boîte, B est crée puis disparaît une fois qu'on sort de la boîte (quelque soit le moyen, donc même si une erreur est rencontrée, B disparaît dans tous les cas !).
 - La boîte (fun_3) permet de créer une variable A, totalement indépendante de la variable A créée par (fun_1) et ayant le même comportement que B avec (fun_2).
 - La boîte (fun_4) permet d'inviter la variable A et de lui rajouter la valeur 1. On peut donc déduire que si l'on utilise la boîte (fun_4) avant la boîte (fun_1), alors on aura une erreur car A n'existe pas encore, donc impossible de lui rajouter une valeur, puisque (fun_4) dépend directement de l'existence de A.

Si désormais on applique cette logique aux fonctions imbriquées :

(defun fun_5 ()
  (defun fun_6 ()
    (setq c 5)
  )
)

alors on a bien

command: (fun_5)
5
command: (fun_6)
5
command: !c
5

Et si l'on déclare cette fois-ci (fun_8) localement, alors celle-ci sera créée uniquement lors de l'utilisation de (fun_7) mais celle-ci reprendra sa valeur initiale une fois l'exécution de (fun_8) terminée, c'est-à-dire nil dans le cas présent :

(defun fun_7 (/ fun_8)
  (defun fun_8 ()
    (setq c 7)
  )
)

donc

command: (fun_7)
7
command: (fun_8)
; erreur: no function definition: FUN_8
command: !c
7

Bien et si désormais je déclare localement (fun_6)

(defun fun_9 (/ fun_6)
  (defun fun_6 ()
    (setq c 9)
  )
)

j'aurais

command: !c
7
command: (fun_9)
9
command: !c
9
command: (fun_6)
5
command: !c
5
command: (fun_5)
5

Donc on comprend bien ici que (fun_6) possède un fonctionnement différent selon qu'elle soit appelée depuis (fun_9) par rapport à (fun_5) ou bien (fun_6). Et si l'on considère que (fun_5) n'a jamais été exécutée auparavant alors on aura

command: (fun_9)
9
command: (fun_6)
; erreur: no function definition: FUN_6
command: (fun_5)
5
command: (fun_6)
5
command: !c
5

car en effet, (fun_6) n'a aucune existence globale avant l'exécution de (fun_5). Et c'est d'ailleurs là tout l'intérêt de déclarer certaines fonctions localement et d'autres non. Car parfois, on va créer une fonction qui servira dans plusieurs fonctions/commandes (donc on ne les déclare pas localement et elles ont un (defun) isolé) et d'autres fois on va avoir besoin d'une fonction utile uniquement pour une unique fonction ou commande. Et plutôt que de se pourrir notre espace avec des fonctions à usage unique local, on préfère les déclarer localement pour quelle n'existent qu'uniquement en cas de besoin.
Cela va arriver très souvent lorsqu'on écrit par exemple une fonction récursive ou même simplement une fonction qui va nous servir localement mais qui n'a pas vraiment de nom défini. Ces fonctions seront bien souvent nommées (foo) voire (f) qui est juste un nom arbitraire et dont on sait qu'aucune autre fonction n'aura besoin d'accéder à cette fonction. C'est d'ailleurs pour cela qu'on se retrouve très souvent avec plusieurs fonctions (foo)/(f) multiples mais totalement différentes dans les déclarations locales de fonctions. Car au même titre qu'il est IMPERATIF de déclarer localement ses variables sauf si une variable DOIT pouvoir être utilisée entre plusieurs fonctions (et dans ce cas précis, on va favoriser un nom différent des autres variables et plutôt équivoque comme par exemple **variable**). Car en effet si on prend l'exemple d'une liste :

(defun fun_10 ()
  (setq l (cons 2 l))
)

(defun fun_11 ()
  (setq l (cons (getpoint "\nPoint : ") l))
)

donc ici on a 2 utilisations différentes pour l, mais comme la variable n'est pas déclarée localement, cela devient vite le bordel

command: (fun_10)
(2)
command: (fun_10)
(2 2)
command: (fun_11)
((10.7014 5.86029 0.0) 2 2)
command: (fun_10)
(2 (10.7014 5.86029 0.0) 2 2)
command: (fun_11)
((43.4978 27.9322 0.0) 2 (10.7014 5.86029 0.0) 2 2)
...

Et donc cela peut s'avérer très problématique par exemple si tu cherches à lister les points d'une polyligne mais que tu ne remets pas la liste à nil une fois le programme terminée. Car à la prochaine polyligne, elle aura des points ne lui appartenant pas !
Pour les fonctions déclarée localement c'est pareil. Notamment tu peux trouver une déclaration locale de la fonction (*error*) et il faut ABSOLUMENT la déclarer localement si tu la modifies (il existe une autre méthode prenant la création d'une fonction erreur perso, de lui affecter la valeur de *error* puis de rendre la valeur initiale de *error* une fois terminée, comme on le ferait pour les (getvar)/(setvar), mais perso c'est pas la méthode la plus sûre à mes yeux). Car il ne faut surtout pas perdre sa définition initiale car les fonctions natives d'AutoCAD l'utilisent aussi donc il vaut mieux éviter de la modifier.

(defun foo (/ *error*)
  (defun *error* (msg)
    (setq a 0)
    (princ msg)
  )
  (setq a (1+ a))
)

Donc par exemple ici, si A n'est pas un nombre alors A prend la valeur 0 (non déclaré localement) en cas d'erreur puis la fonction (*error*) reprend sa valeur initiale telle que prévue par AutoCAD. Donc

command: !a
nil
command: (foo)
; erreur: type d'argument incorrect: numberp: nil
command: !a
0
command: (foo)
1
command: (foo)
2
command: !a
2

Désormais si l'on considère cette vision avec les petite boîte on peut en réalité comprendre qu'on a déjà des boîtes imbriquées via AutoCAD :
 - une boîte correspondant à Windows (par exemple les variables environnements avec (getenv) et (setenv) ou même les HKEY..)
 - une boîte correspondant à la session AutoCAD (comme par exemple (vlax-get-acad-object) permettant de connaître le nom des dessins ouverts)
 - une boîte correspondant au DWG (comme par exemple la base de données DXF de toutes les entités contenues dans le dessin, etc..)
 - et enfin vient les boîtes avec les différentes fonctions/commandes. Puis les sous-fonctions, etc...
C'est pour cela que les variables non déclarées localement ne sont enregistrées que dans le DWG dans lequel elle a été crée, car on ne peut pas remonter l'arborescence avec de simple variables (donc créer une variable A dans Dessin1 et demander la valeur de A dans Dessin2, cela retourne nil). Mais les variables environnement permettent de communiquer entre plusieurs dessins ouverts, et même entre plusieurs sessions d'AutoCAD.

Si on reprend par exemple les fichiers acad.lsp et acaddoc.lsp dont le chargement (si inchangé) se fait respectivement au démarrage d'une session AutoCAD et l'autre à chaque ouverture d'un dessin, et bien le fonctionnement est le même. Si on ouvre un 1er Dessin, puis on modifie l'expression d'une fonction et qu'on ouvre ensuite un 2nd Dessin. Suivant que la fonction soit définie sur acad.lsp ou acaddoc.lsp, notre fonction modifiée aura soit, 2 exécutions différentes entre Dessin1 et Dessin2, soit l'ancienne expression que se soit Dessin1 ou Dessin2.

Bref...je parle trop ^^" mais considère que la déclaration locale des variables est systématique sauf cas exceptionnels tandis que la déclaration locale des fonctions c'est plutôt l'inverse. Tu auras plutôt envie de ne pas les déclarer sauf cas exceptionnel lorsque tu sais que tu n'utiliseras jamais cette fonction ailleurs ou bien que tu n'as pas d'autres choix (*error*). Ensuite si tu réfléchis avec le principe de boîtes, pour répondre à ta question, si tu déclares localement une variable dans une fonction, et que tu as une sous-fonction, cette sous-fonction étant à l'intérieur de la boîte fonction, la variable (même déclarée localement) pourra être utilisée par la sous-fonction comme si elle été "globale". Et inversement si tu déclares une variable localement dans ta sous-fonction, alors ta fonction principale ne pourra pas connaître sa valeur car elle retournera à sa valeur initiale.

(defun fun_12 (/ foo a)
  (defun foo (b)
    (* a (1+ b))
  )
  (setq a 1)
  (foo a)
)

ici on a

command: !a
nil
command: !b
nil
command: (fun_12)
2
command: !a
nil
command: !b
nil

et si tu prends

(defun fun_13 (/ foo b)
  (defun foo (b)
    (setq b (* a (1+ b)))
  )
  (setq a 1)
  (setq a (1+ (foo a)))
  b
)

alors

command: (fun_13)
2
command: !a
3
command: !b
nil

car ici, on définie la variable b avec (foo) qui prend alors la valeur 2, mais comme on déclare b localement dans (fun_13), alors b retourne à nil (ou valeur initiale) une fois (fun_13) terminée.

Bon assez parlé :3

Bisous,
Luna

  • Upvote 1
Posté(e)

Coucou @Luna.
Un super grand merci pour avoir pris autant de temps pour faire une explication très exhaustive (j'en ai fais un petit PDF que j'ai placé à coté du "Introduction à AutoLISP" de (gile))...
Mais je n'arrive pas à m'expliquer l'échec de mon petit code "teste" :
En relisant mainte et mainte fois tes explications, je viens de comprendre pourquoi l'échec de ça :

;;; Petit teste de durée et de partage de variable.
(defun c:MacroA (/ Var1) 
  (setq Var1 "Avant MacroB")
  (princ (strcat "\nVar1=" Var1))
  (MacroB)
  (princ "\nRetour dans MacroA")
  (princ (strcat "\nVar1=" Var1 "\tVar2=" Var2))
  (defun MacroB (/ Var2)
    (princ "\nArrivé dans MacroB")
    (setq Var2 "Dans MacroB")
    (princ (strcat "\nVar2=" Var2))
    (setq Var1 "Dans MacroB")
  ) ;_ Fin de defun
  (princ)
) ;_ Fin de defun

Il faut placer les autres (defun au début :

;;; Petit teste de durée et de partage de variable.
(defun c:MacroA (/ Var1 Var2)
  (defun MacroB (/ )
    (princ "\nArrivé dans MacroB")
    (setq Var2 "Dans MacroB")
    (princ (strcat "\nVar2=" Var2))
    (setq Var1 "Dans MacroB")
  ) ;_ Fin de defun
  (setq Var1 "Avant MacroB")
  (princ (strcat "\nVar1=" Var1))
  (MacroB)
  (princ "\nRetour dans MacroA")
  (princ (strcat "\nVar1=" Var1 "\tVar2=" Var2))
  (princ)
) ;_ Fin de defun

Je ne sais pas si c'est l'âge qui commence à s'installer, mais j'ai pris un temps fou à voir ce détail ! !
Mais que je trouve évident maintenant. Je pense qu'il ne peut être exécuté un (defun s'il n'a pas été "chargé" au préalable.
Je ne vois que ça comme explication...

Donc, en ce moment, je m'"amuse" à retoucher ce petit code teste dans tous les sens pour bien m'exercer à ce genre de manipulation de variables, et bien piger le pourquoi du comment...

En tous cas, encore un super grand merci pour ta réponse. Tu m'as enlevé une belle bûche du pied ! ! !

Bisous, Denis...

Windows 11 / AutoCAD 2024

Sur terre, il y a 10 types de personnes, celles qui comptent en binaire et les autres (developpez.net).
Davantage d'avantages, avantagent davantage (Bobby Lapointe).
La connaissance s'accroît quand on la partage (Socrate).
Tant va la cruche à l'eau que l'habit n'amasse pas mousse avant de l'avoir tué. (Moi)

Posté(e)

Coucou,

Vui, le programme s'exécute ligne après ligne (ou plus exactement, fontions/arguments après fonctions/arguments) et si une erreur apparaît, le programme est interrompu. Donc ici comme le (defun) imbriqué est à la fin, on se retrouve avec le cas de figure de (fun_6) qui a besoin que (fun_5) soit exécutée auparavant, sinon (fun_6) n'existe pas encore dans la "timeline" du programme, entraînant alors une erreur. C'est pour cela que tous les (defun) imbriqués sont toujours en début de programme (ou à minima avant l'appel du sous-programme).
Mais je suis heureuse de voir que tu es compris cela par toi-même 🙂

Bisous,
Luna

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é