এর আগে বেসিকস কনসেপ্টে ফাংশনের সঙ্গে পরিচয় হয়েছিল, এমন কিছু উদাহরণ দিয়ে:
squareit <- function(x) {
x * x
}
squareit(3)
#> [1] 9
# shorter form
squareit_short <- function(x) x ^ 2
squareit-এর ডেফিনিশনটি একটু খেয়াল করে দেখলে আমরা নানা অংশ চিনতে পারি:
x নেয়।{ } দিয়ে ঘেরা হয়।squareit নামের একটি ভ্যারিয়েবলে অ্যাসাইন করা হয়।R-এ একটি ফাংশন ফার্স্ট ক্লাস অবজেক্ট, ঠিক সংখ্যা বা স্ট্রিংয়ের মতোই। তাই squareit <- function... হলো একটি অ্যাসাইনমেন্ট, যা সিনট্যাক্সের দিক থেকে ঠিক x <- 42 লেখার মতোই।
সাধারণ ব্যবহারে ফাংশনের উপাদানগুলোতে ঢুঁ মারা বিরল, তবে বেশ সহজ।
class(squareit)
#> [1] "function"
formals(squareit)
#> $x
body(squareit)
#> {
#> x * x
#> }
formals() দিয়ে পাওয়া আর্গুমেন্টগুলো $x সিনট্যাক্সসহ দেখতে একটি অ্যারে-এর মতো লাগে। আসলে এর টাইপ pairlist, অর্থাৎ কী (key) ও মানের জোড়া ধারণ করা এক বিশেষ ধরনের অ্যারে।
body হলো এক্সিকিউটেবল কোড, যার টাইপ language (এটা নিয়ে আপনার চিন্তা করার দরকার সম্ভবত নেই)।
Python, Julia-র মতো অন্য স্ক্রিপ্টিং ভাষার বিপরীতে R পজিশনাল আর্গুমেন্ট আর কিওয়ার্ড আর্গুমেন্টের মধ্যে স্পষ্ট কোনো পার্থক্য করে না।
ফাংশন কল ভ্যালু পাঠাতে পারে পজিশন অনুযায়ী, আবার নাম ধরেও। নাম ধরে পাঠানো কাজে লাগে অনেক আর্গুমেন্টওয়ালা জটিল ফাংশনে, যেখানে আর্গুমেন্টের ক্রম মনে রাখা কঠিন।
f <- function(x, y) x / y
# call positionally
f(4, 2)
#> [1] 2
# call by name
f(y = 2, x = 4)
#> [1] 2
ফাংশনের ডেফিনিশনে ডিফল্ট আর্গুমেন্ট মান দেওয়া যায়, তবে সেগুলো ডিফল্টহীন সব আর্গুমেন্টের পরে থাকতে হবে।
এরপর আমরা বেছে নিতে পারি, ডিফল্টটাই মেনে নেব, নাকি সেটি ওভাররাইড করব।
g <- function(x, y = 2) x / y
# default y value
g(6)
#> [1] 3
# explicit y value
g(6, 3)
#> [1] 2
ইচ্ছেমতো সংখ্যক অতিরিক্ত আর্গুমেন্ট নিতে ডেফিনিশনে একটি ... (ইলিপসিস) ব্যবহার করুন। ফাংশন কলে দেওয়া অতিরিক্ত যেকোনো মানকে ভেক্টরে রূপান্তর করা সম্ভব, তবে এই "ডট আর্গস" (অন্য অনেক ভাষায় যাকে "ভ্যারার্গস" বলা হয়) ব্যবহারের বিকল্প উপায় জানতে পড়তে থাকুন।
var_f <- function(x, y, ...) {
print(c(...))
}
var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"
আগে বলেছিলাম, ফর্মাল আর্গুমেন্ট আর বডি, দুটোই একটি ফাংশনের উপাদান।
আসলে তৃতীয় একটি উপাদানও আছে: যে এনভায়রনমেন্ট-এ ফাংশনটি ডিফাইন করা হয়।
নেস্টেড ফাংশনের উদাহরণ দিয়ে এটি বোঝা যায়:
outer_func <- function(x) {
inner_func <- function(y) {
x * y
}
inner_func(3)
}
outer_func(5)
#> [1] 15
ফাংশন কলটি বাইরের ফাংশনে x = 5 পাঠায়, আর এই মানটি ওই ফাংশন বডির ভিতরে পাওয়া যায়।
ভিতরের ফাংশনটি বাইরের ফাংশন বডিরই একটি অংশ, তাই এর কাছে x-এর মানও থাকে। অন্যভাবে বললে, x = 5 থাকে ভিতরের ফাংশনের এনভায়রনমেন্ট-এ।
টেকনিক্যালি একে বলা হয় ক্লোজার।
ডট আর্গসের ক্ষেত্রে এনভায়রনমেন্ট বিশেষভাবে গুরুত্বপূর্ণ, কারণ এভাবে দেওয়া যেকোনো মান ফাংশন বডির ভিতরের ফাংশন কলগুলোতে চালিয়ে দেওয়া যায়। ডট আর্গসগুলোর মানে কী, তা বাইরের ফাংশনকে জানতেও হয় না, ভাবতেও হয় না।
f_var <- function(x, ...) {
sum(x, ...)
}
x <- c(1, 2, NA, 6)
# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA
# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9
stringr জাতীয় Tidyverse লাইব্রেরিতে এই কৌশলটি ব্যাপকভাবে ব্যবহার করা হয়। stringr প্যাকেজের অনেক ফাংশনই আসলে ব্যবহারবান্ধব র্যাপার, যা stringi ও base R-এর লো-লেভেল ফাংশনকে ঘিরে তৈরি। str_*() ফাংশনগুলোতে দেওয়া অতিরিক্ত আর্গুমেন্ট সোজা ওই নিচু স্তরের ফাংশনগুলোতে চালিয়ে দেওয়া হয়।
একটি বাইরের ফাংশন ভিতরে আরেকটি ফাংশন (নামযুক্ত, বা নিচে বর্ণিত অ্যানোনিমাস) ডিফাইন করে সেটিকেই রিটার্ন ভ্যালু হিসেবে ব্যবহার করতে পারে।
যে ফাংশনটি রিটার্ন হয়, তার সঙ্গে সেই এনভায়রনমেন্টও থাকে, যেখানে সেটি ডিফাইন করা হয়েছিল।
times_y <- function(x) {
# anonymous function - see next section
\(y) x * y
}
f <- times_y(3)
class(f)
#> [1] "function"
f(4)
#> [1] 12
কোনো ফাংশন ডিফাইন করলে আমরা সাধারণত তৈরি হওয়া ফাংশন অবজেক্টটি একটি ভ্যারিয়েবলের সঙ্গে বেঁধে রাখি:
x <- 2
# function definition captures x from the environment
doubleit_short <- function(y) x * y
এতে স্ক্রিপ্টের পরে ফাংশনটি ব্যবহার করা সহজ হয়, তবে এমন বাঁধন থাকা জরুরি নয়। ছোট, একবার-ব্যবহারের ফাংশন কাছের প্রসঙ্গেই কাজে লাগতে পারে। নামের সঙ্গে না বাঁধলে একে বলা হয় অ্যানোনিমাস ফাংশন।
অ্যানোনিমাস ফাংশনের ব্যবহার এতটাই সাধারণ যে (R v4.1.0 থেকে) সেগুলো ডিফাইন করার একটি শর্টহ্যান্ড সিনট্যাক্স আছে: function শব্দটির বদলে ব্যাকস্ল্যাশ \ লিখুন।
এর একটি উদাহরণ আগের অংশে, রিটার্ন ভ্যালুর প্রসঙ্গে ছিল।
R ভেক্টরের আলাদা আলাদা এলিমেন্টে অ্যাসাইনমেন্ট করতে দেয়। কোনো ভেক্টর ফাংশনের আর্গুমেন্ট হিসেবে পাঠিয়ে ফাংশন বডিতে সেটি পরিবর্তন করে রিটার্ন করলে আমরা পরিবর্তিত ভেক্টর পাই।
কিন্তু মূল ভেক্টরটির কী হলো?
f <- function(vec) {
vec[1] <- 42
vec
}
vals <- c(1, 3, 4)
# f() returns a modified vector
f(vals)
#> [1] 42 3 4
# the original vector is unchanged.
vals
#> [1] 1 3 4
R এমন একটি ভাষা, যা ডেটা সায়েন্সের জন্য তৈরি। সেই ডেটা সংগ্রহ করতে সময়, পরিশ্রম আর সম্ভবত চোখ কপালে ওঠানো পরিমাণ টাকা খরচ হয়: তাই ডেটা নষ্ট না করা জরুরি!
সাধারণ নীতি (কয়েকটি ব্যতিক্রম ছাড়া) হলো পরিবর্তনের সময় কপি করা। কোনো অবজেক্ট (যেমন একটি ভেক্টর) যদি এমনভাবে বদলে যায়, যা পরে সমস্যা ডেকে আনতে পারে, তাহলে R একটি পরিবর্তিত কপি ফেরত দেয় আর মূলটিকে অপরিবর্তিত রেখে দেয়।
বড় ডেটা স্ট্রাকচার কপি করা কম্পিউটেশনালি ব্যয়বহুল হতে পারে, তবে বিকল্পটি যখন ডেটা নষ্ট হওয়া, তখন সাধারণত এটাই কম ক্ষতিকর।
এমন কাজ থেকে সাবধান থাকুন, যেগুলো একই ডেটা বারবার কপি করায়।
ভাবার্থে (Tidyverse-এর লেখক) হ্যাডলি উইকহ্যাম:
লুপ নিজে থেকে ধীর নয়, কিন্তু লুপের ভিতরে ধীর কাজ ঢুকিয়ে দেওয়াটা এরা বিপজ্জনকভাবে সহজ করে তোলে।
ভেক্টরাইজেশন বা হায়ার-অর্ডার ফাংশন আপনাকে এই ধরনের পারফরম্যান্স-নষ্টকারী প্রবণতা থেকে রক্ষা করতে সাহায্য করতে পারে।