LISP(List Processing) Programming language استاد : جناب آقای مهندس گودرزی پور ارائه دهنده : طاهره عبدوس
سرفصل مطالب مقدمه آشنایی با LISP Atom & List مقداردهی عبارت ها 1 آشنایی با LISP 2 Atom & List 3 3 مقداردهی عبارت ها 4 4 توابع اولیه 3 5 مقادیر منطقی و Predicate ها 6 4
سرفصل مطالب دستورات کنترلی و شرطی توابع And & Or تعریف توابع در LISP 7 توابع And & Or 8 تعریف توابع در LISP 9 3 دستورات Lambda و Define 10 4 توابع MNS , PLS , Eval 3 11 ماکروها و کاربردها 4 12
یک داده در لیسپ می تواند خودش یک برنامه باشد. مقدمه زبان LISP در اواخر دهه 50 توسط John McCarthy در MIT بوجود آمده است و از قدیمی ترین زبانهای برنامه نویسی محسوب می شود. هدف عمده طراحی آن محاسبات نمادی (symbolic computation) ویژگی بارز آن یکنواختی زیاد و انعطاف پذیری آن می باشد. ویژگی دیگر LISP یکسان بودن نوع (Type) در انواع داده ها می باشد. به این اجزاء Symbolic Expression (S-Expression) می گویند. تمامی داده ها اعم از اطلاعات و دستورات از این نوع هستند. یک داده در لیسپ می تواند خودش یک برنامه باشد.
آشنایی با LISP Common Lisp، که معمولاً به صورت CL مخفف می شود، یک نوع از Lisp است که به وسیله ی ANSI *30266 – 1994 استاندارد شده و برای استاندارد کردن نسخه های منشعب شده ی Lisp که بخش هایی از آن را دارا هستند، گسترش یافته است. Common Lisp یک زبان برنامه نویسی چند نمونه ای است که: تکنیک های برنامه نویسی مانند برنامه نویسی زبان برنامه نویسی شی گرا، تابعی را پشتیبانی می کند. قابل گسترش در بین خصیصه های استاندارد مانند ماکروها است.
Atom & List S-Expression به 2 صورت است: 1. اتم (Atom) 2. لیست(List) اتم جزء معنی داری است که به اجزای کوچکتری تقسیم نمی شود و می تواند یک نماد یا عدد باشد. در زیر نمونه هایی از اتمها را مشاهده می کنید: Atom
Nil (لیست خالی)، هم اتم هم لیست!!! Atom & List لیست دنباله ای از Expressionها هستند، یعنی اعضای یک لیست هم می توانند یک اتم باشند هم یک لیست دیگر. در زیر نمونه هایی از لیستها را مشاهده می کنید: List Nil (لیست خالی)، هم اتم هم لیست!!!
برنامه هایLISP همانطور که گفتیم برنامه های لیسپ بصورت S-Expression می باشند. با یک مثال شروع می کنیم: این عبارت یک S-Expression است و از نوع لیست (لیست سه عضوی) می باشد. معنای این عبارت همان 2+1 می باشد و حاصل پردازش لیست عدد 3 می باشد. در لیسپ عبارات بصورت Prefix می باشند. اگر عبارات فوق را به Interpreter لیسپ بدهیم عملگر + را روی دو عملوند اعمال می کند؛ ببینید:
برنامه هایLISP حال عبارت (7-3) *(7+3) را محاسبه می کنیم: همیشه یک عبارت به Interpreter می دهیم و عبارت محاسبه می شود. وقتی که یک S-Expression به عنوان یک برنامه به لیسپ داده می شود، تنها کاری که لیسپ می کند محاسبه مقدار آن است.
مقدار یابی عبارتها عبارتها در لیسپ طبق قانون زیر مقدار یابی می شوند: اگر S-Expression یک اتم باشد: اگر عبارت یک عدد باشد حاصل مقدار یابی خودش است. اگر عبارت یک Symbol باشد حاصل مقدار یابی، مقدار منتسب به آن است. اگر S-Expression یک لیست باشد: اولین عضو آن را به عنوان یک تابع می گیرد و آن را روی بقیه اعضاء اعمال(Apply) می کند، البته قبل از این کار دیگر اعضای لیست را مقدار یابی می کند.
توابع و عملگرها دو چیزمتفاوت نیستند مقدار یابی عبارتها عمل مقدار یابی یک عمل بازگشتی (Recursive) است. اگر به Interpreter عبارت (F x y z) را بدهیم معادل F(x,y,z) در دیگر زبانهای برنامه نویسی می باشد. توابع و عملگرها دو چیزمتفاوت نیستند در عبارت (+ 3 2 1) عملگر + بر روی 3و 2و 1 اعمال می شود. + یک Symbol است که به آن یک تابع انتساب داده شده است.
عبارت ( 1 2 3 4 ) با عبارت ( 4 3 2 1) متفاوت است. برنامه هایLISP در برنامه زیر یک Atom داریم که یک عدد است و حاصل برابر خودش است. در برنامه زیر تابع + روی آرگومانهای خود اعمال شده و عدد 10 را به عنوان حاصل باز می گرداند. قبل از اعمال + آرگومانهای آن مقدار یابی می شوند. مقدار a چقدر است؟ در برنامه زیر Interpreter سعی می کند تابع 1 را روی سه آرگومان دیگر اعمال کند و به سبب اینکه چنین تابعی وجود ندارد پیغام خطا می دهد. عبارت ( 1 2 3 4 ) با عبارت ( 4 3 2 1) متفاوت است.
برنامه هایLISP در برنامه زیر قبل از اعمال تابع باید * آرگومانهای آن مقدار یابی شوند. پس اول حاصل (* 1 2) و سپس مقدار (- 3 4) و هم 5(که برابر خودش است) مقدار یابی می شوند. در آخر هم * روی آرگومانهای 3 و5 و1- اعمال می شود. فرض کنید تابعی به جای آرگومانهای عددی با آرگومانهایی از نوع دیگر کار کند. مثلا تابعی به نام Print داریم که یک Symbol را میگیرد و آن را چاپ می کند. اگر آن را بصورت زیر استفاده کنیم چه اتفاقی می افتد؟
برنامه هایLISP قبل از آنکه تابع Print روی آرگومانش اجرا شود، Salam باید مقداریابی شود. پس تابع Print روی مقدار منتسب روی Salam اعمال می شود نه خود Salam. حال اگر مقدار Salam معتبر نباشد با پیام خطا مواجه می شویم. راه حل: به نحوی نماد Salam را به نماد دیگر مثل P انتساب دهیم و بعد P را به عنوان آرگومان به Print بدهیم:
برنامه هایLISP فرض کنید تابعی به نام Length ساخته ایم که یک لیست را به عنوان آرگومان می گیرد و طول آن را بر می گرداند. بیایید آن را روی لیست ( 1 2 3 4 ) امتحان کنیم؛ به نظر شما نتیجه چه خواهد شد؟ قبل از آنکه تابع روی ( 1 2 3 4 ) اجرا شود، لیست باید مقدار یابی شود. Lisp سعی می کند 1 را روی 2و3و4 اعمال کند و چون چنین تابعی نداریم با خطا مواجه می شویم. راه حل: تابعی داشته باشیم که تعدادی آرگومان بگیرد و به عنوان مقدار لیستی از آرگومانهای خود را بر گرداند.
برنامه هایLISP آنگاه تابع Length را به صورت زیر می توان به کار برد: راه حل ساده تر : اگر در هر دو مثال فوق می توانستیم کاری کنیم که Lisp قبل از اعمال تابع روی آرگومانهایش، آن را مقداریابی نکند آنگاه با خطا مواجه نمی شدیم. این کار در Lisp با قرار دادن یک ‘ (Single quote) در ابتدای عبارت انجام می شود و به Interpreter می گوید آن را مقداریابی نکند.
در لیسپ واقعا تابعی با نام List با همین کارائی وجود دارد. برنامه هایLISP مثال : در مثال اول ( + 1 2 ) محاسبه شده است و حاصل کل عبارت برابر 3 می شود. ولی در مثال دوم علامت ‘ مانع مقداریابی می شود و خود لیست بازگردانده می شود. پس دو مثال قبلی باید به صورت زیر تغییر یابند : در لیسپ واقعا تابعی با نام List با همین کارائی وجود دارد.
توابع اولیه نام Lisp از کلمات کلیدی List Processing گرفته شده و از همینجا می توان به اهمیت کار بر روی لیست ها پی برد. در لیسپ تعدادی توابع اولیه برای کار بر روی رشته ها وجود دارد که در زیر با برخی از آنها آشنا می شویم : CAR این تابع یک آرگومان از لیست می گیرد و اولین عضو آن را بر می گرداند:
توابع اولیه CDR این تابع یک آرگومان از نوع لیست می گیرد و یک لیست شامل اعضای دوم به بعد لیست باز می گرداند. توجه کنید که نامهای CAR و CDR مربوط به ساختار اولیه لیسپ می باشند و ممکن است درنسخه های جدید FIRST و REST و یا HEAD و TAIL شده باشند.
(CONS X L) یک لیست است که CAR آن X و CDR آن L است توابع اولیه CONS این تابع یک S-Expression و یک List را به عنوان آرگومان می گیرد و یک لیست باز می گرداند که عضو اول آن همان S-Expression و بقیه اعضای آن همان اعضای لیست است. (CONS X L) یک لیست است که CAR آن X و CDR آن L است
در لیسپ مقدار منتسب به نماد Nil خود نماد Nil است برنامه هایLISP به مثالهای زیر توجه کنید: در مثال آخر چون Nil یک Symbol است و جلو آن Quote هم گذاشته نشده، پس باید اول مقدار آن محاسبه شود و تابع CONS روی مقدار منتسب اعمال شود، ولی مقدار آن با مثال بالایی اش یکی است !!! در لیسپ مقدار منتسب به نماد Nil خود نماد Nil است
برنامه هایLISP به طور کلی می توان گفت که پیاده سازی Lisp براساس لیستهای پیوندی می باشد. داده ها و رکوردها را در Lisp می توان بصورت زیر نگهداری کرد. در مثال زیر تعدادی رکورد در یک لیست ذخیره شده اند: اگر لیست به یک Symbol به نام L منتسب شود، نام خانوادگی رکورد دوم برابر : راه ساده تری نیز برای نوشتن عبارت فوق وجود دارد: ساختاری که در بالا مشاهده می کنید همانند یک Macro قبل از تفسیر به فرم (CAR (CDR (CDR (CAR (CDR L ))))) در می آید.
مقادیر منطقی و Predicateها یکی دیگر از کاربردهای دیگر Nil جهت بیان مقادیر نادرست می باشد. مقدار منتسب به Nil خود Nil می باشد و در عبارات به Quote نیازی نداریم. برای نشان دادن مقدار درست در Lisp از نماد T بهره می بریم. برای مثال تابع ATOM در Listزیر را در نظر بگیرید؛ این تابع جزء توابع پایه Lisp می باشد که یک آرگومان می گیرد و مقداری منطقی جهت اتم بودن آن باز می گرداند.
مقادیر منطقی و Predicateها EQ این تابع دو آرگومان می گیرد و می گوید که آیا این دو آرگومان مربوط به یک خانه حافظه اند یا خیر! اگر دو Symbol مساوی باشند در یک آدرس حافظه قرار می گیرند؛ لیسپ این Symbolها را در جایی به نام Atom Table نگهداری می کند. این تابع در مورد اتم ها همانند تابع بررسی تساوی عمل می کند، ببینید:
جریان کنترلی در Lisp بوسیله محاسبه عبارات صورت می پذیرد دستورات کنترلی قبلا بررسی کردیم که: جریان کنترلی در Lisp بوسیله محاسبه عبارات صورت می پذیرد محاسبه در لیسپ بصورت احضار توابع و اجرای هر تابع روی آرگومانهای خودش است با یک مثال از زبان C شروع می کنیم و می بینیم که اگر دستورات شرطی و حلقه هم نداشته باشیم باز هم می توانیم پیاده سازی نماییم:
دستورات شرطی عملیات شرطی در لیسپ بوسیله تابع COND انجام می شود. آرگومانهای این تابع شامل تعداد زیادی لیست دو عضوی است: عضو اول هر عبارت یک لیست منطقی است که می تواند درست یا غلط باشد.عضو دوم هر لیست یک عبارت است که حاصل آن ممکن است به عنوان کل تابع COND بازگردانده شود. Lisp لیستهای دوتایی را به ترتیب امتحان می کند. اگر عضو اول صحیح بود عضو دوم را به عنوان جواب باز می گرداند وگرنه به سراغ عضو بعدی می رود. تابع COND قبل از اجرا شدن آرگومانهای خود را محاسبه و مقدار یابی نمی کند.
دستورات شرطی در مثال زیر فرض کنید مقدار منتسب به C، X باشد: در مثال فوق چون عبارت (eq x ‘c) صحیح بوده است، مقدار cc’ به عنوان حاصل فراخانی تابع COND برگردانده شده است. دراکثر برنامه های Lisp در تابع COND دستور آخر را به صورت (T exp) نیز می نویسند؛ چرا؟
توابع AND و OR این توابع تعداد دلخواهی آرگومان می گیرند و AND یا OR آنها را بر می گردانند. تفاوت آنها با توابع معمولی در این است که ابتدا تمامی آرگومانهای خود را مقدار یابی نمی کنند(مثل COND) بلکه آنها را به ترتیب از راست به چپ محاسبه می کنند تا جایی که حاصل AND یا OR معلوم شود و بعد از آن دیگر آرگومان های بعدی را محاسبه نمی کنند.
تعریف توابع در LISP با مطالبی که تا کنون در مورد لیسپ آموختیم حتی برنامه های ساده هم نمی توان نوشت، چون حداقل به دستورات انتساب و حلقه نیاز داریم. چگونه می توانیم Iteration (تکرار) را در لیسپ پیاده سازی نماییم؟ یک برنامه لیسپ با تعریف توابع ساخته می شود. در لیسپ حلقه وجود ندارد و اگر در برنامه به تکرار نیاز باشد از Recursion استفاده می شود. در یک تابع می توان دنباله ای از دستورات را نوشت و حتی درون یک تابع توابع جدید نیز تعریف نمود.
Lambda تعریف تابع نیست، بلکه حاصل آن یک تابع است !!! این دستور به عنوان مقدار یک تابع را بر می گرداند. برای مثال فرض کنید تابعی داریم که دو عدد را می گیرد و مجموع آنها را باز می گرداند: آرگومان دوم Lambda لیستی از آرگومانهای تابع مورد نظر است و آرگومان بعدی هم مقدار بازگشتی تابع را با توجه به آرگومانها نشان می دهد. Lambda تعریف تابع نیست، بلکه حاصل آن یک تابع است !!! در این مثال تابع ((Lambda (x y) (+ x y) 2 3) روی آرگومانهای 2و 3 اعمال شده و 5 به عنوان حاصل کل عبارات برگردانده می شود.
دستور Define همیشه ما می خواهیم تابع را یک بار تعریف کنیم و به دفعات از آن استفاده کنیم. برای این کار کافی است که یک تابع (حاصل Lambda) را به یک Symbol انتساب می دهیم. برای این کار از Define استفاده می کنیم: فراموش نشود !!! علامت Quote قبل از آرگومان Define بسیار مهم است بوسیله Define می توان چند تابع تعریف کرد.
توابع PLS و MNS دو تابع PLS و MNS به ترتیب مجموع و تفاضل دو عدد را بر می گردانند: وقتی یک تابع را صدا می زنیم مقدارهایی که به عنوان آرگومان به تابع داده می شوند در حقیقت به آرگومانهایی که در تعریف تابع وجود دارند منتسب می شود.
تعریف توابع در LISP یک دستور دیگر برای تعریف توابع که در اکثر نسخه ها می توانید استفاده کنید (Defun(Define Function)) است که به صورت زیر استفاده می شود: برای مثال تابعی را تعریف می کنیم که جمع دو عدد را حساب کند: با اطلاعاتی که اکنون درباره لیسپ بدست آوردیم، می توانیم اکثر توابع را در لیسپ پیاده سازی کنیم.
برنامه هایLISP برنامه ای بنویسید که عدد n را بگیرد و n! را برگرداند: در برنامه فوق اگر n<2 بود، مقدار 1 را باز می گرداند، در غیر این صورت مقدار (n*Fact(n- 1)) را بر می گرداند . همانطور که مشاهده می کنید تابع Fact خودش را صدا می زند.
برنامه هایLISP IF این تابع در اکثر نسخه های جدید لیسپ وجود دارد و کار را راحت می کند: این دستور<Condition> را محاسبه می کند. اگر درست بود، <Exp1> را مقداریابی می کند در غیر این صورت <Exp2> را باز می گرداند. توجه کنید که IF هم مثل COND همه آرگومانهای خود را مقداریابی نمی کند. حال تابع Fact را با IF می نویسیم:
تنها دستوراتی که بلد هستیم CAR و CDR هستند، پس از بازگشت بهره می بریم برنامه هایLISP برنامه ای بنویسید که یک لیست را به عنوان آرگومان بگیرد و طول آن را بازگرداند: تنها دستوراتی که بلد هستیم CAR و CDR هستند، پس از بازگشت بهره می بریم حال این رابطه را تبدیل به برنامه می کنیم: توجه کنید که اتم Nil معادل لیست خالی است.
راهنمایی: Nامین عضو L همان N-1امین عضو CDR(N-1) است برنامه هایLISP برنامه ای بنویسید که لیست L و عدد N را بگیرد و Nامین عضو L را برگرداند. اگر N از محدوده L خارج است مقدار Nil را بازگرداند: راهنمایی: Nامین عضو L همان N-1امین عضو CDR(N-1) است
می توان گفت کار Eval بر عکس کار ‘ است یک داده در لیسپ خودش می تواند یک برنامه باشد. مثلا داده ای به شکل (+ 2 1) یک لیست سه عضوی است که می تواند بصورت یک برنامه هم در نظر گرفته شود. حال فرض کنید داده هایی داریم و می خواهیم آنها را به Interpreter لیسپ بدهیم تا اجرا کند. مثل اینکه آنها را در خط فرمان Lisp نوشته باشیم. این کار با تابع Eval انجام می پذیرد. این تابع آرگومان خود را محاسبه و مقدار یابی می کند: می توان گفت کار Eval بر عکس کار ‘ است
ماکروها یک ماکرو درLisp به طور سطحی شبیه یک تابع در حال استفاده است و نماینده ی یک تغییر شکل کد اصلی برنامه نیز می باشد. ماکروها به برنامه نویسان Lisp امکان ایجاد فرم های نحوی جدید در زبان را می دهد. این توابع قبل از اینکه کامپایلر کد اصلی نهایی را تولید کند، احضار می شوند. ماکروها هر عملگرCL را می توانند استفاده کنند.
کاربردها acl2: یک ثابت کننده نظریه با وجود انتظارات بزرگ از کمیته ی استاندارد، Common Lisp یک زبان برنامه نویسی مناسب باقی ماند، که اغلب در دانشگاه و یا محیط های کاربردی ویژه که در ارتباط با هوش مصنوعی است به کار می رود. بسیاری از کاربرانCommon Lisp برای معرفی زبان مطلوب شان مثال آشنای سایت بازرگانی yahoo را می زنند که با CL گسترش یافته است. درضمن کاربردهای کد منبع باز موفقی به زبان Common Lisp وجود دارد مثل: acl2: یک ثابت کننده نظریه Maxima: یک سیستم جبری کامپیوتری خبره. Compo: به ساختارهای موسیقی اجازه توصیف روش طبیعی را می دهد. Lisa: سیستم تولید قانون برای ساختن نماینده های نرم افزاری "هوش"
کاربردها به عنوان یک زبان تابعی پردازش لیست طراحی شد. برای کارهای جستجو بسیار مناسب است. بازیهای کامپیوتری در آن به خوبی پیاده سازی می شوند. پردازش متن در آن به خوبی انجام می شود. تفسیر ماشین خودکار که در آن رشته هایی از نمادها می تواند توسط رشته های دیگری جایگزین شود زمینه دیگری از کاربرد این نرم افزار می باشد.
منابع آموزش lisp – علی زرگر – goldsmith.ir Paul Graham.1996. ANSI Common Lisp Programming Languages Structures (Organick, Forsythe, Plummer) Academic Press 1978 Artificial Intelligence (Luger) 2002
پایان با تشکر از توجه شما