homepage/notes/julia/index.markdown
2016-04-21 11:51:36 +02:00

2.6 KiB

layout math
default 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 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