Zpracování n-rozměrných polí v jazyce Go s využitím knihovny narray

27. 10. 2022
Doba čtení: 25 minut

Sdílet

 Autor: Go lang
V dnešním článku se seznámíme s knihovnou nazvanou narray. Jedná se o knihovnu určenou pro programovací jazyk Go, která je navržena pro efektivní práci s n-rozměrnými poli s využitím ručně „vektorizovaného“ kódu napsaného v assembleru.

Obsah

1. Zpracování n-rozměrných polí v jazyce Go s využitím knihovny narray

2. Projekt Gonum Numerical Packages (Gonum)

3. Podpora resp. nepodpora SIMD operací v jazyku Go a možná řešení

4. Projekt narray využívající možností dnešních mikroprocesorů

5. Instalace balíčku narray

6. Konstrukce n-dimenzionálního pole

7. Konstrukce pole s inicializací jeho prvků

8. Součet a součin všech prvků uložených ve vektoru

9. Součet všech prvků dvojice vektorů

10. Skalární součin libovolného počtu vektorů

11. Serializace n-rozměrného pole do souboru

12. Deserializace (načtení) n-rozměrných polí ze souborů

13. Serializace n-rozměrného pole do formátu JSON

14. Operace AddConst a AddScaled

15. Převod n-rozměrných polí se dvěma dimenzemi na matice

16. Základní operace s maticemi

17. Závěrečné zhodnocení

18. Předchozí články o rozsáhlém světu „array programmingu“

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Zpracování n-rozměrných polí v jazyce Go s využitím knihovny narray

Na stránkách Roota jsme se již mnohokrát setkali s programovacími jazyky popř. s knihovnami, které jsou určeny pro zpracování n-rozměrných polí. Není se ostatně čemu divit, protože se skutečně jedná o velmi často využívané datové struktury, s nimiž se setkáme například při zpracování signálů, v lineární algebře, datové analýze atd. Současně se jedná o datové struktury a operace, u nichž má velký smysl využít SIMD instrukce, které jsou dostupné na všech moderních mikroprocesorových architekturách (viz paralelně běžící miniseriál na toto téma). Dnes se seznámíme s knihovnou nazvanou narray, která je navržena pro použití v ekosystému programovacího jazyka Go. Vzhledem k tomu, že standardní překladač Go automatickou vektorizaci neprovádí v dostatečném rozsahu (alespoň prozatím), jsou základní výpočetní operace v knihovně narray optimalizovány ručně, a to přímo v assembleru Go (jenž se v mnoha ohledech odlišuje od ostatních dnes používaných assemblerů).

Současně tato knihovna podporuje matice z projektu Gonum Numerical Packages – viz též krátkou rekapitulaci uvedenou v další kapitole.

Poznámka: narray do značné míry přebírá vlastnosti z jazyka APL, takže se zde setkáme s pojmy rank, shape a strides.

2. Projekt Gonum Numerical Packages (Gonum)

Samotný programovací jazyk Go obsahuje podporu pro práci s maticemi a řezy neboli slices (ostatně se jedná o základní datové typy tohoto jazyka). Práce s těmito datovými strukturami je podporována i ve standardní knihovně jazyka Go. Ovšem například v porovnání se známou a velmi často používanou knihovnou NumPy ze světa Pythonu (nebo s možnostmi Matlabu či R) jsou možnosti standardní instalace Go v této oblasti mnohem menší. Ovšem některé operace, které známe z NumPy, byly implementovány v sadě knihoven, které jsou součástí projektu nazvaného jednoduše Gonum Numerical Packages. Tento projekt obsahuje zejména knihovnu pro práci s maticemi (naprosté základy, ale i složitější koncepty jsme si již řekli v samostatných článcích zmíněných na konci kapitoly), algoritmy lineární algebry, podporu pro tvorbu grafů, podporu práce s takzvanými „datovými rámci“ (ve světě Pythonu se používá pandas) atd.

Obrázek 1: Logo projektu Gonum Numerical Packages.

Poznámka: na tomto místě je však vhodné poznamenat, že integrace NumPy do Pythonu je mnohem lepší, než je tomu v případě projektu Gonum. Je tomu tak z toho důvodu, že jazyk Go nepodporuje přetěžování operátorů, takže například není možné implementovat maticové operace „přirozenou“ cestou (zrovna příklad NumPy ukazuje, že přetěžování operátorů, pokud je použito rozumně, může být velmi užitečné). Nicméně přesto se zdá, že Gonum se stává v této oblasti neoficiálním standardem.

O knihovně Gonum jsme se zmínili v následujících článcích:

  1. Gophernotes: kombinace interaktivního prostředí Jupyteru s jazykem Go
    https://www.root.cz/clanky/gophernotes-kombinace-interaktivniho-prostredi-jupyteru-s-jazykem-go/
  2. Popis vybraných balíčků nabízených projektem Gonum
    https://www.root.cz/clanky/popis-vybranych-balicku-nabizenych-projektem-gonum/

3. Podpora resp. nepodpora SIMD operací v jazyku Go a možná řešení

