Programovací jazyk Julia: volání funkcí naprogramovaných v C či ve Fortranu

Pavel Tišnovský 28. 6. 2016

Pátý článek o jazyce Julia je věnován volání nativních funkcí naprogramovaných většinou v C či ve Fortranu a uložených do sdílených knihoven (.so, .dll). Ukážeme si i práci s řetězci či s poli.

Obsah

1. Programovací jazyk Julia: volání funkcí naprogramovaných v C či ve Fortranu

2. Funkce ccall

3. Volání nativních funkcí bez parametrů a problematika návratových typů

4. Předání parametrů do nativních funkcí

5. Práce s řetězci – specifikace ukazatele a zpětná konverze řetězce

6. Vytvoření vlastní demonstrační sdílené knihovny

7. Volání funkcí ze sdílené knihovny, předání parametrů, zpracování výsledků

8. Druhá demonstrační sdílená knihovna s funkcí zpracovávající pole

9. Předání pole z jazyka Julia do nativní knihovny

10. Segfault a další typické problémy

11. Globální proměnné v nativní knihovně

12. Přístup ke globálním proměnným přes funkce unsafe_load a unsafe_store!

13. Repositář s demonstračními zdrojovými kódy nativních knihoven

14. Odkazy na Internetu

1. Programovací jazyk Julia: volání funkcí naprogramovaných v C či Fortranu

Běhové prostředí programovacího jazyka Julia podporuje kooperaci s nativními knihovnami naprogramovanými typicky v programovacích jazycích C a (možná ještě častěji) ve Fortranu. To ve skutečnosti není nic překvapivého, protože mnoho algoritmů určených pro zpracování numerických dat bylo naprogramováno, odladěno a optimalizováno právě ve Fortranu, takže by bylo kontraproduktivní se snažit o přepis těchto algoritmů do pomalejšího a prozatím (?) méně rozšířeného jazyka Julia. Ostatně přesně tímto způsobem jsou do jazyka Julia přidány algoritmy pro lineární algebru – ve skutečnosti totiž bylo pouze vytvořeno rozhraní ke knihovně LAPACK (Linear Algebra Package) s tím, že toto rozhraní zajišťuje případné datové konverze mezi vektory a maticemi jazyka Julia a týmiž strukturami implementovanými ve Fortranu. Dnes si ukážeme, jak lze volat funkce uložené v prakticky libovolné nativní sdílené knihovně (.so, .dll).

2. Funkce ccall

Pro volání nativních funkcí přímo z programovacího jazyka Julia, resp. přesněji řečeno ze skriptů naprogramovaných v Julii, je určena funkce nazvaná ccall (zkratka operace C-call). Této funkci se předávají následující parametry:

  1. Dvojice obsahující jméno volané funkce a jméno knihovny. Dvojice není nic jiného než n-tice se dvěma prvky. Ve skriptech se n-tice se zapisuje s využitím kulatých závorek.
  2. Typ návratové hodnoty, například UInt8, Int32, Float32 či Float64
  3. Následuje n-tice obsahující popis typů parametrů nativní funkce (pozor na to, že n-tice s jedním prvkem musí končit čárkou)
  4. Vlastní parametry, které se nativní funkci mají předat. V případě potřeby se Julia pokusí o konverzi typů, to však není ve všech případech možné ani bezpečné.

Pro ilustraci řešení celé problematiky volání nativních funkcí přímo z programovacího jazyka Julia si nejprve ukážeme, jakým způsobem se volají ty nativní funkce, které neakceptují žádné parametry. Typickým příkladem takové funkce, který je ostatně použit i v originální dokumentaci programovacího jazyka Julia, je funkce pojmenovaná clock, která vrací čas procesoru v jednotkách, jež lze na sekundy v případě potřeby převést podělením konstantou CLOCKS_PER_SEC (tato konstanta má podle POSIXu hodnotu 1000000, nicméně se ještě stále můžete setkat se systémy, v nichž je tato hodnota odlišná). Funkce clock se poměrně často používá pro změření doby trvání nějaké operace – stačí zjistit počet „tiků“ procesoru před měřenou operací, druhý počet „tiků“ procesoru po této operaci a podělit vypočtený rozdíl výše zmíněnou konstantou CLOCKS_PER_SEC. Funkce clock je samozřejmě podrobně popsána v manuálových stránkách, z nichž zjistíme jak parametry (žádné nejsou), tak i typ návratové hodnoty:

