سؤال

أحتاج إلى بعض المساعدة في فهم بعض النقاط من بول غراهام ما الذي جعل lisp مختلفًا.

  1. مفهوم جديد للمتغيرات. في LISP ، جميع المتغيرات هي مؤشرات فعالة. القيم هي التي لها أنواع ، وليس المتغيرات ، ومتغيرات تعيين أو ربط يعني نسخ المؤشرات ، وليس ما يشيرون إليه.

  2. نوع رمز. تختلف الرموز عن الأوتار التي يمكنك اختبار المساواة من خلال مقارنة المؤشر.

  3. تدوين للرمز باستخدام أشجار الرموز.

  4. اللغة كلها متوفرة دائما. لا يوجد تمييز حقيقي بين وقت القراءة والوقت التجويف ووقت التشغيل. يمكنك تجميع أو تشغيل الرمز أثناء القراءة أو قراءة أو تشغيل الرمز أثناء التجميع أو قراءة أو تجميع الرمز في وقت التشغيل.

ماذا تعني هذه النقاط؟ كيف تختلف في لغات مثل C أو Java؟ هل لدى أي لغات أخرى غير لغات عائلة Lisp أي من هذه البنيات الآن؟

هل كانت مفيدة؟

المحلول

إن تفسير مات جيد تمامًا - وهو يأخذ لقطة مقارنة بـ C و Java ، وهو ما لن أفعله - لكن لسبب ما أستمتع حقًا بمناقشة هذا الموضوع بالذات مرة واحدة في حين ، لذلك - ها هي تسديدة في إجابة.

في النقاط (3) و (4):

يبدو أن النقطتين (3) و (4) في قائمتك هي الأكثر إثارة للاهتمام وما زالت ذات صلة الآن.

لفهمهم ، من المفيد أن يكون لديك صورة واضحة لما يحدث مع كود LISP - في شكل دفق من الأحرف التي كتبها المبرمج - في طريقها إلى تنفيذها. دعونا نستخدم مثالًا ملموسًا:

;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])