Výše zmíněná knihovna Gonum obsahuje i funkce určené pro zpracování vektorů a především dvourozměrných matic. Některé z těchto operací jsou z důvodu větší rychlosti provádění (konkrétně s využitím SIMD instrukcí) napsány v assembleru programovacího jazyka Go. Například se to týká souboru addconst_amd64.s.

Na tomto místě se na chvíli zastavme, protože je důležité si říci, jakým způsobem je vlastně v programovacím jazyku Go možné využít SIMD operace nabízené všemi moderními typy mikroprocesorů. K dispozici je hned několik technologií, ovšem již na úvod je vhodné si říci, že žádná z těchto technologií není (alespoň v současnosti) ideální a prozatím se nezdá, že by se tato situace měla v nejbližší době změnit.

Jeden z nejlepších způsobů spočívá v podpoře automatické „vektorizace“ kódu prováděné přímo překladačem programovacího jazyka Go, například ve chvíli, kdy programátor napíše programovou smyčku, kterou lze nahradit vektorovou operací. Tyto typy optimalizací jsou podporovány například v alternativním překladači programovacího jazyka Go: The GNU Go Compiler. Problém spočívá v tom, že zdaleka ne všechen kód, který by bylo možné vektorizovat, skutečně vektorizován je (to se však pravděpodobně v budoucnosti zlepší).

Další způsob, který je alespoň teoreticky dostupný, spočívá ve využití intrinsic, tedy pojmenovaných šablon vložených přímo do překladače, které jsou použity pro generování strojového kódu namísto volání funkcí. Touto problematikou se z pohledu programovacího jazyka C zabýváme v článcích Podpora SIMD operací v GCC s využitím intrinsic pro nízkoúrovňové optimalizace a Podpora SIMD operací v GCC s využitím intrinsic: technologie SSE. Ovšem intrinsic pro SIMD (a intrinsic obecně) nejsou v jazyce Go dostupné, takže tuto potenciálně velmi zajímavou technologii prozatím nemůžeme přímo v jazyce Go využít.

Třetím způsob je nasnadě – vytvořit takový kód v jiném jazyce, například v C či ve Fortranu, a z programovacího jazyka Go pouze volat již připravené, otestované a optimalizované funkce. Problém spočívá v tom, že se přepíná kontext a navíc je nutné dbát na rozdílné typové systémy a taktéž na to, že Go má automatickou správu paměti. Nicméně tento způsob je stále poměrně dobře použitelný v praxi.

A konečně poslední dostupný způsob je založen na použití assembleru jazyka Go. Ten je v mnoha ohledech pro většinu programátorů zvláštní a asi nejvíce ukazuje, kdo je autorem Go a v jaké oblasti se pohyboval :-). Nicméně se v současnosti může jednat o relativně dobrou alternativu k předchozím třem způsobům – a jedná se o alternativu, která je použita i v projektu narray.

Assemblerem jazyka Go jsme se zabývali v těchto článcích:

  1. Programovací jazyk Go a assembler
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler/
  2. Programovací jazyk Go a assembler (2.část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler-2-cast/
  3. Programovací jazyk Go a assembler (3.část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler-3-cast/

4. Projekt narray využívající možností dnešních mikroprocesorů

Na dnes poměrně úspěšný projekt Gonum nepřímo navazuje projekt nazvaný narray. Jedná se o balíček určený pro použití v programovacím jazyku Go, který umožňuje zpracování n-rozměrných polí, tedy nejenom vektorů a matic. Na tomto projektu je zajímavý především fakt, že většina operací prováděných nad n-rozměrnými poli je ručně „vektorizována“, což je patrné například při pohledu na tento zdrojový kód. Zdrojové kódy jsou přitom generovány na základě šablon pro každou podporovanou architekturu (což je dnes x86 a x86–64) a každý podporovaný datový typ (float32 a float64).

Poznámka: z předchozího textu plyne, že n-rozměrná pole nemohou obsahovat prvky libovolného typu; nejde tedy v tomto pohledu o generické datové struktury. Ovšem vzhledem k tomu, pro jaké účely knihovna narray vznikla, to pravděpodobně nebude vadit.

5. Instalace balíčku narray

Instalace balíčku narray je triviální, protože se jedná (možná o jeden z mála balíčků), který nemá další závislosti (a tranzitivní závislosti se v ekosystému jazyka Go pomalu stávají stejným problémem, jako v NPM). Postačuje vytvořit nový projekt příkazem:

$ go mod init project_name

Dále do libovolného zdrojového kódu projektu přidat import:

narray "github.com/akualab/narray/na32"

nebo:

narray "github.com/akualab/narray/na64"

A při pokusu o překlad:

go: downloading github.com/akualab/narray v0.0.0-20150719231847-f1469ca2dcda
go: added github.com/akualab/narray v0.0.0-20150719231847-f1469ca2dcda

…se upraví soubor go.mod následovně (přidá se zvýrazněný řádek):

module narray-demos
 
go 1.18
 
require github.com/akualab/narray v0.0.0-20150719231847-f1469ca2dcda // indirect

Pro zajímavost se podívejme i na soubor go.sum, v němž jsou uloženy všechny závislosti, včetně tranzitivních závislostí:

github.com/akualab/narray v0.0.0-20150719231847-f1469ca2dcda h1:nQDCnea62a0m8ftKitrMXZfKrj0jBTcewPjTQLF6ZFo=
github.com/akualab/narray v0.0.0-20150719231847-f1469ca2dcda/go.mod h1:6YzkTn3pv9xhDO6WcNUd3bot0eMQ5Zc8/X+IaAfSJOU=

6. Konstrukce n-dimenzionálního pole

N-dimenzionální pole se vytváří konstruktorem New, přičemž už na základě toho, jaký balíček se naimportuje, se rozlišuje, jestli se bude jednat o pole s prvky typu float32 nebo float64. Konstruktoru New se předává počet prvků v každé dimenzi (jedná se tedy o funkci s proměnným počtem parametrů). Můžeme tedy vytvořit skalár, vektor (1D), matici (2D) či trojrozměrné pole:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na32"
)
 
func main() {
        scalar := narray.New()
        vector := narray.New(10)
        matrix := narray.New(4, 3)
        cube := narray.New(3, 4, 5)
 
        fmt.Println(scalar)
        fmt.Println(vector)
        fmt.Println(matrix)
        fmt.Println(cube)
}
Poznámka: tato konstrukce v současné (poslední) verzi knihovny narray nepracuje korektně, takže všechny příklady budou ukázány na n-rozměrných polích typu float32.

Stejnou konstrukci můžeme provést pro pole s prvky typu float64:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        scalar := narray.New()
        vector := narray.New(10)
        matrix := narray.New(4, 3)
        cube := narray.New(3, 4, 5)
 
        fmt.Println(scalar)
        fmt.Println(vector)
        fmt.Println(matrix)
        fmt.Println(cube)
}

Podívejme se nyní na výsledky:

narray rank:   0
narray shape:  []
[] => 0.000000
 
narray rank:   1
narray shape:  [10]
[ 0] => 0.000000
[ 1] => 0.000000
[ 2] => 0.000000
[ 3] => 0.000000
[ 4] => 0.000000
[ 5] => 0.000000
[ 6] => 0.000000
[ 7] => 0.000000
[ 8] => 0.000000
[ 9] => 0.000000
 
narray rank:   2
narray shape:  [4 3]
[ 0 0] => 0.000000
[ 0 1] => 0.000000
[ 0 2] => 0.000000
[ 1 0] => 0.000000
[ 1 1] => 0.000000
[ 1 2] => 0.000000
[ 2 0] => 0.000000
[ 2 1] => 0.000000
[ 2 2] => 0.000000
[ 3 0] => 0.000000
[ 3 1] => 0.000000
[ 3 2] => 0.000000
 
narray rank:   3
narray shape:  [3 4 5]
[ 0 0 0] => 0.000000
[ 0 0 1] => 0.000000
[ 0 0 2] => 0.000000
[ 0 0 3] => 0.000000
[ 0 0 4] => 0.000000
[ 0 1 0] => 0.000000
[ 0 1 1] => 0.000000
[ 0 1 2] => 0.000000
[ 0 1 3] => 0.000000
[ 0 1 4] => 0.000000
[ 0 2 0] => 0.000000
...
...
...
Poznámka: povšimněte si, že se používá stejný přístup, jako v programovacím jazyku APL nebo v NumPy – data jsou uložena v jednorozměrné datové struktuře a metainformace o počtu dimenzí (rank) a tvaru pole (shape) jsou uloženy zvlášť (což je později vhodné pro lepší „vektorizaci“ atd.).

Dokumentaci ke konstruktoru New naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#New.

7. Konstrukce pole s inicializací jeho prvků

Kromě konstruktoru New, který všechny prvky n-rozměrného pole inicializuje na nulu, je možné použít i konstruktor nazvaný NewArray, kterému se kromě rozměrů a počtu dimenzí nového pole předávají i prvky, které se mají do pole uložit. Podívejme se tedy, jakým způsobem se tyto prvky předávají (v řezu – slice) a v jakém pořadí se do nového pole uloží:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector := narray.NewArray([]float64{1, 2, 3}, 3)
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
        cube := narray.NewArray([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 3, 2, 2)
 
        fmt.Println(vector)
        fmt.Println(matrix)
        fmt.Println(cube)
}

Po překladu a spuštění tohoto demonstračního příkladu by se měly zobrazit informace o trojici n-rozměrných polí:

narray rank:   1
narray shape:  [3]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   3
narray shape:  [3 2 2]
[ 0 0 0] => 1.000000
[ 0 0 1] => 2.000000
[ 0 1 0] => 3.000000
[ 0 1 1] => 4.000000
[ 1 0 0] => 5.000000
[ 1 0 1] => 6.000000
[ 1 1 0] => 7.000000
[ 1 1 1] => 8.000000
[ 2 0 0] => 9.000000
[ 2 0 1] => 10.000000
[ 2 1 0] => 11.000000
[ 2 1 1] => 12.000000

Dokumentaci ke konstruktoru NewArray naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NewArray.

Poznámka: teoreticky je možné u již vytvořeného pole změnit jeho tvar (shape) metodou Reshape. Ovšem prozatím není tato metoda implementována:
package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector := narray.NewArray([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 12)
        matrix := vector.Reshape(4, 3)
 
        fmt.Println(vector)
        fmt.Println(matrix)
}

