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

عناوين متن

زبانهاي برنامه‌نويسي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 است كه در اواخر دهه 1950 توسط جان مك كارتي توسعه يافته است. Lisp برمبناي نظريه توابع رياضي و خلاصه‌سازي Lambda است. تعدادي از كاربردهاي مهم و موثرAI در Lisp نوشته شده است. كه ما بعضي از جزئيات اين زبان برنامه‌نويسي را در اين مقاله شرح خواهيم داد. در اوايل دهه 1970 يك الگوي برنامه‌نويسي جديد بنام برنامه‌نويسي منطقي بر اساس محاسبات گزاره‌اي بوجود آمد. اولين و مهمترين زبان برنامه‌نويسي منطقي Prolog است كه توسط آلن كالمرار، رابرت كوالسكي و فيليپ راسل توسعه يافته است. مسئله‌ها در prolog بصورت حقايق، بديهيات و قوانين منطقي براي استنباط حقايق جديد بيان مي‌شوند. Prolog  با قانون رياضي در محاسبات گزاره‌اي و نتايج نظري بدست آمده در زمينه اثبات قضيه خودكار در اواخر دهه 1960 بنا نهاده شده است.


II- برنامه نويسي تابعي
يك تابع رياضي نگاشتي از يك مجموعه (دامنه) به مجموعه ديگر(برد) است. تعريف يك تابع توصيف اين نگاشت است كه يا بطور صريح بوسيله شمارش و يا بطور ضمني بوسيله يك عبارت است. تعريف يك تابع بوسيله نام تابع كه بدنبال آن ليستي از پارامترها در داخل پرانتز قرار دارند و به دنبال آن نيز عبارت توصيفي نگاشت است مشخص مي شود مانند:
X يك عدد حقيقي است cube(X) ≡ X X X , where X is a real number.
آلونسو چارچ توابع بي نام را با استفاده از نمادLambda معرفي مي كند. يك عبارت Lambda  پارامترها و نگاشت تابع را با استفاده از عملگر X مشخص مي كند, مانند λ (X)X X  X آن خودش تابع است, بنابراين شرح بكار رفته در مثال تابع بي نام با يك آرگومان مشخص است. براي مثال:(λ (X) X  X  X)(4).
برنامه نويسي در يك زبان تابعي شامل ساختمان تعريف توابع و بكاربردن كامپيوتر براي ارزيابي عبارات است. يعني بكاربردن توابع با آرگومانهاي واقعي. كار اصلي برنامه نويسي پس از ساخت يك تابع براي يك مساله خاص تركيب توابع تعريف شده قبلي با توجه به اصول رياضي است. كار اصلي كامپيوتر ارزيابي توابع فراخواني شده و چاپ حاصل مقادير تابع است. در اين روش كامپيوتر مشابه يك كامپيوتر جيبي معمولي بكار مي رود البته بسيار انعطاف پذيرتر و قدرتمندتر. يك خاصيت برنامه نويسي تابعي اين است كه اگر عبارت به خوبي مقداردهي شود آنگاه ترتيب انجام ارزيابي كامپيوتر در نتايج ارزيابي تاثيري ندارد. بنابراين نتيجه ارزيابي يك عبارت تنها مقدار آن است. بدين معني است كه در يك زبان تابعي ناب اثرات جانبي وجود ندارد. اثرات جانبي در مدل موقعيت هاي حافظه به متغيرها متصل شده اند.بنابراين در يك زبان برنامه نويسي ناب در مفهوم زبانهاي دستوري متغير وجود ندارد. روشهاي اصلي كنترل جريان، بازگشت (تكرار) و عبارات شرطي هستند. اين كاملاً با زبانهاي دستوري در مفهوم اساسي كنترل ترتيب و تكرار متفاوت است. برنامه نويسي تابعي نيز خصوصيات توابع مرتبه بالا را پشتيباني مي كند. تابع مرتبه بالا تعريف تابعي است كه اجازه مي دهد آرگومانها يا مقدار بازگشتي تابع, مقدار توابع باشند. همه اين جوانب با هم مخصوصاً آخري از اصلي ترين مزاياي سبك برنامه نويسي تابعي در برابر سبك برنامه نويسي دستوري هستند. خلاصه برنامه نويسي تابعي سطح بالايي از درجه پيمانه اي بودن را فراهم مي كند. وقتي يك مسئله با تقسيم آن به مجموعه اي از زير مسئله ها تعريف مي شود, موضوع اصلي روشهايي است كه مي توان زير مسئله ها را به يكديگر چسباند. بنابراين براي افزايش قابليت پيمانه اي بودن يك مسئله مفهومي, ابتدا بايد نوع جديدي از چسب در زبان برنامه نويسي فراهم شود- قدرت اصلي برنامه نويسي تابعي .


III- برنامه نويسي تابعي در Lisp
Lisp اولين زبان برنامه نويسي تابعي است: آن براي پشتيباني محاسبات نمادين با استفاده از ليستهاي پيوندي بعنوان ساختار مركزي داده ها ابداع شده بود ( Lisp يعني پردازشگر ليست). جان مك كارتي دريافت كه روشهاي كنترل جريان توابع رياضي (بازگشت و تكرار) وسيله نظري مناسبي براي انجام محاسبات نمادين هستند. علاوه براين مفاهيم خلاصه سازي تابعي و كاربرد تابعي تعريف شده در محاسبات Lambda , سطح بالايي از خلاصه سازي موردنياز براي مسئله هاي AI مشخص شده را فراهم مي كنند.
Lisp در سال 1958 توسط مك كارتي ابداع شد و اولين نگارش محيط برنامه نويسي Lisp در سال 1960 آماده شد كه شامل يك مفسر, يك كامپايلر و مكانيسم تخصيص و بازپسگيري حافظه پويا بود (بعنوان مجموعه فضاي هرز شناخته شده است). يكسال بعد اولين زبان استاندارد با نام Lisp1.5 معرفي شد. پس از آن تعدادي از نسخه ها و محيط هاي برنامه نويسي Lisp توسعه يافته اند. مانند MacLisp، FranzLisp، InterLisp، CommonLisp، Scheme هر چند آنها در بعضي جزئيات خاص متفاوتند ولي هسته Syntax (نحو) و Semantic (معني) آنها اساساً يكسان است. هسته را در جاي ديگر معرفي خواهيم كرد. پر استفاده ترين نسخه‌هاي
Lisp ، Common Lisp و scheme هستند. در اين مقاله ما Common Lisp را براي نشان دادن جنبه هاي مختلف Lisp با مثالهاي معمولي انتخاب كرده ايم. هرچند مثالها نيز به راحتي مي توانند در نسخه هاي ديگر Lisp سازگار شوند.


 Syntax .A. (نحو) و semantics (معاني) Lisp

 1. عبارات نمادين: عناصر نحوي Lisp عبارات نمادين ناميده مي شوند (كه به صورتS-expressionsشناخته شده‌اند). داده ها و توابع (يعني برنامه هاي Lisp ) بصورت عبارات نمادين نشان داده شده اند كه مي توانند اتم ها يا ليست ها باشند. اتم ها كلمه اي شبيه اشيا‌ هستند. اتم‌ها وابسته به نوع كاراكترهايي كه براي شكل دادن يك اتم مجازند مي توانند به انواع مختلفي تقسيم شوند. انواع اصلي عبارتنداز:

Numbers:1    234-43.14159265358979    -7.5    6.02E+23
 
Symbols:SymbolSym23another-one    t    false    NILBLUE
 
Strings:  ”This is a string””977?”    ”setq””He said: \” I’m here.\” ”

 توضيح اينكه هرچند نماد خاصي مثل BLUE استفاده مي‌شود چون مفهوم مشخص براي برنامه‌نويسي دارد، اما بزودي Lisp تنها ترتيبي از حروف يا تنها يك نماد است. ليستها بندي شبيه اشياء هستند. يك ليست شامل يك پرانتز باز( دنباله‌اي از اعداد دلخواه كه بوسيله فاصله خالي از هم جدا مي‌شوند) و يك پرانتز بسته هستند. هر عنصر ليست مي‌تواند يك اتم يا ليست باشد. اينها مثالهايي از ليستها هستند:   

(This is a list) ((this) ((too))) () (((((((())))))))
 
(a b c d) (john mary tom) (loves john ?X)
 
(* (+ 3 4) 8) (append (a b c) (1 2 3))
 
(defun member (elem list)
 
    (if (eq elem (first list)) T
 
  (member elem (rest list))))