;; this is the interesting bit:
(println (str/replace-re #"\d+" "FOO" "a123b4c56"))

هذا المقتطف من clojure يطبع الكود aFOObFOOcFOO. لاحظ أن Clojure يمكن القول أنه لا يفي تمامًا بالنقطة الرابعة في قائمتك ، لأن وقت القراءة ليس مفتوحًا حقًا لرمز المستخدم ؛ سأناقش ما يعنيه أن يكون ذلك خلاف ذلك.

لذلك ، لنفترض أننا حصلنا على هذا الرمز في ملف في مكان ما ونطلب من Clojure تنفيذه. أيضًا ، لنفترض (من أجل البساطة) أننا تجاوزنا استيراد المكتبة. يبدأ البت المثير للاهتمام في (println وينتهي في ) بعيدا إلى اليمين. هذا هو lexed / تحليل كما يتوقع المرء ، ولكن بالفعل نقطة مهمة تنشأ: والنتيجة ليست بعض تمثيل AST الخاص بالمترجمة-إنه مجرد بنية بيانات Clojure / LISP منتظمة, ، وهي قائمة متداخلة تحتوي #"\d+" حرفي (المزيد على هذا أدناه). يضيف بعض lasps تحولات صغيرة خاصة بهم إلى هذه العملية ، لكن بول غراهام كان يشير في الغالب إلى LISP المشتركة. على النقاط ذات الصلة بسؤالك ، يشبه Clojure CL.

اللغة كلها في وقت الترجمة:

بعد هذه النقطة ، يتعامل جميع المترجمات (سيكون هذا صحيحًا أيضًا بالنسبة لمترجم LISP ؛ يحدث رمز clojure دائمًا أن يتم تجميعه) هو هياكل بيانات LISP التي يتم استخدام مبرمجي LISP للتلاعب بها. في هذه المرحلة ، يصبح الاحتمال الرائع واضحًا: لماذا لا تسمح لمبرمجي LISP بكتابة وظائف LISP التي تعالج بيانات LISP التي تمثل برامج LISP والبيانات المحولة التي تمثل البرامج المحولة ، لاستخدامها بدلاً من النسخ الأصلية؟ بمعنى آخر - لماذا لا تسمح لمبرمجي LISP بتسجيل وظائفهم كإضافات مترجم من نوع ما ، تسمى وحدات الماكرو في LISP؟ وبالفعل أي نظام LISP لائق لديه هذه السعة.

لذلك ، تعد وحدات الماكرو وظائف LISP العادية تعمل على تمثيل البرنامج في وقت الترجمة ، قبل مرحلة التجميع النهائية عند انبعاث رمز الكائن الفعلي. نظرًا لعدم وجود حدود على أنواع وحدات الماكرو التي يُسمح لها بالتشغيل (على وجه الخصوص ، فإن الكود الذي يديرونه غالبًا ما يكون مكتوبًا مع الاستخدام الليبرالي لمرفق الماكرو) ، يمكن للمرء أن يقول أن "اللغة بأكملها متوفرة في وقت الترجمة ".

اللغة كلها في وقت القراءة:

دعنا نعود إلى ذلك #"\d+" regex حرفي. كما ذكر أعلاه ، يتم تحويل هذا إلى كائن نمط حقيقي تم تجميعه في وقت القراءة ، قبل أن يسمع برنامج التحويل البرمجي أول ذكر للرمز الجديد الذي يتم إعداده للتجميع. كيف يحدث هذا؟

حسنًا ، الطريقة التي يتم بها تنفيذ Clojure حاليًا ، تختلف الصورة إلى حد ما عما كان يفكر فيه بول غراهام ، على الرغم من أن أي شيء ممكن اختراق ذكي. في LISP المشتركة ، ستكون القصة أنظف قليلاً من الناحية المفاهيمية. ومع ذلك ، فإن الأساسيات متشابهة: قارئ LISP هو آلة حالة ، بالإضافة إلى إجراء التحولات الحكومية وإعلان في النهاية ما إذا كانت قد وصلت إلى "حالة قبول" ، يبصق هياكل بيانات LISP التي تمثلها الشخصيات. وهكذا الشخصيات 123 تصبح الرقم 123 الخ. النقطة المهمة تأتي الآن: يمكن تعديل جهاز الحالة هذا بواسطة رمز المستخدم. (كما ذكرنا سابقًا ، هذا صحيح تمامًا في حالة CL ؛ بالنسبة إلى Clojure ، يلزم اختراق (لم يتم إحباطه وعدم استخدامه في الممارسة). لكنني أستطرد ، إنه مقال PG الذي من المفترض أن أتفقده ، لذلك ...)

لذلك ، إذا كنت من مبرمج LISP شائع وتصادف فكرة حرفية المتجهات على غرار Clojure ، فيمكنك فقط توصيل القارئ وظيفة للتفاعل بشكل مناسب مع بعض تسلسل الأحرف- [ أو #[ ربما - وتعامل معها باعتبارها بداية للنهاية الحرفية المتجهة في المطابقة ]. مثل هذه الوظيفة تسمى أ القارئ ماكرو ومثل الماكرو العادي ، يمكنه تنفيذ أي نوع من كود LISP ، بما في ذلك الكود الذي تمت كتابته بتكرار غير تقليدي تمكينه من قبل وحدات الماكرو المسجلة مسبقًا. لذلك هناك اللغة بأكملها في وقت القراءة لك.

قم بتغليفه:

في الواقع ، ما تم توضيحه حتى الآن هو أنه يمكن للمرء تشغيل وظائف LISP العادية في وقت القراءة أو وقت الترجمة ؛ الخطوة الواحدة التي يحتاجها المرء أن يأخذ من هنا إلى فهم كيف تكون القراءة والتجميع هي نفسها ممكنة في وقت القراءة أو التجميع أو التشغيل هي إدراك أن القراءة والتجميع يتم تنفيذها بواسطة وظائف LISP. يمكنك الاتصال فقط read أو eval في أي وقت للقراءة في بيانات LISP من تدفقات الأحرف أو ترجمة وتنفيذ رمز LISP ، على التوالي. هذه هي اللغة كلها هناك ، طوال الوقت.

لاحظ كيف أن LISP يرضي النقطة (3) من قائمتك ضرورية للطريقة التي تمكنت بها من تلبية النقطة (4) - تعتمد النكهة الخاصة من وحدات الماكرو التي توفرها LISP اعتمادًا كبيرًا على التعليمات البرمجية التي يتم تمثيلها بواسطة بيانات LISP العادية ، وهو شيء تم تمكينه بواسطة (3). بالمناسبة ، فإن جانب "Tree-ish" فقط من الكود أمر بالغ الأهمية هنا-يمكن أن يكون لديك LISP مكتوب باستخدام XML.

نصائح أخرى

1) مفهوم جديد للمتغيرات. في LISP ، جميع المتغيرات هي مؤشرات فعالة. القيم هي التي لها أنواع ، وليس المتغيرات ، ومتغيرات تعيين أو ربط يعني نسخ المؤشرات ، وليس ما يشيرون إليه.

(defun print-twice (it)
  (print it)
  (print it))

"إنه" متغير. يمكن أن يكون ملزما بأي قيمة. لا يوجد تقييد ولا يوجد نوع مرتبط بالمتغير. إذا اتصلت بالوظيفة ، فلن تحتاج الوسيطة إلى نسخها. المتغير يشبه المؤشر. لديها وسيلة للوصول إلى القيمة المرتبطة بالمتغير. ليست هناك حاجة ل الاحتياطي ذاكرة. يمكننا تمرير أي كائن بيانات عندما نسمي الوظيفة: أي حجم وأي نوع.

تحتوي كائنات البيانات على "نوع" ويمكن الاستعلام عن جميع كائنات البيانات عن "نوعها".

(type-of "abc")  -> STRING

2) نوع رمز. تختلف الرموز عن الأوتار التي يمكنك اختبار المساواة من خلال مقارنة المؤشر.

الرمز هو كائن بيانات يحمل اسمًا. عادة ما يمكن استخدام الاسم للعثور على الكائن:

|This is a Symbol|
this-is-also-a-symbol

(find-symbol "SIN")   ->  SIN

نظرًا لأن الرموز هي كائنات بيانات حقيقية ، يمكننا اختبار ما إذا كانت نفس الكائن:

(eq 'sin 'cos) -> NIL
(eq 'sin 'sin) -> T

هذا يسمح لنا على سبيل المثال بكتابة جملة مع الرموز:

(defvar *sentence* '(mary called tom to tell him the price of the book))

الآن يمكننا حساب عدد الجملة في الجملة:

(count 'the *sentence*) ->  2

في رموز LISP المشتركة ، ليس لها اسم فحسب ، بل يمكن أيضًا أن يكون لها قيمة ووظيفة وقائمة خاصية وحزمة. لذلك يمكن استخدام الرموز لتسمية المتغيرات أو الوظائف. عادة ما تستخدم قائمة الممتلكات لإضافة بيانات التعريف إلى الرموز.

3) تدوين للرمز باستخدام أشجار الرموز.

تستخدم LISP هياكل البيانات الأساسية لتمثيل التعليمات البرمجية.

يمكن أن تكون القائمة (* 3 2) البيانات والرمز:

(eval '(* 3 (+ 2 5))) -> 21

(length '(* 3 (+ 2 5))) -> 3

الشجرة:

CL-USER 8 > (sdraw '(* 3 (+ 2 5)))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 *        3       [*|*]--->[*|*]--->[*|*]--->NIL
                   |        |        |
                   v        v        v
                   +        2        5

4) اللغة كلها متوفرة دائما. لا يوجد تمييز حقيقي بين وقت القراءة والوقت التجويف ووقت التشغيل. يمكنك تجميع أو تشغيل الرمز أثناء القراءة أو قراءة أو تشغيل الرمز أثناء التجميع أو قراءة أو تجميع الرمز في وقت التشغيل.

يوفر LISP الوظائف القراءة لقراءة البيانات والرمز من النص ، وتحميلها لتحميل التعليمات البرمجية ، وتقييم لتقييم التعليمات البرمجية ، وتجميعها لتجميع الرمز والطباعة لكتابة البيانات والرمز إلى النص.

هذه الوظائف متاحة دائما. لا يذهبون بعيدا. يمكن أن تكون جزءا من أي برنامج. هذا يعني أن أي برنامج يمكنه قراءة أو تحميل أو تقييم أو طباعة رمز - دائمًا.

كيف تختلف في لغات مثل C أو Java؟

لا توفر هذه اللغات الرموز أو التعليمات البرمجية كبيانات أو تقييم وقت التشغيل للبيانات كرمز. عادة ما تكون كائنات البيانات في C غير نمطية.

هل لدى أي لغات أخرى غير لغات عائلة Lisp أي من هذه البنيات الآن؟

العديد من اللغات لديها بعض هذه القدرات.

الفرق:

في lisp تم تصميم هذه القدرات في اللغة بحيث تكون سهلة الاستخدام.

للنقاط (1) و (2) ، يتحدث تاريخيا. متغيرات Java هي نفسها إلى حد كبير ، وهذا هو السبب في أنك تحتاج إلى الاتصال .equals () لمقارنة القيم.

(3) يتحدث عن التعبير S. تتم كتابة برامج LISP في هذا الجملة ، والتي توفر الكثير من المزايا على بناء الجملة المخصصة مثل Java و C ، مثل التقاط الأنماط المتكررة في وحدات الماكرو بطريقة أنظف بكثير من قوالب C ++ ، ومعالجة الرمز مع نفس القائمة الأساسية العمليات التي تستخدمها للبيانات.

(4) أخذ C على سبيل المثال: اللغة هي حقًا لغتين فرعيتين مختلفتين: أشياء مثل if () وحيث () ، والمعالج المسبق. يمكنك استخدام المعالج المسبق لحفظ الاضطرار إلى تكرار نفسك طوال الوقت ، أو لتخطي التعليمات البرمجية باستخدام #if/ #IFDEF. لكن كلتا اللغتين منفصلة تمامًا ، ولا يمكنك استخدامها بينما () في وقت الترجم كما يمكنك #F.

C ++ يجعل هذا أسوأ مع القوالب. تحقق من عدد قليل من المراجع على القالب metaprogramming ، والتي توفر وسيلة لتوليد رمز في وقت الترجمة ، ومن الصعب للغاية على غير الخبراء لف رؤوسهم حولها. بالإضافة إلى ذلك ، إنها حقًا مجموعة من الاختراقات والحيل التي تستخدم القوالب ووحدات الماكرو التي لا يمكن للمترجم تقديمها لدعم الدرجة الأولى - إذا قمت بإجراء خطأ في بناء الجملة بسيطًا ، فإن المترجم غير قادر على إعطائك رسالة خطأ واضحة.

حسنًا ، مع Lisp ، لديك كل هذا بلغة واحدة. يمكنك استخدام نفس الأشياء لإنشاء التعليمات البرمجية في وقت التشغيل كما تتعلم في يومك الأول. هذا لا يشير إلى أن metaprogramming تافهة ، ولكنه بالتأكيد أكثر وضوحًا مع دعم اللغة من الدرجة الأولى ودعم التحويل البرمجي.

النقاط (1) و (2) سوف تناسب أيضا بيثون. أخذ مثال بسيط "a = str (82.4)" يقوم المترجم أولاً بإنشاء كائن نقطة عائم ذو قيمة 82.4. ثم يستدعي مُنشئ سلسلة يقوم بعد ذلك بإرجاع سلسلة مع القيمة '82 .4 '. "A" على الجانب الأيسر هو مجرد تسمية لكائن السلسلة هذا. تم جمع كائن النقطة العائمة الأصلية القمامة لأنه لا توجد إشارات إليها.

في المخطط ، يتم التعامل مع كل شيء ككائن بطريقة مماثلة. لست متأكدًا من مشترك Lisp. سأحاول تجنب التفكير من حيث مفاهيم C/C ++. لقد أبطئوني في أكوام عندما كنت أحاول الحصول على رأسي حول البساطة الجميلة للاثنائية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top