man 3 clock
CLOCK(3)                   Linux Programmer's Manual                  CLOCK(3)
 
NAME
       clock - determine processor time
 
SYNOPSIS
       #include <time.h>
 
       clock_t clock(void);
 
DESCRIPTION
       The clock() function returns an approximation of processor time used by
       the program.
 
       ...
       ...
       ...

3. Volání nativních funkcí bez parametrů a problematika návratových typů

V dokumentaci k programovacímu jazyku Julia je ukázán způsob volání výše zmíněné nativní funkce clock. Příkaz pro vyvolání je popsán následovně – volá se funkce se jménem „clock“ uložená ve sdílené knihovně nazvané „libc“, návratovým typem je 32bitové celé číslo a funkce neakceptuje žádné parametry:

julia> ccall( ("clock", "libc"), Int32, ())
1374123

Alternativně lze jméno volané nativní funkce zadat formou symbolu:

julia> ccall( (:clock, "libc"), Int32, ())
1799851

Totéž platí i pro jméno knihovny:

julia> ccall( (:clock, :libc), Int32, ())
1999851

Ve skutečnosti se však při použití návratové hodnoty typu Int32 brzy dostaneme do problémů, protože typ clock_t má na 64bitových systémech šířku 64 bitů, o čemž se můžete v céčku snadno přesvědčit použitím operátoru sizeof:

#include <time.h>
#include <stdio.h>
 
int main(void)
{
    printf("%zu\n", sizeof(clock_t));
    return 0;
}

Na 64bitových systémech sice použití typu Int32 nepovede k vypsání chybového hlášení, protože jazyk Julia nemá možnost takovou kontrolu provést, ale pro správné chování je nutné použít:

julia> ccall( (:clock, "libc"), Int64, ())
1441903

Podobným způsobem lze zavolat například další standardní funkci rand s následujícím popisem v manuálové stránce:

man 3 rand
RAND(3)                    Linux Programmer's Manual                   RAND(3)
 
NAME
       rand, rand_r, srand - pseudo-random number generator
 
SYNOPSIS
       #include <stdlib.h>
 
       int rand(void);
 
       ...
       ...
       ...

Podle konkrétní architektury (32bit, 64bit) použijte jedno z následujících volání:

julia> ccall( ("rand", "libc"), Int32, ())
1804289383
 
julia> ccall( ("rand", "libc"), Int64, ())
1804289383

Vygenerování desetiprvkové sekvence pseudonáhodných čísel s využitím nativní funkce rand vypadá takto:

julia> for i in 1:10
       println(ccall( ("rand", "libc"), Int64, ()))
       end
 
1102520059
2044897763
1967513926
1365180540
1540383426
304089172
1303455736
35005211
521595368
294702567

4. Předání parametrů do nativních funkcí

Poměrně často se setkáme s nutností zavolat nativní funkci, které se musí předat nějaké parametry. Pro jednoduchost si ukážeme volání takových funkcí na příkladu funkce nazvané abs, která je opět součástí standardní céčkovské knihovny (na Linuxu je tato funkce uložena v souboru libc.so.verze, kterou najdete většinou v adresáři /lib/{architektura}-linux-gnu/). Hlavička funkce abs naznačuje, že se akceptuje celé číslo a návratovou hodnotou je absolutní hodnota tohoto čísla (samozřejmě kromě absolutní hodnoty nejmenšího záporného čísla, protože tato absolutní hodnota překračuje rozsah typu int):

ABS(3)                     Linux Programmer's Manual                    ABS(3)
 
NAME
       abs, labs, llabs, imaxabs - compute the absolute value of an integer
 
SYNOPSIS
       #include <stdlib.h>
 
       int abs(int j);
       long int labs(long int j);
       long long int llabs(long long int j);

Podívejme se nyní, jakým způsobem se tato nativní funkce zavolá z programovacího jazyka Julia. Pozor si musíme dát především na způsob zápisu typů parametrů při volání ccall, protože nestačí pouze napsat (Int64), neboť tento zápis neodpovídá jednoprvkové n-tici, ale pouze typu uzavřenému v závorkách. Korektní zápis jednoprvkové n-tice vypadá takto: (Int64,); ostatně podobně je tomu i v Pythonu. Volání výše zmíněné nativní funkce abs tedy bude na 64bitovém systému vypadat následovně:

