لیسپ معمولی

نحو

لیسپ معمولی یک است؛ که از عبارات نحوی برای دلالت بر کد و ساختمان داده استفاده می‌کند. فراخوانی‌های ماکرو و تابع به صورت لیستی با آوردن نام تابع در ابتدا نوشته شده‌است، مانند مثال زیر:
۲ و ۲ را جمع می‌کند و نتیجه ۴ می‌دهد:
 (+ 2 2)
به متغیر "p" مقدار ۳٫۱۴۱۵ می‌دهد:
 (setf *p* 3.1415)
تعریف یک تابع که مجذور یک عدد را حساب می‌کند:
 (defun square (x)
   (* x x))
اجرای تابع که عدد ۹ را برمی‌گرداند:
 (square 3)

انواع داده

لیسپ معمولی انواع داده‌ای زیادی حتی بیشتر از بسیاری زبان‌ها دارد.

انواع اسکالر

انواع عدد شامل اعداد صحیح، کسری، ممیز شناور و اعداد مختلط می‌باشد. لیسپ معمولی از bignums برای نشان دادن مقدارهای عددی با دقت و اندازه دلخواه استفاده می‌کند. نوع کسری، کسرها را با دقت نشان می‌دهد، این ویژگی در بسیاری از زبان‌ها وجود ندارد. لیسپ معمولی به صورت اتوماتیک مقدارهای عددی را بین این داده‌ها به صورت مناسب وارد می‌کند.
نوع کاراکتر لیسپ معمولی به کاراکترهای ASCII محدود نمی‌شود- تعجب‌آور نیست وقتی لیسپ، ASCII را نیز شامل می‌شود. بعضی از پیاده‌سازی‌های مدرن به کاراکترهای یونی کد نیز اجازه استفاده می‌دهد.
نوع سمبل نیز در زبان‌های لیسپ رایج است، اما عموماً خارج از آن‌ها شناخته شده نیست. یک سمبل یک شی داده‌ای نامدار واحد است. سمبل‌ها در لیسپ شبیه شناسه‌ها در زبان‌های دیگر هستند، که به عنوان متغیرها برای نگهداری ارزش‌ها استفاده می‌شوند. اگرچه، آن‌ها بسیار عمومی تر هستند و می‌توانند برای خودشان هم استفاده شوند. معمولاً وقتی یک سمبل ارزیابی می‌شود، مقدارش به عنوان یک متغیر برگردانده می‌شود. استثناهایی نیز وجود دارد: سمبل‌های کلمات کلیدی مثل: foo با خودشان ارزیابی می‌شوند و مقدارهای بولی در لیسپ معمولی به وسیلهٔ سمبل‌های رزرو شدهٔ T و NIL نشان داده می‌شوند.

ساختمان‌های داده

انواع ترتیبی در لیسپ معمولی شامل لیست‌ها، بردارها، بردارهای بیتی و رشته‌ها می‌باشد. مانند لیسپهای دیگر، لیست‌ها در لیسپ معمولی از consها ساختمان داده با دو بخش است که car وcdr نامیده می‌شود. یک لیست یک زنجیره پیوندی از cons هاست؛ که car مربوط به cons به عضوی از یک لیست ارجاع می‌کند (احتمالاً لیست دیگر). هر cdr مربوط به cons، به cons بعدی اشاره می‌کند به جز آخرین cons که مقدارش به nil اشاره می‌کند.consها به راحتی برای پیاده‌سازی درخت‌ها و دیگر ساختمان‌های دادهٔ پیچیده استفاده می‌شوند: اگر چه توصیه می‌شود که از نمونه‌های کلاس یا ساختار به جای آن استفاده شود.
لیسپ معمولی آرایه‌های چند بعدی را پشتیبانی می‌کند، و می‌تواند به صورت پویا در صورت نیاز آرایه‌ها را دوباره سایز دهی بکند. آرایه‌های چند بعدی می‌تواند برای ریاضیات ماتریسی به کار روند. بردار یک آرایهٔ یک بعدی است. آرایه‌ها می‌توانند هر نوع داده‌ای را به عنوان اعضاء داشته باشند (حتی انواع ترکیبی در همان آرایه) یا می‌توانند برای شامل شدن نوع خاصی از اعضاء، تخصصی شوند مثلاً دربرداری از اعداد صحیح. بسیاری از پیاده‌سازی‌ها می‌توانند توابع آرایه‌ای را وقتی که آرایه‌ها در نوع‌های تخصصی شده به کار می‌روند، بهینه کند. دو نوع آرایه‌ای تخصصی شده، استاندارد هستند: رشته برداری از کاراکترهاست در حالی که بردار بیتی، برداری از بیت‌ها است.
جداول Hash پیوستگی بین شی‌های داده‌ای را ذخیره می‌کند. هر شی ممکن است به عنوان کلید یا مقدار استفاده شود. جداول Hash نیز مانند آرایه‌ها می‌توانند به صورت اتوماتیک در مواقع نیاز دوباره سایزدهی شوند.
بسته‌ها مجموعه‌ای از سمبل‌ها هستند که به‌طور عمده برای جداسازی بخش‌های یک برنامه به فضاهای نامی به کار می‌روند. یک بسته بعضی از سمبل‌ها را خارج کرده و به عنوان بخشی از یک واسط عمومی مشخص می‌کند.
ساختارها، شبیه structها در Cو رکوردها در پاسکال، ساختمان‌های داده‌ای پیچیدهٔ دلخواه با هر عدد و نوعی از زمینه‌ها را نشان می‌دهند.

