تعلم Swift من الصفر: دوال المعاملات و الأنواع و التداخل |
لقد أطلعنا في الدرس السابق على أساسيات الدوال في Swift. ومع ذلك فلدى الدوال في Swift الكثير لتقدمه. في هذا الدرس، سنكمل استطلاعنا عن الدوال وكذلك دوال المعاملات والتداخل والأنواع.
1. أسماء المعاملات المحلية والخارجية
أسماء المعاملات المحلية
دعونا نسترجع أحد الأمثلة في الدرس السابق. تعرف دالة printMessage معاملة واحدة، وهي message
func printMessage(message: String) {
println(message)
}
على الرغم من أننا نعطي المعاملة اسماً، message، فإننا لا نستخدم ذلك الاسم عندما نستدعي الدالة. نقوم بتمرير قيمة للمعاملة message.
printMessage("Hello, world!")
إن الاسم الذي قمنا بتعريفه في تعريف الدالة يعتبر اسماً محلياً للمعاملة. يمكن لقيمة هذه المعاملة أن يتم الإشارة لها عن طريق هذا الاسم في محتوى الدالة. تعد دوال المعاملات أكثر مرونة بقليل من ذلك. دعوني أشرح لكم ما أعنيه بذلك.
تعد لغة Objective-C معروفة بأسماء طرقها الطويلة. بينما يمكن أن تبدو هذه طويلة وغير أنيقة للغرباء، فإنها تجعل الطرق سهلة الفهم وبحال تم إختيارها بشكل جيد، فإنها ستكون وصفية جداً. إذا كنت قد ظننت بأنك قد خسرت تلك الفائدة عند تعاملك مع Swift، فإنك حتماً ستتفاجأ.
تعد لغة Objective-C معروفة بأسماء طرقها الطويلة. بينما يمكن أن تبدو هذه طويلة وغير أنيقة للغرباء، فإنها تجعل الطرق سهلة الفهم وبحال تم إختيارها بشكل جيد، فإنها ستكون وصفية جداً. إذا كنت قد ظننت بأنك قد خسرت تلك الفائدة عند تعاملك مع Swift، فإنك حتماً ستتفاجأ.
أسماء المعاملات الخارجية
عندما تقبل دالة ما عدة معاملات، فإنه ليس دائماً واضحاً أي معاملة ستتوافق مع أية معاملة. ألق نظرة على المثال لكي تفهم المشكلة بشكل أفضل.
func power(a: Int, b: Int) -> Int {
var result = a
for _ in 1..<b {
result = result * a
}
return result
}
تقوم دالة power برفع قيمة a عن طريق الأس. b تنتمي كلا المعاملتين لنوع Int. بينما سيقوم معظم الناس بتمرير القيمة الأساسية كالمعاملة الأولى والأس كالمعاملة الثانية، فإن ذلك غير واضحاً من نوع الدالة أو اسمها أو شارتها. وكما رأينا في الدرس السابق، فإن استدعاء الدالة بسيط.
power(2, 3)
ولتفادي الحيرة، فإنه بإمكاننا إعطاء دوال المعاملات اسماً خارجياً. حينئذ يمكننا استخدام هذه الأسماء الخارجية عندما تكون الدالة المستدعاة لتشير إلى أي معاملة تتوافق مع أي معاملة غير واضحة. ألق نظرة على هذا المثال المحدث أسفلاَ.
func power(base a: Int, exponent b: Int) -> Int {
var result = a
for _ in 1..<b {
result = result * a
}
return result
}
لاحظ أن محتوى الدالة لم يتغير ذلك لأن الأسماء المحلية لم تتغير أيضاً. و لكن عندما نستدعي دالة محدثة، فإن الإختلاف سيكون واضحاً وستكون النتيجة أقل إرباكاً بكثير.
power(base: 2, exponent: 3)
على الرغم من أن أنواع كلا الدالتين نفس الأخر، (Int, Int) -> Int, فإن الدالتين مختلفتين. وبكلمات أخرى، فإن الدالة الثانية ليست عبارة عن إعادة تصريح للدالة الأولى. يمكن لتركيبة الكود التي يجب استدعاءها للدالة الثانية أن تذكر بعضاً منكم بلغة Objective-C. أنه ليس فقط أن المعاملات موصوفة بوضوح، وكذلك فإن مجموعة الدوال وأسماء المعاملات تصف هدف الدالة أيضاً.
في بعض الحالات، فإنك ترغب باستخدام نفس الأسماء لأسماء المعاملات المحلية والخارجية. هذا ممكن وليس هنالك حاجة لكتابة اسم المعاملة مرتين. في المثال التالي، سنستخدم base و exponent كأسماء للمعاملات المحلية والخارجية.
في بعض الحالات، فإنك ترغب باستخدام نفس الأسماء لأسماء المعاملات المحلية والخارجية. هذا ممكن وليس هنالك حاجة لكتابة اسم المعاملة مرتين. في المثال التالي، سنستخدم base و exponent كأسماء للمعاملات المحلية والخارجية.
func power(#base: Int, #exponent: Int) -> Int {
var result = base
for _ in 1..<exponent {
result = result * base
}
return result
}
بإعتبار تم تعيين الرمز a # كبادئة للمعاملة، فإن اسم المعاملة يخدم كالاسم المحلي والخارجي للمعاملة. وهذا يعني أيضاً أننا سنحتاج لتحديث محتوى الدالة.
من الضروري ملاحظته أنه بإعتبار أعطينا المعاملة اسماً خارجياً فإنه يجب عليك استخدام ذلك الاسم عند استدعاء الدالة. هذا سينقلنا لما يدعى بالقيم الافتراضية (default).
القيم الافتراضية
لقد أخذنا قيم المعاملات الافتراضية في الدرس السابق، ولكن هنالك سلوك افتراضي مهم يجب أن تدركه. إذا قمت بتعريف قيمة افتراضية لمعاملة ما، سيقوم Swift أوتوماتيكياً بتعيين اسم معاملة خارجية لتلك المعاملة. ألق نظرة على هذا المثال من الدرس السابق.
func printDate(date: NSDate, format: String = "YY/MM/dd") -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.stringFromDate(date)
}
لأن لدى المعاملة الثانية، format، قيمة افتراضية، فإن Swift سيضع أوتوماتيكياً اسم المعاملة الخارجية format لـ: format. وبكلمات أخرى، فإننا سنحصل على النتيجة ذاتها في حال كنا قد عينا الرمز a # كبادئة لـ: format. بإمكانك إختبار ذلك من خلال استدعاء الدالة التي في الأعلى إلى ملعبك. ما الذي سوف يحصل إذا قمت بتمرير الشكل من دون استخدام اسم المعاملة الخارجية للمعاملة الثانية؟ الإجابة موضحة بالأسفل.
تعلم Swift من الصفر: دوال المعاملات و الأنواع و التداخل |
يعتبر Swift واضحاً جداً حول ما يجب فعله. ولنلخص ذلك يمكننا القول بأنه عندما تعرف معاملة كإختياري، فإن Swift سيقوم أوتوماتيكياً بتعيين اسم المعاملة الخارجية لاسم المعاملة المحلية. إن الفكرة من وراء هذا الأسلوب هي تجنب الحيرة والغموض.
في حين أن هذا الأسلوب يعد تمريناً جيداً، إلا أنه يمكننا إيقافه. في تعريف الدالة المحدث في الأسفل, قمنا بإضافة underscore (خط صغير) حيث كنا سنضيف بشكل طبيعي اسم المعاملة الخارجية. هذا سيخبر Swift بأننا لا نريد أن نعين اسم المعاملة الخارجية لتلك المعاملة ذاتها، على الرغم من وجود قيمة افتراضية (default) .
func formatDate(date: NSDate, _ format: String = "YY/MM/dd") -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.stringFromDate(date)
}
يمكننا الآن استدعاء دالة formatDate من دون أن نضع اسماً للمعاملة الثانية.
formatDate(NSDate(), "dd/MM/YY")
2. المعاملات والمتغيرات
دعونا نسترجع أول مثال في هذا الدرس، دالة printMessage. ما الذي سيحصل في حال قمنا بتغيير قيمة المعاملة message داخل محتوى الدالة؟
func printMessage(message: String) {
message = "Print: \(message)"
println(message)
}
لن يستغرق Swift وقتاُ طويلاً حتى يبدأ بالتذمر.
تعلم Swift من الصفر: دوال المعاملات و الأنواع و التداخل |
من خلال الافتراضي، فإن دالة المعاملات تبقى ثابتة. بكلمات أخرى، بينما يمكننا الولوج إلى قيم دوال المعاملات، فإنه لا يمكننا تغيير تلك القيم. ومن أجل تغيير هذا السلوك الافتراضي، سنقوم بإضافة الكلمة الأساسية var لاسم المعاملة في تعريف الدالة. سيقوم Swift حينئذ بإبتكار نسخة متغيرة لقيمة المعاملة متيحاً بذلك المجال لك للعمل ضمن محتوى الدالة.
func printMessage(var message: String) {
message = "Print: \(message)"
println(message)
}
لاحظ أن هذا لا يعني أنه يمكنك عرض القيمة، وتعديلها في الدالة ومن ثم استخدام القيمة المعدلة بعد أن تكون الدالة قد قامت بعملها. سيقوم Swift بإبتكار نسخة عن قيمة المعاملة التي تبقى موجودة طيلة فترة استدعاء الدالة. وهذا موضح أيضاً في حاجز الكود التالي الذي سنعرضه كثابت لدالة printMessage.
func printMessage(var message: String) {
message = "Print: \(message)"
println(message)
}
let myMessage = "test"
printMessage(myMessage)
3. معاملات Variadic
في حين يبدو المصطلح غريباً في بادئ الأمر، إلا أن معاملات variadic تعد شائعة في البرمجة. إن معاملة variadic تقبل الصفر أو قيماً أكثر. يجب على القيم أن تكون من نفس النوع. يعد استخدام المعاملات في Swift سهلاً كما هو موضح في المثال التالي.
func sum(args: Int...) -> Int {var result = 0for a in args {result += a}return result}
تعتبر تركيبة الكود سهلة الفهم أيضاً. لكي تشير المعاملة كـ: variadic، يجب عليك أن تلحق ثلاث نقاط في أخر نوع المعاملة. في محتوى الدالة، يسمح لمعاملة variadic الولوج على أنها مصفوفة. في المثال الذي في الأعلى، تعد args مصفوفة لقيم Int.
لأن على Swift معرفة أي معاملات تتوافق مع أي معاملات، فإن على المعاملة variadic أن تكون أخر معاملة. وهذا يشير أيضاً إلى أنه يمكن لدالة ما أن تملك كحد أعظم معاملة variadic واحدة.
وهذا ينطبق في حال كان لدى الدالة معاملات مع قيم افتراضية. يجب على المعاملة variadic أن تكون أخر معاملة دائماً.
لأن على Swift معرفة أي معاملات تتوافق مع أي معاملات، فإن على المعاملة variadic أن تكون أخر معاملة. وهذا يشير أيضاً إلى أنه يمكن لدالة ما أن تملك كحد أعظم معاملة variadic واحدة.
وهذا ينطبق في حال كان لدى الدالة معاملات مع قيم افتراضية. يجب على المعاملة variadic أن تكون أخر معاملة دائماً.
4. المعاملات الداخلة والخارجة
لقد قمنا مسبقاً في هذا الدرس بتعلم كيفية تعريف تغيرية المعاملات من خلال استخدام الكلمة الأساسية var. في ذلك القسم، قمت بتأكيد أن قيمة المعاملة المتغيرة تكون قابلة للولوج فقط من داخل محتوى الدالة. في حال أردت تمرير قيمة إلى دالة، وتعديلها في الدالة ومن ثم تمريرها خارج الدالة، فإن المعاملات الداخلة والخارجة هي بالضبط ما تبحث عنه.
يعرض المثال التالي مثالاً عن كيفية عمل المعاملات الداخلة والخارجة في Swift وكيف تبدو تركيبة الكود الخاصة بها.
يعرض المثال التالي مثالاً عن كيفية عمل المعاملات الداخلة والخارجة في Swift وكيف تبدو تركيبة الكود الخاصة بها.
func prependString(inout a: String, withString b: String) {
a = b + a
}
لقد قمنا بتعريف المعاملة الأولى كمعاملة داخلة وخارجة من خلال إضافة الكلمة الأساسية inout. أما المعاملة الثانية فهي عبارة عن معاملة نظامية مع اسم خارجي withString واسم محلي b. دعونا نرى كيف يمكننا استدعاء هذه الدالة.
var world = "world"
prependString(&world, withString: "Hello, ")
سنعلن عن متغير، world، من نوع String ونمرره إلى دالة perpendString. تعد المعاملة الثانية نص ذات أحرف. من خلال استدعاء الدالة، فإن قيمة المتغير world ستصبح .Hello، world لاحظ بأن أول معاملة مسبوقة بالبادئة & لتشير إلى أن تلك المعاملة داخلة وخارجة.
من البديهي أن الثوابت والنصوص لا يمكن تمريرها كمعاملات داخلة وخارجة. وإذا فعلت ذلك فإن Swift سيعلن أن هنالك خطأ ما كما هو موضح في الصورة.
من الواضح أن المعاملات الداخلة والخارجة لا يمكن أن تملك قيم افتراضية حتى لو كانت variadic، أو معرفة كـ: var أو let. إذا نسيت هذه التفاصيل، Swift سيقوم بتذكيرك بلطف بأن هنالك خطا ما قد حدث.
من البديهي أن الثوابت والنصوص لا يمكن تمريرها كمعاملات داخلة وخارجة. وإذا فعلت ذلك فإن Swift سيعلن أن هنالك خطأ ما كما هو موضح في الصورة.
تعلم Swift من الصفر: دوال المعاملات و الأنواع و التداخل |
من الواضح أن المعاملات الداخلة والخارجة لا يمكن أن تملك قيم افتراضية حتى لو كانت variadic، أو معرفة كـ: var أو let. إذا نسيت هذه التفاصيل، Swift سيقوم بتذكيرك بلطف بأن هنالك خطا ما قد حدث.
5. التداخل
في كل من C و Objective-C، لا يمكن للدوال والطرق أن تتداخل. ولكن في Swift، الدوال المتداخلة شائعة جداً. تعد الدوال التي رأينا في هذا الدرس والدرس السابق أيضاً أمثلة عن الدوال العالمية، ويتم تعريفهم ضمن المجال العالمي.
عندما نقوم بتعريف دالة داخل دالة عالمية، فإننا نشير إلى تلك الدالة كدالة متداخلة. لدى الدالة المتداخلة وصولاً للقيم المعرفة في دالتها المغلقة. ألق نظرة على المثال التالي لكي تفهم ذلك بشكل أفضل.
عندما نقوم بتعريف دالة داخل دالة عالمية، فإننا نشير إلى تلك الدالة كدالة متداخلة. لدى الدالة المتداخلة وصولاً للقيم المعرفة في دالتها المغلقة. ألق نظرة على المثال التالي لكي تفهم ذلك بشكل أفضل.
func printMessage(message: String) {
let a = "hello world"
func printHelloWorld() {
println(a)
}
}
في حين تعد الدوال في هذا المثال غير مفيدة جداً، فإن المثال يوضح فكرة الدوال المتداخلة والقيم المأخوذة. يمكن الوصول إلى دالة printHelloWorld من داخل دالة printMessage. وكما هو موضح في المثال، فإن لدى دالة printHelloWorld وصولاً للثابت a. تم أخذ القيمة من قبل الدالة المتداخلة ولذلك فإنه يمكن الوصول إليها من داخل تلك الدالة. سيهتم Swift بأخذ القيم بما في ذلك إدارة ذاكرة كل تلك القيم.
6. أنواع الدوال
في الدرس السابق، قمنا بالتطرق لأنواع الدوال بشكل مختصر. لدى الدالة نوعاً محدداً يتألف من أنواع المعاملات ونوع الإعادة. على سبيل المثال، فإن دالة printMessage من نوع .(String) -> () تذكر بأن الرمز () يرمز للفراغ ((void، والذي يعتبر مساوياً لأي حقل فارغ.
ولأن لكل دالة نوع، فإنه من الممكن تعريف دالة تقبل دالة أخرى كمعاملة لها. سيوضح المثال التالي كيفية عمل ذلك.
ولأن لكل دالة نوع، فإنه من الممكن تعريف دالة تقبل دالة أخرى كمعاملة لها. سيوضح المثال التالي كيفية عمل ذلك.
func printMessageWithFunction(message: String, printFunction: (String) -> ()) {
printFunction(message)
}
let myMessage = "Hello, world!"
printMessageWithFunction(myMessage, printMessage)
ستقبل دالة printMessageWithFunction النص كمعاملتها الأولى ونوع الدالة (String) -> () كمعاملتها الثانية. في محتوى الدالة، فإن الدالة التي قمنا بتمريرها سيتم استدعاؤها عن طريق المعاملة message.
يوضح المثال أيضاً كيف يمكننا أن نستدعي الدالة printMessageWithFunction. يتم تمرير الثابت myMessage على أساس أنها المعاملة الأولى والدالة printMessage، التي قد عرفناها مسبقاً، كالمعاملة الثانية. هذا بغاية الروعة، أليس كذلك؟
كما ذكرت مسبقاً، من الممكن إرجاع دالة من دالة. إن المثال التالي معقد بعض الشيء، ولكنه يوضح كيف تبدو تركيبة الكود.
يوضح المثال أيضاً كيف يمكننا أن نستدعي الدالة printMessageWithFunction. يتم تمرير الثابت myMessage على أساس أنها المعاملة الأولى والدالة printMessage، التي قد عرفناها مسبقاً، كالمعاملة الثانية. هذا بغاية الروعة، أليس كذلك؟
كما ذكرت مسبقاً، من الممكن إرجاع دالة من دالة. إن المثال التالي معقد بعض الشيء، ولكنه يوضح كيف تبدو تركيبة الكود.
func compute(addition: Bool) -> (Int, Int) -> Int {
func add(a: Int, b: Int) -> Int {
return a + b
}
func subtract(a: Int, b: Int) -> Int {
return a - b
}
if addition {
return add
} else {
return subtract
}
}
let computeFunction = compute(true)
let result = computeFunction(1, 2)
println(result)
تقبل دالة compute بقيمة منطقية وتعود بدالة من نوع (Int, Int) -> Int. تحوي دالة compute على دالتين متداخلتين واللتان أيضاً من نوع (Int, Int) -> Int, add و subtract.
تقوم دالة compute بإعادة مرجع إما لدالة add أو subtract، وذلك بالإرتكاز على قيمة المعاملة addition. يوضح المثال أيضاً كيفية استخدام دالة compute. سنقوم بتخزين مرجع للدالة التي يتم إعادتها من قبل دالة compute في الثابت computeFunction. وبعد ذلك سنقوم باستدعاء الدالة المخزنة في computeFunction، ممررين بذلك 1 و 2، ونخزن النتيجة في result، ومن ثم نطبع قيمة result. لربما يبدو المثال معقداً، ولكنه في الحقيقة سهل الفهم إذا عرفت ما الذي يحصل بالضبط.
الخلاصة
وبعد كل ذلك يجب أن يكون لديك فهماً جيداً لكيفية عمل الدوال في Swift وما الذي يمكنك فعله بهم. تعد الدوال موضوعاً مهماً في Swift وستستخدمهم بشكل كبير عند العمل مع Swift
أما في الدرس القادم، فإننا سنتعلم عن الإغلاقات، والتي هي عبارة بنية قوية تذكر بتلك الحواجز الموجودة بـ: C و Objective-C ، والإغلاقات الموجودة بـ: JavaScript والحواجز الموجودة بـ: Ruby.
أما في الدرس القادم، فإننا سنتعلم عن الإغلاقات، والتي هي عبارة بنية قوية تذكر بتلك الحواجز الموجودة بـ: C و Objective-C ، والإغلاقات الموجودة بـ: JavaScript والحواجز الموجودة بـ: Ruby.
ليست هناك تعليقات: