عملگرهای سفارشی در سوئیفت — از صفر تا صد

۲۲۰ بازدید
آخرین به‌روزرسانی: ۲۹ شهریور ۱۴۰۲
زمان مطالعه: ۶ دقیقه
عملگرهای سفارشی در سوئیفت — از صفر تا صد

در این مقاله مروری بر یکی از هیجان‌انگیزترین و بحث‌انگیزترین جنبه‌های زبان برنامه‌نویسی سوئیفت یعنی عملگرهای سفارشی در سوئیفت خواهیم داشت. هر کس که زمانی با ریاضیات سر و کار داشته باشد، با عملگرهای مقدماتی که در محاسبات روزمره استفاده می‌کنیم یعنی جمع، تفریق، ضرب و تقسیم آشنایی دارد.

در زبان برنامه‌نویسی سوئیفت نیز این عملگرها حضور دارند. اعضای مقدماتی با اعضای بسیار دیگری مانند عملگرهای بیتی (~، &، |، ^، << و >>) و عملگرهای اورفلو (&+، &-) الحاق می‌شوند. این عملگرهای اضافی گسترش‌پذیری زیادی در بخش‌های ریاضیاتی این زبان ارائه می‌کنند، اما با این حال کافی نیستند.

سوئیفت با معرفی عملگرهای سفارشی به برنامه‌نویس امکان داده است که تابع‌های مبتنی بر عملگر خاص خود را بنویسد. این عملگرها امکان فراخوانی تابع‌های پیچیده‌تر مانند فرمول درجه دوم را با استفاده از یک نماد ساده و آرگومان‌ها فراهم می‌سازند. مثلاً می‌توانیم از +-= به عنوان عملگر معادله درجه دو استفاده کنیم.

تعریف عملگرهای سفارشی

نمادها به خوبی با نمادهای دیگر در یک معادله جای می‌گیرند، در حالی که تابع‌های خود زبان چنین خصوصیتی ندارند. در این وضعیت می‌توان از عملگر سفارشی استفاده کرد.

عملگرهای سفارشی را می‌توان با ترکیب کاراکترهای /، =، -، +، !، *، %، <، >، &، |، یا ~ و همچنین کاراکترهای ریاضیاتی دیگر ساخت.

سه نوع اصلی از عملگرهای سفارشی وجود دارند که در سوئیفت نقش ایفا می‌کنند: infix ،prefix، و postfix. هر نوع عملگر کاربرد خاص خود را در این زبان دارد و موجب بهبود و ساده‌تر شدن بخش‌های مختلف از فرایند توسعه می‌شود.

عملگرهای پیشوندی (Prefix)

عملگر پیشوندی عملگری است که پیش از مقداری که تابع پیوندیافته‌اش باید دستکاری کند، قرار می‌گیرد. این نوع عملگرها در مواردی مانند یافت مقدار مثبت و منفی یک عدد (از طریق ±) یا محاسبه جذر یک عدد (از طریق √) استفاده می‌شوند.

اینک فرض کنید می‌خواهیم جذر شانزده را محاسبه کنیم:

1sqrt(16) // 4

کد فوق منظور ما را به دست می‌دهد، اما شبیه یک معادله معمولی نیست. می‌توانیم یک عملگر پیشوندی برای محاسبه جذر اعداد تعریف کنیم:

1prefix operator √

اینک عملگر تعیین شده است، اما قرار است چه کاری انجام دهد؟ باید عبارت prefix func را به عملگر خود اضافه کنیم:

1prefix operator √
2prefix func √ (_ value: Double) -> Double {
3  return sqrt(value)
4}

اینک می‌توانیم آن را فراخوانی کنیم:

116 // 4.0

ما همچنان پاسخ قبلی را به دست می‌آوریم، اما ظاهر آن شباهت زیادی به یک معادله سنتی یافته است. نکته: ممکن است برای درستی به تابع‌های ریاضیاتی استاندارد مانند sqrt به فراخوانی import Foundation نیاز داشته باشید.

عملگرهای میان‌وندی (Infix)

عملگر میان‌وندی عملگری است که بین دو مقدار قرار می‌گیرد تا آن‌ها را دستکاری کند. آن دو مقدار را عموماً عملوندهای سمت راست و چپ می‌نامند. این نوع عملگر معمولاً به طور عمده در ریاضیات دیده می‌شود. برای نمونه شامل جمع، تفریق، ضرب، تقسیم و موارد بسیار دیگر می‌شود.

