ट्रैक
/
Julia
Julia
/
सिलेबस
/
फंक्शन संयोजन
फं

फंक्शन संयोजन में Julia

1 अभ्यास

फंक्शन संयोजन के बारे में

Julia प्रोग्रामरों को प्रोत्साहित करती है कि वे ज़्यादा से ज़्यादा कोड ऐसे फंक्शनों के अंदर रखें जिन्हें JIT-कंपाइल किया जा सके, और बहुत सारे छोटे फंक्शन बनाना डिज़ाइन से ही अच्छा प्रदर्शन देता है।

इससे अक्सर बहुत सारे छोटे और सरल फंक्शन बच जाते हैं, जिन्हें जटिल काम पूरे करने के लिए आपस में जोड़ना पड़ता है।

एक सीधा तरीका है फंक्शन कॉल को नेस्टेड करना। नीचे दिया गया उदाहरण बहुत बनावटी है, लेकिन बात स्पष्ट कर देता है।

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)

इस तरीके की कमी यह है कि जैसे-जैसे नेस्टिंग गहरी होती जाती है, पढ़ने में आसानी तेज़ी से घटती जाती है।

हमें इससे सरल और ज़्यादा लचीला तरीका चाहिए।

कंपोज़िशन

यह गणितज्ञों का पसंदीदा तरीका है, और Julia गणित की ही सिंटैक्स नकल करती है।

∘ ऑपरेटरों की मदद से कितने भी फंक्शन आपस में composed किए जा सकते हैं (\circ लिखकर टैब दबाइए)। नतीजे को एक ही फंक्शन की तरह इस्तेमाल किया जा सकता है।

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"])

कुछ बातें ध्यान देने लायक हैं:

  • शुरू के फंक्शन उसी क्रम में दिखते हैं जैसे नेस्टिंग में, और इन्हें दाएँ से बाएँ क्रम में चलाया जाता है।
  • कंपोज़ करते समय ब्रॉडकास्टिंग का इस्तेमाल करना आसान नहीं होता, लेकिन कंपोज़ किए गए फंक्शन को कॉल करते समय इसे लगाया जा सकता है।

पाइपलाइनिंग

इसे गणितज्ञों के बजाय प्रोग्रामरों का तरीका माना जा सकता है।

Pipelines का इस्तेमाल Unix शेल स्क्रिप्ट में बहुत पहले से होता आया है, और हाल के सालों में यह मुख्यधारा की प्रोग्रामिंग भाषाओं में लोकप्रिय हो गया (कभी-कभी F# को इन्हें अपनाने का अग्रणी माना जाता है)।

इसका मूल विचार यह है कि कुछ डेटा से शुरू कीजिए, फिर नतीजा पाने के लिए उसे फंक्शनों के एक क्रम से गुज़ारिए।

पाइप ऑपरेटर |> है (जैसा F# और R के हाल के वर्शनों में), हालाँकि Julia में ब्रॉडकास्ट वाला रूप भी है, .|>।

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)

अब कोड पूरी तरह बाएँ से दाएँ चलता है, और हर फंक्शन का आउटपुट तीर की दिशा में बहकर अगले फंक्शन का इनपुट बन जाता है।

सीमाएँ, काम चलाऊ उपाय और दूसरे विकल्प

यह कोई संयोग नहीं है कि कंपोज़िशन और पाइपलाइनिंग दिखाने वाले सारे फंक्शन एक ही आर्गुमेंट लेते हैं।

कुछ पूरी तरह फंक्शनल भाषाएँ पहले आर्गुमेंट को फंक्शन में भेजती हैं, लेकिन बाकी आर्गुमेंट भी शामिल करने देती हैं।

इसके उलट, Julia पाइपलाइन में सिर्फ फंक्शन के नाम (या उनके बराबर कुछ) की उम्मीद करती है, बिना कोई अतिरिक्त आर्गुमेंट दिए।

इसकी कुछ ज़रूरी तकनीकी वजहें हैं (इससे जुड़ी हुई कि currying भाषा के डिज़ाइन का मानक हिस्सा नहीं है)। जो बहुत से लोग करीइंग समझते ही नहीं, उन्हें बस यह मान लेना चाहिए कि यह सीमा कोई लापरवाही से छूटी चूक नहीं है, और आगे के Julia वर्शनों में इसके बदलने की संभावना नहीं है।

काम चलाऊ उपाय

हमें ऐसे फंक्शन चाहिए जो एक ही आर्गुमेंट लें और जो भी ज़रूरत हो, वह करें। अच्छी बात है कि Julia में नए फंक्शन बनाना आसान है।

सबसे आसान तरीका है anonymous function इस्तेमाल करना। उदाहरण के लिए, अगर हमारे पास एक इनपुट स्ट्रिंग है और हम उसे अंडरस्कोर पर तोड़ना चाहते हैं:

julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

फिर उस वेक्टर को पहले की तरह दूसरे फंक्शनों में पाइप किया जा सकता है।

इस मामले में अनोनिमस फंक्शन को गोल ब्रैकेट में लपेटना ज़रूरी नहीं है, लेकिन आम तौर पर यह अस्पष्टता कम करने का अच्छा तरीका है।

इसी तरह, हम प्रोग्राम में पहले एक नाम वाला फंक्शन बना सकते हैं और ज़रूरत पड़ने पर उसे दोबारा इस्तेमाल कर सकते हैं।

Closures इस कॉन्सेप्ट के दायरे से बाहर हैं, लेकिन जो लोग दूसरी भाषाओं से इन्हें जानते हैं, वे पहचान लेंगे कि ये एक ही आर्गुमेंट वाले फंक्शन बनाने का ज़्यादा लचीला तरीका देते हैं।

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"

एक बार makesplit() बन जाने के बाद, इसे किसी भी सेपरेटर के साथ इस्तेमाल किया जा सकता है। ध्यान दीजिए कि makesplit('_') एक फंक्शन कॉल है जिसका मान एक और फंक्शन होता है, और वह फंक्शन आगे पाइप से इनपुट पाता है।

अगर यह उलझन भरा लगे, तो शुरू में ऐसा होना सामान्य है (लेकिन अभ्यास के साथ यह साफ़ हो जाता है)।

दूसरे विकल्प

बेस Julia में पाइप को ज़्यादा बहुमुखी बनाने पर लंबी चर्चा होती रही है, लेकिन अलग-अलग सुझाव एक-दूसरे से मेल नहीं खाते और कोई सहमति नहीं बन पाई है।

इस बीच, उपयोगकर्ताओं ने अपनी खास ज़रूरतें पूरी करने वाले अलग-अलग इंस्टॉल होने योग्य पैकेज बनाने का हमेशा वाला रास्ता अपनाया है। इनमें से कोई भी Exercism के अंदर काम नहीं करेगा, लेकिन अगर आपको दिलचस्पी हो तो इन पर एक नज़र डालिए:

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

फंक्शन संयोजन सीखिए