توضيح اينكه در بسياري از مثالها عناصر ليست خود ليستها هستند.چنين ليستهايي، ليستهاي تو در تو ناميده مي‌شوند. در مورد تو در تويي محدوديتي وجود ندارد. براي مثال يكي از قويترين Lisp ها را شرح مي‌دهيم: پيچيده‌ترين اشياء را به راحتي مي‌توان نوشت. تنها چيزي كه در نظر گرفته مي‌شود درستي عدد داخل پرانتزهاست. مهم توضيح اين است كه معني وابسته به يك ليست نمايش ويژه يا اتم در ليست نمايش وارد نمي‌شود. به اين معني كه همه عبارات نمادين كه در بالا توصيف شده است از لحاظ نحو برنامه‌هاي Lisp را اصلاح مي‌كنند ولي الزاماً از لحاظ معني (semantic) برنامه‌ها رااصلاح نمي‌كنند.
2. Semantics (معاني): هسته هر سيستم برنامه‌نويسي Lisp مفسر است كه كارش محاسبه مقدار براي يك عبارات نمادين داده شده است. اين فرآيند ارزيابي نام دارد. نتيجه يا  مقدار يك عبارت نمادين، يك عبارت نمادين است. كه بعد از كامل شدن ارزيابي برگردانده شده است. توضيح اينكه در واقع Lispداراي Semantics (معاني) عملياتي است كه با يك تعريف رياضي دقيق از نظريه تابع بازگشتي بدست مي‌آيد.
حلقه خواندن- محاسبه- چاپ چگونه مي‌تواند مفسر Lisp را فعال كرده و براي محاسبه عبارات نمادين و بنابراين اجراي واقعي برنامه‌هاي Lisp بكار برود؟
مسئله‌‌هاي Prolog بصورت حقايق، بديهيات و قوانين منطقي   براي استنباط حقايق جديد بيان مي‌‌ شوند . Prolog با قانون رياضي در محاسبات گزاره‌ اي و ونتايج نظري بدست آمده در زمينه اثبات قضيه خودكار در اواخر دهه1960 بنا شده است. مفسر  Lisp در واقع بعنوان يك تابع معمولاً بنام eval و جزئي از هر محيط برنامه‌‌‌نويسي Lisp است تعريف شده است (مانند تابعي كه پيش‌ساخته نام دارد). آن بوسيله فراخواني حلقه خواندن- محاسبه- چاپ در يك سيستم  Lisp جاسازي مي‌شود، وقتي يك عبارت نمادين توسط كاربر داده مي‌‌ شود ابتدا به داخل سيستم Lisp خوانده مي‌شود( خواندن هم يك تابع پيش‌ساخته است). سپس مفسر Lisp كه via نام دارد تابع eval را فراخواني مي‌كند تا عبارت نمادين را محاسبه و نتيجه عبارت نمادين را با چاپ در دستگاه كاربر برگرداند ( شگفت‌آورنيست گفتن اينكه چاپ هم يك تابع پيش‌‌ساخته است). وقتي سيستم Lispدر كامپيوتر شروع به اجرا مي‌‌شود اين حلقه خواندن- محاسبه- چاپ بطور خودكار شروع به اجرا كرده و بوسيله علامت ويژه اعلان  Lisp در ابتداي خط جديد به كاربر علامت مي‌دهد در اين مقاله ما علامت سئوا ل (?) را به عنوان اعلان Lisp بكار خواهيم برد. براي مثال:

 ( 4 3 +) ?
  7

هر وقت سيستم Lisp اجرا شود حلقه خواندن- محاسبه- چاپ فعال خواهد بود.
عبارت نمادين ( 4 3 + ) كه بوسيله هكر Lisp وارد شده است بوسيله مفسر Lisp بصورت فراخواني تابع جمع تفسير شده و نتيجه عبارت نمادين در ابتداي خط جديد 7 چاپ مي‌‌شود ارزيابي مفسر Lisp مطابق سه قانون زير انجام مي‌‌شود:
1- يكساني: يك عدد،‌ يك رشته يا نمادهاي t و nil خودشان را ارزيابي مي‌كنند (بر مي‌گردانند) به اين معني كه ارزش عدد 3،3 و ارزش رشته ”house”، رشته ”house”است. نمادt مقدار t برمي‌گرداند كه به معناي true تفسير مي‌شود وnil ، nil به معني false برمي‌‌گرداند
2- نمادها: ارزيابي يك نماد عبارت نمادين مربوط به آن را برمي‌‌‌گرداند. ( چگونگي‌ اش را در زير نشان خواهيم داد) بنابراين اگر ما فرض كنيم نماد‌ names به ليست
(john mary tom) وابسته است آنگاه ارزيابي names آن ليست را نتيجه مي‌دهد. اگر نماد color را به نماد green وابسته كنيم آنگاه green  بعنوان مقدار color برگردانده مي‌‌شود.
به بيان ديگر نمادها بعنوان متغيرهايي كه به مقاديري متصل(باند) شده‌اند تفسير مي‌‌شوند.
3- ليستها: هر ليست بعنوان يك فراخواني تابع تفسير مي‌‌شود. مفسر اول ليست دلالت بر تابعي دارد كه بايد براي بقيه عناصر( بالقوه خالي)‌ كه آرگومانهاي آن تابع را نشان مي‌دهند بكار رود. در واقع آرگومانهاي يك تابع قبلا بصورت نمادهاي پيشوندي مشخص مي‌‌شوند. اين مزيت را دارد كه توابع به سادگي مي‌توانند با تعداد دلخواهي آرگومان مشخص و استفاده شوند. ليست خالي ( ) داراي عبارت نمادين nil  بعنوان مقدارش مي‌باشد. توضيح اينكه نماد nil در واقع داراي دو معني است: يك نمايش مقدار منطقي false و ديگري نمايش ليست خالي. هر چند ممكن است اين يك بيت فرد بنظر برسد، ولي در واقع در Lisp مشكلي در شناسايي مفهوم nil بكاررفته وجود ندارد.
 ‌ ولي بطور كل آرگومانها قبل از اينكه توابع مقادير آنها  را استفاده كنند ارزيابي مي‌شوند. اولويت ارزيابي ترتيبي از آرگومانها از چپ به راست است. يك آرگو‌مان ممكن است يك اتم يا يك ليست باشد،‌درهر حالت بعنوان يك فراخواني تابع تفيسر شده و مفسر Lisp براي ارزيابي  آن فراخواني مي‌شود. براي مثال، ارزيابي زير در سيستم Lisp يك تابع به حساب مي‌آيد:

 ?(max 4 (min 9 8) 7 5)
 
 8

 در اينجا آرگومانها  5, 7, (min 9 8), 4  هستند كه در اولويتي قبل از تابعي به نام max كه نتيجه مقادير آرگومانها را به كار مي‌برد ارزيابي مي‌شوند. آرگومان اول 4 ،‌ يك عدد است پس مقدار آن 4 است. آرگومان دوم (min 9 8) است كه خودش يك فراخواني تابع است. بنابراين بايد قبل از آرگومان سوم فراخواني شود، (min 9 8) بايد توسط مفسر Lisp ارزيابي شود. چون ما بايد مفسر Lispرا براي بعضي آرگومانها در طول ارزيابي همه فراخواني‌هاي توابع استفاده كنيم مي‌‌توان گفت مفسر Lisp بصورت بازگشتي فراخواني شده است. مفسر Lisp همان مراحل را به كار مي‌برد، پس آرگومان اول 9 قبل از آرگومان دوم 8، ارزيابي مي‌شود. با بكار برروي تابع min حاصل 8 مي‌شود يعني تابع كوچكترين عدد يك مجموعه از اعداد صحيح را محاسبه مي‌‌كند. براي تابع بيروني max هم به اين معني است كه آرگومان  دوم آن 8 ارزيابي مي‌شود.
آرگومانهاي بعدي 7و5هستند كه نتيجه ارزيابي آنها مقادير  7و5 مي‌شود. حال تابع بزرگترين عدد كه max نام دارد مي‌تواند ارزيابي شود كه 8 برمي‌گرداند. اين مقدار نهايي،‌ مقدار فراخواني همه توابع مي‌‌باشد. از آنجايي كه گفته مي‌‌‌شود مفسر 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 4 (min 9 8) 7 5))    يا    ’(max 4 (min 9 8) 7 5)   
قبلاً گفتيم كه مفسر Lisp يك تابع يكتايي پيش‌ساخته است كه eval نام دارد. آن صريحاً آرگومانهايش را وادار مي‌كند تا مطابق قوانين مذكور در بالا ارزيابي شوند. در بعضي حالات، آن مي‌تواند مقابل تابع  quote  قرار بگيرد بنابراين به وضوح لازم است كه يك ليست بعنوان داده مشخص شود تا سيستم Lisp بتواند يك فراخواني تابع تفسير شود، ما مي‌توانيم(eval ’(max 4 (min 9 8) 7 5))  را مشخص كنيم كه مقدار 8 را بطوري كه در بالا توصيف شد بر مي‌گرداند. به همان صورت مشخص كردن (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 3 2 5 1))
 3