با این حال می‌توان از این نوع عملگرها برای محاسبه ریشه اعداد نیز استفاده کرد و این کاری است که می‌خواهیم در ادامه انجام دهیم.

فرض کنید می‌خواهید ریشه یک‌پنجم عدد 243 را محاسبه کنید:

1pow(243, (1/5)) // 3.0

کد فوق این کار را انجام می‌دهد، اما بیشتر شبیه کد است تا یک معادله معمولی ریاضیاتی. بدین منظور یک عملگر میان‌وندی در برنامه خود تعریف می‌کنیم:

1infix operator √

چنان که می‌بینید برای تعریف عملگر از نماد رادیکال استفاده کرده‌ایم. با این حال تکرار عملگرها، تا زمانی که برای انواع مختلفی از معادله (مثلاً پیشوندی در برابر میان‌وندی) استفاده شوند، مجاز است. تابع‌های پیش وندی به صورت prefix func تعریف می‌شدند، اما تابع‌های میانوندی به سادگی با استفاده از func تعریف می‌شوند و بدین ترتیب سردرگمی کمتر می‌شود.

اینک می‌توانیم تابع را با استفاده از عملگر خود به صورت زیر تعریف کنیم:

1infix operator √
2func √ (lhs: Double, rhs: Double) -> Double {
3  return pow(rhs, (1/lhs))
4}

در نهایت آن را فراخوانی می‌کنیم:

15243 // 3.0

کد فوق پاسخ را با ظاهر معادله سنتی ریاضیاتی بازگشت می‌دهد.

عملگرهای پسوندی (Postfix)

عملگرهای پسوندی به آن دسته از عملگرها گفته می‌شود که پس از مقداری که باید دستکاری کنند می‌آیند. این موارد به‌طور عمده در زبان‌های دیگر برای کاهش یا افزایش مقدار متغیر (مانند ++value) استفاده می‌شوند. سوئیفت این نوع از افزایش را ندارد، چون در نسخه 2.2 سوئیفت منسوخ و در نسخه 3 به کلی حذف شده است.

فرض کنید می‌خواهیم عدد خود را که برابر با 2.0 است افزایش دهیم. متد کنونی به صورت 1=+ به خوبی کار می‌کند، اما نسخه محبوب و قدیمی این عملگر یعنی ++ را ندارد.

1var number = 2.0
2number+=1 // 3.0

همانند انواع قبلی ابتدا باید عملگر پسوندی را تعریف کنیم:

1postfix operator ++

عملگرهای پسوندی نیز مشابه عملگرهای پیشوندی به صورت postfix func اعلان می‌شوند. می‌توانیم تابع عملگر را به صورت زیر تعریف کنیم:

1postfix operator ++
2postfix func ++ (value: inout Double) {
3  value+=1
4}

این تابع مقدار را پیش از عملگر می‌گیرد و آن را با استفاده از متد داخلی افزایش می‌دهد. با این حال این تابع نکته متفاوتی دارد و آن این است که مقداری بازگشت نمی‌دهد. به جای آن از کلیدواژه inout استفاده می‌کنیم که معنی آن این است که پارامتر یک مقدار «تغییرپذیر» (Mutable) است و به مقدار ابتدایی اشاره می‌کند. این بدان معنی است که متغیر ارسالی به تابع یعنی عدد مورد نظر بدون انتساب مجدد افزایش می‌یابد. اینک می‌توانیم به صورت زیر آن را فراخوانی کنیم:

1var number = 2.0
2number++ // 3.0

چنان که می‌بینید همان عملگر قبلی را به دست آورده‌ایم.

تقدم و شرکت‌پذیری

تقدم و شرکت‌پذیری دو مبحث مهم در ریاضیات محسوب می‌شوند و ترتیب اجرای عملیات را در معادله مورد ارزیابی تعیین می‌کنند.

تقدم

در دوران ابتدایی با تقدم عملگرهای ریاضیاتی آشنا شدیم و دانستیم که ترتیب انجام آن‌ها به صورت پرانتز، ضرب، تقسیم، جمع و تفریق است. در برنامه‌نویسی گروه‌های تقدم دیگری وجود دارند که شامل شیفت‌های بیتی، ضرب و تقسیم در اولویت اول و سپس عملگر سه‌تایی، تابع‌های arrow و در نهایت انتساب‌ها می‌شود. زمانی که عملگرهای سفارشی را تعریف می‌کنیم، گروه تقدمی آن‌ها را نیز تعریف می‌کنیم که به این ترتیب می‌توان آن‌ها را با عملگرهای دیگر استفاده کرد و به طور صحیحی ارزیابی نمود.

