پنجشنبه, ۶ اردیبهشت, ۱۴۰۳ / 25 April, 2024
مجله ویستا

زبانهای برنامه‌نویسی در هوش مصنوعی


زبانهای برنامه‌نویسی در هوش مصنوعی
● عناوین متن
زبانهای برنامه‌نویسیAI، برنامه‌نویسی تابعی ، برنامه‌نویسی تابعی در Lisp ، A- Syntax (نحو) و semantic های (معانی) Lisp ، لیست انواع داده ، تعریف توابع جدید ، تعریف ساختارهای كنترلی ، تعریف توابع بازگشتی ، توابع مرتبه بالا ، سایر زبانهای برنامه‌نویسی تابعی غیر از Lisp ، برنامه‌نویسی منطقی در Prolog ، سایر روشهای برنامه‌نویسی
● واژه نامه
بندهای برنامه Prolog شامل مجموعه‌ای از جملات بنام بندها هستند كه برای نشان دادن داده‌ها و برنامه‌ها بكار می‌روند.
تابع مرتبه بالا تعریف تابعی است كه اجازه می‌دهد آرگومانها یا مقدار بازگشتی تابع، مقدار توابع باشد. نماد ساختار لیستها اغلب نشان‌دهنده نحوه استفاده از لیست ساختاری داده هستند، كه یك عنصر لیست ممكن است نماد یا لیست دیگر باشد. لیستها ساختاری مركزی Lisp هستند كه برای نشان دادن داده‌ها و برنامه‌ها بكار می‌روند. بازگشت تكنیكی الگوریتمی برای انجام یك كار است كه یك تابع با بعضی از قسمتهای كار خودش را فراخوانی می‌كند.
محاسبات نمادین برنامه‌نویسی AI (اساساً) شامل دستكاری نمادها است نه اعداد. این نمادها می‌توانند اشیاء در جهان و ارتباط بین آن اشیاء را نشان دهند- ساختارهای پیچیده نمادها نیاز به دانش ما از جهان دارند. واژه ساختار اساسی داده‌ها در Prolog واژه‌ای است كه می‌تواند یك ثابت، یك متغیر یا یك ساختار باشد. ساختارها موضوعات ریز محاسبات گزاره‌ای را نشان می‌دهند و شامل یك عملگر نام و یك پارامتر لیست هستند.
زبانهای برنامه‌نویسی هوش مصنوعی(AI) ابزار اصلی بررسی و ساخت برنامه‌های كامپیوتری هستند كه می‌توانند در شبیه‌سازی فرایندهای هوشمند مانند یادگیری،‌ استدلال و فهم اطلاعات نمادین بكار بروند. هر چند اخیراً زبان كامپیوتر اصولاً برای استفاده از كامپیوترها برای انجام محاسبات با اعداد طراحی شده بود، اما بزودی دریافتند كه رشته‌ای از بیتها نه تنها اعداد بلكه می‌توانند اشیای دلخواه را نیز نمایش دهند. عملیات روی ویژه‌گی‌ها یا نمادها می‌تواند با استفاده از قوانین برای ایجاد، انتساب یا دستكاری نشان داده شود. این تصور از محاسبات نمادین بعنوان تعریف الگوریتمهایی كه هر نوع اطلاعات را پردازش می‌كنند و بنابراین می‌تواند برای شبیه‌سازی هوش انسان بكار برود مناسب است.
بزودی برنامه نویسی با نمادها كه نیاز به سطح بالایی از چكیدگی دارند تولید می‌شوند، غیر از امكاناتی كه با زبانهای برنامه نویسی مخصوص پردازش اعداد ممكن بود مانند فرترن
I) زبانهای برنامه نویسی AI
در AI خودكار كردن یا برنامه‌نویسی همه جنبه‌های شناخت انسانی بوسیله بنیادهای شناخت علمی روشهای نمادین و غیر نمادین AI، پردازش زبان طبیعی، دید كامپیوتری و سیستمهای تكامل یا سازگار مطرح می‌شود. لازم است دامنه مسئله‌های خیلی پیچیده در ابتدای مرحله برنامه‌نویسی یك مسئله AI معین، مشخص شود كه كافی نیست. تنها بوسیله تعامل و افزایش اصلاحات خصوصیات بسیار دقیق ممكن است. در حقیقت مسئله‌های معمول AI به بسیاری از زمینه‌های خاص گرایش دارند، بنابراین روشهای ذهنی باید بوسیله تولید و آزمایش روشها بطور تجربی توسعه یابند(مشهور به نمونه سازی سریع).
در اینصورت برنامه‌نویسی AI بطور قابل توجهی با روشهای استاندارد مهندسی نرم‌افزار متفاوت بوده زیرا برنامه‌نویسی معمولا از یك مشخصات رسمی با جزئیات شروع می‌شود. در برنامه‌نویسی AI پیاده‌سازی در واقع جزئی از پردازش مشخصات مسئله است. به اقتضای طبیعت مسئله‌های AI برنامه‌نویسی AI مزایای بسیاری دارد اگر زبانهای برنامه نویسی، برنامه‌نویسAI را آزاد بگذارند و در بسیاری از ساختارهای فنی محدود نكنند (مانند ساختار انواع داده‌ای جدید سطح پایین، دستیابی دستی به حافظه). ترجیحاً سبك برنامه‌نویسی اعلانی برای استفاده در ساختارهای پیش‌ساخته داده‌ای سطح بالا(مانند لیستها و درختها) و عملیات(مانند تطبیق الگوها) مناسب است، بنابراین محاسبات نمادین سطح خلاصه‌سازی بیشتری نسبت به آنچه كه با زبانهای دستوری استاندارد مانند فرترن، پاسكال یا C امكان‌پذیر خواهد بود را پشتیبانی می‌كند. البته طبقه‌بندی خلاصه سازی آسان نیست،‌ زیرا تدوین برنامه‌های AI روی كامپیوترهای استاندارد وان نیومن نمی‌تواند به كارآمدی زبانهای دستوری باشد. هر چند یك مسئله مسلم AI فهم آن است (حداقل جزئیات) امكان دارد با تنظیم مجدد آن به شكل خصوصیات جزئی شده با بكار بردن یك زبان دستوری پیاده‌ سازی مجدد شود.
با توجه به نیازمندیهای محاسبات نمادین و برنامه‌نویسی AI دو الگوی جدید برنامه‌نویسی كه به سبك دستوری پیشنهاد می‌شوند بوجود می‌‌آید: سبك برنامه‌نویسی تابعی و منطقی. هر دو بر مبنای ریاضیات طرح‌ریزی شده‌اند، یعنی نظریه توابع بازگشتی و منطق رسمی. اولین زبان برنامه‌نویسی AI كاربردی كه هنوز هم بطور گسترده استفاده می‌شود زبان برنامه‌نویسی Lisp است كه در اواخر دهه ۱۹۵۰ توسط جان مك كارتی توسعه یافته است. Lisp برمبنای نظریه توابع ریاضی و خلاصه‌سازی Lambda است. تعدادی از كاربردهای مهم و موثرAI در Lisp نوشته شده است. كه ما بعضی از جزئیات این زبان برنامه‌نویسی را در این مقاله شرح خواهیم داد. در اوایل دهه ۱۹۷۰ یك الگوی برنامه‌نویسی جدید بنام برنامه‌نویسی منطقی بر اساس محاسبات گزاره‌ای بوجود آمد. اولین و مهمترین زبان برنامه‌نویسی منطقی Prolog است كه توسط آلن كالمرار، رابرت كوالسكی و فیلیپ راسل توسعه یافته است. مسئله‌ها در prolog بصورت حقایق، بدیهیات و قوانین منطقی برای استنباط حقایق جدید بیان می‌شوند. Prolog با قانون ریاضی در محاسبات گزاره‌ای و نتایج نظری بدست آمده در زمینه اثبات قضیه خودكار در اواخر دهه ۱۹۶۰ بنا نهاده شده است.
II) برنامه نویسی تابعی
یك تابع ریاضی نگاشتی از یك مجموعه (دامنه) به مجموعه دیگر(برد) است. تعریف یك تابع توصیف این نگاشت است كه یا بطور صریح بوسیله شمارش و یا بطور ضمنی بوسیله یك عبارت است. تعریف یك تابع بوسیله نام تابع كه بدنبال آن لیستی از پارامترها در داخل پرانتز قرار دارند و به دنبال آن نیز عبارت توصیفی نگاشت است مشخص می شود مانند:
X یك عدد حقیقی است cube(X) ≡ X X X , where X is a real number.
آلونسو چارچ توابع بی نام را با استفاده از نمادLambda معرفی می كند. یك عبارت Lambda پارامترها و نگاشت تابع را با استفاده از عملگر X مشخص می كند, مانند &#۹۵۵; (X)X X X آن خودش تابع است, بنابراین شرح بكار رفته در مثال تابع بی نام با یك آرگومان مشخص است. برای مثال:(&#۹۵۵; (X) X X X)(۴).
برنامه نویسی در یك زبان تابعی شامل ساختمان تعریف توابع و بكاربردن كامپیوتر برای ارزیابی عبارات است. یعنی بكاربردن توابع با آرگومانهای واقعی. كار اصلی برنامه نویسی پس از ساخت یك تابع برای یك مساله خاص تركیب توابع تعریف شده قبلی با توجه به اصول ریاضی است. كار اصلی كامپیوتر ارزیابی توابع فراخوانی شده و چاپ حاصل مقادیر تابع است. در این روش كامپیوتر مشابه یك كامپیوتر جیبی معمولی بكار می رود البته بسیار انعطاف پذیرتر و قدرتمندتر. یك خاصیت برنامه نویسی تابعی این است كه اگر عبارت به خوبی مقداردهی شود آنگاه ترتیب انجام ارزیابی كامپیوتر در نتایج ارزیابی تاثیری ندارد. بنابراین نتیجه ارزیابی یك عبارت تنها مقدار آن است. بدین معنی است كه در یك زبان تابعی ناب اثرات جانبی وجود ندارد. اثرات جانبی در مدل موقعیت های حافظه به متغیرها متصل شده اند.بنابراین در یك زبان برنامه نویسی ناب در مفهوم زبانهای دستوری متغیر وجود ندارد. روشهای اصلی كنترل جریان، بازگشت (تكرار) و عبارات شرطی هستند. این كاملاً با زبانهای دستوری در مفهوم اساسی كنترل ترتیب و تكرار متفاوت است. برنامه نویسی تابعی نیز خصوصیات توابع مرتبه بالا را پشتیبانی می كند. تابع مرتبه بالا تعریف تابعی است كه اجازه می دهد آرگومانها یا مقدار بازگشتی تابع, مقدار توابع باشند. همه این جوانب با هم مخصوصاً آخری از اصلی ترین مزایای سبك برنامه نویسی تابعی در برابر سبك برنامه نویسی دستوری هستند. خلاصه برنامه نویسی تابعی سطح بالایی از درجه پیمانه ای بودن را فراهم می كند. وقتی یك مسئله با تقسیم آن به مجموعه ای از زیر مسئله ها تعریف می شود, موضوع اصلی روشهایی است كه می توان زیر مسئله ها را به یكدیگر چسباند. بنابراین برای افزایش قابلیت پیمانه ای بودن یك مسئله مفهومی, ابتدا باید نوع جدیدی از چسب در زبان برنامه نویسی فراهم شود- قدرت اصلی برنامه نویسی تابعی .
III) برنامه نویسی تابعی در Lisp
Lisp اولین زبان برنامه نویسی تابعی است: آن برای پشتیبانی محاسبات نمادین با استفاده از لیستهای پیوندی بعنوان ساختار مركزی داده ها ابداع شده بود ( Lisp یعنی پردازشگر لیست). جان مك كارتی دریافت كه روشهای كنترل جریان توابع ریاضی (بازگشت و تكرار) وسیله نظری مناسبی برای انجام محاسبات نمادین هستند. علاوه براین مفاهیم خلاصه سازی تابعی و كاربرد تابعی تعریف شده در محاسبات Lambda , سطح بالایی از خلاصه سازی موردنیاز برای مسئله های AI مشخص شده را فراهم می كنند.
Lisp در سال ۱۹۵۸ توسط مك كارتی ابداع شد و اولین نگارش محیط برنامه نویسی Lisp در سال ۱۹۶۰ آماده شد كه شامل یك مفسر, یك كامپایلر و مكانیسم تخصیص و بازپسگیری حافظه پویا بود (بعنوان مجموعه فضای هرز شناخته شده است). یكسال بعد اولین زبان استاندارد با نام Lisp۱.۵ معرفی شد. پس از آن تعدادی از نسخه ها و محیط های برنامه نویسی Lisp توسعه یافته اند. مانند MacLisp، FranzLisp، InterLisp، CommonLisp، Scheme هر چند آنها در بعضی جزئیات خاص متفاوتند ولی هسته Syntax (نحو) و Semantic (معنی) آنها اساساً یكسان است. هسته را در جای دیگر معرفی خواهیم كرد. پر استفاده ترین نسخه‌های
Lisp ، Common Lisp و scheme هستند. در این مقاله ما Common Lisp را برای نشان دادن جنبه های مختلف Lisp با مثالهای معمولی انتخاب كرده ایم. هرچند مثالها نیز به راحتی می توانند در نسخه های دیگر Lisp سازگار شوند.
Syntax .A. (نحو) و semantics (معانی) Lisp
۱) عبارات نمادین:
عناصر نحوی Lisp عبارات نمادین نامیده می شوند (كه به صورتS-expressionsشناخته شده‌اند). داده ها و توابع (یعنی برنامه های Lisp ) بصورت عبارات نمادین نشان داده شده اند كه می توانند اتم ها یا لیست ها باشند. اتم ها كلمه ای شبیه اشیا‌ هستند. اتم‌ها وابسته به نوع كاراكترهایی كه برای شكل دادن یك اتم مجازند می توانند به انواع مختلفی تقسیم شوند. انواع اصلی عبارتنداز:
Numbers:۱ ۲۳۴-۴۳.۱۴۱۵۹۲۶۵۳۵۸۹۷۹ -۷.۵ ۶.۰۲E+۲۳
Symbols:SymbolSym۲۳another-one t false NILBLUE
Strings: ”This is a string””۹۷۷?” ”setq””He said: ” I’m here.” ”
توضیح اینكه هرچند نماد خاصی مثل BLUE استفاده می‌شود چون مفهوم مشخص برای برنامه‌نویسی دارد، اما بزودی Lisp تنها ترتیبی از حروف یا تنها یك نماد است. لیستها بندی شبیه اشیاء هستند. یك لیست شامل یك پرانتز باز( دنباله‌ای از اعداد دلخواه كه بوسیله فاصله خالی از هم جدا می‌شوند) و یك پرانتز بسته هستند. هر عنصر لیست می‌تواند یك اتم یا لیست باشد. اینها مثالهایی از لیستها هستند:
(This is a list) ((this) ((too))) () (((((((())))))))
(a b c d) (john mary tom) (loves john ?X)
(* (+ ۳ ۴) ۸) (append (a b c) (۱ ۲ ۳))
(defun member (elem list)
(if (eq elem (first list)) T
(member elem (rest list))))
توضیح اینكه در بسیاری از مثالها عناصر لیست خود لیستها هستند.چنین لیستهایی، لیستهای تو در تو نامیده می‌شوند. در مورد تو در تویی محدودیتی وجود ندارد. برای مثال یكی از قویترین Lisp ها را شرح می‌دهیم: پیچیده‌ترین اشیاء را به راحتی می‌توان نوشت. تنها چیزی كه در نظر گرفته می‌شود درستی عدد داخل پرانتزهاست. مهم توضیح این است كه معنی وابسته به یك لیست نمایش ویژه یا اتم در لیست نمایش وارد نمی‌شود. به این معنی كه همه عبارات نمادین كه در بالا توصیف شده است از لحاظ نحو برنامه‌های Lisp را اصلاح می‌كنند ولی الزاماً از لحاظ معنی (semantic) برنامه‌ها رااصلاح نمی‌كنند.
۲) Semantics (معانی):
هسته هر سیستم برنامه‌نویسی Lisp مفسر است كه كارش محاسبه مقدار برای یك عبارات نمادین داده شده است. این فرآیند ارزیابی نام دارد. نتیجه یا مقدار یك عبارت نمادین، یك عبارت نمادین است. كه بعد از كامل شدن ارزیابی برگردانده شده است. توضیح اینكه در واقع Lispدارای Semantics (معانی) عملیاتی است كه با یك تعریف ریاضی دقیق از نظریه تابع بازگشتی بدست می‌آید.حلقه خواندن- محاسبه- چاپ چگونه می‌تواند مفسر Lisp را فعال كرده و برای محاسبه عبارات نمادین و بنابراین اجرای واقعی برنامه‌های Lisp بكار برود؟
مسئله‌‌های Prolog بصورت حقایق، بدیهیات و قوانین منطقی برای استنباط حقایق جدید بیان می‌‌ شوند . Prolog با قانون ریاضی در محاسبات گزاره‌ ای و ونتایج نظری بدست آمده در زمینه اثبات قضیه خودكار در اواخر دهه۱۹۶۰ بنا شده است. مفسر Lisp در واقع بعنوان یك تابع معمولاً بنام eval و جزئی از هر محیط برنامه‌‌‌نویسی Lisp است تعریف شده است (مانند تابعی كه پیش‌ساخته نام دارد). آن بوسیله فراخوانی حلقه خواندن- محاسبه- چاپ در یك سیستم Lisp جاسازی می‌شود، وقتی یك عبارت نمادین توسط كاربر داده می‌‌ شود ابتدا به داخل سیستم Lisp خوانده می‌شود( خواندن هم یك تابع پیش‌ساخته است). سپس مفسر Lisp كه via نام دارد تابع eval را فراخوانی می‌كند تا عبارت نمادین را محاسبه و نتیجه عبارت نمادین را با چاپ در دستگاه كاربر برگرداند ( شگفت‌آورنیست گفتن اینكه چاپ هم یك تابع پیش‌‌ساخته است). وقتی سیستم Lispدر كامپیوتر شروع به اجرا می‌‌شود این حلقه خواندن- محاسبه- چاپ بطور خودكار شروع به اجرا كرده و بوسیله علامت ویژه اعلان Lisp در ابتدای خط جدید به كاربر علامت می‌دهد در این مقاله ما علامت سئوا ل (?) را به عنوان اعلان Lisp بكار خواهیم برد. برای مثال:
( ۴ ۳ +) ? ۷
هر وقت سیستم Lisp اجرا شود حلقه خواندن- محاسبه- چاپ فعال خواهد بود.
عبارت نمادین ( ۴ ۳ + ) كه بوسیله هكر Lisp وارد شده است بوسیله مفسر Lisp بصورت فراخوانی تابع جمع تفسیر شده و نتیجه عبارت نمادین در ابتدای خط جدید ۷ چاپ می‌‌شود .
▪ ارزیابی مفسر Lisp مطابق سه قانون زیر انجام می‌‌شود:
۱) یكسانی: یك عدد،‌ یك رشته یا نمادهای t و nil خودشان را ارزیابی می‌كنند (بر می‌گردانند) به این معنی كه ارزش عدد ۳،۳ و ارزش رشته ”house”، رشته ”house”است. نمادt مقدار t برمی‌گرداند كه به معنای true تفسیر می‌شود وnil ، nil به معنی false برمی‌‌گرداند
۲) نمادها: ارزیابی یك نماد عبارت نمادین مربوط به آن را برمی‌‌‌گرداند. ( چگونگی‌ اش را در زیر نشان خواهیم داد) بنابراین اگر ما فرض كنیم نماد‌ *names* به لیست
(john mary tom) وابسته است آنگاه ارزیابی *names* آن لیست را نتیجه می‌دهد. اگر نماد color را به نماد green وابسته كنیم آنگاه green بعنوان مقدار color برگردانده می‌‌شود.
به بیان دیگر نمادها بعنوان متغیرهایی كه به مقادیری متصل(باند) شده‌اند تفسیر می‌‌شوند.
۳) لیستها: هر لیست بعنوان یك فراخوانی تابع تفسیر می‌‌شود. مفسر اول لیست دلالت بر تابعی دارد كه باید برای بقیه عناصر( بالقوه خالی)‌ كه آرگومانهای آن تابع را نشان می‌دهند بكار رود. در واقع آرگومانهای یك تابع قبلا بصورت نمادهای پیشوندی مشخص می‌‌شوند. این مزیت را دارد كه توابع به سادگی می‌توانند با تعداد دلخواهی آرگومان مشخص و استفاده شوند. لیست خالی ( ) دارای عبارت نمادین nil بعنوان مقدارش می‌باشد. توضیح اینكه نماد nil در واقع دارای دو معنی است: یك نمایش مقدار منطقی false و دیگری نمایش لیست خالی. هر چند ممكن است این یك بیت فرد بنظر برسد، ولی در واقع در Lisp مشكلی در شناسایی مفهوم nil بكاررفته وجود ندارد.
ولی بطور كل آرگومانها قبل از اینكه توابع مقادیر آنها را استفاده كنند ارزیابی می‌شوند. اولویت ارزیابی ترتیبی از آرگومانها از چپ به راست است. یك آرگو‌مان ممكن است یك اتم یا یك لیست باشد،‌درهر حالت بعنوان یك فراخوانی تابع تفیسر شده و مفسر Lisp برای ارزیابی آن فراخوانی می‌شود. برای مثال، ارزیابی زیر در سیستم Lisp یك تابع به حساب می‌آید:
?(max ۴ (min ۹ ۸) ۷ ۵)
۸
در اینجا آرگومانها ۵, ۷, (min ۹ ۸), ۴ هستند كه در اولویتی قبل از تابعی به نام max كه نتیجه مقادیر آرگومانها را به كار می‌برد ارزیابی می‌شوند. آرگومان اول ۴ ،‌ یك عدد است پس مقدار آن ۴ است.
آرگومان دوم (min ۹ ۸) است كه خودش یك فراخوانی تابع است. بنابراین باید قبل از آرگومان سوم فراخوانی شود، (min ۹ ۸) باید توسط مفسر Lisp ارزیابی شود. چون ما باید مفسر Lispرا برای بعضی آرگومانها در طول ارزیابی همه فراخوانی‌های توابع استفاده كنیم می‌‌توان گفت مفسر Lisp بصورت بازگشتی فراخوانی شده است. مفسر Lisp همان مراحل را به كار می‌برد، پس آرگومان اول ۹ قبل از آرگومان دوم ۸، ارزیابی می‌شود. با بكار برروی تابع min حاصل ۸ می‌شود یعنی تابع كوچكترین عدد یك مجموعه از اعداد صحیح را محاسبه می‌‌كند. برای تابع بیرونی max هم به این معنی است كه آرگومان دوم آن ۸ ارزیابی می‌شود.
آرگومانهای بعدی ۷و۵هستند كه نتیجه ارزیابی آنها مقادیر ۷و۵ می‌شود. حال تابع بزرگترین عدد كه max نام دارد می‌تواند ارزیابی شود كه ۸ برمی‌گرداند. این مقدار نهایی،‌ مقدار فراخوانی همه توابع می‌‌باشد. از آنجایی كه گفته می‌‌‌شود مفسر Lisp همیشه سعی می‌كند مقدار یك نماد یا تفسیر یك لیست بعنوان یك فراخوانی تابع را تشخیص دهد ما چگونه می‌توانیم با نمادها و لیستها بعنوان داده رفتار كنیم؟ برای مثال، اگر ما لیست (peter walks home) را وارد كنیم، آنگاه مفسر Lisp فوراً یك خطا می‌دهد كه چیزی شبیه این خطا می‌گوید: تابع peter ناشناخته است (مفسرLisp باید بقدری باهوش باشد كه بتواند ابتدا كنترل كند كه آیا تعریف تابعی برای نام تابع تعیین شده وجود دارد یا نه، قبل از اینكه هر آرماگونی را ارزیابی كند). یا اگر ما فقط house را وارد كنیم، آنگاه مفسر Lisp با خطایی شبیه این خطا خاتمه می‌یابد: مقداری به house متصل نیست (تخصیص نیافته است). حل این مسئله كاملاً آسان است. زیرا عنصر اصلی هر لیست بعنوان نام تابع تفسیر می‌شود،‌هر سیستم Lisp با یك تابع پیش‌ساخته quote می‌‌آید كه یك عبارت نمادین را بعنوان آرگومان پذیرفته و این عبارت نمادین را بدون ارزیابی آن برمی‌گرداند. برای مثال: لیست(quote(peter walks home)) ، به سادگی مقدار
(peter walks home) را برمی‌گرداند، و برای (quote house)، آن house را بر می‌‌گرداند. از آنجایی كه تابع quote زیاد استفاده می‌‌‌شود، می‌توان آن را با كاراكتر ویژه &#۰۳۹; بیان كرد. بنابراین برای مثال بالا می‌توانیم معادل’(Peter walks home) و’house را مشخص كنیم. برنامه‌ها بعنوان داده، یعنی تابع quote به ما امكان می‌‌‌دهد تا با فراخوانی تابع بعنوان داده رفتار كنیم. برای مثال: (quote (max ۴ (min ۹ ۸) ۷ ۵)) یا ’(max ۴ (min ۹ ۸) ۷ ۵)
قبلاً گفتیم كه مفسر Lisp یك تابع یكتایی پیش‌ساخته است كه eval نام دارد. آن صریحاً آرگومانهایش را وادار می‌كند تا مطابق قوانین مذكور در بالا ارزیابی شوند. در بعضی حالات، آن می‌تواند مقابل تابع quote قرار بگیرد بنابراین به وضوح لازم است كه یك لیست بعنوان داده مشخص شود تا سیستم Lisp بتواند یك فراخوانی تابع تفسیر شود، ما می‌توانیم(eval ’(max ۴ (min ۹ ۸) ۷ ۵)) را مشخص كنیم كه مقدار ۸ را بطوری كه در بالا توصیف شد بر می‌گرداند. به همان صورت مشخص كردن (eval ’(peter walks home)) سبب یك خطای Lisp می‌شود زیرا Lisp سعی می‌كند یك تابع peter فراخوانی كند. مزیت اصلی رفتار برنامه‌ها بعنوان داده این است كه ما می‌توانیم برنامه‌های Lisp (توابع) را طوری تعریف كنیم كه قادر به ساخت یا تولید برنامه‌ها باشند بطوریكه ابتدا لیست نمایش متناظر را ساخته و سپس با استفاده از تابع eval ، مفسر Lisp را به منظور ارزیابی لیست ایجاد شده بعنوان یك تابع فراخوانی می‌كند. شگفت‌آور نیست كه به اقتضای این خصوصیات، Lisp هنوز زبان برنامه‌نویسی برتر در زمینه برنامه‌نویسی ژنتیك AI است.
وقتی مقادیر را به نمادها تخصیص می‌دهیم كه برنامه‌نویسی برنامه‌های كاربردی
real-life به ذخیره مقادیری محاسبه شده در یك متغیر نیاز داشته باشد تا اگر در آینده در برنامه‌ دیگری نیاز باشند از هزینه محاسبه مجدد آن جلوگیری شود. در یك نگارش كاملاً تابعی Lisp ‌مقدار یك تابع تنها به تعریف تابع و مقدار آرگومانهایش در فراخوانی بستگی دارد. برای اینكه Lisp را یك زبان كاربردی بكنیم (كاربردی حداقل در این مفهوم كه بتواند بر روی كامپیوترهای وان نیومن به خوبی اجرا شود)، ما نیاز به روشی داریم تا مقادیر را به نمادها تخصیص دهیم.common Lisp با یك تابع پیش‌ساخته بنام Setq می‌آید. Setq دو آرگومان می‌خواهد: نماد (بنام متغیر) كه یك مقدار به آن متصل شده است و یك عبارت نمادین كه باید مقداری را فراهم كند. مفسر Lisp ارزیابی Setq را در روش خاصی انجام می‌دهد بطوریكه آرگومان اول Setq را ارزیابی می‌كند(متغیر)،‌ اما مقدار آرگومان دوم Setq را به متغیر متصل می‌كند(برای فهم چگونگی اتصال یك مقدار به یك نماد نیاز به جزئیات فنی زیادی خواهیم داشت كه در این معرفی كوتاه نمی‌توان به آن پرداخت). مقدار آرگومان دوم Setq مقدار Setq را بر می‌گرداند. اینها مثالهایی هستند:
?color
error: unbound symbol color
?(setq color ’green)
green
?(setq max (max ۳ ۲ ۵ ۱))
۳
توضیح اینكه در واقع Setq حالت مفسر Lisp را تغییر می‌دهد تا دفعه بعدی كه همان متغیر استفاده می‌شود، دارای مقدار بوده و بنابراین مفسرLisp قادر به بازگرداندن آن خواهد بود. اگر این اتفاق نیفتد آنگاه مفسر Lisp یك اخطار خواهد داد زیرا نماد متصل نشده است.
(گام ۲ مفسر Lisp پیدا نشد). بنابراین آن می‌گویدكه Setq یك اثر جانبی تولید می‌كند زیرا حالت مفسر Lisp بطور پویا تغییر می‌دهد. وقتی استفاده از Setq اجباری شد، به هرحال متوجه شد كه در واقع از مسیر semantics (معانی) Lisp ناب دور می‌شود. پس Setq باید با دقت بسیار استفاده شود.
B۰ نوع داده لیست
برنامه‌نویسی در Lisp در واقع به معنی تعریف توابعی است كه روی لیست عمل می‌كنند. مانند ایجاد، پیمایش،‌كپی، تغییر و حذف لیستها. از آنجایی كه این در Lisp مركزی است، هر سیستم Lisp بر مبنای مجموعه‌ای از توابع پیش‌ساخته ابتدایی كه بطور موثری عملیات اصلی لیست را پشتیبانی می‌كند می‌آید. ما بطور خلاصه یكی از مهمترین آنها معرفی می‌كنیم. ابتدا نوع گزاره‌ای،‌ ما می‌دانیم كه یك عبارت نمادین جاری یا یك لیست است یا نیست (یعنی یك اتم). این كار بوسیله تابع Listp انجام می‌شود كه هر عبارت نمادین expr را بعنوان آرگومان پذیرفته و اگر expr لیست باشد نماد t و در غیر این صورت nil برمی‌گرداند. مثالها هستند (ما از فلش راست => برای نشان دادن نتیجه فراخوانی تابع استفاده خواهیم كرد):
(listp ’(۱ ۲ ۳))==>t
(listp ’( ))==>t
(listp ’۳)==>nil
در انتخاب عناصر لیست دو تابع اساسی برای دست‌یابی به عناصر یك لیست وجود دارد: car وcdr هر دو تابع یك لیست را بعنوان آرگومان می‌پذیرند. تابع car اولین عنصر لیست یا اگر لیست خالی از آرگومان باشد nil بر می‌گرداند،‌و cdr همان لیست را بطوری كه عنصر اول آن حذف شده است یا اگر لیست خالی از آرگومان بود nil برمی‌گرداند. مثالها:
(car ’(a b c)) ==>a (cdr ’(a b c)) ==>(b c)
(car ’( )) ==>nil(cdr ’(a)) ==>nil
(car ’((a b) c))==>(a b)
با استفاده از ترتیبی از فراخوانی‌های توابع car و cdr می‌توان یك لیست را از چپ به راست و از عناصر بیرونی به سمت عناصر داخلی لیست پیمایش كرد.
برای مثال، در طول ارزیابی (car (cdr ’(see the quote))) مفسر Lisp ابتدا عبارت
(cdr ’(see the quote))را ارزیابی خواهد كرد كه لیست (the quote) را برمی‌گرداند، سپس به تابع car پاس می‌شود كه نماد the را بر می‌گرداند. اینها مثالهایی دیگر هستند:
(car (cdr (cdr ’(see the quote)))) ==>quote
(car (cdr (cdr (cdr ’(see the quote))))) ==>nil
(car (car ’(see the quote))) ==>?در طول ارزیابی مثال اخیر چه اتفاقی خواهد افتاد؟ ارزیابی (car ’(see the quote)) نماد see را بر‌می‌گرداند.سپس این به عنوان آرگومان به فراخوانی بیرونی car پاس می‌شود. چون تابع car یك لیست را به عنوان آرگومان می پذیرد پس مفسر Lisp بلافاصله ارزیابی دیگر را با خطایی مانند این خطا متوقف خواهد كرد: سعی می‌شود Car SEE بدست آید ولی Listp نیست. یك توضیح كوتاه تاریخی: نامهای Car,cdr از روشهای قدیمی هستند زیرا آنها در اولین نگارش Lisp كه بر مبنای مجموعه عملیات كد ماشین كامپیوتر انتخاب و پیاده سازی شده بودند (car از محتوای ثبات آدرس استفاده می‌كند و cdr از محتوای ثبات كاهش استفاده می‌كند). به منظور نوشتن كد Lisp خواناتر، common Lisp یا در تابع first و rest بوجود آمد. ما نامهای قدیمی را استفاده می‌كنیم تا برای خواندن و فهم كد AI Lisp قدیمی قادر باشیم. برای ساخت لیستها، یك تابع ابتدایی Cons مانند Car و cdr وجود دارد كه برای ساخت یك لیست بكار می‌رود. Cons دو عبارت نمادین را می‌پذیرد كه اولی بعنوان یك عنصر جدید در جلوی دومی وارد می‌شود. در مثالهای زیر ملاحظه می‌كنید:
(cons ’a ’(b c)) ==>(a b c)
(cons ’(a d) ’(b c))==>((a d) b c)
(cons (first ’(۱ ۲ ۳)) (rest ’(۱ ۲ ۳))) ==>(۱ ۲ ۳)
در اصل، Cons و لیست خالی با هم برای ساخت لیستهای خیلی پیچیده كافی هستند، برای مثال:
(cons ’a (cons ’b (cons ’c ’( )))) ==>(a b c)
(cons ’a (cons (cons ’b (cons ’c ’( ))) (cons ’d ’( )))) ==>(a (b c) d)
چون این كار كاملاً طاقت‌فرساست،‌ سیستمهای Lispبسیاری با توابع لیست پیش‌ساخته بسیار پیشرفته بوجود می‌آیند. برای مثال، تابع List با تعداد دلخواهی عبارت نمادین یك لیست می‌سازد، و تابع append با الحاق آرگومانهایش كه باید لیست باشند یك لیست جدید می‌سازد. equal تابعی است كه اگر عناصر و ترتیب آنها در دو لیست یكسان باشد t ، در غیر این صورت nil بر میگرداند. مثال:
(list ’a ’b ’c) ==>(a b c)
(list (list ۱) ۲ (list ۱ ۲ ۳)) ==>((۱) ۲ (۱ ۲ ۳))
(append ’(۱) (list ۲)) ==>(۱ ۲)
(append ’(۱ ۲) nil ’(۳ ۴))==>(۱ ۲ ۳ ۴)
(equal ’(a b c) ’(a b c)) ==>t
(equal ’(a b c) ’(a c b)) ==>nil
C) تعریف توابع جدید
برنامه‌نوسی در Lisp با تعریف توابع جدید انجام می‌شود. در اصل این به این معنی است كه: مشخص كردن لیستها در یك روش نحوی معین. مشابه تابع setq كه بوسیله مفسر Lisp در یك روش خاص رفتار می‌كرد. تابع خاص defun است كه برای ایجاد اشیای تابع جدید توسط مفسر Lisp بكار می‌رود. defunیك نماد دال برنام تابع، یك لیست از پارامترها(ممكن است خالی باشد) برای تابع جدید و تعداد دلخواهی از عبارات نمادینی كه بدنه تابع جدیدرا تعریف می‌كند را به عنوان آرگومانهایش می‌پذیرد. این تعویض از یك تابع ساده به نام my-sum است كه دو آرگومان می‌پذیرد و با استفاده از تابع پیش‌ساخته آنها را جمع می‌كند.
(defun my-sum (x y)
(+ x y))
این عبارت به همان روشی كه بعنوان یك تابع فراخوانی می‌شود در سیستم Lisp وارد می‌شود. ارزیابی یك تعریف تابع نام تابع را بعنوان مقدار برمی‌گرداند، اما یك شئ تابع را بعنوان اثر جانبی ایجاد خواهد كرد و وقتی Lisp شروع به اجرا می‌‌كند آن را به مجموعه تعاریف توابع شناخته شده توسط سیستم Lisp اضافه می‌كند (حداقل مجموعه توابع پیش‌ساخته)
توضیح اینكه در این مثال بدنه شامل تنها یك عبارت نمادین است. هر چند بدنه می‌تواند شامل ترتیب دلخواهی از عبارات نمادین باشد مقدار آخرین عبارت نمادین از بدنه مقدار تابع را تعیین می‌كند. به این معنی است كه در واقع همه عناصر بدنه بی تاثیر هستند مگر اینكه اثرات جانبی تصمیم‌گیری تولید كنند.
لسیت پارامتر تابع جدیدmy-sum به ما می‌گوید وقتی فراخوانی می‌شود درست دو عبارت نمادین را بعنوان آرگومان می‌پذیرد. بنابراین اگر شما(my-sum ۳ ۵) را در سیستمLisp وارد كنید مفسرLisp قادر خواهد بود كه تعریف برای نام تابع مشخص شده بیابد و سپس آرگومانهای داده شده را از چپ به راست پردازش كند وقتی این كار انجام شد آن مقدار هر آرگومان را مطابق پارامتر مشخص شده در لیست پارامتر تعریف تابع وصل خواهد كرد(تخصیص خواهد داد) در مثال ما بدین معنی است كه مقدار آرگومان اول كه۳ است(۳ همان عدد۳ است كه خودش را ارزیابی كرده است) به پارامترx متصل می‌كند. سپس مقدار آرگومان دوم كه ۵ است به پارامترy متصل می‌شود. چون مقدار یك آرگومان به یك پارامتر متصل می‌شود، این روش فراخوانی با مقدار نامیده شده است. بعد از مقدار‌یابی برای همه پارامترها مفسرLisp قادر به ارزیابی بدنه تابع خواهد بود. مثال بدین معنی است كه ( ۳ ۵ +) فراخوانی خواهد شد. نتیجه فراخوانی۸ است كه بعنوان نتیجه فراخوانی(my-sum ۳ ۵) برگردانده می‌شود. بعد از تكمیل فرا‌خوانی تابع اتصالات موقت پارامترهایx وy حذف می‌شوند. هنگامی كه یك تعریف تابع جدید در سیستمLisp وارد می‌شودمی‌تواند به عنوان جزئی از تعریف تابع جدید به همان روش كه بعنوان تابع پیش ساخته استفاده شده است بكار برده شود بطوریكه در مثال زیر نشان داده شده است.
(defun double-sum (x y)
(+ (my-sum x y) (my-sum x y)))
كه با دوبار فراخوانیmy-sum جمع آرگومانهایش را دو برابر خواهد كرد این مثال دیگری از یك تعریف تابع است نشان دادن استفاده از عبارات نمادین چند‌گانه در بدنه تابع است.
(defun hello-world () (print ”Hello World!”) ’done)
این تعریف تابع پارامتری ندارد زیرا لیست پارامتر آن خالی است بنابراین وقتی(hello-world) فراخوانی می‌شود مفسرLisp بلافاصله (print ”Hello World!”) را ارزیابی و رشته
”Hello World!”را روی نمایشگر شما بعنوان یك اثر جانبی چاپ می‌كند سپس نماد’done را ارزیابی خواهد كرد وdone را به عنوان نتیجه فراخوانی تابع برمی‌گرداند.
D) تعریف ساختارهای كنترلی
هر چنداكنون تعریف توابع جدید با تعریف توابع پیش ساخته و توابعی كه كاربر تعریف می‌كند ممكن است برنامه‌نویسی درLisp بسیار خسته كننده خواهد شداگر كنترل جریان اطلاعات بوسیله شاخه‌های شرطی ممكن نبود شاید بارها تكرار می‌شد تا اینكه یك روند توقف اجرا شود گزینشLisp بر مبنای ارزیابی توابع است توابع كنترل تستهایی روی عبارات نمادین واقعی انجام می‌دهد و ارزیابی عبارات نمادین متناوب را بسته به نتایج انتخاب می‌كنند تابع اساسی برای تعیین اثباتهای شرطی درcond،Lisp است.cond تعداد دلخواهی آرگومان رامی‌پذیرد هر آرگومان یك بخش ممكن را بیان می‌كنند و بعنوان یك لیست نمایش داده شده كه عنصر اول یك تست و بقیه عناصر اعمال (عبارات نمادین) هستند كه اگر تست انجام شود ارزیابی می‌شوند مقدار آخرین عمل به عنوان مقدار پیشنهادی برگردانده می‌شود همه آرگومانهای ممكنcond (یعنی بخشها) تا زمانی كه بخش اول بطور مثبت تست شوداز چپ به راست ارزیابی می‌شوند درآن حالت مقدار آن بخش مقدار كل تابعcond است. در واقع این مفهوم بسیار پیچیده تر از آن است اجازه دهید تابعverbalize-prop زیركه یك مقدار احتمال را بیان می‌كند. به عنوان یك عدد حقیقی فرض می‌كنیم.
(defun verbalize–prop (prob-value)
(cond ((> prob–value ۰.۷۵) ’very-probable)
((> prob–value ۰.۵) ’probable)
((> prob–value ۰.۲۵) ’improbable)
(T ’very-improbable)))
وقتی(verbalize-prop ۰.۳۳) فراخوانی می‌شود مقدار واقعی آرگومانها به پارامترprop-value متصل می‌شود.سپسcond با آن اتصالات ارزیابی می‌شود very-probable)’((>prop-value)است.> یك گزاره پیش ساخته است كه تست می‌كند كه آیا آرگومان اول از دومی بزرگتر است،چونpropvalue،۰.۳۳ است. بهnil ارزیابی می‌شود كه به معنی انجام نشدن تست است. بنابراین ارزیابی این بخش پیشنهادی بلافاصله پایان می‌یابد. و سپس پیشنهاد ((> prob–value ۰.۵) ’probable)ارزیابی می‌شود كه تابع تست باز هم nilبرمی‌گرداندبنابراین ارزیابی هم پایان می‌یابد. سپس ((prop-value ۰.۲۵) ’improbable) ارزیابی می‌شود حال با بكار بردن تابع تستT برگردانده می‌شود كه به معنی انجام تست است.آنگاه همه اعمال این بخش كه بطور مثبت تست شده است.
ارزیابی ومقدار آخرین عمل به عنوان مقدارcond برگردانده می‌شود در مثال ما تنها عملimprobable’ تعیین می‌شود كه مقدارimprobable (غیرمحتمل) را برمی‌گرداند از آنجایی كه این مقدارcond را تعیین می‌كند و عبارت cond تنها عبارت بدنه تابعverbalize-prop است. نتیجه فراخوانی improbable ,((verbalize-prop ۰.۳۳) است. توضیح اینكهاگرما (verbalize- prop ۰.۱)را وارد كنیم مقدارvery- improbable را بر‌می‌گرداند زیرا تست هر سه با شكست مواجه شده و باید بخش (T ’very-improbable)ارزیابی شوددر این حالت نمادT به عنوان تستی كه همیشهT بر‌می‌گرداند استفاده شده است بنابراین مقدار این پیشنهاد very- improbable است.
E) تعریف توابع بازگشتی
دومین روش اصلی برای تعریف كنترل جریان درLisp تعاریف توابع بازگشتی هستند. تابعی كه از تعریفش بعنوان جزئی از تعریفش استفاده می‌كند باز‌گشتی نام دارد. بنابراین، یك تعریف بازگشتی، تا جایی كه امكان دارد مسئله‌ای را به قسمتهای كوچكتر تقسیم می‌كند سپس این قسمتهای كوچكتر را با استفاده از توابع مشهور و جمع پاسخهای یكسان حل كرده و حل برنامه را كامل می‌كند. بازگشت یك روش طبیعی برای كنترل ساختارهای داده‌ای است كه اندازه معینی ندارد. مانند لیستها، درختها و گرافها. بنابراین برای مسئله‌هایی كه در یك فاصله از حالات دنبال حل كاندید می‌گردند مناسب است.
Lisp اولین زبان برنامه‌نویسی كاربردی بود كه با روش معین تعریف تعاریف بازگشتی را پشتیبانی كرده است. ما از دو مثال كوچك برای نشان دادن بازگشت درLisp استفاده خواهیم كرد. اولین مثال برای تعیین طول یك لیست طویل دلخواه استفاده می‌شود. طول یك لیست برابر تعداد عناصر آن است. تابع بازگشتی آن به صورت زیر است.
(defun length (list)
(cond ((null list) ۰)
(T (+ ۱ (length (cdr list))))))
وقتی یك تعریف بازگشتی تعریف می‌شود. ما باید حالتهای اساسی راشناسایی كنیم یعنی آن قسمتهایی كه نمی‌توانند بیشتر تجزیه شوند. مسئله اندازه وابسته به لیست است. كوچكترین مسئله اندازه در لیست، لیست خالی است. بنابراین اولین چیزی كه ما باید مشخص كنیم تستی برای شناسایی لیست خالی است و تعیین اینكه طول لیست خالی باید چقدر باشد تابع پیش‌ساخته null تست می‌كند كه آیا این لیست خالی است در این صورت t برمی‌گرداند. از آنجایی كه لیست خالی بدون عنصر است تعریف می‌كنیم كه طول لیست خالی صفر باشد كار دیگری كه باید انجام شود تجزیه مسئله اندازه به قسمتهای كوچكتر است كه همان مسئله می‌تواند برای فسمتهای كوچكتر استفاده شود.
تجزیه لیست می‌تواند با استفاده از توابع cdr,car انجام شود. به این معنی كه ما باید تعیین كنیم تا وقتی كه لیست خالی پیدا شود عنصر اول و بقیه عناصر لیست چه كار بكنند. از آنجایی كه ما ازقبل لیست خالی را بعنوان حالت اساسی شناسایی كردیم، می‌توانیم فرض كنیم تجزیه برروی لیستی شامل حداقل یك عنصر انجام خواهد شد. بنابراین هر بار كه قادر خواهیم بود تا با بكار بردن cdr بقیه عناصر لیست را بدست آوریم، ما یك عنصر اضافی پیدا كردیم كه باید برای افزایش تعداد عناصر لیست قبلا شناسایی شده بوسیله یك استفاده می‌شود. استفاده از این تعریف تابع(length ’( )) بلافاصله صفر بر‌خواهد گرداند و اگر (length ’(a b c)) را فراخوانی كنیم، نتیجه ۳ خواهد بود زیرا برای اینكه لیست خالی شود باید سه فراخوانی بازگشتی member انجام دهیم بعنوان مثال دوم، تعریف بازگشتی را در نظر می‌گیریم كه تست می‌كند كه آیا عنصر داده شده در لیست داده شده قرار دارد اگر عنصر براستی در لیست پیدا شود زیر لیستی كه با عنصر پیدا شده شروع می‌شود را برمی‌گرداند اگر عنصر پیدا نشوددnil برگردانده می‌شود مثال فراخوانی‌ها هستند.
(member ’b ’(a f b d e b c)) ==> (b d e b c)
(member ’k ’(a f b d e b c)) ==> nilمشابه تعریف بازگشتی ما لیست خالی را به عنوان حالت اساسی استفاده می‌كنیم برایmember لیست خالی به این معنی است كه عنصر مورد سوال در لیست پیدا نشود. بنابراین ما باید یك لیست را تا زمانی كه عنصر مورد سوال پیدا می‌شود یا لیست خالی است تجزیه می‌كنیم تجزیه با استفاده ازcar وcdr انجام می‌شود.car برای استخراج عنصر اول لیست به كار می‌رود كه می‌تواند برای كنترل اینكه با عنصر مورد سوال برابر است استفاده شود در این حالت می‌توانیم پردازشهای اضافی را مستقیماً متوقف كنیم اگر برابر نبود آنگاه باید تابعmember را برای بقیه عناصر تا خالی شدن لیست بكار ببریم بنابراین می‌تواند به صورت زیر تعریف شود.
(defun member (elem list)
(cond ((null list) nil)
((equal elem (car list)) list)
(T (member elem (cdr list)))))
F) توابع مرتبه بالا
درLisp توابع می‌توانند بعنوان آرگومان استفاده شود تابعی كه بتواند توابع را بعنوان آرگومانهایش بپذیرد تابع مرتبه بالا نامیده می‌شود. مشكلات فراوانی وجود دارند كه یكی پیمایش یك لیست(یا یك درخت یا یك گراف) است كه باید برای هر لیست عنصر تابع معینی استفاده شود. برای مثالfilter تابعی است كه تستی برای عناصر لیست به‌كار می‌برد و آنهایی كه شكست می‌خورند را حذف می‌كند. نگاشتها توابعی هستند كه همان تابع را روی هر عنصر لیست به كار می‌برند تا لیستی از نتایج را برگردانند. تعاربف توابع مرتبه بالا می‌تواند برای تعریف توابع عمومی پیمایش لیست استفاده شود كه آنها از توابع خاصی كه برای پردازش عناصر لیست بكار می‌روند خلاصه می‌شوند (چكیده می‌شوند). به منظور پشتیبانی تعاریف مرتبه بالا یك تابع خاص است كه یك تابع و دنباله‌ای از آرگومانها را به عنوان آرگومان می‌پذیرد و آن تابع را در آرگومانهای آنها به كار می‌برد. بعنوان مثال با استفاده ازfuncall، تابع عمومیfilter را تعریف خواهیم كرد كه می‌تواند به این صورت فراخوانی شود:
(filter ’(۱ ۳ -۹ -۵ ۶ -۳) #’plusp) ==>(۱ ۳ ۶)
plusp یك تابع پیش ساخته است كه كنترل می‌كند آیا یك عدد داده شده مثبت است یا نه؟ اگر باشد آن عدد را بر‌می‌گرداند در غیر این صورتnil بر‌می‌گرداند نماد خاص# بكار می‌رود تا به مفسرLisp بگوید كه مقدار آرگومان یك شی تابعی است . تعریف به صورت زیر است:
(defun filter (list test)
(cond ((null list) list)
((funcall test (car list))
(cons (car list) (filter (cdr list) test)))
(T (filter (cdr list) test))))
اگر لیست خالی باشد آنگاه بسادگی برمی‌گردد در غیر این صورت تابع تست روی عنصر اول لیست بكار می‌رود. اگر تابع تست موفق شودcons بكار می‌رود تا لیست حاصل را با استفاده از این عنصر و همه عناصری كه در طول فراخوانی بازگشتیfilter ازcdr و تابع تست استفاده می‌كنند بسازد. اگر تابع تست برای عنصر اول با شكست مواجه شود این عنصر بسادگی با بكاربردنfilter بصورت بازگشتی روی عناصر باقیمانده پرش می‌كند. یعنی این عنصر نمی‌تواند جزئی از لیست حاصل باشد تابع می‌تواند برای بسیاری از توابع مختلف تست استفاده شود مانند:
(filter ’(۱ ۳ A B ۶ C ۴) #’numberp) ==> (۱ ۳ ۶ ۴)
(filter ’(۱ ۲ ۳ ۴ ۵ ۶) #’even) ==> (۲ ۴ ۶)
به عنوان مثال دیگری از تعریفfilter تابع مرتبه بالا، مامی‌خواهیم یك تابع نگاشت ساده تعریف كنیم كه یك تابع روی همه عناصر یك لیست بكاررفته، لیستی از همه مقادیر بر‌می‌گرداند. اگر تابع my-map را فراخوانی كنیم آنگاه تعریفی شبیه این داریم:
(defun my-map (fn list)
(cond ((null list) list)
(T (cons (funcall fn (car list)) (my-map fn (cdr list))))))
اگر یك تابع Double وجود داشته یاشد كه تنها عدد را دو برابر كند آنگاه یك فراخوانی ممكن my-map به این صورت می‌تواند باشد:
(my-map #’double ’(۱ ۲ ۳ ۴))==> (۲ ۴ ۶ ۸)
بارها شده كه یك تابع باید یكبار استفاده می‌شد. بنابراین اگر ما بتوانیم مستقیما تعریفی از یك تابع بعنوان آرگومان از تابع نگاشت فراهم كنیم كاملا مناسب خواهد بود برای اینكار تعریف عبارت lambda را پشتیبانی می‌كند. ما قبلا به طور غیر رسمی نماد‌سازی عبارات را در بخش II بعنوان تعریف توابع بی نام یا مستعار معرفی كردیم. در Lisp عبارات lambda با استفاده از نوع خاصی از lambda تعریف می‌شوند نوع عمومی عبارت lambda به این صورت است:
(lambda ( parameter . . . ) body . . . )
یك عبارت lambda امكان می‌دهد تا ما تعریف تابع را از نام تابع تشخیص دهیم عبارات lambda می‌توانند به جای نام تابع در تابع funcall استفاده شوند مانند عبارت كه تابع double ما می‌تواند باشد:
(lambda (x) (+ x x))
برای مثال: فراخوانی تابع my-map بالا می‌تواند با استفاده از عبارت lambda مجدداً به صورت زیر بیان شود:
(my-map #’(lambda (x) (+ x x)) ’(۱ ۲ ۳ ۴) ==> (۲ ۴ ۶ ۸)
یك عبارت lambda یك شئ تابعی بر می‌گرداند كه به نام تابع متصل نیست در تعریف
my-map ، پارامتر fn را بعنوان متغیر نام تابع استفاده می‌كنیم. وقتی شكل lambda محاسبه شد مفسر Lisp شئ تابعی را به متغیر نام تابع متصل خواهد كرد. به این طریق یك پارامتر تابع بصورت یك نام تابع پویا استفاده می‌شود. نماد # صروری است تا به Lisp بگوید كه نه تنها یك شئ تابعی را وصل كند بلكه باید اتصالات محلی و سراسری مقادیر وابسته به شئ تابعی را نیز نگه دارد. این تنها با استفاده از عملگر quote امكان‌پذیر نخواهد بود (متأسفانه به دلیل محدودیت جا جزئیات بیشتری داده نمی‌شود).
G) سایر زبانهای برنامه‌نویسی تابعی غیر از Lisp
ما Lisp را به عنوان نماینده اصلی زبان برنامه‌نویسی تابعی معرفی كردیم (مخصوصاً نسخه پر استفاده Common Lisp )، زیرا هنوز هم زبان برنامه‌نویسی پر استفاده‌ای برای تعدادی از مسئله‌های هوش مصنوعی مانند فهم زبان طبیعی، استخراج اطلاعات، یادگیری ماشین،‌ برنامه‌ریزی AI یا برنامه‌نویسی ژنتیك است. دركنار Lispتعدادی از زبانهای برنامه‌نویسی تابعی دیگر توسعه یافتند. ما بطور خلاصه دو عضو مشهور را ذكر می‌كنیم، ML و Haskell.
ML برگرفته از Meta-Language است یك زبان برنامه‌نویسی تابعی با دامنه ایستاست. تفاوت اصلی‌اش با Lisp درsyntax (نحو) است (كه بیشتر شبیه پاسكال است)، و یك نوع سیستم چند ریختی محض است (یعنی بكاربردن انواع قوی و نوع استنتاجی بوسیله متغیرهایی كه نیاز به اعلان ندارند). نوع هر متغیر اعلان شده و عبارت می‌تواند در زمان كامپایل تعیین شود. MLتعریف انواع داده خلاصه را پشتیبانی می‌كند، به صورتی كه در مثال زیر شرح داده شده است:
datatype tree = L of int
| int * tree * tree;
خوانده می‌شود’’ هر درخت دو دویی دارای یك برگ شامل یك عدد صحیح و یا یك گره
شامل یك عدد صحیح و دو درخت است( زیر درختها)‘‘ در مثال بعدی، مثالی از تعریف یك تابع بازگشتی كه روی یك ساختار درخت بكار می‌رود نشان داده شده است:
fun depth(L ) = ۱
| depth(N(i,l,r)) =
۱ + max(depth l, depth r);
تابع depth نگاشتی از درختها به اعداد است. عمق هر برگ ۱ است و عمق هر درخت دیگر ۱ بعلاوه بیشترین عمق زیر درختهای چپ و راست آن است.
Haskell شبیه ML است: Syntax مشابهی بكار می‌برد، دامنه‌اش هم ایستاست و از همان روش استنتاج استفاده می‌كند. با ML در این تفاوت دارد كه یك زبان كاملاً تابعی است. به این معنی است كه به اثرات جانبی اجازه نداده و شامل هیچ نوع ویژگی دستوری نیست، در اصل متغیر و جملات انتسابی ندارد. بعلاوه از یك تكنیك ارزیابی كند استفاده می‌‌كند، كه زیر عبارت را ارزیابی نمی‌كند تا موقع نیاز مقدارش معلوم باشد. لیستها رایجترین ساختار داده در Haskell هستند. برای مثال [۱,۲,۳] لیستی از سه عدد صحیح ۳,۲,۱ است لیست [۱,۲,۳] در Haskell در واقع خلاصه‌نویسی شده لیست ۱:(۲:(۳:[ ] )) است، كه[ ] لیست خالی است و: عملگری میانوندی است كه آرگومان اولش را جلوی آرگومان دومش اضافه می‌كند( یك لیست). بعنوان مثالی از یك تابع كاربر تعریفی كه روی لیستها عمل می‌كند، مسئله شمارش تعداد عناصر در یك لیست با تعریف تابع length ملاحظه می‌شود.
length :: [a] -> Integer
length [ ] = ۰
length (x:xs) = ۱ + length xs
خوانده می‌شود’’طول لیست خالی ۰ است، و طول لیستی كه عنصر اولش x است و بقیه xs است،۱ بعلاوه طول xs است‘‘. در Haskell تابع invocation احضار با تطبیق الگو راهنمایی می‌كند، برای مثال طرف چپ معادله داری الگوهایی مانند[ ] و x:xs است. در یك كاربرد تابع این الگوها با پارامترهای واقعی تطبیق داده می‌شوند [ ] ) تنها با لیست خالی مطابقت می‌كند، و x :xs با هر لیست با حداقل یك عنصر با موفقیت تطبیق می‌كند، x به عنصر اول و xs به بقیه لیست متصل می‌شوند). اگر تطبیق موفقیت‌آمیز باشد طرف راست معادله ارزیابی و بعنوان نتیجه كاربرد برگردانده می‌شود. اگر با شكست مواجه شود معادله بعدی سعی می‌شود، و اگر همه معادلات با شكست مواجه شوند،‌ حاصل یك خطا می‌شود.
این پایان كوتاه ما از’’سفر در Lisp ‘‘ است. ما تنهای توانستیم جنبه بسیار مهم Lisp را مطرح كنیم. خوانندگان علاقمند به جزئیات خاص بیشتر باید حداقل یكی از كتابهای مذكور در آخر مقاله را كنكاش كنند. بقیه این مقاله معرفی الگوی برنامه‌نویسی دیگری بنام ‌Prolog است كه در برنامه‌نویسی AI بطور گسترده مورد استفاده قرار می‌‌گیرد.
) برنامه‌‌نویسی منطقی در Prolog
در دهه ۱۹۷۰ یك الگوی دیگر برای محاسبات نمادین در برنامه‌نویسی AI از موفقیت در زمینه اثبات قضیه خودكار ارئه شد. حل رویه اثبات بطور قابل توجهی توسط رابینسون
(۱۹۶۵) توسعه یافته كه كه با منطق رسمی نشان داده شده است، در محاسبات گزاره‌ای خاص می‌‌توان بعنوان نمادی برای تعیین الگوریتم‌ها و بنابراین برای انجام محاسبات نمادین استفاده شود. در اوایل (دهه ۱۹۷۰) Prolog ، مخفف(برنامه‌نویسی در منطق) اولین زبان‌‌ برنامه‌‌نویسی بر مبنای منطق پدیدار شد. آن توسط آلن كالمرار، رابرت كووا لسكی و فیلیپ راسل توسعه یافته است. اساس Prolog شامل یك روش برای مشخص كردن گزاره‌های محاسبات گزاره‌ای و تصمیات محدود است. برنامه‌‌نوسی در Prolog شامل مشخصات حقیقی در مورد اشیاء و ارتباط آنها و قوانینی كه ارتباطات را مشخص می‌كند، است.
برنامه‌های Prolog مجموعه‌ای از جملات اعلانی در مورد یك مسئله هستند زیرا آنها نحوه محاسبه نتیجه را مشخص نمی‌‌‌كند.بلكه ساختار منطقی نتیجه را مشخص می‌‌كنند Prolog با برنامه‌نویسی دستوری و حتی برنامه‌‌نویسی تابعی در تعریف نحوه محاسبه نتیجه كاملاً متفاوت است. با استفاده از Prolog برنامه‌نویسی می‌تواند در یك سطح خیلی خلاصه و كاملاً نزدیك به مشخصات رسمی یك مسئله انجام می‌‌گیرد. Prolog هنوز هم مهمترین زبان برنامه‌نوسی منطقی است. تعدادی از سیستمهای برنامه‌نوسی تجاری در بازار موجود است كه شامل ماجولهای مدرن برنامه‌‌‌نویسی هستند، یعنی كامپایلر، Debugger و ابزارهای تجسم. Prolog در تعدادی از زمینه‌های AI مانند سیستم‌های خبره و پردازش زبان طبیعی بطور موفقیت‌آمیزی استفاده شده است. اما در زمینه‌های دیگری مانند سیستم‌ های مدیریت پایگاه داده رابطه‌ای یا در آموزش نیز استفاده می‌شود. یك برنامه Prolog بسیار ساده برنامه‌ای است كه شامل دو حقیقت و یك قاعده است.
scientist(godel).
scientist(einstein).
logician(X) :- scientist(X).
دو جمله اول می‌تواند بصورت ’’Godel is a scientist ‘‘ و ’’Einstein is a scientist ‘‘ تفسیر شود.جمله قانون می‌‌‌گوید: ’’X is a logician if x is a scientist ‘‘. برای تست این برنامه باید عبارات پرس و جو( یا قضایا) را مشخص كنیم كه Prolog سعی می‌كند با استفاده از برنامه مشخص شده به آنها جواب دهد(یا اثبات كند). یك پرس و جوی ممكن این است: ?- scientist(godel).
كه می‌تواند به صورت ’’Is Godel a scientist?‘‘ بیان شود. Prolog با بكار بردن رویه اثبات پیش‌ساخته خودش ’’yes‘‘ جواب خواهد داد، زیرا ممكن است یك حقیقت پیدا شود كه كاملاً مطابق با پرس و جو باشد. دیگر پرس و جوی ممكن بصورت سئوال:
’’who is a scientist?‘‘و در Prolog بصورت زیر بیان می‌شود:
?- scientist(X).Prolog نتیجه خواهد داد’’X = godel , X= Einstein ‘‘. در این حالت Prolog نه‌تنها جواب می‌دهد’’yes ‘‘ بلكه همه متغیرهای متصل به x را كه در طول اثبات موفق پرس و جو پیدا می‌كند را بر می‌گرداند. مثال دیگر، ممكن است ما با پرس و جوی Prolog زیر سئوال كنیم ’’who is a logician ‘‘:
?- logician(X).
اثبات این پرس و جو همان مجموعه‌ای از حقایق را كه قانون مشخص كرده است را نتیجه می‌دهد. سرانجام ممكن است ما پرس و جوی زیر را مشخص كنیم:
?- logician(mickey-mouse).
در این حالت Prolog جواب خواهد داد با ’’No ‘‘. هر چند قانون می‌گوید كسی منطق‌دان است كه دانشمند هم باشد، ‌ولی Prolog حقیقتی نمی‌یابد كه بگویدMickey Mouse دانشمند است. توضیح اینكه Prolog تنها نسبت به برنامه داده شده می‌تواند پاسخ بدهد. در واقع به این معنی است كه ‘‘ No, I couldn’t deduce the fact‘‘. این ویژگی بعنوان فرض جهان بسته یا رد آن بصورت شكست،‌ مشهور است. به این معنی كه Prolog همه اطلاعات لازم برای حل مسئله موجود در پایگاه داده را فرض می‌‌كند.
IVجملات برنامه‌های Prolog شامل مجموعه‌ای از جملات بنام بند هستند كه برای نمایش داده‌ها و برنامه‌ها استفاده می‌شوند. نماد نقطه‌ برای پایان دادن بند بكار می‌رود. یك واژه می‌تواند یك ثابت(نامهای نمادین كه با یك حرف كوچك شروع می‌شوند مانند godel یا eInstein )، یك متغیر(نمادهایی كه با یك حرف بزرگ شروع می‌شوند مانند x یا ‌ Scientist)، یا یك ساختار باشد. ساختارهای گزاره‌های اتمی محاسبات گزار‌ه‌ای را نمایش می‌دهند و شامل عملگر نام و یك لیست پارامتر هستند. هر پارامتر می‌تواند یك واژه باشد به این معنی كه واژه‌ها،‌ اشیاء‌ بازگشتی هستند. Prolog سه نوع بند را تشخیص می‌دهد: حقایق،‌قوانین و پرس و جوها. یك حقیقت با یك ساختار واحد نمایش داده می‌شود كه بعنوان یك گزاره درست ساده تفسیر می‌شود. قبلاً در مثال ساده برنامه بالا دو حقیقت ساده را معرفی كردیم.
اینها چند مثال دیگر هستند:
male(john).
male(bill).
female(mary).
female(sue).
father(john, mary).
father(bill,john).
mother(sue,mary).
توضیح اینكه این حقایق دارای معانی ذاتی نیستند یعنی معنی عملگر نام father تعریف نشده است. برای مثال با بكار بردن حواس معمول ممكن است آن را بصورت’’John is the father of mary‘‘ تفسیر كنیم. هر چند برای Prolog این معنی وجود ندارد و تنها یك نماد است.
قوانین متعلق به نوع دیگری از بندها هستند. یك بند قانون شامل دو قسمت است،‌ سر كه تنها یك واژه است و بدنه كه تنها یك واژه یا یك اتحاد است. یك اتحاد یك مجموعه از واژه‌هاست كه با نماد كاما از هم جدا می‌شوند.
منطقاً یك بند قانون بعنوان یك استدلال تفسیر می‌شود، اگر همه عناصر بدنه درست باشند، آنگاه عنصر سر نیز درست است. بنابراین بدنه بند به صورت قسمت if (اگر) و سر بند بصورت قسمت then (آنگاه) قانون مشخص می‌شوند.
این مثال برای مجموعه‌ای از بندهای قانون است:
parent(X,Y) :- mother(X, Y).
parent(X,Y) :- father(X, Y).
grandparent(X,Z) :- parent(X,Y), parent(Y,Z).
قانون اخیر خوانده می‌شود:
’’X is a grand parent of z if X is a parent of y and y is a parent of z ‘‘
دو قانون اولی می‌گویند:
’’some one is parent if it is the father or mother of some one else‘‘
دلیل رفتار دو قانون اول را هنگام معرفی رویه اثبات Prolog بعنوان فصلی بطور آشكار خواهد آمد. قبل از انجام این كار باید آخرین نوع بند را معرفی كنیم،‌ بند پرس و جو (كه بند هدف نامیده می‌شود). یك پرس و جو برای فعال كردن رویه اثبات Prolog بكار می‌رود.
منطقاً یك پرس و جو مشابه یك قضیه مجهول است. آن شكلی مشابه حقیقت دارد تا به Prolog بگوید كه یك پرس و جو باید اثبات شود، عملگر مخصوص پرس و جو –?است معمولاً در جلوی پرس و جو نوشته می‌شود. در مثالهای ساده برنامه Prolog معرفی شده در بالا، قبلاً توصیفی غیر رسمی از چگونگی استفاده پرس و جو در Prologرا دیدیم.
فرایند استنتاج Prolog شامل دو مؤلفه اساسی است: روش جستجو و یكی كننده. روش جستجو برای جستجو میان حقیقت و قانون پایگاه داده بكار می‌رود در حالی كه یكی‌سازی برای تطبیق الگو و بازگرداندن اتصالاتی كه یك عبارت صحیح می‌سازد بكار می‌رود.
یكی‌ساز روی دو واژه بكار می‌رود و سعی می‌كند با تركیب آن دو یك واژه جدید شكل بدهد. اگر یكی سازی ممكن نباشد آنگاه گفته می‌شود یكی‌سازی شكست خورده است. اگر دو واژه مادی هیچ متغیری نباشند آنگاه یكی‌سازی در واقع از بررسی اینكه آیا واژه‌ها برابرند، خواهد كاست. برای مثال، یكی‌سازی دو واژه father (john,mary) و father(john,mary) موفق می‌شود در حالیكه یكی‌سازی جفت واژه‌های زیر با شكست مواجه خواهند شد.
father(X,mary) و father(john,sue)
sequence(a,b,c) و sequence(a,b)
اگر یك واژه حاوی یك متغیر (یا بیشتر) باشد آنگاه یكی كننده بررسی می‌كند كه آیا متغیر می‌تواند با بعضی از اطلاعات واژه دوم متصل شود، هر چند تنها اگر قسمتهای باقیمانده واژه‌ها یكی شوند. برای مثال، برای دو واژه زیر:
father(X,mary) and father(john,mary)
یكی كننده X را به john متصل خواهد كرد زیرا واژه‌های باقیمانده برابرند. هرچند برای
زوج زیر:
father(X,mary) and father(john,sue)
مفهوم اتصال ساخته نمی‌شود چون mary و sue مطابق نیستند. روش جستجویی كه برای پیمایش فضای جستجو بكار می‌رود بوسیله حقایق و قوانین برنامه Prolog محدود شده است. Prolog یك روش بالا به پائین، روش جستجوی عمقی (dfs) استفاده می‌كند. این به چه معنا است؟ همه مراحل كاملاً شبیه به روش تابع ارزیابی استفاده شده در Lisp است اگر یك پرس و جوی Q مشخص شده باشد آنگاه ممكن است آن مطابق یك حقیقت یا یك قاعده باشد. در حالتی از قاعده Prolog ,R ابتدا سعی می‌كند سر R را تطبیق دهد و اگر موفق شود آنگاه سعی می‌كندهمه عناصر بدنه R كه زیر پرس و جو نامیده می‌شوند را تطبیق دهد اگر سر R حاوی متغیرها باشد آنگاه اتصالات در طول اثبات از زیر پرس و جوها استفاده خواهند كرد.
از آنجایی كه اتصالات تنها برای زیر پرس و جوها معتبر هستند، گفته می‌شود كه برای یك قاعده محلی هستند. یك زیر پرس و جو هم می‌تواند یك قاعده باشد و هم یك حقیقت. اگر یك قاعده باشد آنگاه فرایند استنتاج Prolog بطور بازگشتی برای بدنه این پرس و جو بكار می‌رود. این، قسمت بالا به پائین روش جستجو را می‌سازد. عناصر بدنه یك قاعده از چپ به راست بكار می‌روند و تنها اگر عنصر جاری بتواند با موفقیت اثبات شود عنصر بعدی سعی می‌شود. این روش جستجوی عمقی را می‌سازد. ممكن است برای اثبات یك زیر پرس و جو دو یا چند حقیقت یا قاعده دیگر تعریف شوند.
در آن صورت A, Prolog را انتخاب می‌كند و سعی می‌كند آن را اثبات كند، اگر لازم باشد زیر پرس و جوهای A را نیز پردازش می‌كند. اگر A با شكست مواجه شود Prolog به نقطه‌ای كه اثبات A شروع شده بر می‌گردد(با حذف همه اتصالهایی كه در طول اثبات A انتساب داده شده است) و سعی می‌كند دیگری را اثبات كند. این فرایند عقب‌گرد نام دارد . به منظور شرح همه روشها پرس و جوهای نمونه زیر را می‌توانیم ملاحظه كنید (مثال معرفی شده در بندهای پاراگراف قبلی را بعنوان پایگاه داده Prolog استفاده می‌كنیم):
?- grandparent(bill,mary).
تنها بندی كه با این پرس و جو تطبیق می‌كند قاعده زیر است.
grandparent(X,Z) :- parent(X,Y), parent(Y,Z).
و یكی‌سازی پرس و جو با سر قاعده اتصالهای زیر را بر می‌گرداند: Z=mary,X=bill برای اثبات قاعده، باید دو عنصر بدنه قاعده از چپ به راست اثبات شوند. توضیح اینكه متغیرهای مشترك قواعد با سر قاعده و بنابراین اتصالهای محاسبه شده در طول تطبیق سر با پرس و جو برای پاسخ به زیر پرس و جوها موجودند. بنابراین زیر پرس و جوی اول در واقع بصورت parent(bill,y) و زیر پرس و جوی دوم بصورت parent (y,mary) معرفی شود. حال برای اثبات بند اول prolog دو قاعده parent دیگر می‌یابد. اجازه دهید فرض كنیم prolog اولی را انتخاب می‌كند.( برای یاد‌آوری بیش از یك انتخاب، prolog یك نقطه انتخاب مشخص می‌كند)
parent(X,Y) :- mother(X, Y).
یكی‌سازی زیر پرس و جوها با سه قاعده به راحتی ممكن است و متغیرx به واژه bill متصل خواهد شد . این عنصر تك بدنه‌ای بصورت (bill,y) mother معرفی می‌شود. متاسفانه هیچ حقیقتی كه این زیر پرس و جو را معتبر كند در پایگاه داده وجود ندارد. چون یكی‌سازی (bill,y) mother با شكست مواجه می‌شود. پس همه قاعده انجام می‌شود. سپس prolog به نقطه انتخابی كه اولین قاعده parent ممكن را انتخاب كرده بود، برگشته و دومی را انتخاب می‌كند.
parent(X,Y) :- father(X, Y)
یكی‌سازی زیر پرس و جوی (هنوز فعال) parent(bill,y) ، father(bill,y) معرفی خواهد شد. اینبار یكی‌سازی ممكن است،‌اتصال y=john برگردانده می‌شود. حال اولین زیر پرس و جوی parent از قاعده grand parent اثبات شده متغیرهای واقعی X=bill Z=mary,Y=john, هستند. عنصر دوم از بدنه قاعده grandparent،
parent (john, mary) معرفی می‌شود (توضیح اینكه مقدار z بعد از انتخاب قاعده grand parent فوراً متصل شده است).
همان روش برای این زیر پرس و جو بكار رفته و prolog حقایق كافی برای اثبات موفقیت‌آمیز آن خواهد یافت. وقتی كه دو عنصر بدنه قاعده grand parent به طور معتبر اثبات شد، prolog به پایان می‌رسد كه اولین پرس و جو true می‌شود. توسعه prolog ، به منظور استفاده از prolog برای برنامه‌نویسی كاربردی است. كه با توسعه‌هایی مانند لیست ساختارهای داده، عملكردهایی برای كنترل واضح پیمایش از فاصله جستجو با یك برنامه prolog(بنام عملگر برش) و روالهایی برای رابطهای ورودی /خروجی، تست درستی (ردیابی) و اشكالزدایی می‌آید. ما نمی‌توانیم همه این توسعه‌ها را در متن این مرور كوتاه شرح دهیم. ما تنها بطور خلاصه نشان می‌دهیم كه لیستها در prolog چگونه می‌توانند استفاده شوند. Prolog لیستها را بعنوان یك ساختار داده‌ای پایه‌ای با استفاده از syntax متداول پشتیبانی می‌كند. عناصر لیست با كاما جدا می‌شوند. كل لیست با براكت تعیین می‌شود. یك عنصر لیست می‌تواند یك واژه دلخواه یا یك لیست باشد، بنابراین كاملاً شبیه ساختارهای لیست در Lisp است. این مثالی از یك لیست prolog است:
[john, mary, bill]
لیست خالی بصورت [ ] نمایش داده می‌شود. برای ایجاد و پیمایش لیستها، prolog یك تركیب خاص مبنی بر سر و دنبال یك لیست فراهم می‌كند. [X | Y]یك لیست است شامل یك سرلیست x و یك دنباله y است. برای مثال لیست بالا می‌تواند بصورت زیر مشخص شود.
[john | mary, bill]
ما گزارهmember را بصورت مثالی برای نحوه رفتار لیستها در prolog استفاده خواهیم كرد. این گزاره تعیین خواهد كرد كه آیا یك عنصر داده شده در یك لیست داده شده واقع می‌شود؟ با توجه به توضیحات بالا یك عنصر در یك لیست است اگر سر لیست آن لیست باشد یا اگر در جایی از دنباله لیست واقع شود، با استفاده از تعریف غیررسمی گزاره member ما می‌توانیم برنامه prolog زیر را طرح كنیم. (نمادی كه یك متغیر بی‌نام را مشخص می‌كند،‌استفاده می‌شود تا به prolog بگوید مهم نیست مقدار یكی كننده به آن متصل شود)
member(Element,[Element | ]).
member(Element,[ | List]) :- member(Element,List).
با فرض پر س و جوی زیر
?- member(a, [b,c,a,d]).
Prolog ابتدا كنترل می‌كند كه آیا سر لیست [b | c,a,d]برابر a است.
به این علت بند اول با شكست مواجه می‌شود، پس دومی سعی می‌شود. این زیر پرس و جوی member (a,[c,a,d]) معرفی خواهد شد كه معنی‌اش این است كه از روی عنصر اول بسادگی می‌پرد با بكار بردن بازگشی member،prolog سعی می‌كند تا اثبات كند كه آیا سر لیست [c | a,d]با a برابر است، كه با شكست مواجه می‌شود.، زیر پر س و جوی جدید member (a,[a,d]) را با معرفی بند دوم بدست می‌آوریم. گام بازگشتی بعدی لیست [a | d]را كنترل خواهد كرد. اینبار a براستی با عنصر سر لیست این لیست برابر می‌شود، بنابراین prolog با "yes" پایان خواهد یافت.برنامه‌نویسی منطقی محدودیت (clp)تصمیمی از سبك برنامه‌نویسی (ساده)‌prologاست. در clp واژه یكی‌سازی به حل محدودیت تعمیم یافته است. در برنامه‌نویسی منطقی محدودیت مولفه‌های اصلی یك مسئله بصورت محدودیت‌ها حالت یافته‌اند (یعنی ساختار اشیاء در سؤال) و مسئله بصورت یك كل كه با گذاشتن محدودیتهای مختلف بوسیله قواعد ارائه شده است. (اساساً بوسیله تعریف بندها) برای مثال بند معین زیر نمونه یك تجزیه ریز از گرامر یك زبان طبیعی مانند انگلیسی است.
sign(X۰) ←
sign(X۱),
sign(X۲),
X۰ syn cat = s,
X۱ syn cat = np,
X۲ syn cat = vp,
X۱ syn agr = X۲ syn ag
بیان می‌شود یك شی زبانی بصورت یك عبارت S طبقه‌بندی می‌شود كه باید مركب از یك شیء طبقه‌بندی شد كه بصورت یك NP (عبارت اسمی) و یك شئ طبقه‌بندی شده بصورت یك VP(عبارت لفظی) باشد و قرارداد اطلاعات (مانند شخص، حالت) باید بین NP و VP یكسان باشد.
همه اشیایی كه حداقل این محدودیتها را انجام می‌دهند جزء‌اشیای S هستند. توضیح اینكه هیچ ترتیب پیش فرضی برای VP,NPبعنوان حالتی برای گرامر زبان طبیعی مبنی بر ظواهر وجود ندارد كه متن بدون استحكام به آن تكیه كند. اگر یك محدودیت نیاز به محدودیتهای اضافی داشته باشد. باید به قاعده اضافه شود، برای نمونه زیر ریشه‌ها باید با الحاق تركیب شوند از نجاطیآنآن آنجایی كه محدودیتهای مثال بالا تنها شرایط لازم برای شئ از كلاس S را مشخص می‌كند آنها اطلاعات مختصری بیان می‌كنند.
این برای دانش مبنی بر استدلال خیلی مهم است زیرا در كل ما تنها اطلاعات مختصری درباره جهان (محیط)‌داریم، ‌ما برای پردازش چنین خصوصیاتی دلیل مبنی بر حل محدودیت و الگوی برنامه‌نویسی منطقی می‌خواهیم. چون یكی‌سازی، فقط حالت خاصی از حل محدودیت است، برنامه‌های منطقی محدودیت توان بیان بالایی دارند.
تعدادی از زبانهای برنامه‌نویسی منطقی محدودیت (همراه با رابط كاربر سطح بالا و ابزارهای توسعه) تحقق یافته‌اند. مانند CHIP یا زبان OZ كه برنامه‌نویسی اعلانی، برنامه‌نویسی شئ گرا، برنامه‌نویسی محدودیت و همزمانی را بعنوان جزئی از كل منسجم پشتیبانی می‌كند. OZ زبانی محدودیت قدرتمندی با متغیرهای منطقی،‌دامنه‌متناهی، مجموعه‌های متناهی، درختهای عقلانی و ركورد محدودیت‌هاست. آن در صدد است تا یك روش یكتا و انعطاف‌پذیر بدون شاخ و بندها برای برنامه‌نویسی منطقی فراهم كند. OZ بین روشهای مستقیم و غیر مستقیم برنامه‌نویسی منطقی اعلانی تفاوت قایل می‌شود.
V) سایر روشهای برنامه‌نویسی
‌در این مقاله قبلاً زبانهای AI را با روشهای برنامه‌نویسی دستوری مقایسه كردیم. زبانهای شیء گرا به الگوی برنامه‌نویسی مشهور دیگری تعلق دارند. در این جور زبانها اولین وسیله برای تعیین مسئله‌ها، تعیین خلاصه ساختارهای داده است كه كلاس‌ها، اشیاء‌نام دارند. یك كلاس شامل یك ساختار داده همراه با عملیات اصلی‌اش كه اغلب اسلوبها (روشها) نام دارند است. یك ویژگی مهم این است كه ممكن است كلاسها در سلسله مراتبی از كلاسها و زیر كلاسها مرتب شوند. یك كلاس می‌تواند صفات سوپر كلاسهایش كه پیمانه‌ای بودن را پشتیبانی می‌كنند را به ارث ببرد.
مشهورترین زبانهای شیءگرا C++,Eiffel و Java (جاوا) هستند. سیستم Common Lisp شیءگرا یك توسعه از common Lisp است. آن یكپارچه‌سازی كامل برنامه‌نویسی تابعی و شیءگرا را پشتیبانی می‌كند. اخیراً جاوا در بعضی از زمینه‌ها AI، خصوصاً در فن‌آوری عامل هوشمند، موتورهای جستجوی اینترنت یا استخراج داده‌ها كاملاً مشهور شده است. جاوا بر مبنای C++ است و زبان اصلی برای برنامه‌نویسی كاربردهای اینترنتی است. مهمترین ویژگیهای زبان كه جاوا را از چشم‌آنداز AI جذاب می‌سازد فضای هرز خودكار پیش‌ساخته آن و مكانیزم چند نخی (چند وظیفه‌ای) آن است.
با افزایش تحقیقات در زمینه وب هوشمند یك الگوی برنامه‌نویسی جدید- برنامه‌نویسی عامل‌‌گرا – پدیدار شد. برنامه‌نویسی عامل‌گرا یك الگوی جدید برنامه‌نویسی است كه یك نمای اجتماعی از محاسبه را به خوبی پشتیبانی می‌كند. در AOP اشیاء بعنوان عاملهایی شناخته می‌شوند كه برای دستیابی به اهداف شخصی عمل می‌كنند. عامل در یك ساختار می‌تواند به پیچیدگی شبكه سراسری اینترنت یا به سادگی یك پیمانه (ماجول) از یك برنامه معمولی باشد. عاملها می‌توانند موجودیتهای مستقل باشند یعنی بدون دخالت كاربر برای گام بعدی‌شان تصمیم بگیرند، یا می‌توانند قابل كنترل باشند، یعنی بعنوان وسیله‌ای بین كاربر و عاملهای دیگر بكار بردند. از آنجایی كه عاملها زنده در نظر گرفته می‌شوند، با رشد موجودیتهای نرم‌افزار، به نظر می‌رسد انتقالی از نقطه‌نظر زبانهای برنامه‌نویسی به طرف نقطه‌نظر سكوی پیشرفت نرم‌افزار پدیدار می‌شود. اینجا تأكید روی طراحی سیستم، سكوی پیشرفت و اتصال است. سئوالات حساس عبارتنداز: چگونه تعدادی از منابع پیشرفته AI كه در زبانها و سكوهای مختلف موجودند می‌توانند با سایر منابع استفاده‌كننده از ابزارهای پیشرفت سیستم جدید مانند CORBA (معماری عادی رابط درخواست شئ) تركیب شوند (یكپارچه شوند)، خلاصه‌سازی عمومی انواع داده و زبانهای تفسیری(یادداشت حاشیه‌ای) مانند XML و زبان استاندارد ارتباطات عامل‌گرا مانند KQML (زبان شناخت پرس و جو و دستكاری).
بنابراین آینده برنامه‌نویسی AI كمتر نگران سئوالاتی مثل: ” مناسب‌ترین الگوی برنامه‌نویسی چیست؟ “ است ولی باید به سئوالاتی مثل: ” چگونه می‌توانم الگوهای مختلف برنامه‌نویسی را زیر یك سایبان یكپارچه كنم؟ “ و ” بهترین زبان ارتباطی برای نرم‌افزارهای مستقل پیمانه‌ای هوشمند چیست؟ “ پاسخ دهیم.
نویسنده: Gunter Neumann
German Research Center for Artificial Intelligence (LT–Lab, DFKI)
ترجمه: احد محمّدی خواجه
E-mail: ae۱۳۵۹m@gmail.com
دانشجوی كارشناسی ناپیوسته كامپیوتر(تراكتورسازی تبریز)
این مقاله ترجمه‌ای است از:
Neumann, Gunter Programming Languages in Artificial Intelligence. In: Bentley & Bidgoli: Encyclopedia of Information Systems,
Academic Press, San Diego, ۲۰۰۲
http://www.dfki.de/~neumann/publications/new-ps/ai-pgr.pdf
VI. منابعی برای مطالعه بیشتر
۱.Charniak, E., Riesbeck, C.K., McDermott, D.V. and Meehan, J.R., ۱۹۸۰, Artificial Intelli- gence Programming, Lawrence Erlbaum Associates, Hillsdale, New Jersey.
۲.Clocksin, W.F. and Mellish, C.S, ۱۹۸۷, Programming in Prolog, Springer, Berlin, Germany.
۳.Keene, S.E., ۱۹۸۸, Object–Oriented Programming in Common Lisp, Addison–Wesley, Read-ing, Massachusetts.
۴.Luger, G.F. and Stubblefield, W.A., ۱۹۹۳, Artificial Int elligence: Structures and Strategies
for Complex Problem Solving, second edition, Benjamin/Cummings, Redwood City, Cali-fornia.
۵.Norvig, P., ۱۹۹۲, Artificial Intellig ence Programming, Morgan Kaufman Publishers, San ateo, California.
۶.Pereira, F.C.N. and Shieber, S.M., ۱۹۸۷, Prolog and Natural Language Analysis, CSLI Lecture Notes, Number ۱۰, Stanford University Press, Stanford, California, ۱۹۸۷.
۷.Sebesta, R.W., ۱۹۹۹, Concepts of Programming Languages, fourth edition, Addison–Wesley, Reading, Massachusetts.
۸.Ullman, J.D., ۱۹۹۷, Elements of ML Programming, second edition, Prentice-Hall.
۹.Watson, M., ۱۹۹۷, Intelligent Java Applications for the Internet and Intranets, Morgan Kaufman Publishers, San Mateo, California.
منبع : نما مجله الکترونیکی پژوهشگاه اطلاعات و مدارک علمی ایران


همچنین مشاهده کنید