قُدّمت الدوال سابقًا في مفهوم الأساسيات، بأمثلة مثل هذا:
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، وهو نوع خاص من المصفوفات يحتوي على أزواج من المفتاح والقيمة.
أما body فهو كود قابل للتنفيذ، ونوعه language (وهو أمر لا تحتاج غالبًا إلى الاهتمام به).
لا يميّز R تمييزًا واضحًا بين الوسائط الموضعية والوسائط المُسمّاة، على عكس لغات برمجة نصية أخرى مثل Python و Julia.
يمكن لنداءات الدوال أن تمرّر القيم إما موضعيًا أو بالاسم. وهذا الأخير مفيد مع الدوال المعقدة التي لها وسائط كثيرة، حيث يصعب تذكّر ترتيبها.
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
لقبول عدد اعتباطي من الوسائط الإضافية، استخدم ... (علامة الحذف) في التعريف.
ومن الممكن تحويل أي قيم إضافية في نداء الدالة إلى متجه، لكن تابع القراءة لتعرف طريقة بديلة لاستخدام «وسائط النقاط» هذه (وتسمى «varargs» في عدة لغات أخرى).
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
تُستخدم هذه التقنية على نطاق واسع في مكتبات Tidyverse مثل stringr.
العديد من دوال stringr ليست إلا غلافًا سهل الاستخدام حول دوال منخفضة المستوى من stringi ومن 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):
الحلقات ليست بطيئة بطبيعتها، لكنها تجعل من السهل بشكل خطير أن تضع عمليات بطيئة داخل الحلقة.
ويمكن أن يساعدك تحويل العمليات إلى متجهات أو استخدام الدوال عالية الرتبة على تجنّب هذا النوع من قتلة الأداء.