برنامهٔ کواین – دادن سورس به این راحتیا نیست!

به نام خدا… سلام!

چند روز پیش داشتم توی ویکی‌پدیا برنامه‌های مشهور کامپیوتری (مثل !Hello, World) رو نگاه می‌کردم که به یه برنامهٔ جالب به اسم Quine برخوردم:

https://en.wikipedia.org/wiki/Quine_(computing)

اگه انگلیسی‌تون خوبه برین همون رو کامل بخونین چون من توضیح بیشتری از اون ندادم!


خب… بریم ببینیم این برنامه چیه و چیکار میکنه!

اول از همه دربارهٔ اسمش توضیح میدم. این اسم رو سازنده‌ش به افتخار ویلارد کواین روش گذاشته:

https://en.wikipedia.org/wiki/Willard_Van_Orman_Quine

حالا چرا این اسم؟ برای این که این فرد صاحب یک پارادوکس مشهور به Quine’s paradox ـه؛ یه چیزی تو مایه‌های همین برنامه Quine که پایین توضیح میدم. فعلاً ببینیم پارادوکس کواین چیه؟

“yields falsehood when preceded by its quotation” yields falsehood when preceded by its quotation.

پارادوکس کواین همینه. فکر میکنم یه چیزی مثل این باشه:

«حرفی که دارم میزنم (یا مینویسم!) دروغی بیش نیست»

درواقع هر دو دارن به دروغین بودن کل عبارت اشاره میکنن و در نتیجه عبارتی دروغین که به دروغ اشاره کنه راست در میاد (منفی در منفی میشه مثبت) و دوباره میره از اول!


ولی به هر حال از این حال و هوای فلسفه و استنتاج که در بیایم می‌رسیم به برنامهٔ زیبای کواین.

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

و هدف اصلی برنامهٔ Quine اینه که سورس‌کد خودش رو چاپ کنه!

توی هر کدوم از این زبان‌ها یه برنامه نوشته‌شده که سورس برنامهٔ زبان کناریش رو میده!

خب شاید بگین «چه مسخره! این که خیلی ساده‌ـست!» ولی باید بگم به این سادگیا هم نیست!

شاید اولین راه پیشنهادیتون اینه که سورس برنامه رو چاپ کنین. این کد پایتون رو ببینین:

print(&quot...&quot)

الان جای «…» باید چه چیزی قرار بگیره؟ دیدین که به همین راحتی هم نیست. اگه هنوز متوجه موضوع یا مشکل نشدین بزارین اینطوری توضیح بدم: الان باید سورس برنامه رو جای سه‌نقطه بنویسین. سورس برنامه هم که همونیه که بالا نوشتم. پس یعنی باید بشه همچین چیزی:

print(&quotprint(&quot...&quot)&quot)

خب حالا باید داخل اون print توی رشته چی باشه؟ همون چیزی که داخل print اولی وجود داره! پس یعنی باید دوباره یه print جای سه‌نقطه بنویسیم و همینطوری مینویسیم و مینویسیم و ادامه داره…

پس متوجه شدین که این راه و روش درستش نیست. درواقع باید یکم باهوش‌تر باشیم.

راستش خیلی نکات هستن که باید بهشون توجه کنیم:

  • نمیتونیم از character escaping استفاده کنیم چون اون شکلی باید همون escape رو توی سورس‌کد escape کنیم و escape هایی که escape شدن رو دوباره escape کنیم و…. نگران نباشین خودمم نفهمیدم چی گفتم! ولی مهم اینه که مثل همون print توی یه گودال بی‌پایان میفتیم.
  • چون نمیتونیم از escape استفاده کنیم پس نمیتونیم از کوتیشن‌های همشکل کوتیشن‌های بیرونی استفاده کنیم. منظورم اینه که وقتی یه رشته رو با دابل‌کوتیشن مشخص کردیم نمیتونیم داخلش از دابل‌کوتیشن استفاده کنیم چون در این صورت باید اون دابل‌کوتیشن رو با escape قرار بدیم و اینجوری مشکل ایجاد میشه.
  • به همون دلیل escape از new line هم نمیشه مستقیم استفاده کرد.
  • و غیره و غیره و غیره….

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

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

