Y@SiN
09-13-2009, 06:30 PM
نويسنده: Gunter Neumann
German Research Center for Artificial Intelligence (LT–Lab, DFKI)
ترجمه: احد محمّدي خواجه
E-mail: ae1359m@gmail.com (ae1359m@gmail.com)
دانشجوي كارشناسي ناپيوسته كامپيوتر(تراكتورسازي تبريز)
عناوين متن
زبانهاي برنامهنويسي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 است.
German Research Center for Artificial Intelligence (LT–Lab, DFKI)
ترجمه: احد محمّدي خواجه
E-mail: ae1359m@gmail.com (ae1359m@gmail.com)
دانشجوي كارشناسي ناپيوسته كامپيوتر(تراكتورسازي تبريز)
عناوين متن
زبانهاي برنامهنويسي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 است.