Julia (ohjelmointikieli)
Julia | |
---|---|
Paradigma | monia; imperatiivinen, funktionaalinen, olioperusteinen |
Tyypitys | dynaaminen, vahva, nominatiivinen, parametrinen, vapaaehtoinen |
Yleinen suoritusmalli | ajonaikaisesti käännettävä (tyyppikoodi, LLVM) |
Muistinhallinta | roskienkeruu |
Julkaistu | 14. helmikuu 2012 |
Vakaa versio | 1.11.0 |
Vaikutteet | R, MATLAB, Python, Lisp, Perl, Lua, Ruby[1] |
Käyttöjärjestelmä | alustariippumaton |
Verkkosivu | julialang.org, github.com/JuliaLang/julia |
Julia on ohjelmointikieli, jota on kehitetty erityisesti tieteelliseen laskentaan, tavoitteenaan yhdistää hitaiden dynaamisten kielten helppokäyttöisyys ja perinteisten staattisten kielten suorituskyky.[1]
Julian keskeisin piirre ja ohjelmointitapa on multiple dispatch eli funktion koko tyyppijälki määrittää, mitä toteutusta eli metodia tietystä funktiosta kutsutaan. Monissa muissa kielissä metodit kuuluvat yhdelle objektille, eli vain ensimmäisen parametrin (esim. self
) tyypillä on merkitystä (engl. single dispatch).[1]
Käyttöaiheet
[muokkaa | muokkaa wikitekstiä]Lineaarialgebra on yksi numeerisen laskennan keskeisistä alueista, joten tähän löytyy hyvä tuki Julian perus- ja standardikirjastosta (using LinearAlgebra
):[2]
AbstractArray
ja tämän alatyypit mahdollistavat moniulotteiset taulukot suoraanBase
-paketista- Monipuolinen vektori-
[1, 2, 3]
ja matriisisyntaksi[1 2 3; 4 5 6]
- Matriisioperaattorit
+
,-
,*
,/
,⋅
,×
- Jälki
tr(M)
, determinanttidet(M)
ja kääntämineninv(M)
- Eigen-arvot
eigvals(M)
, eigen-vektoriteigvecs(M)
ja faktorointifactorize(M)
- Matriisityypit kuten
Symmetric
,Hermitian
jaTridiagonal
- Faktorointityypit kuten
BunchKaufman
,LU
jaQR
- Lyhennelmät
[x*y for x=1:5, y=5:10]
, jotka tekevät taulukon - Generaattorit
(x^5 for x=1:10)
, jotka voidaan iteroida
Käyttö
[muokkaa | muokkaa wikitekstiä]Julia-komentoriviohjelman voi ladata kielen virallisilta web-sivuilta valmiina ajotiedostona ohjeiden mukaan. Kieli voidaan asentaa myös juliaup-työkalulla[3], joka helpottaa myös kielen päivittämistä ja useiden eri versioiden rinnakkaista käyttöä.
julia --help
näyttää saatavilla olevat asetukset ja julia
käynnistää REPL-konsolin. Komentoriviä voidaan käyttää poistumatta REPL-tilasta ;
-komennolla ja paketinhallintatilaan päästään ]
-komennolla. ]?
näyttää kaikki Pkg-pakettienhallinnan REPL-tilan komennot, jotka vastaavat Pkg-paketin funktioita. REPL-ohjelmasta poistutaan <CTRL>-D
. Julia-kääntäjää käytetään yhdelle lähdetiedostolle yksinkertaisesti julia hei-wikipedia.jl
. (Tämä Julia-kääntäjä toimii ns. JAOT-periaatteella (engl. just-ahead-of-time) eli Julia-koodi käännetään tyypitetyn ja LLVM-välikoodin kautta konekielelle, mutta tämä tapahtuu ajon aikana eikä erillisessä käännösvaiheessa.)
Julia REPL mahdollistaa interaktiivisemman ohjelmointitavan, missä kehitettävät ohjelmat ovat omassa moduulissaan ja tiedostossaan, testit ovat omassa moduulissaan ja tiedostossaan, testit ajetaan Julia-konsolissa tyylillä include("testit.jl")
ja tämän lisäksi tehdään interaktiivista testaamista konsolissa. Kun interaktiivisesti löydetään jotain uutta testaamisen arvoista, tämä voidaan lisätä testimoduuliin. Muuten kehityksen aikana tehdään moduuleihin muutoksia palautteen mukaan ja toistetaan kehityssilmukkaa. Revise
-paketti mahdollistaa päivitettyjen ohjelmien testaamisen käynnistämättä Juliaa uudelleen.[4]
Pluto.jl on Julialla Julialle kehitetty, Jupyter-työkirjojen kaltainen web-selaimeen ja ajettaviin soluihin perustuva kehitysympäristö, joka mahdollistaa interaktiivisen kirjallisen ohjelmoinnin (engl. literate programming). Työkirjat ovat aina toistettavia, sillä Pluto automatisoi pakettienhallinnan import
- ja using
-lauseiden perusteella.[5]
Esimerkki
[muokkaa | muokkaa wikitekstiä]Fibonaccin luvut.
function fibonacci(n) f = [0, 1] # Julia-listojen indeksit alkavat ykkösestä. n in [0, 1] && return f[n + 1] # Ajon fibonacci(n = 2) tulos tulee listan indeksiin 3, jne. for i = 3:(n + 1) # Huutomerkkiin päättyvät funktiot muokkaavat parametrejaan. append!(f, f[i - 1] + f[i - 2]) end # end-sana viittaa listan vimeiseen indeksiin. return f[end] end
Rakenne
[muokkaa | muokkaa wikitekstiä]Modularisointi
[muokkaa | muokkaa wikitekstiä]Julian Pkg
-paketti mahdollistaa vakioitujen ympäristöjen luomisen, pääosin activate
ja add
/rm
funktioidensa avulla. Tietty pakettiversio voidaan vaatia syntaksilla Pkg.add("Nimi@1.3")
(semanttinen versiointi) tai Pkg.add("Nimi#master")
(Git). Myös suora URL toimii, jos kyseinen paketti ei ole käytetyssä rekisterissä. Nämä komennot muokkaavat kahta projektikansion juuressa olevaa tiedostoa: Project.toml
(metadata) ja Manifest.toml
(riippuvuudet). Pkg.instantiate
asentaa halutun Julia-ympäristön uudelleen näiden tiedostojen pohjalta. Julia-koodin lisäksi muiden riippuvuuksien käsittelemiseksi voidaan käyttää Artifacts.toml
-tiedostoa, jonka avulla kunkin riippuvuuden paikallinen tiedostopolku saadaan käyttöön projektin Julia-koodissa syntaksilla polku = artifact"nimi"
(engl. non-standard string literal).[6]
Kun ympäristö koostuu useista paketeista, niin paketti muodostuu yleensä useiden moduulien hierarkiasta. Moduulit module Nimet ... end
luovat uuden globaalin nimiavaruuden, johon tuodaan nimiä import
ja using
lauseilla ja lähdetiedostoja include()
-kutsulla ja josta paljastetaan nimiä export
-lauseilla (vain using
ottaa export
-lauseet huomioon). Paikalliseen moduuliin viittaamiseksi tarvitaan pistesyntaksia using .Nimi
, koska pisteetön syntaksi viittaa Julia-paketteihin.[7]
Juliassa on moduulin luoma globaali näkymä ja tämän sisällä ns. heikkoja ja vahvoja paikallisia näkymiä. if..else
ja begin
lauseet eivät luo ollenkaan uutta näkymää, kun taas struct
, for
, while
ja try
lauseet luovat paikallisen näkymän, jossa globaalien nimien uudelleenkäyttö on kielletty (heikko näkymä). Funktiot ja makrot luovat vahvan paikallisen näkymän eli ulkonäkymien nimiin sijoittaminen on sallittua eikä muokkaa niitä. Kullakin paikallisella näkymällä on kuitenkin lukuoikeus sen ulkonäkymissä määriteltyihin muuttujiin. Esim. funktiot perivät arvot määrittelympäristöstään, eli esim. tietyn moduulin funktiot voivat lukea samoja globaaleja muuttujia moduulistaan ilman erityisiä avainsanoja.[8]
Tyypit
[muokkaa | muokkaa wikitekstiä]Julian tyyppijärjestelmä on dynaaminen, nominatiivinen, parametrinen ja yhtenäinen. Kaikilla rakenteilla on jokin tyyppi ajon aikana.[9]
Kielen mukana tulevia tietotyyppejä (typeof()
) ovat muun muassa seuraavat:
AbstractString
,AbstractChar
ja näiden Unicode-UTF-8-alityypitString
jaChar
.Substring{String}
edustaa tekstiviipaletta. Unicode UTF-8 sisältää joitakin monitavuisia merkkejä, joten turvalliseen indeksointiin voidaan käyttäänextind()
-,prevind()
- taieachindex()
-funktioita.[10]Number
on kaikkien numeroiden (Int64
,Real
) ylätyyppi.Missing
ja tämän ainoa arvomissing
. Numeerisissa ohjelmissamissing
tekee tuloksesta epävarman, joten odotettu tulos on yleensämissing
. Tämä ei pidä välttämättä paikkaansa muissa tilanteissa, jolloin voidaan käyttää esim.Missings.passmissing
taiBase.skipmissing
funktioita. Puuttuvia sisältävälle kokoelmalle voidaan kutsua myöscollect()
.[11]- Kaikki funktiot ovat omaa tyyppiänsä ja
Function
-tyypin alatyyppejä. Funktioita voidaan syöttää funktioihin, käsitellä funktioissa ja palauttaa funktioista muiden arvotyyppien tapaan.
Uusi yhdistelmätyyppi luodaan sanoilla struct Nimi ... end
. Instansseja voidaan muokata vain, kun on käytetty mutable struct Nimi ... end
, ellei tyypin alkiota olla määritelty sanalla const
. Tyyppejä voidaan yhdistää myös tyylillä MaybeInt = Union{Int, Nothing}
. Tyyppiparametreja käytetään syntaksilla struct Tyyppi{T} ... end
, mikä määrittelee kerralla joukon tyyppejä. Parametrina olevien tyyppien joukko voidaan rajoittaa alatyypeihin tyylillä Tyyppi{T<:YlinTyyppi}
. Tyyppi voidaan myös parametrisoida vain osittain syntaksilla Array{T,1} where T
. Abstrakteja tyyppejä abstract type Tyyppi <: Supertyyppi end
taas ei voida käyttää suoraan vaan ne täytyy ensin konkretisoida. Tämän jälkeen abstraktia tyyppiä voidaan käyttää viittaamaan kaikkiin tämän alatyyppeihin. (Juliassa ei ole olioperusteisemmille kielille tyypillistä perimismekanismia.)[9]
Luodut tyyppiobjektit toimivat itse alustajinaan struct Tyyppi ... end; t = Tyyppi(...)
. Tämä alustaja on tavallinen funktio, joten sille voidaan määritellä useita metodeja Tyyppi(...) = ...
. Tyypin alustajaa voidaan käyttää myös tyypin omassa määritelmässä erityisen new()
-funktion kautta.[12] Alustettujen arvojen tyyppiä voidaan selvissä tapauksissa muuttaa convert(Tyyppi, arvo)
- tai promote(arvo, arvo)
-funktiolla. (Numeroita edustaville String
-arvoille ("2.54"
) löytyy oma parse()
-funktionsa.)[13]
Tietylle tyypille määritellään metodeja Juliassa tavallisten funktioiden tapaan function f(x::Tyyppi) ... end
. Metodi ei kuitenkaan kuulu ainoastaan kyseiselle tyypille vaan kutsuttava metodi valitaan koko funktion tyyppijäljen perusteella (engl. multiple dispatch). Funktionkin tyyppijälki voidaan parametrisoida tyylillä funktio(x::T, y::T, z::T) where {T} ... end
(eli tämä metodi valitaan, kun kolmen argumentin tyypit ovat samoja). Näin myös objekteista itsestään voidaan tehdä kutsuttavia (ns. funktoreita) antamalla niiden tyypille metodi tyylillä function (x::Tyyppi)() ... end
.[14] Olemassaolevien rajapintojen toteuttaminen tapahtuu toteuttamalla tarvittavien funktioiden metodit – uudesta tyypistä voidaan tehdä muun muassa iteroitava, indeksoitava ja listamainen määrittelemällä sille metodit muutamista erityisistä funktioista kuten iterate
, getindex
ja size
[15].
Funktiot
[muokkaa | muokkaa wikitekstiä]Funktiot määritellään function nimi(x) ... end
tai yhdellä rivillä nimi(x) = ...
. Nimettömille funktioille voidaan käyttää syntaksia (x, y) -> ...
tai function (x) ... end
tai x -> begin ... end
. funktio(y) do x ... end
tekee anonyymin funktion do
-lauseen avulla ja syöttää sen edellä olevan funktion ensimmäiseksi argumentiksi. Funktion voi ns. vektorisoida eli kutsua jonkin kokoelman jokaiselle alkiolle käyttämällä pistesyntaksia kuten x .= sin.(y)
(tai sama pistemakrolla @. x = sin(y)
, kun kaikki operaatiot vektorisoidaan). Funktioiden yhdistäminen on mahdollista muun muassa putkioperaattorilla x = 1:3 .|> sum |> sqrt
.[16]
Funktioiden parametrit ovat Juliassa uusia muuttujia, mutta ne vain viittaavat annettuun arvoon eli kopioita ei synny. Funktiot voidaan tyypittää tyylillä function funktio(x::Tyyppi=0, args...; y::Tyyppi="oletus", kwargs...)::Tyyppi end
, mutta yleensä Julian tyypinpäättelyn vuoksi korkeintaan parametrien tyyppien merkitseminen on tarpeen. Funktio voi hyväksyä vaihtelevan määrän parametreja tyylillä funktio(x,y,z...)
, missä z
on Tuple
funktion sisällä tai mikä tahansa iteroitava tyyppi funktion ulkona. Funktioiden tulisi palauttaa vain yhden tyypin arvoja, jolloin tulostyyppi on Julia-kääntäjän pääteltävissä. Tyyppien merkitseminen voi kuitenkin parantaa koodin luettavuutta. Destrukturointi onnistuu taas tyylillä a, b, _ = iteraattori(x)
.[16]
Sivuvaikutusten takia kutsutut funktiot palauttavat käytännön mukaan nothing
. Parametrejaan muuttavien funktioiden nimi päättyy huutomerkkiin teejotain!
. Funktioiden nimeämiskäytäntö on ilman alaviivoja kuten teenytjotain()
, kun tämä vain on luettavissa – muussa tapauksessa sanat erotetaan alaviivalla.[16]
Juliassa myös kaikki operaattorit ovat funktioita: esim. x[i]
kutsuu getindex
, [1 2 3]
kutsuu hcat
ja x.nimi = y
kutsuu setproperty!
.[16]
Metaohjelmointi
[muokkaa | muokkaa wikitekstiä]Metaohjelmointi onnistuu suoraan käsittelemällä Julia-ohjelmien abstraktia syntaksipuuta. Tämä mahdollistaa täyden metaohjelmoinnin. Julia-koodin tyyppi on aluksi tyyppiä String
, joka voidaan jäsentää Meta.parse(koodi)
lauseketyypin Expr
arvoksi. Lausekkeita voidaan määritellä myös syntaksilla :( 1 + 2 + $numero )
ja usean rivin lausekkeita syntaksilla quote ... end
. Kaksoispisteellä tehdään myös Symbol
-tyypin arvoja :nimi
(engl. string interning). Lausekkeet voidaan lopuksi ajaa eval
-funktiolla. Lausekkeita voidaan siis käsitellä normaaleissa Julia-funktioissa, mutta nämä funktiot ajetaan vasta ajon aikana, kun taas metaohjelmoinnissa on yleensä tehokkaampaa tuottaa halutut lausekkeet jo käännösvaiheessa.[17]
Käännösaikaiseen metaohjelmointiin tarvitaan makroja. Makrot sijoittavat argumenttinsa suoraan tuloksena tuotettuun lausekkeeseen, joka käännetään sitten normaalin Julia-koodin tapaan. Makrot määritellään tyylillä macro nimi() ... end
ja kutsutaan syntaksilla @ajamakro x y
tai @ajamakro(x, y)
(tai yhdelle literaalitaulukolle @ajamakro[1, 2, 3]
). Makro saa argumenttinsa vain lausekkeina Expr
, symboleina :nimi
tai literaaleina. Koska Julia on dynaamisesti tyypittävä, makrot eivät tiedä saamiensa arvojen tyyppejä. Makrot noudattelevat muuten paljolti samoja sääntöjä kuin funktiot.[17]
Normaalin funktion ja makron lisäksi voidaan määritellä myös ns. tuotettu funktio tyylillä @generated function nimi() ... end
, joka asettuu ominaisuuksiltaan funktion ja makron välille. Tällainen funktio ajetaan vasta, kun argumenttien tyypit tiedetään, mutta ennen kuin funktio on käännetty. Makrojen tapaan nämä erityisfunktiot lisäävät abstraktiokykyä ja suorityskykyä siirtämällä laskentaa ajovaiheesta käännösvaiheeseen.[17]
Rinnakkaisohjelmointi
[muokkaa | muokkaa wikitekstiä]@async
-makro tekee annetusta lausekkeesta asynkronisesti ajettavan Task
-tyypin objektin, jolle voidaan kutsua schedule()
, wait()
ja fetch()
. Vastaavasti @sync
-makron avulla voidaan odottaa kaikkien makron sisältämien @async
-lausekkeiden valmistumista. Task
-objekteja voidaan luoda myös käyttämällä @task
-makroa tai tyypin alustajaa Task(() -> x)
. Channel
on taas ikäänkuin rinnakkainen versio generaattorista; kanavan tuottaja kutsuu put
-funktiota ja kuluttaja take
-funktiota tai iteroi kanavaa muiden iteroitavien tyyppien mukaan.[18]
Julia käyttää oletuksena yhtä säiettä (julia --threads=1
). Julia ei myöskään varmista muistiturvallisuutta (vrt. Rust (ohjelmointikieli)) vaan tämä on ohjelmoijan vastuulla. Esim. datakisavirheiden ehkäisemiseksi tulee käyttää lukkomekanismia, kuten ReentrantLock()
, lock()
ja unlock()
, tai ns. atomisia arvoja, kuten Threads.Atomic{Tyyppi}(arvo)
ja Threads.atomic_add!()
. Threads
-paketti määrittelee helppokäyttöisen @threads
-makron, joka tekee annetusta silmukkalausekkeesta automaattisesti monisäikeisen.[19]
julia -p x
tekee paikallisen klusterin, jossa on annettu määrä prosesseja, ja julia --machine-file f
tekee usean koneen klusterin käyttämällä salasanatonta ssh-viestintää näiden välillä. Distributed
-paketti mahdollistaa hajautetun laskennan useissa prosesseissa ja perustuu etäreferensseihin (Future
ja RemoteChannel
) ja etäkutsuihin (@spawnat :any ...
, remotecall()
). Future
-tyypille kutsutaan lopulta fetch()
tuloksen saamiseksi pääprosessiin. Lähdetiedosto voidaan lisätä jokaiseen prosessiin tyylillä @everywhere include("Nimi.jl")
tai tekemällä tästä Julia-paketti. Prosesseja voidaan lisätä, poistaa ja muokata funktioilla kuten addprocs
ja rmprocs
. Esimerkiksi DistributedArrays
-paketin DArray
on taulukon hajautettu versio ja standardikirjaston SharedArray
mahdollistaa hajautettujen osasten jakamisen useiden prosessien välillä.[20]
Grafiikkaprosessoreiden käyttöön löytyy tukea muun muassa paketeista CUDA.jl ja MPI.jl.[20]
Muut
[muokkaa | muokkaa wikitekstiä]Dokumentointi onnistuu Juliassa tekstiliteraaleilla kohteena olevan määritelmän yläpuolella (engl. docstring). Teksti voi sisältää Markdown- ja LateX-merkintöjä.[21]
Julian tärkeimpiin siirrännän työkaluihin kuuluvat ensinnäkin stdin
- ja stdout
-virrat, jotka tukevat print
, show
, read
ja write
funktioita. open
-funktio tekee tiedostonimestä IOStream
-objektin ja close
-sulkee virran, jos open
kutsuttiin ilman funktioargumenttia. Sockets
-paketti määrittelee TCP-viestintään pääfunktiot listen
, accept
, connect
ja close
.[22]
Lähteet
[muokkaa | muokkaa wikitekstiä]- ↑ a b c https://docs.julialang.org/en/v1/#man-introduction
- ↑ https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/
- ↑ https://github.com/JuliaLang/juliaup
- ↑ https://docs.julialang.org/en/v1/manual/workflow-tips/
- ↑ Pluto.jl. https://plutojl.org/. Viitattu 1.3.2023.
- ↑ https://pkgdocs.julialang.org/v1/
- ↑ https://docs.julialang.org/en/v1/manual/modules/
- ↑ https://docs.julialang.org/en/v1/manual/variables-and-scoping/
- ↑ a b https://docs.julialang.org/en/v1/manual/types/
- ↑ https://docs.julialang.org/en/v1/manual/strings/
- ↑ https://docs.julialang.org/en/v1/manual/missing/
- ↑ https://docs.julialang.org/en/v1/manual/constructors/
- ↑ https://docs.julialang.org/en/v1/manual/conversion-and-promotion/
- ↑ https://docs.julialang.org/en/v1/manual/methods/
- ↑ https://docs.julialang.org/en/v1/manual/interfaces/
- ↑ a b c d https://docs.julialang.org/en/v1/manual/functions/
- ↑ a b c https://docs.julialang.org/en/v1/manual/metaprogramming/
- ↑ https://docs.julialang.org/en/v1/manual/asynchronous-programming/
- ↑ https://docs.julialang.org/en/v1/manual/multi-threading/
- ↑ a b https://docs.julialang.org/en/v1/manual/distributed-computing/
- ↑ https://docs.julialang.org/en/v1/manual/documentation/
- ↑ https://docs.julialang.org/en/v1/manual/networking-and-streams/
Aiheesta muualla
[muokkaa | muokkaa wikitekstiä]- Julia-kielen kotisivu (englanniksi)
- Julia-kielen lähdekoodi (englanniksi)