Kurzusok
/
Julia
Julia
/
Tanterv
/
Függvénykompozíció
Fü

Függvénykompozíció ebben a kurzusban: Julia

1 feladat

A(z) Függvénykompozíció fogalomról

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.

Kompozíció

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:

  • A kiinduló függvények ugyanabban a sorrendben szerepelnek, mint egymásba ágyazáskor, és jobbról balra haladva futnak le.
  • A broadcasting használata kompozíciónál nem egyszerű, de az összefűzött függvény meghívásakor alkalmazható.

Csővezeték

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.

Korlátok, kerülőutak és egyéb lehetőségek

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.

Kerülőutak

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).

Egyéb lehetőségek

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:

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Függvénykompozíció fogalmat