Při pokusu o spuštění tohoto příkladu aplikace zhavaruje:

panic: not implemented
 
goroutine 1 [running]:
github.com/akualab/narray/na64.(*NArray).Reshape(...)
        /home/ptisnovs/go/pkg/mod/github.com/akualab/narray@v0.0.0-20150719231847-f1469ca2dcda/na64/narray.go:678
main.main()
        /home/ptisnovs/src/go-root/article_95/04_reshape.go:11 +0x1e5
exit status 2

Dokumentaci k metodě Reshape naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.Reshape.

8. Součet a součin všech prvků uložených ve vektoru

V poměrně mnoha algoritmech se setkáme s nutností provést součet či součin všech prvků uložených ve vektorech, takže je vhodné, aby tyto operace byly (ručně) „vektorizovány“. Implementaci těchto funkcí samozřejmě v knihovně narray nalezneme, a to v metodách Sum a Prod.

Vyzkoušejme si nejprve součet prvků pětiprvkového vektoru:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
 
        sum := vector1.Sum()
        fmt.Println(sum)
}

Hodnota získaná po překladu a spuštění tohoto příkladu:

15

Podobně si můžeme otestovat výpočet provedený metodou Prod (tedy součin prvků vektoru):

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
 
        prod := vector1.Prod()
        fmt.Println(prod)
}

Po spuštění tohoto příkladu získáme očekávanou hodnotu:

120

Dokumentaci k metodám Sum a Prod naleznete na adresách https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.Sum a https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.Prod.

9. Součet všech prvků dvojice vektorů

Další velmi často vyžadovanou operací je součet prvků z dvojice vektorů (tedy přesněji řečeno vždy odpovídajících si prvků). Jedná se o jednu z operací, která je „vektorizována“ již tradičně, protože byla implementována již v superpočítačích Cray. V knihovně narray nalezneme funkci (nikoli metodu) nazvanou Add, která vyžaduje reference na tři n-rozměrná pole – cílový vektor a dva zdrojové vektory. Pokud je namísto cílového vektoru použita hodnota nil, bude alokován a vrácen v návratové hodnotě funkce Add:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
        vector2 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
 
        vector3 := narray.Add(nil, vector1, vector2)
        fmt.Println(vector1)
        fmt.Println(vector2)
        fmt.Println(vector3)
}

Výsledky:

narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 2.000000
[ 1] => 4.000000
[ 2] => 6.000000
[ 3] => 8.000000
[ 4] => 10.000000

Často však nechceme, aby se cílový vektor stále vytvářel (což zatěžuje GC). Proto můžeme v prvním parametru funkce Add předat referenci na již alokovaný vektor:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
        vector2 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
        vector3 := narray.New(5)
 
        narray.Add(vector3, vector1, vector2)
        fmt.Println(vector1)
        fmt.Println(vector2)
        fmt.Println(vector3)
}

Výsledky:

narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 2.000000
[ 1] => 4.000000
[ 2] => 6.000000
[ 3] => 8.000000
[ 4] => 10.000000
Poznámka: namísto vektorů je pochopitelně možné použít jakékoli n-rozměrné pole.

Dokumentaci k funkci Add naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#Add.

10. Skalární součin libovolného počtu vektorů

Další v praxi často vyžadovanou operací je skalární součin dvou vektorů popř. libovolného počtu vektorů. Nejprve si ukažme způsob provedení operace, která provede skalární součin dvojice vektorů (pochopitelně o shodné délce). Tato operace je realizována funkcí nazvanou Dot:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
 
        dotProduct := narray.Dot(vector1, vector1)
        fmt.Println(dotProduct)
        fmt.Println(1*1 + 2*2 + 3*3 + 4*4 + 5*5)
}

V demonstračním příkladu provádíme skalární součin vektorů s hodnotami [1, 2, 3, 4, 5], takže výsledkem by měla být hodnota 55:

55
55

Ve skutečnosti ovšem nejsme omezeni pouze na skalární součin dvou vektorů, ale můžeme provést skalární součin většího množství vektorů o shodné délce. V dalším demonstračním příkladu je ukázán součin pro trojici vstupních vektorů, z nichž každý obsahuje jen tři prvky:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3}, 3)
        vector2 := narray.NewArray([]float64{4, 5, 6}, 3)
        vector3 := narray.NewArray([]float64{7, 8, 9}, 3)
 
        dotProduct := narray.Dot(vector1, vector2, vector3)
        fmt.Println(dotProduct)
}

Výsledek v tomto případě vznikne výpočtem 1×4×7 + 2×5×8 + 3×6×9 = 28 + 80 + 162 = 270. A skutečně:

270

Funkce pro výpočet skalárního součinu vektorů je zdokumentována na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.Dot.

11. Serializace n-rozměrného pole do souboru

Díky existenci metody WriteFile je možné serializovat (uložit) n-rozměrné pole do souboru. Přitom je použit textový formát, jehož výhodou je čitelnost, nevýhodou pak fakt, že u velkých polí budou soubory obrovské a budou se ukládat i načítat pomalu, a to kvůli nutnosti převodu hodnot typu float32 nebo float64 do textové podoby (a zpět).