julia> ccall( ("abs", "libc"), Int64, (Int64,), 42)
42
 
julia> ccall( ("abs", "libc"), Int64, (Int64,), -42)
42

Podobným způsobem je možné zavolat nativní funkci nazvanou atan2, která akceptuje dvě reálné hodnoty typu double, což je typ, který v jazyce Julia odpovídá typu Float64. Pozor je zapotřebí dát na to, že tuto funkci nenalezneme v knihovně libc.so:

julia> ccall( ("atan2", "libc"), Float64, (Float64,Float64), 0, -1)
ERROR: ccall: could not find function atan2 in library libc
 in anonymous at no file

ale v knihovně libm.so. Korektní volání tedy vypadá následovně:

julia> ccall( ("atan2", "libm"), Float64, (Float64,Float64), 0, -1)
3.141592653589793

5. Práce s řetězci – specifikace ukazatele a zpětná konverze řetězce

Mnoho nativních funkcí vyžaduje jako své parametry řetězce, resp. přesněji řečeno céčkové řetězce, v nichž jsou zapsány jednotlivé bajty, celý řetězec je ukončen bajtem s hodnotou nula a řetězec je předán nikoli hodnotou, ale referencí (adresou jeho prvního znaku). Mezi céčkovými řetězci a řetězci používanými v jazyku Julia existuje několik rozdílů (v jazyku Julia jsou řetězce neměnné, podporovány jsou všechny Unicode znaky apod.), ovšem při předávání řetězců pomocí reference (adresy) postačuje u řetězce použít typ Cstring popř. Ptr{UInt8}. V případě použití typu Cstring je provedena kontrola, zda řetězec předávaný z jazyka Julia neobsahuje bajty s nulou, pokud však použijete Ptr{UInt8}, tato kontrola se neprovede, což je u některých funkcí nezbytné (práce s binárními daty apod.).

Ukažme si nyní, jakým způsobem lze zavolat funkci nazvanou system, která jako svůj jediný parametr akceptuje řetězec představující příkaz shellu (s případnými parametry atd.), který se má spustit (opět pozor na způsob zápisu jednoprvkové n-tice). Návratovou hodnotou této funkce je návratový kód spouštěného příkazu (nikoli tedy například jméno uživatele v případě volání „whoami“):

julia> ccall( ("system", "libc"), Int32, (Ptr{UInt8},), "whoami")
tester
0

Můžeme samozřejmě spustit i složitější příkaz:

julia> ccall( ("system", "libc"), Int32, (Ptr{UInt8},), "ls -1")
a.out
julia
julia-debug
libtest1.so
libtest2.so
test1.c
test1.o
test2.c
test2.o
0

Popř. si nechat na standardní výstup vypsat aktuální datum a čas:

julia> ccall( ("system", "libc"), Int32, (Ptr{UInt8},), "date")
Pá čen 24 22:54:25 CEST 2016
0

Opačná operace, tedy přečtení řetězce vráceného nativní funkcí, je nepatrně složitější. Pro příklad si vyzkoušejme nativní funkci nazvanou getenv:

GETENV(3)                  Linux Programmer's Manual                 GETENV(3)
 
NAME
       getenv, secure_getenv - get an environment variable
 
SYNOPSIS
       #include <stdlib.h>
 
       char *getenv(const char *name);

První pokus o přečtení proměnné prostředí nazvané PATH nedopadne přesně podle našich představ, a to z toho důvodu, že se pouze zobrazí hodnota ukazatele (ostatně my jsme skutečně vyžadovali získání hodnoty typu Ptr{UInt8}):

julia> ccall((:getenv, "libc"), Ptr{UInt8}, (Ptr{UInt8},), "PATH")
Ptr{UInt8} @0x00007fff14ae9c5c

Konverzi na skutečný řetězec jazyka Julia z céčkového řetězce zajišťuje funkce bytestring:

julia> bytestring( ccall((:getenv, "libc"), Ptr{UInt8}, (Ptr{UInt8},), "PATH"))
"/home/tester/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/tester/bin"

Poznámka: ve skutečnosti má funkce bytestring i opačný význam v závislosti na typu předaného parametru.

6. Vytvoření vlastní demonstrační sdílené knihovny

Nyní se podívejme na způsob realizace vlastní nativní knihovny, jejíž funkce bude možné zavolat přímo ze skriptů programovacího jazyka Julia. Naše demonstrační knihovna bude obsahovat tři jednoduché funkce – první funkce neakceptuje žádné parametry a vrací číslo typu integer, druhá funkce akceptuje dva celočíselné parametry a taktéž vrací integer a konečně třetí funkce akceptuje dva parametry typu double a stejného typu je i její návratová hodnota:

int answer(void)
{
    return 42;
}
 
int add_integers(int x, int y)
{
    return x+y;
}
 
double add_doubles(double x, double y)
{
    return x+y;
}

Při překladu knihovny do objektového kódu je nutné použít parametr -fPIC, aby byl vygenerovaný strojový kód nezávislý na absolutních adresách, na nichž je uložen:

gcc -Wall -fPIC -ansi -c test1.c

Výsledná sdílená knihovna se následně vygeneruje linkerem, kterému se předá požadované jméno knihovny:

gcc -shared -Wl,-soname,libtest1.so -o libtest1.so test1.o

Výsledkem by měl být soubor pojmenovaný libtest1.so.

7. Volání funkcí ze sdílené knihovny, předání parametrů, zpracování výsledků

Před pokusem o zavolání některé funkce z právě vytvořené sdílené knihovny je nutné nastavit proměnnou prostředí LD_LIBRARY_PATH takovým způsobem, aby tato proměnná ukazovala na adresář se sdílenými knihovnami. Pro jednoduchost předpokládejme použití aktuálního adresáře, v němž je současně uložen i spustitelný soubor s interpretrem jazyka Julia. Proměnná prostředí LD_LIBRARY_PATH se nastaví takto:

tester ~/temp/julia-2ac304dfba/bin $ export LD_LIBRARY_PATH=.

Po tomto nastavení již spustíme interpret s interaktivní smyčkou REPL:

tester ~/temp/julia-2ac304dfba/bin $ ./julia
                _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.4.5 (2016-03-18 00:58 UTC)
 _/ |\__'_|_|_|\__'_|  |  Official http://julialang.org/ release
|__/                   |  x86_64-unknown-linux-gnu

Následně se můžeme pokusit zavolat první funkci, která neakceptuje žádné parametry a vrací hodnotu typu integer:

julia> ccall( ("answer", "libtest1"), Int32, ())
42

Fungovat bude i prosté:

julia> ccall( ("answer", "libtest1"), Int, ())
42

Součet dvou celých čísel, který zajišťuje nativní funkce pojmenovaná add_integers, provedeme takto:

julia> ccall( ("add_integers", "libtest1"), Int, (Int, Int), 1, 2)
3

alternativně též:

julia> ccall( ("add_integers", "libtest1"), Int32, (Int32, Int32), 10, 20)
30

Poslední funkce pojmenovaná add_doubles sečte dvě čísla typu double, resp. v typovém systému jazyka Julia dvě čísla typu Float64:

julia> ccall( ("add_doubles", "libtest1"), Float64, (Float64, Float64), 1.2, 3.4)
4.6

Některé konverze se provádí automaticky (zde celé číslo na číslo typu double):

julia> ccall( ("add_doubles", "libtest1"), Float64, (Float64, Float64), 1, 2)
3

Další konverze se však (automaticky) neprovádí, což je ostatně jen dobře, protože o tyto konverze musí uživatel explicitně požádat:

julia> ccall( ("add_integers", "libtest1"), Int, (Int, Int), 1.2, 2)
ERROR: InexactError()
 in anonymous at no file

8. Druhá demonstrační sdílená knihovna s funkcí zpracovávající pole

Kromě předávání běžných skalárních hodnot (celé číslo, číslo s plovoucí řádovou čárkou atd.) a řetězců se velmi často setkáme s nutností předat do volaných nativních funkcí pole. Ve skutečnosti se nejedná o žádnou složitou operaci a pokud se dodrží správné datové typy, rozměry, dimenze atd., je předávání polí do nativních funkcí bezpečné. Vyzkoušejme si práci s poli na vlastní nativní sdílené knihovně s jedinou funkcí, která spočítá součet všech prvků v poli. Vzhledem k tomu, jakým způsobem s poli pracuje jazyk C (jedná se o pouhý blok v paměti bez dalších metainformací, tj. bez mezí, délky atd.), je nutné do funkce předat i délku pole:

double sum(double *array, int length)
{
    double *p = array;
    double result = 0;
    int i;
 
    for (i=0; i<length; i++) {
        result += *p++;
    }
 
    return result;
}

Zdrojový kód přeložíme stejným způsobem, jako tomu bylo u první demonstrační knihovny:

gcc -Wall -fPIC -ansi -c test2.c

Následuje druhá operace volající linker, který sdílenou knihovnu vygeneruje:

gcc -shared -Wl,-soname,libtest2.so -o libtest2.so test2.o

Výsledkem činnosti linkeru by měl být soubor pojmenovaný libtest2.so.

Před spuštěním interpretru opět musíme nastavit proměnnou prostředí LD_LIBRARY_PATH (pokud samozřejmě již nezůstala nastavená z předchozích pokusů):

tester ~/temp/julia-2ac304dfba/bin $ export LD_LIBRARY_PATH=.

9. Předání pole z jazyka Julia do nativní knihovny

Z předchozích částí tohoto seriálu již víme, jakým způsobem se v programovacím jazyce Julia vytváří pole s prvky majícími požadovaný typ. Naše nativní sdílená knihovna obsahuje funkci akceptující typ double, což v jazyce Julia odpovídá typu Float64. Vytvořme si tedy pole se třemi prvky tohoto typu. Povšimněte si, že se prvky nijak neinicializují a mohou obsahovat náhodné hodnoty:

julia> a=Array{Float64}(3)
3-element Array{Float64,1}:
 6.93643e-310
 6.936e-310
 6.936e-310

Dále toto pole naplníme hodnotami, například takto:

julia> a[1]=1
1
 
julia> a[2]=2
2
 
julia> a[3]=3
3

Nyní již je vše připraveno pro zavolání nativní funkce, která sečte prvky pole:

julia> ccall( ("sum", "libtest2"), Float64, (Ptr{Float64}, Int), a, 3)
6.0

Pole samozřejmě můžeme vytvořit i dalšími konstruktory, například funkcemi zeros, ones či fill. Podívejme se například na nepřímý důkaz, že hodnotu 0.1 nelze typem double přesně reprezentovat:

julia> b=fill(0.1, 10)
10-element Array{Float64,1}:
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1

Součet deseti prvků s hodnotou „přibližně 0.1“ nebude roven jedničce:

julia> ccall( ("sum", "libtest2"), Float64, (Ptr{Float64}, Int), b, 10)
0.9999999999999999

10. Segfault a další typické problémy

Interpret programovacího jazyka Julia při volání nativních funkcí nepoužívá žádné speciální techniky pro kontrolu předávaných parametrů ani jejich typů (a už vůbec ne kontrolu mezí polí). Je tedy velmi snadné dosáhnout pádu interpretru způsobeného například porušením ochrany paměti v nativní knihovně apod. Příklad je jednoduchý – specifikujeme pole nesprávného typu a současně namísto skutečného pole předáme pouze jedinou skalární hodnotu. Výsledek bude poněkud depresivní :-):

julia> ccall( ("sum", "libtest2"), Float64, (Ptr{Int}, Int), 0, 10)
WARNING: convert(::Type{Ptr}, ::Int64) methods should be converted to be methods of unsafe_convert
 in depwarn at deprecated.jl:73
 [inlined code] from deprecated.jl:418
 in unsafe_convert at no file:0
 in anonymous at no file
while loading no file, in expression starting on line 0
 
signal (11): Segmentation fault
sum at ./libtest2.so (unknown line)
anonymous at no file:0
unknown function (ip: 0x7fb03b9aef73)
jl_toplevel_eval_in at /home/tester/temp/julia-2ac304dfba/bin/../lib/julia/libjulia.so (unknown line)
eval_user_input at REPL.jl:62
jlcall_eval_user_input_21168 at  (unknown line)
jl_apply_generic at /home/tester/temp/julia-2ac304dfba/bin/../lib/julia/libjulia.so (unknown line)
anonymous at REPL.jl:92
unknown function (ip: 0x7fb03b9a08f4)
unknown function (ip: (nil))
Segmentation fault