توضيح اينكه در واقع Setq حالت مفسر Lisp را تغيير مي‌دهد تا دفعه بعدي كه همان متغير استفاده مي‌شود، داراي مقدار بوده و بنابراين مفسرLisp قادر به بازگرداندن آن خواهد بود. اگر اين اتفاق نيفتد آنگاه مفسر Lisp يك اخطار خواهد داد زيرا نماد متصل نشده است.
(گام 2 مفسر Lisp پيدا نشد). بنابراين آن مي‌گويدكه Setq يك اثر جانبي توليد مي‌كند زيرا حالت مفسر Lisp بطور پويا تغيير مي‌دهد. وقتي استفاده از Setq اجباري شد، به هرحال متوجه شد كه در واقع از مسير semantics (معاني) Lisp ناب دور مي‌شود. پس Setq بايد با دقت بسيار استفاده شود.
 
 B. نوع داده ليست
 برنامه‌نويسي در Lisp در واقع به معني تعريف توابعي است كه روي ليست عمل مي‌كنند. مانند ايجاد، پيمايش،‌كپي، تغيير و حذف ليستها. از آنجايي كه اين در Lisp مركزي است، هر سيستم Lisp بر مبناي مجموعه‌اي از توابع پيش‌ساخته ابتدايي كه بطور موثري عمليات اصلي ليست را پشتيباني مي‌كند مي‌آيد. ما بطور خلاصه يكي از مهمترين آنها معرفي مي‌كنيم. ابتدا نوع گزاره‌اي،‌ ما مي‌دانيم كه يك عبارت نمادين جاري يا يك ليست است يا نيست (يعني يك اتم). اين كار بوسيله تابع Listp انجام مي‌شود كه هر عبارت نمادين expr را بعنوان آرگومان پذيرفته و اگر expr ليست باشد نماد t و در غير اين صورت nil برمي‌گرداند. مثالها هستند (ما از فلش راست => براي نشان دادن نتيجه فراخواني تابع استفاده خواهيم كرد):

(listp ’(1 2 3))==>t
 
(listp ’( ))==>t 
 
(listp ’3)==>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 ’(1 2 3)) (rest ’(1 2 3)))    ==>(1 2 3)


 در اصل، 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 1) 2 (list 1 2 3))    ==>((1) 2 (1 2 3))
 
(append ’(1) (list 2)) ==>(1 2)   
 
(append ’(1 2) nil ’(3 4))==>(1 2 3 4)
 
(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   3  5) را در سيستمLisp وارد كنيد مفسرLisp قادر خواهد بود كه تعريف براي نام تابع مشخص شده بيابد و سپس آرگومانهاي داده شده را از چپ به راست پردازش كند وقتي اين كار انجام شد آن مقدار هر آرگومان را مطابق پارامتر مشخص شده در ليست پارامتر تعريف تابع وصل خواهد كرد(تخصيص خواهد داد) در مثال ما بدين معني است كه مقدار آرگومان اول كه3 است(3 همان عدد3 است كه خودش را ارزيابي كرده است) به پارامترx متصل مي‌كند. سپس مقدار آرگومان دوم كه 5 است به پارامترy متصل مي‌شود. چون مقدار يك آرگومان به يك پارامتر متصل مي‌شود، اين روش فراخواني با مقدار ناميده شده است. بعد از مقدار‌يابي براي همه پارامترها مفسرLisp قادر به ارزيابي بدنه تابع خواهد بود. مثال بدين معني است كه (  3  5 +) فراخواني خواهد شد. نتيجه فراخواني8 است كه بعنوان نتيجه فراخواني(my-sum  3 5) برگردانده مي‌شود. بعد از تكميل فرا‌خواني تابع اتصالات موقت پارامترهاي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 0.75) ’very-probable)
 
 ((> prob–value 0.5) ’probable)
 
 ((> prob–value 0.25) ’improbable)
 
   (T ’very-improbable)))

وقتي(verbalize-prop 0.33) فراخواني مي‌شود مقدار واقعي آرگومانها به پارامترprop-value متصل مي‌شود.سپسcond با آن اتصالات ارزيابي مي‌شود very-probable)’((>prop-value)است.> يك گزاره پيش ساخته است  كه تست مي‌كند كه آيا آرگومان اول از دومي بزرگتر است،چونpropvalue،0.33 است. بهnil ارزيابي مي‌شود كه به معني انجام نشدن تست است. بنابراين ارزيابي اين بخش پيشنهادي بلافاصله پايان مي‌يابد. و سپس پيشنهاد
 ((> prob–value 0.5) ’probable)ارزيابي مي‌شود كه تابع تست باز هم  nilبرمي‌گرداندبنابراين ارزيابي هم پايان مي‌يابد. سپس ((prop-value 0.25) ’improbable) ارزيابي مي‌شود حال با بكار بردن تابع تستT برگردانده مي‌شود كه به معني انجام تست است.آنگاه همه اعمال اين بخش كه بطور مثبت تست شده است. ارزيابي ومقدار آخرين عمل به عنوان مقدارcond برگردانده مي‌شود در مثال ما تنها عملimprobable’ تعيين مي‌شود كه مقدارimprobable (غيرمحتمل) را برمي‌گرداند از آنجايي كه اين مقدارcond را تعيين مي‌كند و عبارت cond تنها عبارت بدنه تابعverbalize-prop است. نتيجه فراخواني improbable ,((verbalize-prop 0.33) است. توضيح اينكهاگرما (verbalize- prop 0.1)را وارد كنيم مقدارvery- improbable را بر‌مي‌گرداند زيرا تست هر سه با شكست مواجه شده و بايد بخش (T ’very-improbable)ارزيابي شوددر اين حالت نمادT به عنوان تستي كه هميشهT بر‌مي‌گرداند استفاده شده است بنابراين مقدار اين پيشنهاد
 very- improbable است.
 
E. تعريف توابع بازگشتي
 دومين روش اصلي براي تعريف كنترل جريان درLisp تعاريف توابع بازگشتي هستند. تابعي كه از تعريفش بعنوان جزئي از تعريفش استفاده مي‌كند باز‌گشتي نام دارد. بنابراين، يك تعريف بازگشتي، تا جايي كه امكان دارد مسئله‌اي را به قسمتهاي كوچكتر تقسيم مي‌كند سپس اين قسمتهاي كوچكتر را با استفاده از توابع مشهور و جمع پاسخهاي يكسان حل كرده و حل برنامه را كامل مي‌كند. بازگشت يك روش طبيعي براي كنترل ساختارهاي داده‌اي  است كه اندازه معيني ندارد. مانند ليستها، درختها و گرافها. بنابراين براي مسئله‌هايي كه در يك فاصله از حالات دنبال حل كانديد مي‌گردند مناسب است.   
Lisp اولين زبان برنامه‌نويسي كاربردي بود كه با روش معين تعريف تعاريف بازگشتي را پشتيباني كرده است. ما از دو مثال كوچك براي نشان دادن بازگشت درLisp استفاده خواهيم كرد. اولين مثال براي تعيين طول يك ليست طويل دلخواه استفاده مي‌شود. طول يك ليست برابر تعداد عناصر آن است. تابع بازگشتي آن به صورت زير است.

(defun length (list)
 
(cond ((null list) 0)
 
 (T (+ 1 (length (cdr list))))))

 وقتي يك تعريف بازگشتي تعريف مي‌شود. ما بايد حالتهاي اساسي راشناسايي كنيم يعني آن قسمتهايي كه نمي‌توانند بيشتر تجزيه شوند. مسئله اندازه وابسته به ليست است. كوچكترين مسئله اندازه در ليست، ليست خالي است. بنابراين اولين چيزي كه ما بايد مشخص كنيم تستي براي شناسايي ليست خالي است و تعيين اينكه طول ليست خالي بايد چقدر باشد تابع پيش‌ساخته  null تست مي‌كند كه آيا اين ليست خالي است در اين صورت t برمي‌گرداند. از آنجايي كه ليست خالي بدون عنصر است تعريف مي‌كنيم كه طول ليست خالي صفر باشد كار ديگري كه بايد انجام شود تجزيه مسئله اندازه به قسمتهاي كوچكتر است كه همان مسئله مي‌تواند براي فسمتهاي كوچكتر استفاده شود. تجزيه ليست مي‌تواند با استفاده از توابع cdr,car انجام شود. به اين معني كه ما بايد تعيين كنيم تا وقتي كه ليست خالي پيدا شود عنصر اول و بقيه عناصر ليست چه كار بكنند. از آنجايي كه ما ازقبل ليست خالي را بعنوان حالت اساسي شناسايي كرديم، مي‌توانيم فرض كنيم تجزيه برروي ليستي شامل حداقل يك عنصر انجام خواهد شد. بنابراين هر بار كه قادر خواهيم بود تا با بكار بردن cdr بقيه عناصر ليست را بدست آوريم، ما يك عنصر اضافي پيدا كرديم كه بايد براي افزايش تعداد عناصر ليست قبلا شناسايي شده بوسيله يك استفاده مي‌شود. استفاده از اين تعريف تابع(length ’( ))  بلافاصله صفر بر‌خواهد گرداند و اگر
(length ’(a b c)) را فراخواني كنيم، نتيجه 3 خواهد بود زيرا براي اينكه ليست خالي شود بايد سه فراخواني بازگشتي 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 ’(1 3 -9 -5 6 -3) #’plusp)   ==>(1 3 6)
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 ’(1 3 A B 6 C 4) #’numberp)    ==> (1 3 6 4)
 
(filter ’(1 2 3 4 5 6) #’even)    ==> (2 4 6)


  به عنوان مثال ديگري از تعريف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 ’(1 2 3 4))==>   (2 4 6 8)
بارها شده كه يك تابع بايد يكبار استفاده مي‌شد. بنابراين اگر ما بتوانيم مستقيما تعريفي از يك تابع بعنوان آرگومان از تابع نگاشت فراهم كنيم كاملا مناسب خواهد بود براي اينكار تعريف عبارت 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)) ’(1 2 3 4)    ==>   (2 4 6 8)

 يك عبارت 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 ) = 1
 