Vyzkoušejme si nyní, jak lze serializaci provést. Opět použijeme trojici polí s jednou, dvěma a třemi dimenzemi:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector := narray.NewArray([]float64{1, 2, 3}, 3)
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
        cube := narray.NewArray([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 3, 2, 2)
 
        fmt.Println(vector)
        fmt.Println(matrix)
        fmt.Println(cube)
 
        vector.WriteFile("vector.dat")
        matrix.WriteFile("matrix.dat")
        cube.WriteFile("cube.dat")
}

Soubor „vector.dat“ vypadá takto:

{"rank":1,"shape":[3],"data":[1,2,3],"strides":[1]}

Soubor „matrix.dat“ vypadá následovně:

{"rank":2,"shape":[3,2],"data":[1,2,3,4,5,6],"strides":[2,1]}

A konečně se podívejme na soubor „cube.dat“:

{"rank":3,"shape":[3,2,2],"data":[1,2,3,4,5,6,7,8,9,10,11,12],"strides":[4,2,1]}
Poznámka: povšimněte si, že kromě počtu dimenzí (rank) a tvaru pole (shape) se ukládají i offsety mezi prvky (strides), což umožňuje rychlý přístup k jednotlivým prvkům či k celým polím o dimenzi o jedničku menším.

Dokumentaci k metodě WriteFile lze nalézt na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.WriteFile.

12. Deserializace (načtení) n-rozměrných polí ze souborů

Opětovné načtení (neboli deserializaci) n-rozměrného pole ze souboru lze provést funkcí (pochopitelně nikoli metodou) nazvanou ReadFile. Tato funkce vrací dvojici hodnot – deserializované pole a datovou strukturu s informacemi o chybě, která může v průběhu deserializace vzniknout. Jak je v jazyce Go zvykem, je druhá návratová hodnota rovna nil ve chvíli, kdy k chybě při načítání nedošlo, takže test na případnou chybu je triviální.

Programový kód určený pro opětovné načtení polí ze souborů může vypadat takto:

package main
 
import (
        "fmt"
        "log"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector, err := narray.ReadFile("vector.dat")
        if err != nil {
                log.Panic(err)
        }
 
        matrix, err := narray.ReadFile("matrix.dat")
        if err != nil {
                log.Panic(err)
        }
 
        cube, err := narray.ReadFile("cube.dat")
        if err != nil {
                log.Panic(err)
        }
 
        fmt.Println(vector)
        fmt.Println(matrix)
        fmt.Println(cube)
}

Po překladu a spuštění tohoto demonstračního příkladu by se měly zobrazit obsahy všech tří deserializovaných polí:

narray rank:   1
narray shape:  [3]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   3
narray shape:  [3 2 2]
[ 0 0 0] => 1.000000
[ 0 0 1] => 2.000000
[ 0 1 0] => 3.000000
[ 0 1 1] => 4.000000
[ 1 0 0] => 5.000000
[ 1 0 1] => 6.000000
[ 1 1 0] => 7.000000
[ 1 1 1] => 8.000000
[ 2 0 0] => 9.000000
[ 2 0 1] => 10.000000
[ 2 1 0] => 11.000000
[ 2 1 1] => 12.000000
Poznámka: opět platí, že pro rozsáhlejší pole není ukládání do textových souborů a opětovné načítání pole z těchto souborů vhodnou technologií.

Dokumentaci k funkci ReadFile lze nalézt na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#ReadFile.

13. Serializace n-rozměrného pole do formátu JSON

Knihovna narray obsahuje i metodu nazvanou ToJSON, která – jak již ostatně její název velmi dobře napovídá – slouží pro serializaci n-rozměrného pole do JSONu. Opět platí, že se taková serializace vyplatí provádět pouze ve chvíli, kdy jsou pole relativně malá (tedy nikoli například pro serializaci rastrového obrázku a už vůbec ne videa :-). Podívejme se, jak se tato serializace provede v praxi:

package main
 
import (
        "fmt"
        "log"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector := narray.NewArray([]float64{1, 2, 3}, 3)
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
        cube := narray.NewArray([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 3, 2, 2)
 
        j, err := vector.ToJSON()
        if err != nil {
                log.Panic(err)
        }
 
        fmt.Println(j)
 
        j, err = matrix.ToJSON()
        if err != nil {
                log.Panic(err)
        }
 
        fmt.Println(j)
 
        j, err = cube.ToJSON()
        if err != nil {
                log.Panic(err)
        }
        fmt.Println(j)
}

Výsledky získané spuštěním tohoto demonstračního příkladu ukazují serializaci do JSONu provedenou bez „pretty printingu“:

{"rank":1,"shape":[3],"data":[1,2,3],"strides":[1]}
 
{"rank":2,"shape":[3,2],"data":[1,2,3,4,5,6],"strides":[2,1]}
 
{"rank":3,"shape":[3,2,2],"data":[1,2,3,4,5,6,7,8,9,10,11,12],"strides":[4,2,1]}

Ovšem můžeme použít nástroj typu jq pro vylepšení výstupu, ovšem za cenu prodloužení objemu dat:

{
  "rank": 1,
  "shape": [
    3
  ],
  "data": [
    1,
    2,
    3
  ],
  "strides": [
    1
  ]
}
{
  "rank": 2,
  "shape": [
    3,
    2
  ],
  "data": [
    1,
    2,
    3,
    4,
    5,
    6
  ],
  "strides": [
    2,
    1
  ]
}
{
  "rank": 3,
  "shape": [
    3,
    2,
    2
  ],
  "data": [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    11,
    12
  ],
  "strides": [
    4,
    2,
    1
  ]
}

Dokumentaci k metodě ToJSON lze nalézt na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#NArray.ToJSON.

14. Operace AddConst a AddScaled

Vraťme se ještě na chvíli k dalším operacím, které je možné provádět se všemi prvky n-rozměrných polí. Jednou z těchto operací je přičtení stejné konstanty ke všem prvkům takového pole, což se provede funkcí AddConst, která se chová podobně, jako již popsaná funkce Add (včetně možnosti použití již připraveného vektoru pro uložení výsledků). Tuto operaci si otestujeme snadno:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
        vector2 := narray.New(5)
 
        narray.AddConst(vector2, vector1, 100)
        fmt.Println(vector1)
        fmt.Println(vector2)
}

