لیسپ (به انگلیسی: Lisp) یک زبان برنامهنویسی رایانه است که در سال ۱۹۵۸ به وسیلهٔ جان مککارتی ابداع شدهاست.[۱] این زبان، مانند زبان برنامهنویسی پرولوگ، بیشتر برای برنامهنویسی هوش مصنوعی مورد استفاده قرار میگیرد و با توجه به اینکه زبان لیسپ از نحو سادهای برخوردار است، تجزیه و پیادهسازی آن نسبتاً با سهولت انجام میشود.[۱]
ساختار کلی زبان لیسپ
متن برنامههای لیسپ عموماً از نمادها و فهرستهایی از نمادها تشکیل میشود و بدین خاطر است که این زبان Lisp (کوتهنوشت LISt Processor به معنی "پردازشگر فهرست") نامیده شدهاست. یکی از ویژگیهای جالب زبان لیسپ این است که خود برنامههای لیسپ نیز فهرست هستند و بنابراین، میتوان با برنامهها به عنوان دادهها رفتار کرد یا دادهها را به عنوان برنامه ارزیابی نمود.
لیسپ دارای گویشهای مختلفی است که بعضی از آنها دارای قابلیتهای شیءگرا نیز هستند. از این میان میتوان به کامن لیسپ اشاره کرد.
در ابتدا لیسپ به عنوان علامتگذاری و نمادسازی ریاضیات و برای برنامهنویسی رایانه ابداع شد. زبان لیسپ به سرعت مورد توجه برنامه نویسان از جمله برای تحقیقات علمی هوش مصنوعی قرار گرفت. لیسپ یکی از ابتداییترین زبانهای برنامهنویسی میباشد و در علوم رایانه بر بسیاری از تفکرات و ایدهها پیشگام بود. لیسپ شامل ساختمان دادهٔ درخت، مدیریت نگهداری اتوماتیک، برنامهنویسی پویا، برنامهنویسی شیء گرا و کامپایلر مستقل میباشد.
لیست یک مفهوم پایهای در زبان لیسپ
نام لیسپ از زبان پردازش لیست گرفته شدهاست. لینک لیست یکی از قسمتهای اصلی ساختمان دادهٔ زبان لیسپ است و سورس کد لیسپ از لیستها ساخته شدهاست و میتواند به عنوان ساختمان داده عمل کند. پیشرفت و توسعهٔ سیستم ماکرو به برنامهنویسان اجازه میدهد تا ترکیبهای جدید ویا حتی حیطهٔ زبانهای برنامهنویسی ویژهای را ایجاد کرده و در زبان لیسپ تعبیه کنند.
قابلیت تبادل کدها و دادهها به زبان لیسپ قابلیت تشخیص ترکیبها را میدهد، همهٔ کدهای برنامه به صورت عبارتهای نمادین یا لیستهای پرانتزگذاری شده نوشته شدهاند.
یک تابع میتواند توسط خودش و یا توابع دیگر فراخوانی شود و یا طبق قواعد نحوی نوشتن یک لیست و استفاده از اول نام عملگرها و پیروی کردن از قواعد آرگومانها ایجاد شود. به عنوان مثال تابع f دارای ۳ آرگومان میباشد و به صورت مقابل توانایی فراخوانی را دارد و مورد استفاده قرار میگیرد:
(fxyz)
تاریخچه زبان لیسپ
زبان برنامهنویسی لیسپ توسط جان مک کارتی در سال ۱۹۵۸ در حالی که در مؤسسهٔ فناوری ماساچوست (MIT) بود ابداع شد. مک کارتی طرح خودش را در یک مقالهٔ مرتبط با انجمن ماشین آلات کامپیوتری در سال ۱۹۶۰ منتشر کرد. طرح وی در ابتدا به صورت «بخش اول: توابع بازگشتی از دید عبارتهای نمادین و محاسبهٔ آنها توسط ماشین» ارائه شد و بخش دوم آن هیچگاه منتشر نشد. وی نشان داد که با یک تعداد ساده و کمی از عملگرها و علامتگذاری توابع میتوان یک زبان تورینگ کامل برای الگوریتمها ایجاد کرد.
زبان پردازش اطلاعات اولین زبان هوش مصنوعی بود. از سال ۱۹۵۵ یا ۱۹۵۶ و پیش از آن ایدههای بسیاری بر زبان لیسپ وارد شد از جمله پردازش لیست و توابع بازگشتی که در زبان لیسپ به کار برده شد.
ثبتهای اصلی مک کارتی به صورت عبارتهای غیر نمادین که خواستار تفسیر کردن و برگرداندن به عبارتهای نمادین بود. به عنوان مثال عبارت غیر نمادین car[consA,B معادل عبارت نمادین (car (cons A B)بود که در زبان لیسپ به کار گرفته شده بود. برنامه نویسان به سرعت عبارت نمادین را انتخاب و عبارتهای غیر نمادین را ترک کردند.
لیسپ برای اولین بار توسط استفان راسل روی یک کامپیوتر IBM 704 اجرا شد. راسل مقالهٔ مک کارتی را مطالعه کرد و دریافت که توابع لیسپ میتوانند در کد ماشین اجرا شوند. این نتیجه از مطالعه و دریافت راسل نشان میدهد که مفسر لیسپ میتوانست برای اجرای برنامههای لیسپ و ارزیابی صحیح عبارت لیسپ استفاده شود.
دو زبان اسمبلی به عنوان دو عملیات اصلی و ابتدائی تجزیه و جدا کردن عناصر اصلی لیست برای IBM 704 شد. این دو زبان اسمبلی car (مضمون آدرس ثبات) و cdr (محتوای کاهش میزان ثباتها) نسخهٔ لیسپ هنوز ازcar وcdr برای عملیاتی که اولین عنصر در یک لیست و باقیماندهٔ لیست را برمیگرداند، استفاده میکند.
اولین کامپایلر تکمیل شدهٔ لیسپ، در سال ۱۹۶۲توسط تام هارت و مایک لوین در MIT اجرا شد، این کامپایلر معرفی شده مدل لیسپ با کامپایلر نحوی در هر کامپایل و ترجمهٔ توابع میتواند بهطور رایگان در هم بیامیزد.
زبان به کار گرفته شده در ثبت هارت و لوین نسبت به کدهای ابتدائی مک کارتی به شیوهٔ لیسپ مدرن و جدید نزدیک تر میباشد.
ویژگیهای زبان لیسپ
پیوستن به هوش مصنوعی
بعد از شروع لیسپ، لیسپ به انجمن تحقیقاتی هوش مصنوعی پیوست، خصوصاً به سیستمهای PDP، زبان لیسپ به عنوان پیادهساز طرح کوچک زبان برنامهنویسی استفاده میشود که مبنایی برای سیستم معروف هوش مصنوعی SHRLU بود.
در سال ۱۹۷۰ تحقیقات علمی هوش مصنوعی به شاخههای تجاری انشعاب پیدا کرد که کارایی سیستم لیسپ موجود در این زمینه یک روند رو به رشد شد.
لیسپ یک سیستم مشکل برای اجرا، مهارت کامپایلر و سختافزار ذخیرهکننده را در سال ۱۹۷۰ دارا باشد.
بازیابی عادی حافظه، توسط دانشجوی فارغالتحصیل MIT (دانیل ادوارد) گسترش داده شده، که برای اجرای لیسپ روی سیستمهای محاسباتی ساخته شده بود اما راندمان آن هنوز یک مشکل بود.
برای رهبری ماشین لیسپ: سختافزار اختصاصی برای اجرای محیط لیسپ و برنامههای آن استفاده میشود.
پیشروی در هردو سختافزار کامپیوتر و فناوری کامپایلر از ماشینهای لیسپ از کار افتاده الهام گرفته شدهاست.
طی شک کوشش بزرگ نسخههای بیشماری از زبان لیسپ را در یک زبان واحد متمرکز و متحد کردند (نسخههای برجسته و قابل ملاحظهای شامل: اینترلیسپ، مک لیسپ، متالیسپ، و فرانزلیسپ) زبانهای جدید (لیسپ عمومی و مشترک) در اصل یک زیر مجموعهٔ سازگاری از نسخههای تعویض شده بود.
در سال ۱۹۹۴، ANSI یک لیسپ عمومی و مشترک استاندارد منتشر کرد. لیسپ عمومی و مشترک زبان برنامهنویسی فناوری اطلاعات ANSI X3.226-1994 در آن زمان فروشگاههای جهانی برای لیسپ خیلی کوچکتر از المان بود.
ترکیب و معناشناسی
لیسپ یک عبارت جهتدار است، برخلاف بیشتر زبانهای دیگر، بین عبارتها و جملهها تمایز و فرقی وجود ندارد. همهٔ کدها و دادهها به عنوان عبارتها نوشته شدهاند – زمانی که یک عبارت ارزیابی میشود یک مقدار (یا یک لیستی از مقادیر) را میسازد، که آن هم در داخل عبارات دیگر جاسازی میشود.
مقالهٔ ۱۹۵۸ مک کارتی دو نوع از ترکیبها را معرفی کرد: عبارت نمادین Sexps هم نامیده میشود، که بازتابی از نمایش داخلی کدها و داده هاست و عبارت غیر نمادین هرگز مورد توجه قرار نگرفت و تقریباً همهٔ زبانها امروزه از عبارات نمادین استفاده میکنند.
استفاده از پرانتزگذاریها تفاوت بسیار آشکار و مشهودی میان لیسپ و دیگر زبانهای برنامهنویسی ایجاد کردهاست. اسم مستعار LISP از Lost In Stupid Parenthese یا Lost of Irritating Supper fluous parenthese گرفته شدهاست. هرچند ترکیب عبارتهای نمادین مسئولی برای توان لیسپ است، این ترکیب به شدت با قاعده و منظم است.
هرچند ترکیبات لیسپ به نمادگذاری قدیمی محدود نشدهاند میتواند به سبکهای دیگر توسعه پیدا کند.
تکیه روی عبارتها، قابلیت انعطافپذیری زیادی به زبان میدهد، زیرا توابع لیسپ به صورت لیست نوشته شدهاند، آنها دقیقاً مانند دادهها میتوانند پردازش شوند، این قابلیت اجازه میدهد برنامههای لیسپ به سادگی و راحتی نوشته شوند و به نسبت برنامههای دیگر به راحتی اداره شوند. (برنامهنویسی غیر نمادین) بسیاری از نسخههای زبان لیسپ با عناصر جدا شده توسط فاصلههای سفید و پرانتزگذاری شدهها نوشته میشود. برای مثال (1 2 f00) یک لیست است که عنصرهای آن سه اتم هستند (اتم: کوچکترین عضو لیست): این مقادیر ۱ و ۲ و F00 هستند. این مقادیر ضمناً دارای نوع دادهای خاصی هستند، مثلاً این لیست دارای دو عدد صحیح ۱ و ۲ و یک نوع دادهٔ ویژهٔ لیسپ که یک Symbol یا نماد نامیده میشود.
همچنین یک لیست خالی () به عنوان یک اتم ویژهٔ صفر یا پوچ معرفی شدهاست. موجودیت یک لیسپ از اتم و لیست تشکیل میشود.
عباتها به عنوان لیست نوشته شدهاند، استفاده کردن از ثبتهای پیشوندی، عناصر ابتدایی در لیست نامی از یک شکل تابع، عملگرها، ماکروها یا اپراتورهای ویژهاست.
آرگومانها باقیماندههایی از لیستها هستند، برای مثال تابع list آرگومانها را به عنوان یک لیست برمیگرداند، بنابراین عبارت (list ‘۱ ‘۲ ‘foo) ارزیابی میشود و حاصل این ارزیابی لیست (۱٬۲،foo) میباشد.
نیازی به ارزیابی کردن اعداد نیست چون ارزیابی عدد ۱ عدد ۱ میشود. آرگومانهای مثال قبل از اعداد هستند یعنی آرگومانهای ویژه که این آرگومانها از ارزیابی کردن آرگومانها جلوگیری میکنند چون مقادیر آنها مشخص است. هر عبارتی که بیان میشود قبل از اینکه با عبارات دیگر پیوست داده شود به صورت بازگشتی ارزیابی میشود.
(list(1 2 (list(3 4)))) در این مثال حاصل ارزیابی به صورت لیست (۱٬۲(۳٬۴)) میباشد، توجه کنید این لیست دارای ۳ آرگومان میباشد، لیستها میتوانند به صورت تو در تو باشند. اپراتورهای حسابگر به صورت همسان رفتار میکنند.
حاصل عبارت (+۱ ۲ ۳ ۴) عدد ۱۰ میباشد. عبارت معادل عبارت بالا به صورت ۱+۲+۳+۴ میباشد که از نشانگذاری میان وندی استفاده شدهاست. اپراتورهای حسابگر در زبان لیسپ variadic(n-ary) که زبان لیسپ توانایی پذیرفتن هر تعداد آرگومان را داراست.
عملگرهای ویژه ساختمان کنترل لیسپ را آماده میکنند. برای مثال، اپراتور ویژه if سه آرگومان میپذیرد، اگر اولین آرگومان صفر یا خالی باشد دومین آرگومان ارزیابی میشود و در غیر این صورت هٔرگومان سوم بررسی میشود. بنابراین if(nill(list 1 2 “foo”)(list 3 4 “bar”) که تنها آرگومان (list 3 4 “bar”) بررسی میشود.
عبارتهای لامبدا (Lambda)
دیگر عبارتهای ویژه لاندا میباشد که برای وصل کردن متغیرها به مقادیرشان که درون یک عبارت ارزیابی میشوند استفاده میشود.
این عملگر همچنین برای ایجاد کردن توابع هم استفاده میشود. آرگومانهای درون لاندا یک لیستی از آرگومانها هستند و عبارت ارزیابی توابع میباشند. مقادیر بازگشتی مقادیری از عبارت قبلی که ارزیابی شدهاند هستند.
عبارت (Lambda(arg)(+arg1)) زمانی که این تابع به کار برده میشود به صورت یک تابع ارزیابی میشود و وظیفهٔ این تابع معرفی کردن یک آرگومان و اتصال دادن آرگومان به arg و در نهایت برگرداندن یک عدد بزرگتر از آرگومان قبلی میباشد عبارتهای لاندا خیلی متفاوت با نام تابع رفتار نمیکند بنابراین اگر در عبارت (Lambda(arg)(+arg1))5->۶ عدد ۵ را وارد کنیم خروجی آن ۶ میشود.
اتمها: در نسخهٔ اصلی لیسپ دو نوع دادهٔ ابتدایی وجود دارد: اتمها و لیستها
یک لیست یک رشتهٔ منظم و محدودی از عناصر میباشد، که هر عنصر در درون خودش یکی از این اتمها یا لیستها را دارد و یک اتم یک عدد یا یک نماد میباشد.
در اصل یک نماد یک رقم منحصربهفرد میباشد و به عنوان یک رشتهٔ عددی در سورس کد نوشته شده و هر دو به عنوان یک نام متغیر و یک رقم دادهای در پردازش نمادین استفاده میشود برای مثال list(foo(BAR 1)2) شامل سه عنصر: Symbol foo و list(BAR 1) و عدد ۲ میباشد.
تفاوت اصلی بین اتمها و لیستها این است که اتمها تغییرناپذیر و منحصربهفرد میباشند.
دو اتم که دقیقاً به یک صورت و به یک روش در یک شیء نوشته شده باشد در مکان متفاوتی در سورس کد ظاهر میشوند، هر لیست یک شیء مجزا میباشد و به خاطر اینکه مستقل از دیگر لیست هاست و از دیگر لیستها به وسیلهٔ مقایسهٔ عملگرها مشخص میشود.
Consها و لیستها
یک لیست لیسپ یک لینک لیست جداست، هر ذره از این لیست یک Cons نامیده میشود و از دو اشاره گر که Car و Cdr نامیده میشوند ترکیب شدهاست این دو اشاره گر به ترتیب معادل دو فیلد Data و Next در مقالهٔ لینک لیست میباشد.
Car->DataCdr->Next
بسیاری از ساختمان دادهها میتوانند ترکیبهایی از خانههای Cons را داشت باشند، یکی از این ساختمان دادههای ابتدایی لیست مخصوص نامیده میشود، یک لیست مخصوص هر دو نماد لیست خالی nill یا خانهها Cons را داراست که در هر یک از این خانهها هر اشاره گر Car به یک داده اشاره میکند (که ممکن است این اشاره گر Cons به یک لیست اشاره کند) و یک اشاره گر Cdr به یک لیست مخصوص دیگر اشاره میکند. اگر یک Cons داده به سر یک لینک لیست برده شود سپس اشاره گر Car آن به اولین عنصر از لیست و اشاره گر Cdr آن به انتهای یک لیست اشاره میکند به همین دلیل عملکرد Car و Cdr را به ترتیب first و rest هم نامیده میشود.
ارایهٔ لیست عبارت نمادین
نمایش پرانتزگذاری عبارت نمادین ساختمان لینک لیست.
چندین راه برای نمایش لیست یکسان به عنوان یک عبارت نمادین وجود دارد. یک خانه (Cons) میتواند به صورت نشانگذاری جفت نقطهگذاری شده نوشته شود به عنوان مثال (a.b) که در آن a یک Car و b یک Cdr است. یک لیست مخصوص بلند ممکن است به صورت یک نشانگذاری جفت نقطهگذاری شده نوشته شود. (a.(b.(c.(d.nill))))
طبق قرارداد کوتاه شدهٔ عبارت بالا به صورت (a b c d) در نمادسازی لیست میباشد یک لیست مخصوص ممکن است در یک ترکیبی از دو صورت (a b c.d) نوشته شود. برای سیستمی از سه Cons که آخرین Cdr آن d است.
دستورالعملهای پردازش لیست
لیسپ دستورالعملهای زیادی را برای دستیابی و کنترل لیستها فراهم میکند. لیستها میتوانند مستقیماً با پردازهٔ لیست ایجاد شوند. لیست هر تعدادی از آرگومانها را میپذیرد و تعدادی از آرگومانها را برمیگرداند.
به این دلیل راهی که لیستها ایجاد میشوند از جفتهای Cons (Car,Cdr) پردازهٔ Cons میتواند برای اضافه کردن یک عنصر به جلوی یک لیست استفاده شود. توجه کنید که پردازهٔ Cons در هدایت و به کار بردن آرگومانهای لیست نامتقارن است، بدین دلیل روشهای لیستها ایجاد میشوند.
پردازهٔ Oppend دو یا چند لیست را با هم ادغام میکند و یک لیست واحد ایجاد میکند زیرا لیست لیسپ یک لینک لیست است و پیچیدگی زمانی الحاق کردن لیستها از مرتبهٔ پیچیدگی زمانی O(n) میباشد.
ساختار اشتراکی: لیستهای لیسپ لینک لیستهای ساده میتوانند با یکی دیگر از لیستها در ساختمان مشترک باشند به عبارت دیگر دو لیست میتوانند دم یکسانی داشته باشند یا رشتهٔ پایانی از Consهای یکسانی داشته باشند مثلاً:
(setffoo(list'a'b'c))(setfbar(cons'x(cdrfoo)))
لیست foo و bar به ترتیب به صورت (a b c) و (X b c) هستند هرچند دم (b c) در هر دو لیست ساختار یکسانی دارند ولی مانند هم نیستند، خانههای Cons اشاره گر به b و c در محل حافظهٔ یکسانی برای هردو لیست قرار دارد.
ساختار اشتراکی سریع تر از کپی کردن میتواند به صورت چشمگیری کارایی را بهبود بخشند. هرچند، این مهارت میتواند متقابلاً در راههای نامطلوب با عملکردهایی که تغییرات لیستهای گذشته روی آرگومانهای آن تأثیر بگذارد، اثر کند.
تغییرات یک لیست از قبیل تغییر دادن C با یک goose روی دیگری نیز تأثیر میگذارد
setf(thirdfoo)'goose)
که این تغییر نتیجه را به صورت (a b goose) تغییر میدهد اما bar هم تغییر میکند (X b goose) که ممکن است یک نتیجهٔ غیرمنتظره باشد.
زبانهای برنامهنویسی Lisp معمولاً از یک خط دستور محاورهای استفاده میکنند، که میتواند با یک محیط پیچیدهٔ گسترش یافته ترکیب شود. کاربر اصطلاحات و دستورها را در خط دستور وارد کرده یا با رهبری IDE آنها را به سیستم Lisp میفرستد. Lisp دستورها را میخواند، آنها را ارزیابی میکند و نتایج را چاپ میکند. به این دلیل است که خط دستور زبان Lisp به حلقهٔ Read-Eval-Print یا REPL معروف است.
نمونهٔ سادهای از عملیات REPL در زیر آمدهاست. این یک شرح سادهاست که بسیاری از المانهای Lispواقعی در آن نمیآید مانند ماکروها و کوئتها.
تابع read جملات متنی را به عنوان ورودی میپذیرد و آنها را به ساختار لیست تجزیه میکند. به عنوان مثال، وقتی شما رشتهٔ (+ ۱ ۲) را در اعلان تایپ میکنید، تابع read آن را به یک لیست پیوندی حاصل از ۳ المان ترجمه میکند: علامت +، عدد ۱ و عدد ۲. خیلی اتفاق میافتد که لیست قسمت مؤثری از یک کد Lisp باشد که قابل ارزیابی است. به همین دلیل است که یک قطار از لیست به یک تابع نام عملگر مع میدهد.
تابع eval ساختار لیست را ارزیابی میکند و نوعی دیگر از ساختار را به عنوان نتیجه بازمیگرداند. ارزیابی کردن لزوماً تفسیر کردن معنی نمیدهد؛ بعضی سیستمهای Lisp هر عبارتی را به زبان ماشین تبدیل میکنند. خیلی سادهاست؛ به هر حال؛ برای تعریف ارزیابی به عنوان تفسیر: برای ارزیابی یک لیست که نام تابع دارد، eval ابتدا تمام آرگومانهای داده شده به cdr اش را ارزیابی میکند و سپس تابع را روی آن آرگومانها اعمال میکند. در این مثال، تابع عمل جمع است و به آرگومانهای (۱ ۲) اعمال میشود که نتیجه ۳ است. این نتیجهٔ ارزیابی است.
این وظیفهٔ تابع print است که نتیجه را به کاربر نمایش دهد. برای نتیجهٔ سادهٔ ۳ این کار ناقابل است. یک عبارت که با قسمتی از ساختار لیست ارزیابی میشود میاز دارد که print لیست را به حرکت درآورد و در خروجی به شکل یک عبارت S نمایش دهد.
برای اجرا کردن یک REPL در Lisp، تنها لازم است که این سه تابع را اجرا کنید و یک تابع حلقه بینهایت را. (بهطور طبیعی اجرای eval پس از اجرای عملگرهای ویژهای مانند if پیچیده خواهد شد) یک REPL ساده به خودی خود با یک خط کد انجام شد: (loop(print(eval(red))))
لیست در اصل تعداد کمی ساختار کنترلی دارد. منتها در تکامل و گسترش زبان تعداد زیادی به آن اضافه شدند. (عملگر اصلی شرایط در زبان Lisp که cond بود بعداً متشکل شد از ساختار if-then-else)
برنامه نویسان در نسخهٔ Scheme حلقهها را به صورت بازگشت دم(tail recursion) بیان میکنند. موسسات متعارف علوم کامپیوتر Scheme بعضی دانشجویان را متقاعد میکند که تنها راه تکرار در زبان Lisp استفاده از بازگشت دم است؛ این اشتباهاست. تمامی نسخههای متداول دیده شده از Lisp دارای ساختارهای الزامی برای تکرار هستند. درScheme دستور do به عنوان دستور حلقه پیچیدهٔ Lisp است. علاوه بر این مسئلهٔ اصلی که شیء گرایی را مهمتر از مسئلهٔ فاعلی کرده این است که Scheme نیازهای ویژهای برای کار کردن با فراخوانی دم(tail calls)دارد، در نتیجه دلیل ترغیب Scheme به استفاده از بازگشت دم این است که روش صراحتاً با تعریف خود زبان پشتیبانی میشود. در مقابل، ANSI Common Lisp نیازی به بهینهسازی که معمولاً به حذف فراخوانی دم گفته میشود ندارد. در نتیجه این حقیقت که بازگشت دم به عنوان یک جایگزین تصادفی برای استفاده از ساختارهای مبتنی بر تکرار (مانند do dolist loop) توصیه نمیشود تنها یک مسئلهٔ برتری ادبی نیست، ولی بالقوه یکی از کارآمدهاست (بعد از این که این روش فقط به عنوان یک پرش ساده به کار نرفت) و به عنوان یک تصحیح برنامهاست.
بعضی از ساختارهای کنترلی Lisp عملگرهای ویژهای هستند، هم ارز کلیدواژههای ترکیبی باقی زبانها. عباراتی که این عملگرها استفاده میکنند ظاهری شبیه فراخوانی تابع دارد، تفاوت اینجاست که آرگومانها ضرورتاً نباید ارزیابی شوند یا در مورد تکرار شاید بارها ارزیابی شوند.
در مقابل اکثر زبانهای برنامهنویسی، Lisp به برنامه نویسان اجازه میدهد با خود زبان ساختارهای کنترلی را پیادهسازی کنند. ساختارهای کنترلی زیادی در ماکروهای Lisp پیادهسازی میشوند و برنامه نویسان میتوانند هر ماکرو را گسترش دهند، برای آنانی که میخواهند بدانند چطور کار میکند.
هر دوی Lisp Common و Scheme دارای عملگرهای کنترلی غیر محلی هستند. تفاوت این عملگرها یکی از عمیقترین تفاوتها مابین این دو نسخهٔ زبان است. Scheme از ورودی مستمر با استفاده از روش call/cc پشتیبانی میکند، که به برنامه اجازهٔ ذخیره (و بعداً بازیابی کردن) یک عملیات ویژه را میدهد. Common Lisp از ورودی مستمر پشتیبانی نمیکند ولی از راههای زیادی برای انجام رهایی از تکرار پشتیبانی میکند.