ماکروها

یک ماکرو در Lisp به‌طور سطحی شبیه یک تابع در حال استفادده‌است. اگرچه، نشان دهندهٔ عبارتی است که ارزیابی شده‌است، نمایندهٔ یک تغییر شکل کد اصلی برنامه نیز می‌باشد. ماکروها به برنامه نویسان Lisp امکان ایجاد فرم‌های نحوی جدید در زبان را می‌دهد. به عنوان نمونه، این ماکرو شکل حلقه until را ایجاد می‌کند، که ممکن است در زبان‌هایی مثل Perl آشنا به نظر می‌رسد:
 (defmacro until (test &body body)
   `(do ()
        (,test)
      ,@body))

 ;; example
 (until (= (random 10) 0)
   (write-line "Hello"))
همهٔ ماکروها باید قبل از این که کد اصلی شامل آن‌ها ارزیابی یا کامپایل شود، گسترش یابند. ماکروها می‌توانند به عنوان توابعی که درخت‌های نحوی انتزاعی را می‌پذیرند و برمی‌گردانند ملاحظه شوند. این توابع قبل از اینکه کامپایلر کد اصلی نهایی را تولید کند، احضار می‌شوند. ماکروها در لیسپ معمولی معمولی نوشته شده‌اند، و هر عملگر لیسپ معمولی را می‌توانند استفاده کنند. نکته‌ای که در بالا ذکر شد توسط لیسپ معمولی برای ساده کردن مورد معمول از جایگزینی در قالب‌های یک کد، ایجاد شده‌است.

گرفتن متغیرها و سایه اندازی آن‌ها

ماکروهای لیسپ معمولی قادر به گرفتن متغیر هستند، موقعیتی که در آن‌ها سمبل‌ها در بدنه گسترش یافتهٔ ماکرو با سمبل‌های متن فراخوانی شده و منطبق می‌شوند. گرفتن متغیرها گاهی اوقات اثر مطلوبی دارد: به برنامه نویسان امکان ایجاد ماکروها را جایی که سمبل‌های مختلف، معنایی ویژه دارند، می‌دهد. اگرچه، می‌تواند خطاهای غیرمعمول و غیرقابل انتظاری را نیز ایجاد کند.
بعضی از سیستم‌های لیسپ مانند scheme، با استفاده از ماکروها از گرفتن متغیر جلوگیری می‌کنند — که ماکروهای تمیز نامیده می‌شوند. در لیسپ معمولی می‌توان بوسیلهٔ gensyms از گرفتن متغیرهای ناخواسته جلوگیری کرد – gensyms سمبل‌های واحد گارانتی شده‌ای هستند که می‌توانند در گسترش ماکروها بدون خطر گرفته شدن استفاده شوند.
مسئلهٔ دیگر سایه اندازی غیرعمدی عملگرهای استفاده شده در گسترش ماکروها است. مثلاً، کد زیر را ملاحظه کنید که یک گرفتن متغیر غیرعمدی را نشان می‌دهد:
 ;; expansion of UNTIL makes liberal use of DO
 (defmacro until (expression &body body)
   `(do () (,expression) ,@body))

 ;; macrolet establishes lexical operator binding for DO
 (macrolet ((do (...) ... something else ...))
   (until (= (random 10) 0) (write-line "Hello")))