Je nutné si uvědomit, že mezi nativním kódem a skriptem naprogramovaným v jazyce Julia leží pouze tenká vrstva rozhraní reprezentovaná funkcí ccall, takže veškeré typové kontroly musí provádět uživatel. To je daň, která se platí za použití rychlého rozhraní.

11. Globální proměnné v nativní knihovně

Vytvořme si třetí a současně i poslední nativní sdílenou knihovnu. Tentokrát v této knihovně nebudou uloženy žádné funkce, ale pouze čtveřice globálních proměnných:

int i = 0;
int answer = 42;
float pi_f = 3.1415f;
double pi_d = 3.1415;

Průběh překladu a slinkování již známe:

gcc -Wall -fPIC -ansi -c test3.c
gcc -shared -Wl,-soname,libtest3.so -o libtest3.so test3.o

12. Přístup ke globálním proměnným přes funkce unsafe_load a unsafe_store!

Ke globálním proměnným lze přistupovat s využitím funkce nazvané cglobal, která se v mnoha ohledech podobá funkci ccall, protože i zde musíme v n-tici předat jméno proměnné a jméno knihovny. Druhým parametrem je typ globální proměnné. Návratovou hodnotou této funkce je však ukazatel:

julia> cglobal(("answer", "libtest3"), Int32)
Ptr{Int32} @0x00007fd1a9acb030
 
julia> cglobal(("pi_f", "libtest3"), Float32)
Ptr{Float32} @0x00007fd1a9acb034
 
julia> cglobal(("pi_d", "libtest3"), Float64)
Ptr{Float64} @0x00007fd1a9acb038
 
julia> cglobal(("i", "libtest3"), Int32)
Ptr{Int32} @0x00007fd1a9acb044

Načtení hodnoty, na níž ukazuje vrácený ukazatel, zajišťuje funkce nazvaná unsafe_load. Přečtení hodnoty globální proměnné answer tedy vypadá následovně:

widgety

julia> answer_ptr = cglobal(("answer", "libtest3"), Int32)
Ptr{Int32} @0x00007f99fd7d2030
 
julia> unsafe_load(answer_ptr)
42

Pro zápis se používá funkce nazvaná unsafe_store! (i s vykřičníkem na konci). Změna globální proměnné i vypadá takto:

julia> i_ptr = cglobal(("i", "libtest3"), Int32)
Ptr{Int32} @0x00007f99fd7d2044
 
julia> unsafe_load(i_ptr)
0
 
julia> unsafe_store!(i_ptr, 1000)
Ptr{Int32} @0x00007f99fd7d2044
 
julia> unsafe_load(i_ptr)
1000

13. Repositář s demonstračními zdrojovými kódy nativních knihoven

Všechny tři demonstrační zdrojové kódy, které se mají přeložit do nativních sdílených knihoven, byly uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/pre­sentations/:

# Soubor Popis Adresa
1 test1.c zdrojový text pro knihovnu se třemi funkcemi https://github.com/tisnik/pre­sentations/blob/master/ju­lia/test1.c
2 test2.c zdrojový text pro knihovnu s funkcí akceptující pole https://github.com/tisnik/pre­sentations/blob/master/ju­lia/test2.c
3 test3.c zdrojový text pro knihovnu s globálními proměnnými https://github.com/tisnik/pre­sentations/blob/master/ju­lia/test3.c

14. Odkazy na Internetu

  1. clock(3) – Linux man page
    http://linux.die.net/man/3/clock
  2. rand_r(3) – Linux man page
    http://linux.die.net/man/3/rand_r
  3. atan2(3) – Linux man page
    http://linux.die.net/man/3/atan2
  4. Calling C and Fortran Code
    http://docs.julialang.org/en/release-0.4/manual/calling-c-and-fortran-code/?highlight=symbol
  5. Array Programming
    https://en.wikipedia.org/wi­ki/Array_programming
  6. Discovering Array Languages
    http://archive.vector.org­.uk/art10008110
  7. no stinking loops – Kalothi
    http://www.nsl.com/
  8. Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
    http://www.vector.org.uk/
  9. APL Interpreters
    http://www.vector.org.uk/?a­rea=interpreters
  10. APL_(programming_language
    http://en.wikipedia.org/wi­ki/APL_(programming_langu­age
  11. APL FAQ
    http://www.faqs.org/faqs/apl-faq/
  12. APL FAQ (nejnovější verze)
    http://home.earthlink.net/~swsir­lin/apl.faq.html
  13. A+
    http://www.aplusdev.org/
  14. APLX
    http://www.microapl.co.uk/
  15. FreeAPL
    http://www.pyr.fi/apl/index.htm
  16. J: a modern, high-level, general-purpose, high-performance programming language
    http://www.jsoftware.com/
  17. K, Kdb: an APL derivative for Solaris, Linux, Windows
    http://www.kx.com
  18. openAPL (GPL)
    http://sourceforge.net/pro­jects/openapl
  19. Parrot APL (GPL)
    http://www.parrotcode.org/
  20. Learning J (Roger Stokes)
    http://www.jsoftware.com/hel­p/learning/contents.htm
  21. Rosetta Code
    http://rosettacode.org/wiki/Main_Page
  22. Why APL
    http://www.acm.org/sigapl/whyapl.htm
  23. Introducing Julia/Functions
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Functi­ons
  24. Functions (Julia documentation)
    http://docs.julialang.org/en/release-0.4/manual/functions/
  25. Evaluate binomial coefficients
    http://rosettacode.org/wi­ki/Evaluate_binomial_coef­ficients
  26. Ackermann function
    http://rosettacode.org/wi­ki/Ackermann_function
  27. Julia (front page)
    http://julialang.org/
  28. Julia – dokumentace
    http://docs.julialang.org/en/release-0.4/
  29. Julia – repositář na GitHubu
    https://github.com/JuliaLang/julia
  30. Julia (programming language)
    https://en.wikipedia.org/wi­ki/Julia_%28programming_lan­guage%29
  31. IJulia
    https://github.com/JuliaLan­g/IJulia.jl
  32. Introducing Julia
    https://en.wikibooks.org/wi­ki/Introducing_Julia
  33. Julia: the REPL
    https://en.wikibooks.org/wi­ki/Introducing_Julia/The_REPL
  34. Introducing Julia/Metaprogramming
    https://en.wikibooks.org/wi­ki/Introducing_Julia/Meta­programming
  35. Month of Julia
    https://github.com/DataWo­okie/MonthOfJulia
  36. Learn X in Y minutes (where X=Julia)
    https://learnxinyminutes.com/doc­s/julia/
  37. 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
  38. Julia: A Fast Dynamic Language for Technical Computing
    http://karpinski.org/publi­cations/2012/julia-a-fast-dynamic-language
  39. The LLVM Compiler Infrastructure
    http://llvm.org/
  40. Julia: benchmarks
    http://julialang.org/benchmarks/
  41. Type system
    https://en.wikipedia.org/wi­ki/Type_system
  42. Half-precision floating-point format
    https://en.wikipedia.org/wiki/Half-precision_floating-point_format
Našli jste v článku chybu?
120na80.cz: Galerie: Čínští policisté testují českou minerálku

Galerie: Čínští policisté testují českou minerálku

DigiZone.cz: Ultra HD v praxi a v Portugalsku

Ultra HD v praxi a v Portugalsku

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Podnikatel.cz: ČSSZ posílá přehled o důchodovém kontě

ČSSZ posílá přehled o důchodovém kontě

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Lupa.cz: Cimrman má hry na YouTube i vlastní doodle

Cimrman má hry na YouTube i vlastní doodle

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Vitalia.cz: Antibakteriální mýdla nepomáhají, spíš škodí

Antibakteriální mýdla nepomáhají, spíš škodí

Lupa.cz: Proč jsou firemní počítače pomalé?

Proč jsou firemní počítače pomalé?

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Root.cz: Hořící telefon Samsung Note 7 zapálil auto

Hořící telefon Samsung Note 7 zapálil auto

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Podnikatel.cz: Tyto pojmy k #EET byste měli znát

Tyto pojmy k #EET byste měli znát

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?