سوئیفت به صورت داخلی عملگر نمایی ندارد، از این رو قصد داریم آن را با تقدیم اختصاصی‌اش تعریف کنیم. گروه تقدمی جدیدی را به صورت زیر تعریف می‌کنیم:

1precedencegroup ExponentiationPrecedence {
2  associativity: right
3  higherThan: MultiplicationPrecedence
4}

این گروه جدید ExponentiationPrecedence نام دارد و دو خصوصیت به صورت associativity و higherThan دارد. خصوصیت higherThan تعیین می‌کند که عملگر در جدول تقدمی کجا قرار می‌گیرد. در این مورد گروه مورد نظر ما بالاتر از MultiplicationPrecedence قرار می‌گیرد. شرکت‌پذیری نیز در بخش بعدی مورد بررسی قرار گرفته است.

اینک که گروه تقدمی ما تعریف شده است، نوبت آن است که عملگر خود را تعریف کنیم:

1infix operator **: ExponentiationPrecedence

این عملگر مانند عملگر قبلی تعریف می‌شود به جز این که به گروه ExponentiationPrecedence اتصال می‌یابد. تابع را به صورت زیر تعریف می‌کنیم:

1func ** (lhs: Double, rhs: Double) -> Double {
2  return pow(lhs, rhs)
3}

اینک می‌توانیم از عملگر خود برای محاسبه توان‌های اعداد استفاده کنیم:

12**3 // 8.0

از آنجا که یک گروه تقدمی برای آن تعیین کرده‌ایم، می‌توانیم از آن همراه با عملگرهای دیگر نیز استفاده کنیم:

12**3*2 // 16.0

شرکت‌پذیری

شرکت‌پذیری ترتیب عملگرها در گروه یکسان را تعریف کرده و آن‌ها را مورد ارزیابی قرار می‌دهد. برای نمونه ضرب و تقسیم هر دو در یک گروه قرار می‌گیرند و از سمت چپ به راست، چنان که در معادله ظاهر می‌شوند، مورد ارزیابی قرار می‌گیرند. این بدان معنی است که گروه «شرکت‌پذیری از چپ» (left associativity) دارد. به گروه تقدمی سفارشی زیر توجه کنید:

1precedencegroup ExponentiationPrecedence {
2  associativity: right
3  higherThan: MultiplicationPrecedence
4}

آرگومان associativity به صورت راست تعیین می‌شود که به این معنی است که از راست به چپ و بر حسب ظاهر شدن در معادله ارزیابی می‌شود. این بدان معنی است که اگر معادله به صورت زیر باشد:

12**3**2 // 512.0

ابتدا می‌توانیم آن موردی را که در سمت راست ظاهر شده ارزیابی کنیم. در این مورد آن را به صورت 3**2 ارزیابی می‌کنیم که 9 را به دست می‌دهد. در ادامه نتیجه عملگر سمت چپ را ارزیابی می‌کنیم که 9**2 است و نتیجه کلی به صورت 512 خواهد بود.

سخن پایانی

عملگرهای سفارشی جنبه شگفت‌انگیزی از زبان سوئیفت هستند و میانبرها و عملیات مفیدی برای برنامه‌هایی که نوشته می‌شوند، ارائه می‌کنند. این عملگرها به طور خاص زمانی که به صورت عمیقی روی گزینه‌های ریاضیاتی زبان متمرکز هستیم با استفاده از گروه‌های تقدمی و شرکت‌پذیری به کار می‌آیند. با این حال، نوشتن و استفاده از عملگرهای سفارشی در اغلب موارد دشوار است و از این رو در صورت امکان باید از نمادهایی که کاربرد کمتری دارند، پرهیز کنید. به طور کلی عملگرهای سفارشی باید تنها در مواردی استفاده شوند که مطلقاً ضرورت داشته باشند و باید به وسیله نمادهایی تعریف شوند که برای آن عملیات معنی‌دار باشند. امیدواریم از مطالعه این مقاله، بهره آموزشی لازم را برده باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *