امید گل پرور
امید گل پرور
خواندن ۴ دقیقه·۶ سال پیش

پیاده‌سازی عملگر دلخواه در سوییفت

داشتم در مورد «مطابقت الگو» توی سوییفت می‌خوندم، که یه عملگر چشمم رو گرفت!!؛ اونم عملگر اختصاصی سوییفت برای این کار بود: =~ . از این عملگر میشه بعنوان مثال، توی بررسی کد HTTP پاسخ سرور استفاده کرد؛ که مثلا اگه موفق نبوده، کد دریافتی توی چه محدوده‌ای هست.

if 400..<500 ~= error.code, error.code != 401 { //Handle Error }

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

? توی این صفحه می‌تونین لیستی از عملگرهای سوییفت رو ببینین.

انواع عملگرها در سوییفت

توی سوییفت، عملگرها توی چهار دسته‌بندی تقسیم میشن:

  • دسته Infix : عملگر بین دو مقدار مورد استفاده قرار می‌گیره. مثل: <Value1> + <Value2>
  • دسته Prefix : عملگر قبل از یه مقدار مورد استفاده قرار می‌گیره. مثل: <Value1>!
  • دسته Postfix : عملگر بعد از یه مقدار مورد استفاده قرار می‌گیره. مثل: !<Value2>
  • دسته Ternary : دوتا عملگر بین سه تا مقدار مورد استفاده قرار می‌گیرن. مثل:

<Value1> ? <Value2> : <Value3>

? توی سوییفت، راهی برای پیاده‌سازی عملگر دلخواه از دسته Ternary وجود نداره.

چجوری یه عملگر دلخواه بسازیم؟

خب. الان می‌خوایم برای نمونه، یه عملگر بسازیم تا جذر یه عدد رو بهمون بده. بهترین و قشنگ‌ترین کاری که می‌تونیم انجام بدیم، اینه که کاراکتر مربوط به اون عملگر رو برابر ( √ ) قرار بدیم!! ?

? توی سوییفت تابعی هست به نام sqrt که میاد و جذر عدد ورودی خودش رو برمی‌گردونه. مام در نهایت از همین تابع استفاده خواهیم کرد؛ البته با روشی بمراتب خوشگل‌تر!!

برای پیاده‌سازی این عملگر، طبیعی هست که باید از نوع Prefix باشه، و نحوه استفاده‌اش هم چیزی شبیه به این خواهد بود:

let someVal = 25 let squareRoot = √someVal // result is 5


مراحل قدم به قدم پیاده‌سازی

  • اولین قدم اینه که کاراکتر مورد نظرمون رو بعنوان یه عملگر Prefix معرفی‌ش کنیم:
prefix operator √
  • قدم بعدی اینه که ما یه تابع (هم‌نام کاراکتر مورد نظرمون) تعریف کنیم. این تابع یه ورودی از نوع Double داره، و یه خروجی هم از نوع Double خواهد داشت. بدنه این تابع، در واقع میاد و جذر عدد ورودی رو حساب می‌کنه و اون رو برمی‌گردونه:
prefix func √(lhs: Double) -> Double { return sqrt(lhs) }

تمام!! نمونه اجرایی‌اش هم میشه اینجوری:

prefix operator √ prefix func √(lhs: Double) -> Double { return sqrt(lhs) } let number: Double = 36 let squareRoot = √number print(squareRoot) // 6.0


بیاین با استفاده از علامت ◉، یه عملگر infix بسازیم

این عملگر دلخواه ما، دو تا مقدار به نام‌های lhs و rhs قبول می‌کنه و مجموع مربعات اونا رو برمی‌گردونه. بعنوان مثال اگه ورودی‌هاش ۲ و ۳ باشه، مقدار ۱۳ رو برمی‌گردونه.

خب، مثل توضیحات قبلی، قدم به قدم می‌ریم جلو.

  • اول عملگرمون رو با استفاده از علامت مورد نظرمون تعریف می‌کنیم:
infix operator ◉
  • قدم بعدی اینه که تابعی که در واقع بدنه اجرایی این عملگر هست رو تعریف کنیم:
infix func ◉(lhs: Double, rhs: Double) -> Double { return lhs * lhs + rhs * rhs }

اگه کدهای تا اینجا رو اجرا بگیرین، کامپایلر بهتون یه همچین خطایی رو نشون میده:

error: ‘infix’ modifier is not required or allowed on func declarations

برای رفع خطا، فقط کافیه کلمه infix رو از تعریف تابع حذف کنیم.

تا اینجا ما یه عملگر اختصاصی با علامت و بدنه دلخواه‌مون تعریف کردیم. البته اینم در نظر بگیرین که این تمام ماجرا نیست، و یه قسمتی از تعریف این عملگر، از تنظیمات پیشفرض استفاده کرده. موارد دیگه‌ای که می‌تونیم برای عملگرمون تعریف کنیم (و در مواردی هم باید این کار رو انجام بدیم)، تقدم و شرکت‌پذیری عملگر هست، که در ادامه توضیحش رو میدیم.

برای دیدن توضیحات در مورد تقدم و شرکت‌پذیری می‌تونین این صفحه در مورد ترتیب عملگرها و این صفحه در مورد تعریف عملگرها رو مطالعه کنین.

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

یکی از مهم‌ترین نکات توی تعریف عملگرها، تقدم (Precedence) و شرکت‌پذیری (Associativity) هست. این صفحه مستندات رسمی اپل برای این نکات توی سوییفت هست.

بطور کلی،

تقدم عملگرها، به بعضی از عملگرها اولویت بالاتری نسبت به بقیه عملگرها میده. این اولویت توی اجرا خودش رو نشون میده و عملگرهای با اولویت بالاتر، زودتر اجرا میشن.

شرکت‌پذیری عملگرها، این رو مشخص می‌کنه که عملگرهای دارای اولویت یکسان یا برابر، به چه صورتی باهم دسته‌بندی میشن؛ به سمت چپ دسته‌بندی میشن یا به سمت راست.

مثال زیر، نمونه‌ای از عملگرهایی هست که به سمت چپ دسته‌بندی میشن:

v1 + v2 + v3 == (v1 + v2) + v3

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

Raywenderlich گرفته از وبسایت
Raywenderlich گرفته از وبسایت

برای دیدن لیست کامل عملگرهای سوییفت و توضیحات مربوط به اونا، مستندات رسمی اپل در مورد عملگرهای سوییفت رو بخونین.


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

  • یه گروه جدید اولویت با اسم SquareSumOperatorPrecedence تعریف می‌کنیم:
precedencegroup SquareSumOperatorPrecedence { lowerThan: MultiplicationPrecedence higherThan: AdditionPrecedence associativity: left assignment: false }

توی کد بالا، ما یه گروه اولویت جدید ساختیم که اولویت اجراییش از AdditionPrecedence بیشتره، و از MultiplicationPrecedence کمتره و به سمت چپ دسته‌بندی میشه.

مقادیر none و یا left و یا right ، مقادیر قابل قبول برای associativity هستن.
توضیح در مورد assignment ، توی نسخه اصلی مقاله هست. خوندمش، ولی چون نتونستم درست متوجه بشم یا منظور رو برسونم، از ذکر توضیحش خودداری کردم.
  • حالا که گروه اولویت رو تعریف کردیم، نحوه تعریف عملگر رو تغییر میدیم بطوریکه از گروه اولویتی که ما تعریف کردیم پیروی کنه:
infix operator ◉: SquareSumOperatorPrecedence

در نهایت کد ما یه همچین چیزی میشه:

//Create PrecedenceGroup precedencegroup SquareSumOperatorPrecedence { lowerThan: MultiplicationPrecedence higherThan: AdditionPrecedence associativity: left assignment: false } //Create Infix Operator infix operator ◉: SquareSumOperatorPrecedence //Define Infix Operator func ◉(lhs: Double, rhs: Double) -> Double { return lhs * lhs + rhs * rhs } let a: Double = 2 let b: Double = 3 let squareSum = a ◉ b print(squareSum) // 13.0


خب بعدش؟

  • برای قدم‌های بعدی می‌تونین در مورد Operator Overloading (نمی‌دونم چی‌چی ترجمه‌اش کنم!! ?) بخونین. Classها و Structها می‌تونن پیاده‌سازی اختصاصی خودشون از عملگرهای فعلی رو داشته باشن. این کار Overload کردن عملگرهای موجود نام برده میشه.
struct Vector2D { var x = 0.0 var y = 0.0 } extension Vector2D { static func + (lhs: Vector2D, rhs: Vector2D) -> Vector2D { return Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } }
  • همچنین می‌تونین در مورد کاربرد Genericها توی تعریف عملگرها بخونین.
prefix operator ++++ prefix func ++++<T: Numeric>(lhs: T) -> T { return lhs * 4 } let n = 12 let m = ++++n print(m) // 48



منبع مطلب: این مطلب رو بر اساس این پست نوشتم.

برنامه‌نویسیسوییفت
آی ام وان آو موست ادونسد هیومنوید اوپریتینگ سیستم! ?
شاید از این پست‌ها خوشتان بیاید