|   depth(N(i,l,r)) =
 
  1 + max(depth l, depth r);


 
تابع depth نگاشتي از درختها به اعداد است. عمق هر برگ 1 است و عمق هر درخت ديگر 1 بعلاوه بيشترين عمق زير درختهاي چپ و راست آن است.
Haskell شبيه ML است:  Syntax مشابهي بكار مي‌برد، دامنه‌اش هم ايستاست و از همان روش استنتاج استفاده مي‌كند. با ML در اين تفاوت دارد كه يك زبان كاملاً تابعي است. به اين معني است كه به اثرات جانبي اجازه نداده و شامل هيچ نوع ويژگي دستوري نيست، در اصل متغير و جملات انتسابي ندارد. بعلاوه از يك تكنيك ارزيابي كند استفاده مي‌‌كند، كه زير عبارت را ارزيابي نمي‌كند تا موقع نياز مقدارش معلوم باشد. ليستها رايجترين ساختار داده در Haskell هستند. براي مثال [1,2,3] ليستي از سه عدد صحيح 3,2,1 است ليست [1,2,3] در Haskell در واقع خلاصه‌نويسي شده ليست 1:(2:(3:[ ] )) است، كه[ ]  ليست خالي است و: عملگري ميانوندي است كه آرگومان اولش را جلوي آرگومان دومش اضافه مي‌كند( يك ليست). بعنوان مثالي از يك تابع كاربر تعريفي كه روي ليستها عمل مي‌كند، مسئله شمارش تعداد عناصر در يك ليست با تعريف تابع length ملاحظه مي‌شود.


length :: [a] -> Integer
 
length [ ] = 0
 