S výsledky:

narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 101.000000
[ 1] => 102.000000
[ 2] => 103.000000
[ 3] => 104.000000
[ 4] => 105.000000

Dokumentaci k funkci AddConst naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#AddConst.

Další užitečnou operací je součet prvků vektorů, přičemž prvky druhého vektoru jsou vynásobeny nějakou konstantou. Jedná se tedy o kombinaci součtu Add s vynásobením jednoho vektoru zvolenou konstantou. Otestování této funkce je snadné:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        vector1 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
        vector2 := narray.NewArray([]float64{1, 2, 3, 4, 5}, 5)
 
        narray.AddScaled(vector1, vector2, 100)
        fmt.Println(vector1)
        fmt.Println(vector2)
}

Nyní po překladu a spuštění příkladu dostaneme tyto výsledky:

narray rank:   1
narray shape:  [5]
[ 0] => 101.000000
[ 1] => 202.000000
[ 2] => 303.000000
[ 3] => 404.000000
[ 4] => 505.000000
 
narray rank:   1
narray shape:  [5]
[ 0] => 1.000000
[ 1] => 2.000000
[ 2] => 3.000000
[ 3] => 4.000000
[ 4] => 5.000000

Dokumentaci k funkci AddScaled naleznete na adrese https://pkg.go.dev/github­.com/akualab/narray@v0.0.0–20150719231847-f1469ca2dcda/na64#AddScaled.

15. Převod n-rozměrných polí se dvěma dimenzemi na matice

Ve druhé kapitole jsme se zmínili o projektu Gonum Numerical Packages. Tento projekt je dnes poměrně rozšířený, i když se v oblasti numerických výpočtů spíše používá jazyk Julia. Nicméně autoři knihovny narray považovali za vhodné podporovat základní datový typ z knihovny gonum, což je konkrétně typ Matrix. Pro převod dvourozměrného pole (tedy n-rozměrného pole se dvěma dimenzemi) na matici lze použít metodu Matrix tak, jak je to ukázáno v dalším příkladu. Převádět lze i vícerozměrná pole, ovšem tehdy je nutné provést „projekci“ pouze na dvě dimenze:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
 
        trueMatrix := matrix.Matrix(-1, -1)
        fmt.Println(matrix)
        fmt.Println(trueMatrix)
}

S výsledky:

narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000

16. Základní operace s maticemi

S maticemi, tedy striktně dvourozměrnými poli, je možné provádět některé základní operace. Příkladem může být metoda Col určená pro přečtení n-tého sloupce z matice, přičemž se sloupce indexují od nuly a návratovou hodnotou je řez hodnot typu float32 nebo float64:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
 
        trueMatrix := matrix.Matrix(-1, -1)
        fmt.Println(matrix)
        fmt.Println(trueMatrix)
 
        col1 := trueMatrix.Col(nil, 0)
        fmt.Println(col1)
 
        col2 := trueMatrix.Col(nil, 1)
        fmt.Println(col2)
}

Po tisku původní matice by se měl vypsat obsah prvního a druhého sloupce:

narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
[1 3 5]
[2 4 6]

Podobným způsobem můžeme metodou Row získat libovolný řádek matice:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
 
        trueMatrix := matrix.Matrix(-1, -1)
        fmt.Println(matrix)
        fmt.Println(trueMatrix)
 
        row1 := trueMatrix.Row(nil, 0)
        fmt.Println(row1)
 
        row2 := trueMatrix.Row(nil, 1)
        fmt.Println(row2)
}

Opět se podívejme na výsledek vypsaný všemi operacemi:

narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
[1 2]
[3 4]

A konečně metoda At slouží pro přečtení vybraného prvku z matice. V tomto případě se pochopitelně musí uvést index sloupce i řádku, jak je to ostatně patrné ze zdrojového kódu:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
 
        trueMatrix := matrix.Matrix(-1, -1)
        fmt.Println(matrix)
        fmt.Println(trueMatrix)
 
        fmt.Println(trueMatrix.At(0, 0))
        fmt.Println(trueMatrix.At(0, 1))
        fmt.Println(trueMatrix.At(0, 2))
        fmt.Println(trueMatrix.At(1, 0))
        fmt.Println(trueMatrix.At(1, 1))
        fmt.Println(trueMatrix.At(1, 2))
}