ماکرو UNTIL به شکلی گسترش می‌یابد که DO را فراخوانی کند، که به منظور ارجاع شکل ویژه پایه‌ای DO طرح‌ریزی شده‌است. اگر چه، در این متن، DO ممکن است معنای کاملاً متفاوتی داشته باشد.
لیسپ معمولی مشکل سایه اندازی عملگر را با ممانعت از تعریف مجدد عملگرهای ساختمانی مثل DO در این مثال، اصلاح می‌کند. در ضمن، کاربران ممکن است کدهایشان را به بسته‌هایی تقسیم کنند. سمبل‌های ساختمانی در بستهٔ لیسپ معمولی p یافت می‌شوند، که بوسیلهٔ سمبل‌ها در بستهٔ کاربر سایه اندازی نمی‌شوند.

مقایسه با Lispهای دیگر

لیسپ معمولی بسیار با Scheme مقایسه می‌شود. چون آن‌ها دو نسخهٔ معروف از Lispها هستند. Scheme از لحاظ تاریخی جدیدتر از LC است، و نه تنها از همان Lisp می‌آید بلکه به وسیله بعضی از همان مهندسین نیز ارائه شده‌است.
Guy L. Steele با Gerald Jay Sussman, Scheme را طراحی کردند و کمیته استاندارد را برای لیسپ معمولی به کرسی نشاندند.
اغلب سیستم‌های Lisp که طراحی‌هایشان در لیسپ معمولی توزیع شده مثل Zetalisp و Franz Lisp تنها از متغیرهایی که به صورت پویا محدود شده‌اند استفاده کرده‌اند. Scheme متغیرهایی را به Lisp معرفی می‌کند که از لحاظ لغوی محدود شده‌اند و به صورت گسترده‌ای به عنوان یک ایدهٔ خوب شناخته شده و توسط CL اقتباس شده‌اند. CL متغیرهای محدود شدهٔ پویا را پشتیبانی می‌کند اما باید به‌طور صریح ذکر شود که آن‌ها دارای این مشخصه هستند.
لیسپ معمولی گاهی Lisp۲ نامیده می‌شود و Scheme, Lisp۱ که برمی گردد به استفادهٔ CL از فضاهای نامی مجزا برای توابع و متغیرها، (در حقیقت، CL فضاهای نامی زیادی دارد مانند آن‌هایی که بری کلمات کلیدی loop و نام‌های بلوکی و قالب go به کار می‌روند).
به علاوه پیاده‌سازی‌ها قصد دارند به مجموعه‌های منشعبی از بسته‌های کتابخانه برسند که عملکردی را که در استاندارد پوشش داده نشده، تأمین کنند. بعضی از این خصیصه‌ها به استاندارد اضافه شدند مانند CLOS و قرارداد LOOP؛ بقیه ویژهٔ پیاده‌سازی باقی ماندند.
متأسفانه بسیاری از وسایل ارزشمند برای برنامه‌نویس مدرن مثل شبکه بندی TCP/IP همچنان استاندارد نشده باقی مانده‌اند. اگر چه، بسته‌های با کد باز برای پشتیبانی چنین خصیصه‌هایی به روش قابل انتقال ایجاد شده‌اند، یک نمونه قابل توجه آن پروژهٔ مجمومهٔ کد باز لیسپ معمولی است.
لیسپ معمولی برای پیاده‌سازی وسیلهٔ کامپایلیرهای افزایشی طراحی شده‌است. تعریف‌های استاندارد به منظور بهینه کردن کامپایل در مشخصات زبان پیشنهاد شده‌است. اغلب پیاده‌سازی‌های لیسپ معمولی، توابع را برای بومی‌کردن کد ماشین کامپایل می‌کنند. بقیه به کد بایت کامپایل می‌کنند، که سرعت را کاهش می‌دهد ولی قابلیت انتقال کد باینری را آسان می‌کند. این تصور غلط که Lisp یک زبان کاملاً تفسیر شده‌است، بسیار متحمل است در نتیجهٔ این حقیقت باشد که محیط‌های لیسپ معمولی یک اعلان با اثر متقابل را تأمین می‌کند و اینکه توابع یکی یکی به یک ترتیب افزایشی کامپایل می‌شوند.
بعضی از پیاده‌سازی‌ها براساس Unix مثل CLISP، می‌توانند به عنوان مفسرهای متنی استفاده شوند؛ یعنی به وسیله سیستم به صورت شفاف به ترتیبی که مفسر Perl ویا Unix shell است، احضار شوند.