---
layout: default
math: true
---

# Macros

En Julia, il est possible de définir des expressions ("quoted expressions") qui sont analysées syntaxiquement ("parsées"),
mais pas évaluées, ce qui est assez pratique pour réutiliser un bout de code régulièrement. Par exemple, on peut a souvent
besoin d'échanger le contenu de deux variables, ce qui peut se faire sans variables intermédiaires avec un ou exclusif\* :

    swap = :(
        x = x ^ y
        y = y ^ x
        x = x ^ y
    )

\* c'est une [mauvaise idée](https://en.wikipedia.org/wiki/XOR_swap_algorithm#Reasons_for_avoidance_in_practice) en pratique.

On peut alors insérer `eval(swap)` dans notre code pour échanger le contenu de `x` et `y`,
pour calculer la distance entre `x` et `y` de manière totalement inéfficace, par exemple :


    function distance(x, y)
        if x < y
            eval(swap)
        end
        return x - y
    end


Pas très pratique, puis qu'on ne peut faire ça que si les variables qui nous intéressent
sont `x` et `y`. Pour plus de flexibilité, on peut définir une macro, sorte d'expression "quotée"
paramétrée. Pour assigner à un vecteur les coordonnées du k-ième vecteur de la base canonique, ça
donnerait :

    macro set_canonical(vector, k)
        :( fill!($vector, 0); $vector[$k] = 1 )
    end

On peut alors faire quelque chose comme

    vector = ones(4)/2;
    @set_canonical(vector, 3)
    vector
    # 4-element Array{Float64,1}:
    #  0.0
    #  0.0
    #  1.0
    #  0.0

Les différences avec une fonction :
- le code est remplacé à la compilation, il n'y a pas d'appel.
- le code d'une macro est collé tel quel à l'endroit où elle est utilisée, elle peut faire référence à des variables du contexte parent.

À la différence d'`eval`, une macro est substituée à la compilation.

## Portée des variables et modules

Comme une fonction, une macro peut-être définie dans un module.

    # algebre.jl
    module algebre
    macro set_canonical(vector, k)
        :( fill!($vector, 0); $vector[$k] = 1 )
    end
    end

Mais il faut alors faire attention :

    import algebre.jl

    vector = zeros(4)
    @algebre.set_canonical(vector, 3)

    # ERROR: LoadError: UndefVarError: vector not defined

Les variables évaluées dans la macro sont évaluées dans le contexte du module, pas celui où la macro est utilisée. C'est plus compliqué d'une simple copie de code. Comme `vector` n'existe pas dans le contexte du module, d'où l'erreur. Pour palier à ça, on peut utiliser `esc`, qui permet d'accéder au contexte parent depuis un module.

    macro set_canonical(vector, k)
        esc(:( fill!($vector, 0); $vector[$k] = 1 ))
    end