S výsledky:

narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
1
2
3
3
4
5

Poslední metoda, s níž se dnes setkáme, slouží pro přečtení rozměrů matice. Opět se jedná o metodu, která je vyžadována rozhraním získaným z projektu Gonum:

package main
 
import (
        "fmt"
 
        narray "github.com/akualab/narray/na64"
)
 
func main() {
        matrix := narray.NewArray([]float64{1, 2, 3, 4, 5, 6}, 3, 2)
 
        trueMatrix := matrix.Matrix(-1, -1)
        fmt.Println(matrix)
        fmt.Println(trueMatrix)
 
        fmt.Println(trueMatrix.Dims())
}

Výsledkem bude:

narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
narray rank:   2
narray shape:  [3 2]
[ 0 0] => 1.000000
[ 0 1] => 2.000000
[ 1 0] => 3.000000
[ 1 1] => 4.000000
[ 2 0] => 5.000000
[ 2 1] => 6.000000
 
3 2

17. Závěrečné zhodnocení

Projekt narray je v současnosti zajímavý zejména z technologického hlediska, tedy proto, jakým způsobem jsou operace s n-rozměrnými poli ručně „vektorizovány“ s využitím assembleru programovacího jazyka Go (a nikoli například voláním nativních funkcí naprogramovaných v céčku). Taktéž návaznost na projekt Gonum je poměrně dobrá, a to i z praktického hlediska. I když se však jedná o poměrně starý balíček, není stále ještě zcela dokončen a dokonce obsahuje i základní chyby při práci s poli s prvky typu float32, což pochopitelně může přinášet obavy z jeho dalšího využití ve vlastních projektech. I z těchto důvodů jsme prozatím tento balíček do našich „produkčních“ projektů nezařadili, i když za to do jisté míry platíme menší výkonností aplikací, které nedokážou plně využít možností moderních mikroprocesorů s podporou SIMD operací.

bitcoin_skoleni

Poznámka: popravdě se pro oblast lineární algebry či zpracování signálů programovací jazyk Go prozatím příliš nehodí. Nejedná se sice o tu nejhorší technologii, ale k dispozici jsou lepší alternativy, například již zmíněný jazyk Julia.

18. Předchozí články o rozsáhlém světu „array programmingu“

Programovacími jazyky, které jsou z větší či menší míry odvozeny od APL, jsme se již na stránkách Roota zabývali v několika článcích. Odkazy na tyto články naleznete pod odstavcem:

  1. Jazyky umožňující operace s poli aneb rozsáhlý svět „array programmingu“
    https://www.root.cz/clanky/jazyky-umoznujici-operace-s-poli-aneb-rozsahly-svet-bdquo-array-programmingu-ldquo/
  2. Specializované jazyky pro práci s N-dimenzionálními poli: jazyk J
    https://www.root.cz/clanky/spe­cializovane-jazyky-pro-praci-s-n-dimenzionalnimi-poli-jazyk-j/
  3. Programovací jazyky odvozené od APL: BQN a ivy aneb 1~×`1↓↕10
    https://www.root.cz/clanky/pro­gramovaci-jazyky-odvozene-od-apl-bqn-a-ivy-aneb-1–1–10/
  4. Programovací jazyk K: důkaz, že mezi námi žijí mimozemšťané
    https://www.root.cz/clanky/pro­gramovaci-jazyk-k-dukaz-ze-mezi-nami-ziji-mimozemstane/
  5. Programovací jazyk K: důkaz, že mezi námi žijí mimozemšťané (dokončení)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-k-dukaz-ze-mezi-nami-ziji-mimozemstane-dokonceni/
  6. Nial Array Language: další z jazyků inspirovaných APL
    https://www.root.cz/clanky/nial-array-language-dalsi-z-jazyku-inspirovanych-apl/
  7. Programování mainframů: jazyk APL
    https://www.root.cz/clanky/pro­gramovani-mainframu-jazyk-apl/
  8. Programovací jazyk APL: programování bez smyček
    https://www.root.cz/clanky/pro­gramovaci-jazyk-apl-programovani-bez-smycek/
  9. Programovací jazyk APL – dokončení
    https://www.root.cz/clanky/pro­gramovaci-jazyk-apl-dokonceni/
  10. Oslava 55 let od vzniku první implementace jazyka APL
    https://www.root.cz/clanky/oslava-55-let-od-vzniku-prvni-implementace-programovaciho-jazyka-apl/

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – na dnešní poměry relativní malý, dnes má přibližně patnáct až šestnáct megabajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 01_narray_na32.go konstrukce pole s nulovými prvky typu float32 https://github.com/tisnik/go-root/blob/master/article95/01_na­rray_na32.go
2 02_narray_na64.go konstrukce pole s nulovými prvky typu float64 https://github.com/tisnik/go-root/blob/master/article95/02_na­rray_na64.go
3 03_new_array.go konstrukce pole se specifikací hodnot prvků https://github.com/tisnik/go-root/blob/master/article95/03_new_a­rray.go
       
4 04_reshape.go funkce pro změnu tvaru pole (reshape) https://github.com/tisnik/go-root/blob/master/article95/04_resha­pe.go
5 05_sum.go součet všech prvků pole https://github.com/tisnik/go-root/blob/master/article95/05_sum.go
6 06_prod.go součin všech prvků pole https://github.com/tisnik/go-root/blob/master/article95/06_prod­.go
7 07_add.go součet prvků dvou vektorů https://github.com/tisnik/go-root/blob/master/article95/07_add.go
8 08_add.go součet prvků do s uložením do určeného vektoru https://github.com/tisnik/go-root/blob/master/article95/08_add.go
9 09_dot.go skalární součin dvou vektorů https://github.com/tisnik/go-root/blob/master/article95/09_dot.go
10 10_dot.go skalární součin tří vektorů https://github.com/tisnik/go-root/blob/master/article95/10_dot.go
       
11 11_writefile.go serializace pole do souboru https://github.com/tisnik/go-root/blob/master/article95/11_wri­tefile.go
12 12_readfile.go deserializace pole ze souboru https://github.com/tisnik/go-root/blob/master/article95/12_re­adfile.go
13 13_to_json.go serializace pole do formátu JSON https://github.com/tisnik/go-root/blob/master/article95/13_to_json­.go
14 14_add_constant.go funkce pro přičtení konstanty ke všem prvkům pole https://github.com/tisnik/go-root/blob/master/article95/14_ad­d_constant.go
15 15_add_scaled.go kombinace add a add_constant https://github.com/tisnik/go-root/blob/master/article95/15_ad­d_scaled.go
       
16 16_to_matrix.go převod pole na typ Matrix https://github.com/tisnik/go-root/blob/master/article95/16_to_ma­trix.go
17 17_matrix_column.go získání celého sloupce matice (selektor) https://github.com/tisnik/go-root/blob/master/article95/17_ma­trix_column.go
18 18_matrix_row.go získání celého řádku matice (selektor) https://github.com/tisnik/go-root/blob/master/article95/18_ma­trix_row.go
19 19_matrix_at.go získání zvoleného prvku (selektor) https://github.com/tisnik/go-root/blob/master/article95/19_ma­trix_at.go
20 20_matrix_dims.go přečtení rozměrů matice https://github.com/tisnik/go-root/blob/master/article95/20_ma­trix_dims.go

20. Odkazy na Internetu

  1. Package narray
    https://github.com/akualab/narray
  2. Dokumentace k balíčku narray/na32
    https://pkg.go.dev/github­.com/akualab/narray/na32
  3. Dokumentace k balíčku narray/na64
    https://pkg.go.dev/github­.com/akualab/narray/na64
  4. The Gonum Numerical Computing Package
    https://www.gonum.org/pos­t/introtogonum/
  5. Gonum Numerical Packages
    https://www.gonum.org/
  6. Accelerating data processing in Go with SIMD instructions
    https://docs.google.com/pre­sentation/d/1MYg8PyhEf0oIv­Z9YU2panNkVXsKt5UQBl_vGEa­CeB1k/htmlpresent#!
  7. Programovací jazyk Go a assembler
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler/
  8. Programovací jazyk Go a assembler (2.část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler-2-cast/
  9. Programovací jazyk Go a assembler (3.část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-go-a-assembler-3-cast/
  10. Comparison of programming languages (array)
    https://en.wikipedia.org/wi­ki/Comparison_of_programmin­g_languages_(array)
  11. Julia (front page)
    http://julialang.org/
  12. Julia – dokumentace
    http://docs.julialang.org/
  13. Julia – repositář na GitHubu
    https://github.com/JuliaLang/julia
  14. Julia (programming language)
    https://en.wikipedia.org/wi­ki/Julia_%28programming_lan­guage%29
  15. IJulia
    https://github.com/JuliaLan­g/IJulia.jl
  16. Introducing Julia
    https://en.wikibooks.org/wi­ki/Introducing_Julia
  17. Julia: the REPL
    https://en.wikibooks.org/wi­ki/Introducing_Julia/The_REPL
  18. Month of Julia
    https://github.com/DataWo­okie/MonthOfJulia
  19. Learn X in Y minutes (where X=Julia)
    https://learnxinyminutes.com/doc­s/julia/
  20. 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
  21. Julia: A Fast Dynamic Language for Technical Computing
    http://karpinski.org/publi­cations/2012/julia-a-fast-dynamic-language
  22. Array Programming
    https://en.wikipedia.org/wi­ki/Array_programming
  23. Discovering Array Languages
    http://archive.vector.org­.uk/art10008110
  24. no stinking loops – Kalothi
    http://www.nsl.com/
  25. Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
    http://www.vector.org.uk/
  26. APL Wiki
    https://aplwiki.com/wiki/
  27. The Array Cast
    https://www.arraycast.com/e­pisodes/episode-03-what-is-an-array
  28. EnthusiastiCon 2019 – An Introduction to APL
    https://www.youtube.com/wat­ch?v=UltnvW83_CQ
  29. Dyalog
    https://www.dyalog.com/
  30. Try APL!
    https://tryapl.org/
  31. The GNU Go Compiler
    https://gcc.gnu.org/onlinedocs/gccgo/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.