این کدی هست که من با همین روش سه‌حلقه‌ای تو پایتون نوشتم:

quote = chr(34)
source = [
    &quotquote = chr(34)&quot,
    &quotsource = [&quot,
    &quot   &quot,
    &quot]&quot,
    &quotfor i in range(2):&quot,
    &quot   print(source[i])&quot,
    &quotfor i in range(len(source)):&quot,
    &quot   print(source[2] + quote + source[i] + quote + ',')&quot,
    &quotfor i in range(3, len(source)):&quot,
    &quot   print(source[i])&quot,
]
for i in range(2):
    print(source[i])
for i in range(len(source)):
    print(source[2] + quote + source[i] + quote + ',')
for i in range(3, len(source)):
    print(source[i])

خب احتمالاً خودتون متوجه شدین که قضیه از چه قراره. ولی من بازم توضیح میدم.

  1. اول از همه quote رو برابر کوتیشن قرار دادم و دقت کنین که غیرمستقیم این کار رو انجام دادم چون اگه مستقیم بود یعنی کاراکتر دابل‌کوتیشن رو قرار داده‌بودم و توی source باید از این کاراکتر استفاده میکردم و اگه این کار رو میکردم باید escape میشد و خودتون بهتر از من میدونین که چرا نمیتونم انجامش بدم.
    هرچند میشد با یه روش‌هایی انجامش داد ولی این‌طوری کدمون از این هم پیچیده‌تر میشد.
  2. داخل source همون‌طور که قبلاً گفتم، خط به خط برنامه رو کپی کردم (البته من که کپی نکردم خودم نوشتم!!!) و دقت کنین که فرورفتگی‌ها رو هم درست وارد کردم؛ ولی نکتهٔ مهم‌تر اینه که اون قسمت که باید خود source رو مقداردهی کنیم رو خالی گذاشتم و فقط مقدار فرورفتگی (۴ تا اسپیس که اول همهٔ خطوط source باید باشه) رو بهش دادم.
  3. توی حلقهٔ اول، تا قبل از مقدار source رو (که این‌جا کلاً میشه دو خط!) چاپ کردم.
  4. توی حلقهٔ دوم، کل آرایهٔ source رو با فرورفتگی و کوتیشن‌های هر خونه، چاپ کردم.
  5. توی حلقهٔ سوم و آخر، قسمت‌های بعد از مقداردهی source رو چاپ کردم.

امیدوارم مفهوم رو رسونده‌باشم. ببخشید اگه یکم سخت بود! حالا میریم سراغ انواع و قالب‌های مختلف برنامهٔ کواین یا Quine:

  • ساده‌ترین نوع همون نوعیه که تا الان درباره‌ش حرف زدم. این نوع از Quine فقط میاد و سورس خودش رو چاپ میکنه.
  • یک نوع پیچیده‌تر، نوعی از کواین هست که با دو برنامه در دو زبان مختلف اجرا میشه. با اجرای برنامهٔ اول، سورس برنامهٔ دوم و با اجرای برنامهٔ دوم، سورس برنامهٔ اول رو میبینین! خیلی جالبه، نه؟!
  • نوع سوم همون نوع دوم هست با این تفاوت که بیشتر از دو زبان داره! برای مثال برنامهٔ پایتون، سورس روبی رو نشون میده و برنامهٔ روبی، سورس سی و همینطور ادامه داره! جالبه که بدونین، یه برنامه‌نویس هنرمند خلاق (و صدالبته، بیکار!) اومده یه مجموعه کواین این‌شکلی ساخته با ۱۲۸ تا برنامه!!! باور میکنین؟ اگه باور میکنین یا اگه نمیکنین (b && !b) برین پایین همین نوشته، لینکش رو گذاشتم.
  • نوع آخر هم به multiquine معروفه، کاری که میکنه این هست که چند برنامه تو زبان‌های مختلف هستن و هر برنامه قابلیت نشون دادن همهٔ سورس‌ها رو داره! یعنی مثلاً توی سه برنامه و سه زبان، هر برنامه‌ای سورس خودش و سورس بقیه زبان‌ها رو داره و با یه ورودی (میتونه به‌عنوان آرگومان به برنامه داده‌بشه) مشخص میشه که برنامهٔ کدوم زبان رو نشون بده! اصلاً من عاشق این نوعش شدم!

تقلب ممنوع!

احتمالاً خودتون هم میدونین که با یه روش‌هایی میشه سختی‌های ساخت Quine رو دور زد. درواقع میتونیم از قوانین نانوشتهٔ کواین سرپیچی کنیم و کار خودمون رو خیلی راحت کنیم. ولی اینطوری دیگه کسی نمیگه این برنامه Quine ـه. چون واقعاً به درد نمیخوره! ولی به هر حال، چند نمونه رو این پایین آوردم:

نیاز به پرینت نیست، نتیجه خود به خود چاپ میشه!

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

توی این‌جور زبان‌هاست که میشه برنامه‌های کواین یک‌بایتی نوشت! مثل برنامهٔ زیر:

1

همون‌طور که حتماً متوجه شدین، این برنامه (!) توی اون نوع زبان‌هایی که توضیح دادم، خروجی ۱ رو به‌همراه داره. یعنی وقتی برنامه اجرا بشه، عدد ۱ توی پایانه (Terminal) چاپ میشه.

در این حالت، درسته که سورس برنامه چاپ شده؛ ولی به این معنی نیست که کل زحمات ما (همون کدهای بالا) الکی بوده! چون این برنامه، یه‌جورایی تقلب کرده و کواین محسوب نمیشه.

چرا این همه زحمت؟ کد رو از تو فایل بخون دیگه!

منم وقتی داشتم از تو ویکی‌پدیا مقالهٔ Quine رو میخوندم؛ به این فکر افتادم که میشه سورس‌کد رو از توی فایل برنامه خوند دیگه!

cat $0

ولی از این خبرا نیست… این کد Bash هم نمیتونه کمکی بکنه. و یه نمونهٔ کواین به حساب نمیاد.

یعنی با eval و exec هم نمیتونیم انجامش بدیم؟!

اینم یکی دیگه از کارهاییه که اکثرمون موقع ساخت Quine به فکرش میفتیم:

exec(s := 'print(&quotexec(s := %r)&quot % s)')

کاری که این اسکریپت پایتون میکنه اینه که دقیقاً کد خودش رو چاپ میکنه.

و خیلی خوشحال میشم که بهتون بگم، این برنامه، یک نمونهٔ عالی Quine حساب میشه!

هیچ حقه‌ای هم در کار نیست. این برنامه واقعاً کواین هست و براش زحمت هم کشیده‌شده!


یادت نره، Quine یه حلقهٔ بی‌نهایته!

منظورم اینه که هر نمونه‌ای از کواین، باید بتونه هرچند بار که دلتون میخواد اجرا بشه.

خب کواین‌های ساده که سورس خودشون رو برمیگردونن و میتونن بی‌نهایت اجرا بشن.

کواین‌های دوتایی هم باید جوری باشن که بتونین نتیجه برنامه اول (نتیجهٔ برنامهٔ اول = سورس برنامهٔ دوم) رو اجرا کنین و سورس برنامهٔ اول (نتیجهٔ برنامهٔ دوم = سورس برنامهٔ اول) رو بگیرین.

کواین‌های چندتایی و مولتی‌کواین‌ها هم به همین صورت باید باشن.

البته توجه کنین که اگه واقعاً کواین درستی طراحی کرده‌باشین، خودکار این‌جوریه و نیازی به بررسی نیست.


خب اینم پیوند به اون پروژه که ۱۲۸ تا زبان رو تو یه Quine قرار داده!

https://github.com/mame/quine-relay/

خب امیدوارم از این نوشته خوشتون اومده‌باشه

اگه متوجه اشکالی شدین حتماً بهم بگین

ممنون… و خدانگهدار

نوشته برنامهٔ کواین – دادن سورس به این راحتیا نیست! اولین بار در ویرگول پدیدار شد.

گردآوری توسط ایده طلایی

دیدگاهتان را بنویسید