چگونه ایمکس‌مان را تربیت کنیم (قسمت دوم)

·

6 min read

یکی از اجزای جدا نشدنی توابع برنامه نویسی‌، آرگومان‌هایی هستند که می‌توان با استقاده از آن‌ها کارکرد توابع را تحت تاثیر قرار داد‌. توابع ایمکس هم از این قائده مستثنا نیستند‌. برای مثال می‌توان با C-u 3 C-x C-n نشانگر را به سه پنجره بعد از پنجرهٔ حاضر منتقل کرد‌. می‌خواهید امتحان کنید؟ ایمکس‌تان را باز کنید و آن را به چهار پنجره تقسیم کنید و فرمان بالا را وارد کنید‌. همان‌طور که می‌بینید تابع other-window که در این‌جا مورد بحث ماست‌، با استفاده از آرگومان دریافتی توسط C-u که همان عدد سه است‌، می‌تواند به سه پنجره بعد بپرد و ما را از فراخوانی پیاپی‌اش نجات دهد‌. حالا نکته این‌جاست که این تابع آرگومان منفی‌ای مثل ‎-۱ را هم می‌پذیرد‌. و احتمالا این دقیقا همان چیزی است که ما برای حرکت در پنجره‌ها به سمت چپ نیاز داریم‌. یک بار امتحانش کنید: C-u -1 C-x C-n

نکته: اکثر توابع موجود در ایمکس با استفاده از مقادیری مثل ‎-۱ عکس عمل عادی‌شان را انجام می‌دهند‌. فکر کنید که با این ترکیب چه توابع جالب و کاربردی‌ای می‌توان ساخت‌. مثلا کلید delete عقب‌گرد که در ایمکس به طور عادی تعریف نشده D: (در آینده یک نمونه خواهیم ساخت ;-))

تعریف اولیه other-window-backward

برای این که از سختی وارد کردن همچون دستوری بکاهیم‌، بهتر است که آن را به صورت یک تابع تعریف کنیم تا هر جایی که نیاز بود بتوانیم به راحتی از آن استفاده کنیم:

(defun other-window-backward ( )

"Select the previous window."

(interactive)

(other-window -1))

خوب این خطوط به چه معناست؟ به ترتیب آن‌ها را بررسی می‌کنیم:

(defun other-window-backward ( )

در این خط مثل اکثر زبان‌های برنامه‌نویسی دیگر‌، شروع به تعریف تابع می‌کنیم‌. defun عبارتی است مانند def در پایتون که وظیفهٔ تعریف توابع را بر عهده دارد‌. در ادامه نیز اسم تابع other-window-backward و یک جفت پرانتز خالی (که محل تعریف آرگومان‌هایمان در آینده است) را قرار می‌دهیم‌.

در خط دوم:

"Select the previous window"

تنها یک توضیح کوتاه برای عملکرد تابع می‌نویسیم‌. این توضیح عملا تاثیری در روند اجرا تابع ندارد‌. اما وقتی کاربر بخواهد با دستوری مثل describe-function از نحوهٔ عملکرد تابع مطلع شود‌، این توضیح است که نمایش داده می‌شود‌.

خط سوم شامل:

(interactive)

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

و خط آخر:

(other-window -1))

همان‌طور که معلوم است‌، تابع other-window آشنایمان را با آرگومان ‎-۱ صدا می‌کند‌. این آرگومان منفی همانطور که توضیح دادیم به تابع می‌فهماند که باید عقب‌گرد حرکت کند‌.

حالا دیگر تقریبا کارمان تمام است‌. تنها کافی است که آن را به کلید ترکیبی مد نظرمان (C-x C-p چنان که در قسمت قبل تصمیم گرفتیم) پیوند دهیم:

(global-set-key "\C-x\C-p" 'other-window-backward)

و کار تقریبا تمام است‌.

افزودن مزایای آرگومان‌ها

تا این‌جای کار other-window-backward کاری که می‌خواهیم را درست انجام می‌دهد‌. اما هنوز یک کمبود دارد‌. کدام کمبود؟ همانطور که بالا‌تر توضیح دادم ما می‌توانستیم با C-u به other-window بگوییم که مثلا به دو پنجره بعد بپرد و ما را از فراخوانی دوباره و دوباره‌اش نجات دهد‌. اما تابعی که الان تعریف کردیم‌، این قابلیت را ندارد‌. در نتیجه کاربر بخت برگشته باید برای پرش به سه پنجره قبل سه بار این کلید‌ها را فشار دهد‌! و این یعنی عذاب ;-)

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

(defun other-window-backward (n)

"Select Nth previous window."

(interactive "p")

(other-window (- n)))

همانطور که می‌بینید در خط اول‌، یک آرگومان به لیست آرگومان‌های تابع به اسم n اضافه کردیم‌. با این کار به تابع فهماندیم اگر کاربر با C-u آرگومانی را فرستاد (مثلا ۲) آن را در متغیر n قرار بده‌.

همینطور در خط آخر به جای استفاده از ‎-۱ تابع other-window را با آرگومان منفی n صدا زدیم‌. یعنی هر عددی به n فرستاده شد‌، به ترکیب فوق‌، به صورت منفی به other-window فرستاده می‌شود‌.

توجه: به فاصلهٔ میان علامت منفی و حرف n توجه کنید‌. در صورتی که بخواهید آن را به صورت:

(-n)

بنویسید‌، مفسر لیسپ به مشکل بر می‌خورد‌. احتمالا یادتان هست که برای تفریق دو عدد چه کار باید می‌کردیم:

(- 2 6)

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

اما یک نکتهٔ مهم و اساسی در خط سوم وجود دارد که باید به آن نیز بپردازیم‌. ما در این خط به صورت زیر عمل کردیم‌:

(interactive "p")

جریان آن p مشکوک چیست؟ اگر تابع مثل روش اول‌مان‌، هیچ آرگومانی نگیرد‌، interactive هم بدون آرگومان می‌ماند‌. اما اگر مثل روش اخیرمان بخواهیم در تابع از آرگومانی استفاده کنیم‌، باید یک آرگومان به صورت رشته کد به interactive هم اضافه کنیم‌. کد p کوچک به تابع می‌فهماند که اگر یک آرگومان به تابع فرستاده شد آن را به صورت یک عدد ترجمه کن و به تابع برگردان‌. اما اگر آرگومانی فرستاده نشد‌، خودت به صورت پیش‌فرض عدد ۱ را به تابع برگردان‌. و این دقیقا کاری است که ما تا این‌جای کار از تابع‌مان می‌خواهیم‌.

اختیاری کردن آرگومان

همان‌طور که می‌بینید‌، این تابع قابلیت فراخوانی توسط توابع دیگر لیسپ را نیز دارد‌. اما در این وضعیت (منظورم در حالتی است که تابع توسط توابع دیگر فراخوانده شود و نه در صورتی که توسط کاربر صدا زده می‌شوند) حتما باید به همراه فراخوانی تابع یک آرگومان هم به آن اطلاق شود‌. و این کمی کار را پیچیده می‌کند و ممکن است در آینده خسته کننده به نظر بیاید‌. بهتر است تابع را طوری تعریف کنیم که وقتی به صورت other-window-backward صدا شد خودکار روش انجام کار پیش‌فرضش را پیش بگیرد‌. پس آن را به صورت زیر ویرایش می‌کنیم:

(defun other-window-backward (&optional n)

"Select Nth previous window."

(interactive "p")

(if n

(other-window (- n)) ; if n is non-nil

(other-window -1))) ; if n is nil

در خط اول با ‏‎&optional به تابع می‌فهمانیم که آرگومان n باید اختیاری باشد‌. یعنی اگر فرستاده شد مقدار دهی شود و اگر نه‌، بی‌خیالش باشد‌. همچنین از یک عبارت کنترلی if هم استفاده کردیم‌. در این عبارت اگر n مقدار داشته باشد‌، پس if با یک وضعیت non-nil یا همان True در اکثر زبان‌های برنامه نویسی روبروست‌. و اگر مقدار نداشته باشد‌، برابر است با nil یا False. با توجه با کامنت‌های موجود در کد احتمالا می‌توانید بفهمید که در صورت وقوع هر یک از این وضعیت‌ها کدام حالت رخ خواهد داد‌.

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

پی‌نوشت: مرا به علت دیر‌کرد این چند وقته در انتشار ادامهٔ مطلب حاضر ببخشید‌. امروز هم احتمالا نتوانسته‌ام آن‌طور که باید و شاید حق مطلب رو عدا کنم‌. حقیقتش امروز از آن روز‌هایی بود که در مود نوشتن برای وبلاگ نبودم ولی باید حتما تا این‌جای کار را بیان می‌کردم تا نوشته‌ها از خودم عقب نمانند ;-)