A Julia arra biztatja a programozókat, hogy a lehető legtöbb kódot JIT-tel lefordítható függvényekbe tegyék, és sok kis függvény létrehozása a nyelv tervezéséből adódóan is hatékony.
Így általában sok kicsi, egyszerű függvény marad, amelyeket össze kell fűzni ahhoz, hogy nem triviális feladatokat lássunk el.
Az egyik kézenfekvő megközelítés, ha egymásba ágyazzuk a függvényhívásokat. A következő példa nagyon erőltetett, de jól szemlélteti a lényeget.
julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
Ennek a megközelítésnek a hátránya, hogy az olvashatóság gyorsan romlik, ahogy mélyül az egymásba ágyazás.
Egyszerűbb és rugalmasabb megközelítésre van szükségünk.
Ez a matematikusok kedvenc technikája, és a Julia a matematikai szintaxist másolja.
Tetszőleges számú függvényt composed módon fűzhetünk össze ∘ operátorokkal (a beírásuk: \circ, majd tab).
Az eredmény egyetlen függvényként használható.
julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])
Pár dolgot érdemes megjegyezni:
Egy másik lehetőséget a programozók megközelítésének tekinthetünk, szemben a matematikusokéval.
A Pipelines régóta használatos a Unix shell-szkriptekben, az utóbbi időben pedig a mainstream programozási nyelvekben is népszerűvé vált (az F#-ot szokás a bevezetésük úttörőjeként említeni).
A mögötte álló alapgondolat, hogy valamilyen adatból kiindulva egy függvénysorozaton vezetjük át, és így kapjuk meg az eredményt.
A pipe operátor a |> (ahogy az F#-ban és az R újabb verzióiban), a Juliában viszont van egy broadcast változat is: .|>.
julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
A végrehajtás most szigorúan balról jobbra halad: az egyes függvények kimenete a nyíl irányában halad tovább, és a következő függvény bemenetévé válik.
Nem véletlen, hogy a kompozíció és a csővezeték szemléltetésére használt függvények mind egyetlen argumentumot várnak.
Egyes tisztán funkcionális nyelvekben az első argumentumot fűzik be a függvénybe, a többi argumentumot viszont szintén meg lehet adni.
A Juliában ezzel szemben egy csővezetékben csak függvény_neveket_ (vagy azzal egyenértékű dolgokat) adhatsz meg, további argumentumok nélkül.
Ennek fontos technikai okai vannak (összefügg azzal, hogy a currying nem része szabványosan a nyelv felépítésének).
A sok ember, aki nem érti a curryingot, egyszerűen fogadja el, hogy ez a korlát nem egy hanyag mulasztás, és a Julia jövőbeli verzióiban sem valószínű, hogy változik.
Olyan egyargumentumos függvényekre van szükségünk, amelyek pontosan azt teszik, amire szükség van. Szerencsére új függvényeket definiálni a Juliában egyszerű.
A legegyszerűbb megoldás egy anonymous function használata.
Ha például egyetlen bemeneti stringünk van, és alulvonások mentén szeretnénk felbontani:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Ezt a vektort aztán tovább vezethetjük más függvényekhez, ahogy korábban is.
Ebben az esetben az anonymous function zárójelbe tétele elhagyható, általában viszont hasznos módja ez a kétértelműség csökkentésének.
Ugyanígy létrehozhatunk egy nevesített függvényt is korábban a programban, és szükség szerint újra felhasználhatjuk.
A Closures túlmutatnak ennek a fogalomnak a keretein, de aki más nyelvekből ismeri őket, az felismeri, hogy rugalmasabb módot kínálnak egyargumentumos függvények létrehozására.
julia> function makesplit(sep)
fs(str) = split(str, sep)
fs
end
makesplit (generic function with 1 method)
julia> f_us = makesplit('_')
(::var"#fs#32"{Char}) (generic function with 1 method)
julia> "my_test_strings" |> f_us
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
# alternatively:
julia> "my_test_strings" |> makesplit('_')
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Ha egyszer definiáltuk a makesplit() függvényt, bármilyen elválasztóval használhatjuk.
Figyeld meg, hogy a makesplit('_') egy függvényhívás, amelynek eredménye egy másik függvény, és ez kapja meg a csővezeték bemenetét.
Ha ez elsőre zavarosnak tűnik, az teljesen normális (a gyakorlás viszont segít).
Régóta folyik a vita arról, hogyan lehetne a pipe-okat sokoldalúbbá tenni a Julia alapnyelvében, de a különböző javaslatok nem férnek meg egymással, és nem született megegyezés.
Addig is a felhasználók a szokásos utat választották: különböző telepíthető csomagokat készítettek konkrét igényekre. Ezek egyike sem működik az Exercism-ön belül, de ha érdekel, vess rájuk egy pillantást: