توابعی که خروجی تابع دارند
اجازه بدین باز هم جلوتر بریم! بجای برگردوندن یه عکس، تابع ما میتونه یه «تابع» برگردونه که مستطیلهای لبهگرد رو با «اندازهی مشخصشده» تولید میکنه. اگه تابحال ندیدین که یه تابع، بعنوان نتیجهی خروجی یه تابع دیگه برگردونده بشه، ممکنه الان نفستون بند بیاد. اما فراموش نکنید که درنهایت یه تابع میتونه بعنوان یه مقدار مورد استفاده قرار بگیره. ما تا الان یه تابع رو بعنوان پارامتر برای یه تابع دیگه موقع فراخوانی فرستادیم. حالا هم میتونیم یه تابع رو از یه تابع دیگه موقع فراخوانی تحویل بگیریم:
اجازه بدین کدمون رو با آرامش تحلیل کنیم:
اما ممکنه هنوز با دهان باز دارین به makeRoundedRectangleMaker نگاه میکنین و تعجب کردین از اینکه چطور باید صداش بزنین و وقتی صداش زدین، چی تحویل میگیرین؟ اجازه بدین امتحان کنیم:
خوب الان مقدار متغیر maker چیه؟ یه تابعه که پارامتر ورودی نداره و خروجیش UIImage حاوی مستطیل لبهگرد با ابعاد مشخصشده یعنی 45,20 هست. حرفمو باور نمیکنین؟ ثابت میکنم. کافیه تابع رو که الان توی متغیر maker ذخیره شده صدا بزنیم:
حالا که متوجه شدین که چطور میشه یه تابع، یه تابع دیگه تولید کنه و بعنوان نتیجه برگردونه، یکبار دیگه به پیادهسازی makeRoundedRectangleMaker دقتکنید و اجازهبدین دوباره بهشکل دیگری تحلیلش کنیم. یادتون باشه که من اون تابع رو برای اینکه فقط نشون بدیم که یه تابع میتونه یه تابع دیگه رو برگردونه ننوشتم. من این مثال رو برای نمایش کلوژرها نوشتم. اجازه بدین درمورد چگونگی دراختیارگرفتن محیط فکر کنیم:
تابع f هیچ پارامتری نمیگیره ولی دوبار داخل بدنهی تابع f (که با علامت ستاره مشخص کردم، از متغیر sz استفاده شده. بدنهی تابع f میتونه sz رو ببینه که پارامتر ورودی تابع بیرونی محسوب میشه چون توی محدودهی بیرونی تابع f قرار داره. تابع f ارجاع به sz رو زمانی که تابع makeRoundedRectangleMaker صدا زده میشه، «در اختیار میگیره» و این ارجاع رو وقتی که تابع f برگردونده میشه و به متغیر maker نسبت داده میشه، «نگه میداره»:
بخاطر همینه که maker الان تابعی هست که وقتی فراخوانی بشه، یه تصویر با ابعاد مشخص 45,20 میسازه حتی در زمانی که خودش رو «بدون هیچ پارامتری» صدا میزنیم:
اگه جور دیگه به قضیه نگاه کنیم، تابع makeRoundedRectangleMaker یه کارخانه برای ساخت تمام خانوادهی توابع مشابه maker هست که هرکدوم تصویری با ابعاد خاص خودشون رو میسازن. این مثال، خیلی قشنگ قدرت بستارها (Closure) رو نشون میده.
قبل از اینکه تابع makeRoundedRectangleMaker رو رها کنیم، دوست دارم اون رو بهشکل جذابتری با قابلیتهای Swift بازنویسی کنم. داخل f نیاز به تولید im و برگردوندنش نداریم! میتونیم نتیجهی فراخوانی imageOfSize رو مستقیماً برگردونیم:
اما در اینجا حتی نیاز به تعریف f و بعد، برگردوندنش هم نداریم و میتونیم این دو مرحله رو هم ترکیب کنیم:
ولی الان تابع بینام ما فقط شامل یه دستوره که اونم نتیجهی فراخوانی imageOfSize رو برمیگردونه (تابع بینام فراخوانی imageOfSize چند دستور داره ولی خود فراخوانی imageOfSize یه دستور در زبان سویفت محسوب میشه). بنابراین نیاز به دستور return هم نداریم:
اجازه بدین باز هم جلوتر بریم! بجای برگردوندن یه عکس، تابع ما میتونه یه «تابع» برگردونه که مستطیلهای لبهگرد رو با «اندازهی مشخصشده» تولید میکنه. اگه تابحال ندیدین که یه تابع، بعنوان نتیجهی خروجی یه تابع دیگه برگردونده بشه، ممکنه الان نفستون بند بیاد. اما فراموش نکنید که درنهایت یه تابع میتونه بعنوان یه مقدار مورد استفاده قرار بگیره. ما تا الان یه تابع رو بعنوان پارامتر برای یه تابع دیگه موقع فراخوانی فرستادیم. حالا هم میتونیم یه تابع رو از یه تابع دیگه موقع فراخوانی تحویل بگیریم:
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { // 1 func f() -> UIImage { // 2 let im = imageOfSize(sz) { let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), cornerRadius: 8 ) p.stroke() } return im } return f // 3 }
اجازه بدین کدمون رو با آرامش تحلیل کنیم:
- تعریف تابع، سختترین قسمتشه. نوع (امضای) تابع makeRoundedRectangleMaker چیه؟ درحقیقت امضای این تابع اینه:
(CGSize) -> () -> UIImage
این عبارت دو عملگر فلش داره. برای اینکه درکش کنین، بخاطر بسپارین که هر چیزی بعد از عملگر فلش، نوع مقدار بازگشتی رو مشخص میکنه. بنابراین، تابع makeRoundedRectangleMaker یه تابع هست که یه پارامتر CGSize میگیره و یه ()->UIImage برمیگردونه. خوب حالا این چیه؟ ما اینو میدونیم: یه تابع هست که هیچ پارامتری نمیگیره و یه UIImage برمیگردونه. بنابراین makeRoundedRectangleMaker تابعی هست که یه CGSize میگیره و یه تابع برمیگردونه که اون تابع هم وقتی فراخوانی بشه، پارامتری نمیگیره و یه UIImage تحویل میده. - حالا توی بدنهی تابع makeRoundedRectangleMaker هستیم و اولین قدم تا تعریف یه تابع هست (تابعی داخل یه تابع دیگه، یا یه تابع محلی). این تابع دقیقاً از همون نوعی هست که میخوایم برگردونیم (امضاش با خروجی تابع اصلی یکیه)، یعنی پارامتر ورودی نداره و خروجیش UIImage هست. در اینجا این تابع رو f نامگذاری کردیم. کارکرد این تابع ساده و آشناست: تابع imageOfSize رو صدا میزنه و یه تابع بینام براش میفرسته که یه تصویر از مستطیل با لبهی گرد میسازه (im) و بعد اون رو برمیگردونه.
- درنهایت ما از دستور return برای برگردوندن f استفاده میکنیم. بنابراین ما قرارداد امضای تابع رو رعایت کردیم: ما گفتیم که یه تابع بدون پارامتر ورودی و با خروجی UIImage برمیگردونیم و همینکار رو هم انجام دادیم.
اما ممکنه هنوز با دهان باز دارین به makeRoundedRectangleMaker نگاه میکنین و تعجب کردین از اینکه چطور باید صداش بزنین و وقتی صداش زدین، چی تحویل میگیرین؟ اجازه بدین امتحان کنیم:
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20))
خوب الان مقدار متغیر maker چیه؟ یه تابعه که پارامتر ورودی نداره و خروجیش UIImage حاوی مستطیل لبهگرد با ابعاد مشخصشده یعنی 45,20 هست. حرفمو باور نمیکنین؟ ثابت میکنم. کافیه تابع رو که الان توی متغیر maker ذخیره شده صدا بزنیم:
self.myImageView.image = maker()
حالا که متوجه شدین که چطور میشه یه تابع، یه تابع دیگه تولید کنه و بعنوان نتیجه برگردونه، یکبار دیگه به پیادهسازی makeRoundedRectangleMaker دقتکنید و اجازهبدین دوباره بهشکل دیگری تحلیلش کنیم. یادتون باشه که من اون تابع رو برای اینکه فقط نشون بدیم که یه تابع میتونه یه تابع دیگه رو برگردونه ننوشتم. من این مثال رو برای نمایش کلوژرها نوشتم. اجازه بدین درمورد چگونگی دراختیارگرفتن محیط فکر کنیم:
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { func f () -> UIImage { let im = imageOfSize(sz) { // * let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), // * cornerRadius: 8 ) p.stroke() } return im } return f }
تابع f هیچ پارامتری نمیگیره ولی دوبار داخل بدنهی تابع f (که با علامت ستاره مشخص کردم، از متغیر sz استفاده شده. بدنهی تابع f میتونه sz رو ببینه که پارامتر ورودی تابع بیرونی محسوب میشه چون توی محدودهی بیرونی تابع f قرار داره. تابع f ارجاع به sz رو زمانی که تابع makeRoundedRectangleMaker صدا زده میشه، «در اختیار میگیره» و این ارجاع رو وقتی که تابع f برگردونده میشه و به متغیر maker نسبت داده میشه، «نگه میداره»:
let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20))
بخاطر همینه که maker الان تابعی هست که وقتی فراخوانی بشه، یه تصویر با ابعاد مشخص 45,20 میسازه حتی در زمانی که خودش رو «بدون هیچ پارامتری» صدا میزنیم:
self.myImageView.image = maker()
اگه جور دیگه به قضیه نگاه کنیم، تابع makeRoundedRectangleMaker یه کارخانه برای ساخت تمام خانوادهی توابع مشابه maker هست که هرکدوم تصویری با ابعاد خاص خودشون رو میسازن. این مثال، خیلی قشنگ قدرت بستارها (Closure) رو نشون میده.
قبل از اینکه تابع makeRoundedRectangleMaker رو رها کنیم، دوست دارم اون رو بهشکل جذابتری با قابلیتهای Swift بازنویسی کنم. داخل f نیاز به تولید im و برگردوندنش نداریم! میتونیم نتیجهی فراخوانی imageOfSize رو مستقیماً برگردونیم:
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { func f () -> UIImage { return imageOfSize(sz) { // * let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), // * cornerRadius: 8 ) p.stroke() } } return f }
اما در اینجا حتی نیاز به تعریف f و بعد، برگردوندنش هم نداریم و میتونیم این دو مرحله رو هم ترکیب کنیم:
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { return { return imageOfSize(sz) { // * let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), // * cornerRadius: 8 ) p.stroke() } } }
ولی الان تابع بینام ما فقط شامل یه دستوره که اونم نتیجهی فراخوانی imageOfSize رو برمیگردونه (تابع بینام فراخوانی imageOfSize چند دستور داره ولی خود فراخوانی imageOfSize یه دستور در زبان سویفت محسوب میشه). بنابراین نیاز به دستور return هم نداریم:
func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage { { imageOfSize(sz) { // * let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), // * cornerRadius: 8 ) p.stroke() } } }