ট্র্যাক
/
Julia
Julia
/
সিলেবাস
/
ফাংশন কম্পোজিশন
ফা

ফাংশন কম্পোজিশন মধ্যে Julia

১টি অনুশীলনী

ফাংশন কম্পোজিশন সম্পর্কে

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('_') একটি ফাংশন কল, যা আরেকটি ফাংশনে পরিণত হয়, আর সেটি আবার পাইপ থেকে ইনপুট পায়।

এটা যদি বিভ্রান্তিকর মনে হয়, প্রথমে সেটাই স্বাভাবিক (তবে চর্চার সাথে সাথে এটা স্পষ্ট হয়ে ওঠে)।

অন্যান্য অপশন

base Julia-তে পাইপকে আরও বহুমুখী করার বিষয়ে দীর্ঘ আলোচনা হয়েছে, তবে বিভিন্ন প্রস্তাব পরস্পরবিরোধী এবং কোনো সমঝোতায় পৌঁছানো যায়নি।

এদিকে ব্যবহারকারীরা চিরাচরিত পথেই হেঁটেছেন, নির্দিষ্ট প্রয়োজন মেটানোর জন্য বিভিন্ন ইনস্টলযোগ্য প্যাকেজ তৈরি করেছেন। এর কোনোটিই Exercism-এর ভেতরে কাজ করবে না, তবে আগ্রহ থাকলে এগুলো একবার দেখে নিন:

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি নতুন একটি উইন্ডো বা ট্যাবে খুলবে

ফাংশন কম্পোজিশন শিখুন