Obsah
1. Programovací jazyk Julia: další stříbrná kulka v IT?
3. Další možnosti nabízené REPL
4. Aritmetické operátory, implicitní násobení
9. Použití typů BigInt a BigFloat
11. Speciální funkce zobrazující generovaný nativní kód
1. Programovací jazyk Julia: další stříbrná kulka v IT?
„If you started from the beginning, you could recreate the things that people liked about those languages without so many of the problems.“
Jedním z nejnovějších příspěvků do světa vyšších programovacích jazyků je jazyk nazvaný Julia, jehož první funkční verze byla zveřejněna před čtyřmi roky (i když práce na tomto jazyku začala již v roce 2009). Jedná se o programovací jazyk, který byl navržen takovým způsobem, aby dokázal nahradit takové nástroje, jakými jsou Matlab, R, GNU Octave či Python s knihovnami Numpy a SciPy a současně dokázal překládat programy do optimalizovaného kódu, který by mohl konkurovat Céčku či (dokonce) Fortranu. Důvod je jednoduchý – tvůrci jazyka Julia používali pro svou práci (numerická matematika a statistika) různé nástroje, z nichž každý byl specializován pro určitou oblast (Matlab pro výpočty s maticemi), R pro statistické výpočty apod.), ovšem kooperace mezi těmito nástroji nebyla ideální. Obecné jazyky typu Ruby či Python byly zase (opět podle mínění autorů jazyka Julia) pomalé, zejména při porovnání s klasickým céčkem, ale i s Javou.
Snahou původního autora Stefana Karpinského bylo vytvořit nový programovací jazyk založený na ve své době nejnovějších technologiích typu LLVM a současně používající to nejlepší ze světů imperativních i funkcionálních programovacích jazyků. Výsledkem měl být jazyk, který by byl snadno použitelný (zejména pro amatérské programátory), robustní, škálovatelný a současně i dostatečně rychlý, aby mohl soutěžit s C či Fortranem. Navíc měl jazyk Julia nabízet možnost snadné kooperace s knihovnami vytvořenými právě v C či Fortranu. Jestli se tento cíl skutečně podařilo splnit se pokusíme zjistit jak v tomto článku, tak i v několika navazujících článcích.
2. Interaktivní smyčka REPL
Podobně jako je tomu u mnoha dalších vyšších programovacích jazyků, je i Julia vybavena interaktivní smyčkou REPL (Read-Eval-Print-Loop), do níž může uživatel zadávat jednotlivé příkazy či deklarace, které se ihned předávají interpretru, který je zpracuje a popř. vypíše výsledek příkazu či chybovou zprávu. Smyčka REPL je vybavena pamětí dříve zapsaných příkazů a především pak možností automatického doplňování jmen funkcí atd. klávesou Tab. Nesmíme zapomenout ani na okamžité přepínání smyčky mezi několika režimy (zadávání příkazů, nápověda, shell atd.).
Po instalaci balíčku s programovacím jazykem Julia se interaktivní smyčka spustí příkazem julia. Na mém systému s Fedorou je dostupný poměrně starý interpret verze 0.3.9, ovšem na stránce http://julialang.org/downloads/ lze získat novější verze (nebojte se toho, že i nejnovější verze je pořád 0.x, ostatně ani tak vyspělý nástroj, jakým je Inkscape, taktéž ještě nedosáhl verze 1.x :-):
$ julia _ _ _ _(_)_ | A fresh approach to technical computing (_) | (_) (_) | Documentation: http://docs.julialang.org _ _ _| |_ __ _ | Type "help()" for help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 0.3.9 _/ |\__'_|_|_|\__'_| | |__/ | x86_64-redhat-linux julia>
Ve smyčce REPL funguje automatické doplňování příkazů, které se vyvolá klávesou Tab. Pokud smyčka nedokáže najít a doplnit příkaz jednoznačně (tj. když zadanému textu odpovídá více příkazů), vypíše všechny relevantní příkazy a očekává další akci uživatele. Zkusme si například vypsat všechny dostupné příkazy začínající na „a“:
julia> a abs accept acot acscd airyai airyprime angle applicable asec asind atan2 abs2 acos acotd acsch airyaiprime airyx any apply asecd asinh atand abspath acosd acoth addprocs airybi all any! apropos asech assert atanh abstract acosh acsc airy airybiprime all! append! ascii asin atan atexit
Ve chvíli, kdy uživatel stlačí klávesu „?“, přepne se smyčka REPL do režimu nápovědy, což je ihned viditelné změnou textu i barvy výzvy (promptu):
help?>
Můžeme si vyzkoušet získat nápovědu pro funkci quit, což je velmi snadné (opět lze použít automatické doplňování):
help?> quit Base.quit() Quit the program indicating that the processes completed succesfully. This function calls "exit(0)" (see "exit()"). julia>
3. Další možnosti nabízené REPL
Kromě klávesy Tab, která slouží pro automatické doplňování jmen funkcí a dalších příkazů, je možné používat paměť již zadaných příkazů. Pro vyvolání příkazů z paměti smyčky REPL se používají klasické kurzorové šipky a taktéž klávesové zkratky Ctrl+S a Ctrl+R, jejichž význam je prakticky stejný, jako v shellu.
Zajímavá situace nastane ve chvíli, kdy na vstupním řádku zmáčkneme znak „;“ (středník). V tomto okamžiku se interaktivní smyčka REPL přepne do režimu shellu, v němž očekává stejné příkazy, jako v klasickém shellu (například v BASHi). To, že se režim smyčky REPL změní, poznáme snadno: změní se jak prompt (výzva), tak i barva promtpu na červenou:
shell>
Zkusme si nějaký příkaz spustit:
shell> whoami tisnik julia>
Užitečná je i funkce apropos(), která dokáže najít zadaný identifikátor ve všech dostupných modulech:
julia> apropos("quit") INFO: Loading help data... Base.quit() Base.exit([code]) Base.Profile.clear_malloc_data() Base.less(file::String[, line]) Base.less(function[, types]) Base.edit(file::String[, line]) Base.edit(function[, types])
julia> apropos("push") Base.push!(collection, items...) -> collection Base.pushdisplay(d::Display) Base.Pkg.publish() Base.Collections.heappush!(v, x[, ord])
Interpret si pamatuje poslední výsledek, který lze vyvolat pomocí automaticky naplňované proměnné nazvané ans (což možná někteří uživatelé znají z kalkulaček HP či z Matlabu):
julia> 6*8 48 julia> ans 48 julia> ans*ans 2304 julia> ans 2304
4. Aritmetické operátory, implicitní násobení
V jazyce Julia jsou (podle očekávání) dostupné všechny základní aritmetické operátory, tj. součet, rozdíl, součin, podíl, podíl modulo a umocnění. Pouze je zapotřebí si dát pozor na to, že pro dělení se používá operátor /, zatímco zdvojené lomítko // se používá pro zápis zlomků, což je jeden z datových typů tohoto jazyka:
julia> 1+2 3 julia> 6*7 42 julia> 10/3 3.3333333333333335 julia> 10//3 10//3 julia> 10%3 1 julia> 2^10 1024 julia> 1+2*3 7
Ve skutečnosti jsou všechny aritmetické operátory implementovány běžnými funkcemi pojmenovanými stejným znakem, jaký odpovídá danému operátoru. To například znamená, že součet dvou čísel lze zapsat jako 1+2, ale také formou funkce +(1,2), což je sice možná poněkud podivné, ale jeden z významů bude vysvětlen v šesté kapitole v souvislosti s funkcemi vyššího řádu:
julia> +(1,2,3) 6 julia> *(1,2,3) 6
Užitečné je, že při násobení proměnné číselnou konstantou je možné vynechat operátor * a zapsat násobení tak, jak jsou uživatelé zvyklí z matematiky:
julia> x=10 10 julia> 2*x 20 julia> 2x 20 julia> 2*x+3*x^2 320 julia> 2x+3x^2 320
Pozor je zapotřebí dát na to, že tímto způsobem není možné násobit dvě proměnné – což je ostatně logické, neboť interpret neví, kde začíná jméno druhé proměnné:
julia> x=6 6 julia> y=7 7 julia> 2x 12 julia> 2x+3y 33 julia> x y ERROR: syntax: extra token "y" after end of expression julia> xy ERROR: xy not defined
Některé konstanty a samozřejmě též uživatelem deklarované proměnné mohou ve svém jménu používat prakticky libovolné znaky Unicode. Příkladem je již dopředu deklarovaná konstanta π. V následujícím příkladu je tato konstanta vynásobena dvěma, což je možné, protože již v předchozím textu jsme se zmínili o možnosti vynechání znaku * při násobení číselnou konstantou:
julia> 2π 6.283185307179586
5. Funkce
Funkce se v programovacím jazyku Julia vytváří podobným způsobem, jaký známe například z jazyka Lua. První způsob vypadá následovně:
function add(x,y) return x+y end add (generic function with 1 method) julia> add(1,2) 3
Druhý způsob je prakticky stejný, ovšem vynechá se příkaz return, který zde není nutný, neboť z funkce se vrací poslední vyhodnocovaný výraz:
function add(x,y) x+y end add (generic function with 1 method) julia> add(1,2) 3
Poslední „jednořádkový“ způsob se použije ve chvíli, kdy je tělo funkce skutečně jednoduché. Tento způsob se nejvíce podobá matematickému zápisu funkcí:
julia> mul(x,y)=x*y mul (generic function with 1 method) julia> mul(6,7) 42
Pokud si chcete být jisti tím, jaká funkce se volá a s jakými typy parametrů, použijte makro @which:
julia> @which abs(42) abs(x::Signed) at int.jl:75 julia> @which sin(42) sin(x::Real) at math.jl:126
6. Funkce vyššího řádu
Programovací jazyk Julia sice není, na rozdíl od Haskellu a částečně i Clojure, čistě funkcionální jazyk, nicméně i zde hrají při vývoji aplikací i jednoduchých výpočtů velkou roli funkce vyššího řádu, tj. funkce, které jako své parametry akceptují jiné funkce popř. dokonce vrací (nové) funkce jako svoji návratovou hodnotu. Mezi dvě základní funkce vyššího řádu, patří funkce nazvané map a taktéž apply. Funkce map jako svůj první parametr akceptuje jinou funkci (s jedním parametrem) a druhým parametrem musí být seznam, n-tice, pole atd.. map postupně aplikuje předanou funkci na jednotlivé prvky seznamu a vytváří tak novou sekvenci. Podívejme se na dva příklady:
julia> inc(x)=x+1 inc (generic function with 1 method) julia> inc(2) 3 julia> map(inc, range(1,10)) 10-element Array{Int64,1}: 2 3 4 5 6 7 8 9 10 11
julia> square(x)=x*x square (generic function with 1 method) julia> map(square, range(1,10)) 10-element Array{Int64,1}: 1 4 9 16 25 36 49 64 81 100
Funkce apply se chová poněkud odlišně – aplikuje totiž nějakou funkci (svůj první parametr) na předaný seznam, n-tici, pole atd. Typický „školní“ příklad s binární funkcí * (tj. funkcí se dvěma parametry) může vypadat následovně (jedná se o výpočet faktoriálu):
julia> apply(*, range(1,10)) 3628800
7. Programové smyčky
V jazyku Julia lze použít všechny základní typy programových smyček. Podívejme se na způsob jejich zápisu. Povšimněte si, že se nepoužívají žádné dvojtečky (viz Python) ani klíčová slova typu do (Lua) či begin:
Smyčka typu while:
while výraz příkaz příkaz příkaz end
Smyčka typu for-each:
for item in iter příkaz příkaz příkaz end
Smyčka typu for má několik forem, z nichž nejpoužívanější je pravděpodobně forma, v níž se používá počitadlo, které postupně nabývá hodnoty ze zadaného rozsahu s případným krokem. Následující příklad vytiskne čísla 1 až 10 na jediný řádek:
for i=1:10 print(i) end 12345678910
Samozřejmě můžeme provést odřádkování po každém vypsaném číslu a současně provést nějaký výpočet (zde konkrétně počítáme mocninu dvou):
for i=1:10 println(i,"\t",2^i) end 1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024
Podívejme se ještě na použití funkce range a * společně s funkcí vyššího řádu nazvanou apply() pro výpočet faktoriálu:
for i=1:10 println(i,"\t",apply(*, range(1,i))) end 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Smyčka typu for s větším množstvím řídicích proměnných:
for i=..., j=... příkaz příkaz příkaz end
Podrobnosti o použití těchto smyček ve složitějších či rozsáhlejších algoritmech si řekneme v následujícím článku.
8. Datové typy
Mezi základní datové typy, s nimiž je možné v programovacím jazyku Julia pracovat, patří především Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, Float16, Float32, Float64, Complex64 a Complex128. U každé hodnoty popř. proměnné lze typ velmi snadno zjistit, a to v režimu nápovědy. Příkladem může být zjištění typu konstanty 42 – nejprve stlačíme „?“ pro přepnutí do režimu nápovědy a následně napíšeme 42 + Enter. Výsledek by měl vypadat zhruba následovně:
help?> 42 42 is of type DataType : Int64 supertype: Signed
Povšimněte si použití proměnné ans, o níž jsme se již zmiňovali v předchozích kapitolách:
julia> 1/3 0.3333333333333333 help?> ans 0.3333333333333333 is of type DataType : Float64 supertype: FloatingPoint
Zlomky zapisované symbolem // mají svůj vlastní datový typ:
julia> 1//3 1//3 help?> ans 1//3 is of type DataType : Rational{Int64} (constructor with 1 method) supertype: Real fields : (:num,:den)
Pracovat je možné i s hodnotami nekonečno (Inf) a „nečíslo“ (NaN):
help?> Inf Inf is of type DataType : Float64 supertype: FloatingPoint help?> NaN NaN is of type DataType : Float64 supertype: FloatingPoint
U těchto hodnot je samozřejmě definováno jejich přesné chování u většiny aritmetických operací i u většiny funkcí:
julia> 1/0 Inf julia> Inf-Inf NaN julia> 1/Inf 0.0
Dalším datovým typem je komplexní číslo, které se vytváří jednoduše – použije se konstanta im, kterou se vynásobí hodnota, která se má převést na imaginární složku:
help?> im im is of type DataType : Complex{Bool} (constructor with 1 method) supertype: Number fields : (:re,:im)
Jedna z možných konstrukcí komplexního čísla může vypadat takto:
julia> 1+2im 1 + 2im help?> ans 1 + 2im is of type DataType : Complex{Int64} (constructor with 1 method) supertype: Number fields : (:re,:im)
Samozřejmě budou pracovat i obvyklé operace s komplexními čísly:
julia> z=1+2im 1 + 2im julia> z^2 -3 + 4im
Složenými datovými typy, poli atd. se budeme zabývat příště.
9. Použití typů BigInt a BigFloat
Zkusme provést zdánlivě jednoduchý výpočet:
julia> 2^70 0
Výsledkem je překvapivě nula, což je samozřejmě špatný výsledek. Je tomu tak z toho důvodu, že konstanty 2 i 70 jsou typu integer (konkrétně 64bitové celé číslo se znaménkem), tudíž i výpočet se provádí s typem integer:
help?> 2 2 is of type DataType : Int64 supertype: Signed
Můžeme však jednu z konstant převést na BigInt, což jsou celá čísla s prakticky neomezeným rozsahem. Nyní již výpočet proběhne bez chyby:
julia> BigInt(2)^70 1180591620717411303424
Výpočet faktoriálu 100! nyní podle očekávání taktéž nedopadne správně:
julia> apply(*, range(1,100)) 0
Po malé úpravě je již vše v pořádku:
julia> apply(*, range(BigInt(1),100)) 933262154439441526816992388562667004907159682643816214685929638952175 999932299156089414639761565182862536979208272237582511852109168640000 00000000000000000000
Namísto konstruktoru BigInt lze použít i funkci big(), která podle typu vstupní hodnoty vytvoří BigInt či BigFloat:
julia> apply(*, range(big(1),100)) 933262154439441526816992388562667004907159682643816214685929638952175 999932299156089414639761565182862536979208272237582511852109168640000 00000000000000000000
Funkce big() nám pomůže i při výpočtu hodnot s nekonečným rozvojem:
julia> 1/33 0.030303030303030304 julia> big(1)/33 3.030303030303030303030303030303030303030303030303030303030303030303030303030301e-02 with 256 bits of precision julia> big(1)/7 1.428571428571428571428571428571428571428571428571428571428571428571428571428568e-01 with 256 bits of precision julia> pi π = 3.1415926535897... julia> big(pi) 3.141592653589793238462643383279502884197169399375105820974944592307816406286198e+00 with 256 bits of precision
10. Makra
V programovacím jazyku Julia je možné vytvářet i poměrně složitá makra, která se podobají makrům známým například z různých variant LISPu či z programovacího jazyka Clojure. V dnešním článku nemáme dostatek prostoru pro vysvětlení, jakým způsobem se makra zapisují a jak se vyhodnocují, ale ukážeme si některé možnosti jejich použití. Proč se vlastně makra používají a k čemu jsou užitečná? Makra jsou spouštěna ve chvíli, kdy dochází ke zpracování vstupního zdrojového kódu, což znamená, že se makra dají použít pro úpravu tohoto kódu (manipulaci s kódem) ještě předtím, než dojde k jeho interpretaci či překladu. Parametry maker tedy ještě nejsou vyhodnoceny, na rozdíl od parametrů předávaných funkcím. To mj. znamená, že v makrech lze implementovat smyčky či podmínky (podmínka typu if nelze zapsat běžnou funkcí, protože by došlo k vyhodnocení všech parametrů).
Podívejme se nyní na některá užitečná makra. To, že se volá makro, se dá poznat z použití znaku @ před jménem makra. Příkladem může být @which, které nalezne a vypíše konkrétní funkci, která se pro daný výraz zavolá:
julia> @which abs(42) abs(x::Signed) at int.jl:75 julia> @which sin(x^2) sin(x::Real) at math.jl:126
Pokud budeme potřebovat zjistit, jak dlouho trvá nějaký výpočet, lze použít makro nazvané @time, které vypíše čas běhu výpočtu a alokovanou paměť. Výsledkem je však přímo výsledek výpočtu, tj. okolní kód „nepozná“, že se volalo makro:
julia> sin(2pi) -2.4492935982947064e-16 julia> @time sin(2pi) elapsed time: 9.708e-6 seconds (112 bytes allocated) -2.4492935982947064e-16 julia> @time apply(*,range(big(1),100)) elapsed time: 0.000279437 seconds (44632 bytes allocated) 933262154439441526816992388562667004907159682643816214685929 638952175999932299156089414639761565182862536979208272237582 51185210916864000000000000000000000000
Podobně se chová makro nazvané @timed, které ovšem vrací odlišný výsledek, konkrétně n-tici obsahující jak výsledek výpočtu, tak i čas běhu a alokovanou paměť v bajtech. To znamená, že tyto hodnoty je možné programově zpracovat:
julia> @timed sin(x^2) (-0.9917788534431158,7.962e-6,96,0.0) julia> @timed apply(*,range(big(1),100)) (93326215443944152681699238856266700490715968264381621468592 963895217599993229915608941463976156518286253697920827223758 251185210916864000000000000000000000000,0.000265888,44632,0.0)
Užitečným makrem při ladění je makro nazvané @show, které zobrazí vyhodnocovaný výraz a současně vrátí i jeho výsledek:
julia> @show sin(2^x)/cos(2) sin(2^x) / cos(2) => -2.2108206945184055 -2.2108206945184055
11. Speciální funkce zobrazující generovaný nativní kód
V úvodní kapitole jsme si řekli, že jazyk Julia využívá on-the-fly překlad do nativního kódu, a to s využitím technologie LLVM. Pomocí několika maker se můžeme podívat, jak přibližně takový překlad může vypadat. Dnes prozatím jen velmi stručně:
julia> @code_llvm sin(x^2) define double @julia_sin_20564(i64) { top: %1 = sitofp i64 %0 to double, !dbg !8 %2 = call double inttoptr (i64 140194494233504 to double (double)*)(double %1), !dbg !8 %3 = fcmp uno double %1, 0.000000e+00, !dbg !8 %4 = fcmp ord double %2, 0.000000e+00, !dbg !8 %5 = or i1 %4, %3, !dbg !8 br i1 %5, label %pass, label %fail, !dbg !8 fail: ; preds = %top %6 = load %jl_value_t** @jl_domain_exception, align 8, !dbg !8, !tbaa %jtbaa_const call void @jl_throw_with_superfluous_argument(%jl_value_t* %6, i32 126), !dbg !8 unreachable, !dbg !8 pass: ; preds = %top ret double %2, !dbg !8 }
Průběh překladu do nativního kódu (zde používám stroj s procesorem řady x86_64):
julia> @code_native sin(x^2) .text Filename: math.jl Source line: 0 push rbp mov rbp, rsp Source line: 126 sub rsp, 16 cvtsi2sd xmm0, rdi movsd qword ptr [rbp - 8], xmm0 movabs rax, 140194494233504 call rax ucomisd xmm0, xmm0 jp 6 add rsp, 16 pop rbp ret movsd xmm1, qword ptr [rbp - 8] ucomisd xmm1, xmm1 jp -17 movabs rax, 140194893713480 mov rax, qword ptr [rax] movabs rcx, 140194891018528 mov esi, 126 mov rdi, rax call rcx
(jedná se jen o ukázku možností prostředí jazyka Julia, nikoli o podrobný popis, jak přesně se provádí překlad zdrojový kód→AST→LLVM→nativní kód).
12. Odkazy na Internetu
- Julia (front page)
http://julialang.org/ - Julia – dokumentace
http://docs.julialang.org/en/release-0.4/ - Julia – repositář na GitHubu
https://github.com/JuliaLang/julia - Julia (programming language)
https://en.wikipedia.org/wiki/Julia_%28programming_language%29 - IJulia
https://github.com/JuliaLang/IJulia.jl - Introducing Julia
https://en.wikibooks.org/wiki/Introducing_Julia - Julia: the REPL
https://en.wikibooks.org/wiki/Introducing_Julia/The_REPL - Introducing Julia/Metaprogramming
https://en.wikibooks.org/wiki/Introducing_Julia/Metaprogramming - Month of Julia
https://github.com/DataWookie/MonthOfJulia - Learn X in Y minutes (where X=Julia)
https://learnxinyminutes.com/docs/julia/ - New Julia language seeks to be the C for scientists
http://www.infoworld.com/article/2616709/application-development/new-julia-language-seeks-to-be-the-c-for-scientists.html - Julia: A Fast Dynamic Language for Technical Computing
http://karpinski.org/publications/2012/julia-a-fast-dynamic-language - The LLVM Compiler Infrastructure
http://llvm.org/ - Julia: benchmarks
http://julialang.org/benchmarks/