length (x:xs) = 1 + length xs


 خوانده مي‌شود’’طول ليست خالي 0 است، و طول ليستي كه عنصر اولش x است و بقيه xs است،1 بعلاوه طول xs است‘‘. در Haskell تابع invocation احضار با تطبيق الگو راهنمايي مي‌كند، براي مثال طرف چپ معادله داري الگوهايي مانند[ ] و x:xs است. در يك كاربرد تابع اين الگوها با پارامترهاي واقعي تطبيق داده مي‌شوند [ ] ) تنها با ليست خالي مطابقت مي‌كند، و x :xs  با هر ليست با حداقل يك عنصر با موفقيت تطبيق مي‌كند، x به عنصر اول و xs به بقيه ليست متصل مي‌شوند). اگر تطبيق موفقيت‌آميز باشد طرف راست معادله ارزيابي و بعنوان نتيجه كاربرد برگردانده مي‌شود. اگر با شكست مواجه شود معادله بعدي سعي مي‌شود، و اگر همه معادلات با شكست مواجه شوند،‌ حاصل يك خطا مي‌شود.
اين پايان كوتاه ما از’’سفر در Lisp ‘‘ است. ما تنهاي توانستيم جنبه بسيار مهم Lisp را مطرح كنيم. خوانندگان علاقمند به جزئيات خاص بيشتر بايد حداقل يكي از كتابهاي مذكور در  آخر مقاله را كنكاش كنند. بقيه اين مقاله معرفي الگوي برنامه‌نويسي ديگري بنام ‌Prolog است كه در برنامه‌نويسي AI بطور گسترده مورد استفاده قرار مي‌‌گيرد.


IV. برنامه‌‌نويسي منطقي در Prolog
در دهه 1970 يك الگوي ديگر براي محاسبات نمادين در برنامه‌نويسي AI از موفقيت در زمينه اثبات قضيه خودكار ارئه شد. حل رويه اثبات بطور قابل توجهي توسط رابينسون
(1965) توسعه يافته كه كه با منطق رسمي نشان داده شده است، در محاسبات گزاره‌اي خاص مي‌‌توان بعنوان نمادي براي تعيين الگوريتم‌ها و بنابراين براي انجام محاسبات نمادين استفاده شود. در اوايل (دهه 1970) 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 همه اطلاعات لازم براي حل مسئله موجود در پايگاه داده را فرض مي‌‌كند.
جملات برنامه‌هاي 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(X0) ←
 sign(X1),
 
 sign(X2),
 
 X0 syn cat = s,
 
 X1 syn cat = np,
 
 X2 syn cat = vp,
 
 X1 syn agr = X2 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: ae1359m@gmail.com 

دانشجوي كارشناسي ناپيوسته كامپيوتر(تراكتورسازي تبريز)


*اين مقاله ترجمه‌اي است از:


Neumann, Gunter Programming Languages in Artificial Intelligence. In: Bentley & Bidgoli: Encyclopedia of Information Systems,
Academic Press, San Diego, 2002

http://www.dfki.de/~neumann/publications/new-ps/ai-pgr.pdf 
 

VI. منابعي براي مطالعه بيشتر

 1.Charniak, E., Riesbeck, C.K., McDermott, D.V. and Meehan, J.R., 1980, Artificial Intelli- gence Programming, Lawrence Erlbaum Associates, Hillsdale, New Jersey.
 
2.Clocksin, W.F. and Mellish, C.S, 1987, Programming in Prolog, Springer, Berlin, Germany.
 
3.Keene, S.E., 1988, Object–Oriented Programming in Common Lisp, Addison–Wesley, Read-ing, Massachusetts.
 
4.Luger, G.F. and Stubblefield, W.A., 1993, Artificial Int elligence: Structures and Strategies
 
for Complex Problem Solving, second edition, Benjamin/Cummings, Redwood City, Cali-fornia.
 
5.Norvig, P., 1992, Artificial Intellig ence Programming, Morgan Kaufman Publishers, San ateo, California.
 
6.Pereira, F.C.N. and Shieber, S.M., 1987, Prolog and Natural Language Analysis, CSLI Lecture Notes, Number 10, Stanford University Press, Stanford, California, 1987.

7.Sebesta, R.W., 1999, Concepts of Programming Languages, fourth edition, Addison–Wesley, Reading, Massachusetts.

8.Ullman, J.D., 1997, Elements of ML Programming, second edition, Prentice-Hall.
9.Watson, M., 1997, Intelligent Java Applications for the Internet and Intranets, Morgan Kaufman Publishers, San Mateo, California.

منبع :مجله الکترونيکي پژوهشگاه اطلاعات و مدارک علمي ايران

1,876 total views, 2 views today

Share

1 فکر می‌کنند “زبانهاي برنامه‌نويسي در هوش مصنوعي”

پاسخ دهید

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