Hlavní navigace

Programovací jazyk Julia: metaprogramování, makra a AST

11. 8. 2016
Doba čtení: 18 minut

Sdílet

Sedmá část seriálu o jazyce Julia je věnována konceptu metaprogramování a tvorbě maker. V jazyce Julia lze pracovat přímo s parsovaným kódem, modifikovat tento kód či si dokonce s využitím maker nechat kód vygenerovat.

Obsah

1. Programovací jazyk Julia: metaprogramování, makra a AST

2. Parsing zdrojového kódu, použití funkce parse()

3. Interní reprezentace výrazů

4. Použití funkce dump()

5. Složitější výrazy a rekurzivní podoba AST

6. Zobrazení S-výrazu parsované části zdrojového kódu

7. Vyhodnocení výrazů s využitím funkce eval()

8. Programová či „ruční“ konstrukce výrazů

9. Použití znaku : namísto konstruktoru Expr

10. Tvorba výrazů a použití interpolace

11. Makrosystém programovacího jazyka Julia

12. Odkazy na Internetu

1. Programovací jazyk Julia: metaprogramování, makra a AST

Programovací jazyk Julia byl navržen takovým způsobem, aby byl snadno pochopitelný, čitelný a taktéž s ohledem na jeho případnou rozšiřitelnost v budoucnosti. Ovšem fakt, že tvorba krátkých skriptů i rozsáhlejších programů v jazyku Julia je poměrně jednoduchá, nemusí nutně znamenat, že v Julii není možné v případě potřeby vytvářet i složitější konstrukce popř. si ušetřit práci a nechat si část programového kódu (nikoli nutně zdrojového kódu) vygenerovat. Je to umožněno především díky existenci makrosystému, který je založen na manipulaci se symboly, které vzniknou po parsingu zdrojového kódu a které tvoří AST (Abstract Syntax Tree). Ovšem na tomto místě je vhodné poznamenat, že makra používaná v jazyku Julia nemají prakticky nic společného s makry, které známe například z jazyků C či C++, popř. s makry, na jejichž zpracování je založen jazyk m4.

V jazycích C, C++ i m4 se totiž makra používají pro „pouhé“ provádění textových substitucí, zatímco makrosystém implementovaný v jazyku Julia je založen na modifikaci AST, což umožňuje mnohem hlubší zásahy do kódu (například přidání nových operátorů). V tomto ohledu má jazyk Julia velmi blízko k LISPovským jazykům, v nichž je většinou makrosystém prakticky nedílnou součástí programovacího jazyka, protože jsou v něm realizovány mnohdy i základní programové konstrukce (navíc je LISP homoikonickým jazykem, což situaci dále zjednodušuje). Asi nejtypičtějším příkladem použití maker v LISPu je makro loop použité v Common Lispu (na druhou stranu někteří vývojáři soudí, že podobná makra zbytečně do Common Lispu přidávají imperativní kód). Některé vlastnosti tohoto makra jsou popsány na stránce http://www.ai.sri.com/pkar­p/loop.html.

Poznámka: v odkazech na další zdroje naleznete i několik článků o makrech v jazyku Clojure. Není to náhoda, protože způsob tvorby maker i použitou terminologii nalezneme právě v jazyku Julia.

2. Parsing zdrojového kódu, použití funkce parse()

Ještě předtím, než si popíšeme způsob tvorby maker v jazyku Julia, je vhodné se zmínit o tom, jakým způsobem je vlastně zpracováván zdrojový kód zapisovaný uživatelem do interaktivní smyčky REPL či kód delších programů a knihoven načítaný z externích souborů. Jednotlivé výrazy a z nich tvořené ucelené bloky kódu (například programové smyčky) jsou předány do funkce nazvané parse(). Tato funkce analyzuje text (s použitím lexikální a syntaktické analýzy), zkonstruuje z něho AST a nakonec vrátí objekt typu Expr, který vlastně (poněkud zjednodušeně řečeno) reprezentuje AST daného výrazu či bloku kódu. Funkce parse() ve své základní podobě akceptuje řetězec, v němž nalezne a analyzuje první ucelený výraz, ovšem lze ji volat i s dalšími parametry, které ovlivní její činnost. To je ostatně patrné i při pohledu do dokumentace:

help?> parse
search: parse parseip parseint parsefloat ParseError sparse sparsevec
 
  parse(str, start; greedy=true, raise=true)
 
  Parse the expression string and return an expression (which could later be
  passed to eval for execution). start is the index of the first character to
  start parsing. If greedy is true (default), parse will try to consume as
  much input as it can; otherwise, it will stop as soon as it has parsed a
  valid expression. Incomplete but otherwise syntactically valid expressions
  will return Expr(:incomplete, "(error message)"). If raise is true
  (default), syntax errors other than incomplete expressions will raise an
  error. If raise is false, parse will return an expression that will raise an
  error upon evaluation.
 
  parse(str; raise=true)
 
  Parse the expression string greedily, returning a single expression. An
  error is thrown if there are additional characters after the first
  expression. If raise is true (default), syntax errors will raise an error;
  otherwise, parse will return an expression that will raise an error upon
  evaluation.
 
  parse(type, str, [base])
 
  Parse a string as a number. If the type is an integer type, then a base can
  be specified (the default is 10). If the type is a floating point type, the
  string is parsed as a decimal floating point number. If the string does not
  contain a valid number, an error is raised.

Podívejme se, jak vypadá výsledek volání funkce parse(). Zdánlivě a minimálně na první pohled se nejedná o žádnou „velkou vědu“, neboť výsledkem volání je opticky prakticky stejný text, který je akorát umístěn do závorek, před nimiž je znak : (dvojtečky). Jak však uvidíme dále, jedná se pouze o zkrácený zápis obsahu objektu typu Expr:

julia& parse("1+2")
:(1 + 2)
julia& parse("a+b")
:(a + b)
julia& parse("a+b*2")
:(a + b * 2)
julia& parse("1+2*3")
:(1 + 2 * 3)
julia& parse("1<2 && 3<=4")
:(1 < 2 && 3 <= 4)
julia& parse("while i&0; println(i); i=i-1; end")
:(while i & 0 # none, line 1:
        println(i) # none, line 1:
        i = i - 1
    end)

3. Interní reprezentace výrazů

Každý objekt typu Expr obsahuje tři atributy pojmenované head, args a typ. Atribut head obsahuje symbol popisující typ výrazu, atribut args jeho argumenty (mohou se zde nacházet symboly, rekurzivně vnořené výrazy popř. konstanty a literály) a konečně atribut typ obsahuje typ výsledku. V případě jednoduchých výrazů obsahuje atribut args pouze pole symbolů a konstant. Podívejme se nejprve, jak k atributům parsovaného výrazu přistupovat. Nejprve si uložíme objekt typu Expr do proměnné:

julia& expression1=parse("1+2")
:(1 + 2)

Posléze si již můžeme jednoduše vypsat typ výrazu:

julia& expression1.head
:call

Vidíme, že typ výrazu je symbol call značící, že se jedná o volání nějaké funkce (již víme, že operátory se dají volat i jako běžné funkce).

Dále si můžeme vypsat návratový typ. Zde se neděje nic překvapivého:

julia& expression1.typ
Any

Podle očekávání je nejzajímavějším atributem atribut args obsahující argumenty volání. Předávají se tři parametry – symbol představující volanou funkci (+) a její dva parametry:

julia& expression1.args
3-element Array{Any,1}:
  :+
 1
 2

Podobně si lze zobrazit i argumenty nepatrně složitější funkce se třemi parametry:

julia& expression2=parse("1+2+3")
:(1 + 2 + 3)
 
julia& expression2.args
4-element Array{Any,1}:
  :+
 1
 2
 3

Nebo volání „obyčejné“ funkce:

julia& expression3=parse("sin(3.1415)")
:(sin(3.1415))
 
julia& expression3.args
2-element Array{Any,1}:
  :sin
 3.1415
julia& expression4=parse("length(\"asd\")")
:(length("asd"))
 
julia& expression4.args
2-element Array{Any,1}:
 :length
 "asd"

4. Použití funkce dump()

Přímý přístup k atributům objektu typu Expr je mnohdy zbytečně zdlouhavý. Pokud je nutné častěji zkoumat AST vygenerovaný výše popsanou funkcí parse(), může být výhodnější použít další užitečnou funkci nazvanou jednoduše dump(). Této funkci lze předat prakticky jakýkoli objekt, samozřejmě včetně objektů typu Expr. Výsledkem je textová podoba interní struktury objektu (v případě objektu Expr tedy atributy head, argstyp):

help?& dump
search: dump xdump randjump reducedim mapreducedim module_name
 
  dump(x)
 
  Show all user-visible structure of a value.

Výše zmíněné výrazy si tedy můžeme přímo v REPLu analyzovat i následujícím způsobem:

julia& expression1=parse("1+2")
:(1 + 2)
 
julia& dump(expression1)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
  typ: Any
julia& expression2=parse("1+2+3")
:(1 + 2 + 3)
 
julia& dump(expression2)
Expr
  head: Symbol call
  args: Array(Any,(4,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
    4: Int64 3
  typ: Any
julia& expression3=parse("sin(3.1415)")
:(sin(3.1415))
 
julia& dump(expression3)
Expr
  head: Symbol call
  args: Array(Any,(2,))
    1: Symbol sin
    2: Float64 3.1415
  typ: Any
julia& expression4=parse("length(\"asd\")")
:(length("asd"))
 
julia& dump(expression4)
Expr
  head: Symbol call
  args: Array(Any,(2,))
    1: Symbol length
    2: ASCIIString "asd"
  typ: Any

5. Složitější výrazy a rekurzivní podoba AST

Při pohledu na výsledky vypisované funkcí dump() si možná čtenář pokládá otázku, proč se vlastně bavíme o AST (Abstract Syntax Tree), když je výsledkem parsingu jednoduchá datová struktura popisující volání nějaké funkce s parametry. Prozatím jsme si totiž ukazovali velmi jednoduché příklady výrazů, které skutečně vedly k takto jednoduchým výsledkům. Ovšem programovací jazyk Julia podle očekávání musí umět zpracovat i složitější výrazy a programové konstrukce. Podívejme se například, co se stane, když namísto výrazu „1+2+3“ použijeme výraz „1+(2+3)“:

julia& expression2=parse("1+2+3")
:(1 + 2 + 3)
 
julia& dump(expression2)
Expr
  head: Symbol call
  args: Array(Any,(4,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
    4: Int64 3
  typ: Any
julia& expression5=parse("1+(2+3)")
:(1 + (2 + 3))
 
julia& dump(expression5)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol +
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any

Vidíme, že se zde volá funkce specifikovaná symbolem +, prvním parametrem této funkce je konstanta 1 a druhý parametr je rekurzivně vyjádřen dalším objektem typu Expr. Zde již tedy začíná být patrné, že se skutečně jedná o rekurzivní datovou strukturu.

I následující výraz bude převeden na strom, přesněji řečeno na dva objekty Expr zanořené do kořenového Expr:

julia& expression6=parse("1<2 && 4>=3")
:(1 < 2 && 4 >= 3)
 
julia& dump(expression6)
Expr
  head: Symbol &&
  args: Array(Any,(2,))
    1: Expr
      head: Symbol comparison
      args: Array(Any,(3,))
        1: Int64 1
        2: Symbol <
        3: Int64 2
      typ: Any
    2: Expr
      head: Symbol comparison
      args: Array(Any,(3,))
        1: Int64 4
        2: Symbol >=
        3: Int64 3
      typ: Any
  typ: Any

Podobně vypadá AST pro konstrukci podmínky:

julia& expression7=parse("if 2>1 then; println(\"vetsi\"); end")
:(if 2 > 1 # none, line 1:
        then # none, line 1:
        println("vetsi")
    end)
 
julia& dump(expression7)
Expr
  head: Symbol if
  args: Array(Any,(2,))
    1: Expr
      head: Symbol comparison
      args: Array(Any,(3,))
        1: Int64 2
        2: Symbol >
        3: Int64 1
      typ: Any
    2: Expr
      head: Symbol block
      args: Array(Any,(4,))
        1: LineNumberNode
          file: Symbol none
          line: Int64 1
        2: Symbol then
        3: LineNumberNode
          file: Symbol none
          line: Int64 1
        4: Expr
          head: Symbol call
          args: Array(Any,(2,))
          typ: Any
      typ: Any
  typ: Any

Posledním příkladem, který si ukážeme, je parsing nekonečné smyčky:

julia& expression8=parse("while true; println(\"looping\"); end")
:(while true # none, line 1:
        println("looping")
    end)
 
julia& dump(expression8)
Expr
  head: Symbol while
  args: Array(Any,(2,))
    1: Bool true
    2: Expr
      head: Symbol block
      args: Array(Any,(2,))
        1: LineNumberNode
          file: Symbol none
          line: Int64 1
        2: Expr
          head: Symbol call
          args: Array(Any,(2,))
          typ: Any
      typ: Any
  typ: Any

6. Zobrazení S-výrazu parsované části zdrojového kódu

Programátoři, kteří se již seznámili s některých jazykem patřícím do LISPovské rodiny, tedy například s AutoLISPem, Common Lispem, Scheme či Clojure, ví, že se v těchto jazycích program zapisuje s použitím takzvaných S-výrazů (symolických výrazů) neboli S-expression (někdy se tento název „vtipně“ zkracuje na sexprs nebo sexps). S-výrazy vlastně přímo vyjadřují podobu AST, což mj. naznačuje, proč je tvorba maker v LISPovských jazycích celkem jednoduchá a přímočará. Vzhledem k tomu, že díky použití S-výrazů lze AST reprezentovat v poměrně čitelné a současně i kompaktní podobě (jako seznam vnořených seznamů, symbolů a konstant), obsahuje i knihovna programovacího jazyka Julia možnost, jak si nechat vypsat libovolný objekt typu Expr „lispovským způsobem“. Vše potřebné zařídí funkce Meta.show_sexpr(), což je ostatně patrné i při pohledu na další příklady (pro ilustraci používám stejné příklady, jako v předchozích kapitolách):

julia& expression1=parse("1+2")
:(1 + 2)
 
julia& Meta.show_sexpr(expression1)
(:call, :+, 1, 2)
julia& expression2=parse("1+2+3")
:(1 + 2 + 3)
 
julia& Meta.show_sexpr(expression2)
(:call, :+, 1, 2, 3)
julia& expression3=parse("sin(3.1415)")
:(sin(3.1415))
 
julia& Meta.show_sexpr(expression3)
(:call, :sin, 3.1415)
julia& expression4=parse("length(\"asd\")")
:(length("asd"))
 
julia& Meta.show_sexpr(expression4)
(:call, :length, "asd")
julia& expression5=parse("1+(2+3)")
:(1 + (2 + 3))
 
julia& Meta.show_sexpr(expression5)
(:call, :+, 1, (:call, :+, 2, 3))
julia& expression6=parse("1<2 && 4>=3")
:(1 < 2 && 4 >= 3)
 
julia& Meta.show_sexpr(expression6)
(:&&, (:comparison, 1, :<, 2), (:comparison, 4, :(>=), 3))
julia& expression7=parse("if 2>1 then; println(\"vetsi\"); end")
:(if 2 > 1 # none, line 1:
        then # none, line 1:
        println("vetsi")
    end)
 
julia& Meta.show_sexpr(expression7)
(:if, (:comparison, 2, :>, 1), (:block,
    :( # none, line 1:),
    :then,
    :( # none, line 1:),
    (:call, :println, "vetsi")
  ))
julia& expression8=parse("while true; println(\"looping\"); end")
:(while true # none, line 1:
        println("looping")
    end)
 
julia& Meta.show_sexpr(expression8)
(:while, true, (:block,
    :( # none, line 1:),
    (:call, :println, "looping")
  ))
julia& Meta.show_sexpr(parse("1+2*(3+4)"))
(:call, :+, 1, (:call, :*, 2, (:call, :+, 3, 4)))

7. Vyhodnocení výrazů s využitím funkce eval()

S první částí činnosti interpretru programovacího jazyka Julia jsme se již seznámili v předchozím textu – je jím parser. Druhá část je nejméně důležitá a z uživatelského hlediska je představována funkcí eval(), které se předá AST a výsledkem volání funkce eval() je příslušný vyhodnocený výraz a popř. i nějaké vedlejší efekty (například výpis textu funkcí println, vytvoření nové proměnné, vytvoření a spuštění nového vlákna atd.). Základní použití funkce eval() je velmi jednoduché – budeme jí totiž zpočátku předávat pouze AST korektně vygenerované funkcí parse():

julia& expression1=parse("1+2")
:(1 + 2)
 
julia& eval(expression1)
3
julia& expression2=parse("1+2+3")
:(1 + 2 + 3)
 
julia& eval(expression2)
6
julia& expression3=parse("sin(3.1415)")
:(sin(3.1415))
 
julia& eval(expression3)
9.265358966049026e-5
julia& expression4=parse("length(\"asd\")")
:(length("asd"))
 
julia& eval(expression4)
3
julia& expression5=parse("1+(2+3)")
:(1 + (2 + 3))
 
julia& eval(expression5)
6
julia& expression6=parse("1<2 && 4>=3")
:(1 < 2 && 4 >= 3)
 
julia& eval(expression6)
true
julia& expression7=parse("if 2>1 then; println(\"vetsi\"); end")
:(if 2 > 1 # none, line 1:
        then # none, line 1:
        println("vetsi")
    end)
 
julia& eval(expression7)
vetsi

Poznámka: v tomto případě není řetězec „vetsi“ výsledkem funkce eval(), ale pouhým vedlejším efektem volání funkce println().

julia& expression8=parse("while true; println(\"looping\"); end")
:(while true # none, line 1:
        println("looping")
    end)
 
julia& eval(expression7)
looping
looping
looping
looping
looping
looping
looping
...

V tomto případě funkce eval() podle předpokladů nikdy neskončí.

Povšimněte si toho, že výraz může obsahovat neznámé proměnné, což při jeho parsingu nevadí. Ovšem při vyhodnocování již taková proměnná musí existovat:

julia& expression9=parse("i/(i+1.0)")
:(i / (i + 1.0))
 
julia& dump(expression9)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol /
    2: Symbol i
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol +
        2: Symbol i
        3: Float64 1.0
      typ: Any
  typ: Any
 
julia& eval(expression9)
ERROR: UndefVarError: i not defined
 
julia& i=3
3
 
julia& eval(expression9)
0.75

8. Programová či „ruční“ konstrukce výrazů

Nyní již máme k dispozici dostatek informací k tomu, abychom mohli začít konstruovat vlastní výrazy. Nejsložitější (resp. přesněji řečeno „nejukecanější“) je explicitní použití konstruktoru objektů typu Expr. Zkusme si nyní vytvořit výraz odpovídající zápisu „1+2*3“:

julia& my_expression=Expr(:call, :+, 1, Expr(:call, :*, 2, 3))
:(1 + 2 * 3)
 
julia& dump(my_expression)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any
 
julia& eval(my_expression)
7

9. Použití znaku : namísto konstruktoru Expr

V demonstračním příkladu ukázaném v předchozí kapitole se nám sice výraz zkonstruovat podařilo, ale samotný zápis jeho konstruktoru jistě neoplývá čitelností ani srozumitelností – takto by se nám makra tvořila velmi špatně. Základem pro radikální zjednodušení konstrukce výrazů je použití symbolu : (dvojtečka), který se používá pro takzvanou „citaci“ (quoting):

julia& my_expression=:(1+2*3)
:(1 + 2 * 3)
 
julia& dump(my_expression)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any
 
julia& eval(my_expression)
7

Pro delší výrazy, například pro zápis programových smyček, je však i použití dvojtečky zbytečně nečitelné. Zde přichází na řadu syntaktický cukr představovaný slovy quote a end:

julia& my_expression=quote
       1+2*3
       end
quote  # none, line 2:
    1 + 2 * 3
end
 
julia& dump(my_expression)
Expr
  head: Symbol block
  args: Array(Any,(2,))
    1: LineNumberNode
      file: Symbol none
      line: Int64 2
    2: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol +
        2: Int64 1
        3: Expr
          head: Symbol call
          args: Array(Any,(3,))
          typ: Any
      typ: Any
  typ: Any
 
julia& eval(my_expression)
7

Použití při deklaraci programové smyčky:

julia& my_loop=quote
       while i<10
       println(i)
       i=i+1
       end
       end
quote  # none, line 2:
    while i < 10 # none, line 3:
        println(i) # none, line 4:
        i = i + 1
    end
end

Poznámka: první klíčové slovo end uzavírá programovou smyčku, druhé slovo end pak blok quote.

Použití takto vytvořeného objektu typu Expr (především reprezentace AST) je přímočaré:

julia& i=0
0
 
julia& eval(my_loop)
0
1
2
3
4
5
6
7
8
9

10. Tvorba výrazů a použití interpolace

Kromě operace citace (quote) se při tvorbě maker ještě velmi často uplatňuje opačná operace – takzvaná interpolace. Interpolace se zapisuje s využitím znaku $ (dolar) a ruší efekt citace pro následující symbol (tento symbol je ihned vyhodnocen – do AST se zapíše až jeho vyhodnocená forma). Ukažme si jednoduchý příklad a rozdíl mezi přímým vyhodnocením výrazu, parsingem výrazu s použitím citace a interpolace a jeho následným vyhodnocením.

Přímé vyhodnocení výrazu:

julia& x=42
42
 
julia& 1+2*x
85

Vytvoření výrazu citací, ale bez interpolace:

julia& ex2=:(1+2*x)
:(1 + 2x)
 
julia& dump(ex2)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 2
        3: Symbol x   zde se stále pracuje se jménem proměnné
      typ: Any
  typ: Any
 
julia& eval(ex2)
85

Vytvoření výrazu citací s interpolací. Proměnná x se vyhodnotí a do AST je uložen výsledek:

julia& ex3=:(1+2*$x)
:(1 + 2 * 42)
 
julia& dump(ex3)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 2
        3: Int64 42   zde se pracuje s vyhodnoceným výrazem/symbolem x
      typ: Any
  typ: Any
 
julia& eval(ex3)
85

Interpolaci lze použít i na složitější výraz, nejenom na jediný symbol. V dalším příkladu se již při konstrukci AST vyhodnotí 2*42=84 a teprve hodnota 84 je skutečně uložena do AST:

julia& ex4=:(1+$(2*x))
:(1 + 84)
 
julia& dump(ex4)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Int64 84
  typ: Any
 
julia& eval(ex4)
85

Na citaci se můžeme dívat jako na pozdržení vyhodnocení výrazu až do okamžiku jeho zavolání, zatímco na interpolaci se můžeme dívat jako na přesně opačnou operace – vynucení vyhodnocení označené části výrazu.

11. Makrosystém programovacího jazyka Julia

Makrosystém programovacího jazyka Julia je založen na tom, že makra jsou spouštěna již ve chvíli, kdy je kód parsován, tedy předtím, než je vůbec spuštěn interpretrem. To znamená, že makra mohou do kódu (resp. do jeho AST) vkládat další symboly, modifikovat symboly, které do původní podoby kódu zapsal uživatel atd. Makro se deklaruje podobným způsobem jako funkce, samozřejmě s tím rozdílem, že se používá klíčové slovo macro. Musíme se však uvědomit, že parametrem či parametry makra není nějaký již vyhodnocený výraz (typicky konstanta), ale zpracovaný AST:

julia& macro sayhello(name)
                  return :( println("Hello, ", $name) )
              end

Použití makra je od volání funkcí odlišné a lze použít dvě podoby:

julia& @sayhello "Root.cz"
Hello, Root.cz
 
julia& @sayhello("Root.cz")
Hello, Root.cz

Pokud by makro mělo více parametrů, budou se v prvním případě oddělovat mezerami, ve druhém čárkami (jako u funkcí).

Další makro můžeme použít v případě, kdy potřebujeme najít chybu v nějakém výrazu, nebo pouze zjišťujeme, jaké výrazy se v programu používají. Makro nejdříve vypíše tvar výrazu a poté vrátí výsledek jeho vyhodnocení:

julia& macro debug(expression)
       println("Evaluating: ", expression)
       return eval(expression)
       end
 
julia& @debug 1+2*3
Evaluating: 1 + 2 * 3
7
 
julia& @debug(1+2*3)
Evaluating: 1 + 2 * 3
7

Prozatím poslední makro, které si ukážeme, lze použít pro aserci. Povšimněte si použití citace i interpolace:

UX DAy - tip 2

julia& macro assert(ex)
           return :( $ex ? nothing : throw(AssertionError($(string(ex)))) )
       end

Příklad použití:

julia& assert(1<2)
 
julia& assert(1>2)
ERROR: AssertionError:
 in assert at error.jl:38

Složitější makra si popíšeme a ukážeme příště.

12. Odkazy na Internetu

  1. S-expression (Wikipedia)
    https://en.wikipedia.org/wiki/S-expression
  2. S-Expressions (Rosetta Code)
    http://rosettacode.org/wiki/S-Expressions
  3. Metaprogramming (Julia)
    http://julia.readthedocs.i­o/en/latest/manual/metapro­gramming/
  4. Introducing Julia/Metaprogramming
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Meta­programming
  5. Tutorial for the Common Lisp Loop Macro
    http://www.ai.sri.com/pkarp/loop.html
  6. Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-i-getting.html
  7. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  8. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  9. Clojure Macros and Metaprogramming
    http://clojure-doc.org/articles/language/macros.html
  10. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  11. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  12. CS 2101 Parallel Computing with Julia
    https://www.coursehero.com/fi­le/11508091/CS-2101-Parallel-Computing-with-Julia/
  13. Julia By Example
    https://samuelcolvin.github­.io/JuliaByExample/
  14. Tasks and Parallel Computing
    http://docs.julialang.org/en/release-0.4/stdlib/parallel/
  15. clock(3) – Linux man page
    http://linux.die.net/man/3/clock
  16. rand_r(3) – Linux man page
    http://linux.die.net/man/3/rand_r
  17. atan2(3) – Linux man page
    http://linux.die.net/man/3/atan2
  18. Calling C and Fortran Code
    http://docs.julialang.org/en/release-0.4/manual/calling-c-and-fortran-code/?highlight=symbol
  19. Array Programming
    https://en.wikipedia.org/wi­ki/Array_programming
  20. Discovering Array Languages
    http://archive.vector.org­.uk/art10008110
  21. no stinking loops – Kalothi
    http://www.nsl.com/
  22. Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
    http://www.vector.org.uk/
  23. APL Interpreters
    http://www.vector.org.uk/?a­rea=interpreters
  24. APL_(programming_language
    http://en.wikipedia.org/wi­ki/APL_(programming_langu­age
  25. APL FAQ
    http://www.faqs.org/faqs/apl-faq/
  26. APL FAQ (nejnovější verze)
    http://home.earthlink.net/~swsir­lin/apl.faq.html
  27. A+
    http://www.aplusdev.org/
  28. APLX
    http://www.microapl.co.uk/
  29. FreeAPL
    http://www.pyr.fi/apl/index.htm
  30. J: a modern, high-level, general-purpose, high-performance programming language
    http://www.jsoftware.com/
  31. K, Kdb: an APL derivative for Solaris, Linux, Windows
    http://www.kx.com
  32. openAPL (GPL)
    http://sourceforge.net/pro­jects/openapl
  33. Parrot APL (GPL)
    http://www.parrotcode.org/
  34. Learning J (Roger Stokes)
    http://www.jsoftware.com/hel­p/learning/contents.htm
  35. Rosetta Code
    http://rosettacode.org/wiki/Main_Page
  36. Why APL
    http://www.acm.org/sigapl/whyapl.htm
  37. Introducing Julia/Functions
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Functi­ons
  38. Functions (Julia documentation)
    http://docs.julialang.org/en/release-0.4/manual/functions/
  39. Evaluate binomial coefficients
    http://rosettacode.org/wi­ki/Evaluate_binomial_coef­ficients
  40. Ackermann function
    http://rosettacode.org/wi­ki/Ackermann_function
  41. Julia (front page)
    http://julialang.org/
  42. Julia – dokumentace
    http://docs.julialang.org/en/release-0.4/
  43. Julia – repositář na GitHubu
    https://github.com/JuliaLang/julia
  44. Julia (programming language)
    https://en.wikipedia.org/wi­ki/Julia_%28programming_lan­guage%29
  45. IJulia
    https://github.com/JuliaLan­g/IJulia.jl
  46. Introducing Julia
    https://en.wikibooks.org/wi­ki/Introducing_Julia
  47. Julia: the REPL
    https://en.wikibooks.org/wi­ki/Introducing_Julia/The_REPL
  48. Month of Julia
    https://github.com/DataWo­okie/MonthOfJulia
  49. Learn X in Y minutes (where X=Julia)
    https://learnxinyminutes.com/doc­s/julia/
  50. New Julia language seeks to be the C for scientists
    http://www.infoworld.com/ar­ticle/2616709/application-development/new-julia-language-seeks-to-be-the-c-for-scientists.html
  51. Julia: A Fast Dynamic Language for Technical Computing
    http://karpinski.org/publi­cations/2012/julia-a-fast-dynamic-language
  52. The LLVM Compiler Infrastructure
    http://llvm.org/
  53. Julia: benchmarks
    http://julialang.org/benchmarks/
  54. Type system
    https://en.wikipedia.org/wi­ki/Type_system
  55. Half-precision floating-point format
    https://en.wikipedia.org/wiki/Half-precision_floating-point_format

Byl pro vás článek přínosný?