ตัวแปร Lisp เต็มแบบคงที่เป็นไปได้หรือไม่?


107

ตัวแปร Lisp เต็มแบบคงที่เป็นไปได้หรือไม่? มันสมเหตุสมผลหรือไม่ที่สิ่งนี้จะมีอยู่จริง? ฉันเชื่อว่าหนึ่งในคุณธรรมของภาษา Lisp คือความเรียบง่ายของคำจำกัดความ การพิมพ์แบบคงที่จะประนีประนอมหลักการสำคัญนี้หรือไม่?


10
ฉันชอบมาโครรูปอิสระของ Lisp แต่ฉันชอบความแข็งแกร่งของระบบประเภทของ Haskell ฉันอยากเห็นว่า Lisp ที่พิมพ์แบบคงที่จะเป็นอย่างไร
mcandre

4
คำถามที่ดี! ฉันเชื่อว่าshenlanguage.orgทำเช่นนั้น ฉันหวังว่ามันจะกลายเป็นกระแสหลักมากขึ้น
Hamish Grubijan


คุณทำการคำนวณเชิงสัญลักษณ์ด้วย Haskell ได้อย่างไร? (แก้ 'x' (= (+ xy) (* xy))) หากคุณใส่ไว้ในสตริงจะไม่มีการตรวจสอบ (ไม่เหมือนกับ Lisp ซึ่งสามารถใช้มาโครเพื่อเพิ่มการตรวจสอบได้) หากคุณใช้ประเภทข้อมูลหรือรายการเกี่ยวกับพีชคณิต ... มันจะละเอียดมาก: แก้ (Sym "x") (Eq (บวก (Sym "x") (Sym "y")) (Mult (Sym "x") (Sym "y"))
aoeu256

คำตอบ:


57

ใช่เป็นไปได้มากแม้ว่าระบบประเภท HM-style มาตรฐานมักจะเป็นตัวเลือกที่ไม่ถูกต้องสำหรับรหัส Lisp / Scheme สำนวนส่วนใหญ่ ดูTyped Racketสำหรับภาษาล่าสุดที่เป็น "Full Lisp" (เหมือน Scheme มากกว่า) ที่มีการพิมพ์แบบคงที่


1
ปัญหาคือรายการประเภทใดที่ประกอบเป็นซอร์สโค้ดทั้งหมดของโปรแกรมแร็กเก็ตที่พิมพ์
Zorf

18
Sexprที่มักจะเป็น
Eli Barzilay

แต่ฉันสามารถเขียนcoerce :: a->bในรูปแบบของ eval ประเภทความปลอดภัยอยู่ที่ไหน?
ssice

2
@ssice: เมื่อคุณใช้ฟังก์ชันที่ไม่ได้พิมพ์เช่นevalคุณต้องทดสอบผลลัพธ์เพื่อดูว่ามีอะไรออกมาบ้างซึ่งไม่มีอะไรใหม่ใน Typed Racked (จัดการเช่นเดียวกับฟังก์ชันที่ใช้ประเภทการรวมกันStringและNumber) วิธีที่ชัดเจนเพื่อดูว่าสิ่งนี้สามารถทำได้คือคุณสามารถเขียนและใช้ภาษาที่พิมพ์แบบไดนามิกในภาษาที่พิมพ์ด้วย HM-statically
Eli Barzilay

37

หากสิ่งที่คุณต้องการคือภาษาที่พิมพ์แบบคงที่ซึ่งดูเหมือน Lisp คุณสามารถทำได้ค่อนข้างง่ายโดยการกำหนดโครงสร้างไวยากรณ์นามธรรมที่แสดงถึงภาษาของคุณแล้วแมป AST นั้นกับนิพจน์ S อย่างไรก็ตามฉันไม่คิดว่าฉันจะเรียกผลลัพธ์ว่า Lisp

หากคุณต้องการบางสิ่งที่มีลักษณะ Lisp-y จริง ๆ นอกเหนือจากไวยากรณ์คุณสามารถทำได้ด้วยภาษาที่พิมพ์แบบคงที่ อย่างไรก็ตามมีหลายลักษณะของเสียงกระเพื่อมที่ยากต่อการพิมพ์แบบคงที่ที่มีประโยชน์มาก เพื่อเป็นตัวอย่างเรามาดูโครงสร้างรายการที่เรียกว่าข้อเสียซึ่งเป็นส่วนประกอบหลักของ Lisp

การเรียกข้อเสียว่ารายการแม้ว่าจะ(1 2 3)ดูเหมือนเป็นการเรียกชื่อผิดเล็กน้อย ตัวอย่างเช่นไม่สามารถเทียบเคียงได้กับรายการที่พิมพ์แบบคงที่เช่น C ++ std::listหรือรายการของ Haskell รายการเหล่านี้เป็นรายการที่เชื่อมโยงแบบมิติเดียวโดยที่เซลล์ทั้งหมดเป็นประเภทเดียวกัน (1 "abc" #\d 'foo)เสียงกระเพื่อมอย่างมีความสุขช่วย นอกจากนี้แม้ว่าคุณจะขยายรายการที่พิมพ์แบบคงที่ของคุณเพื่อให้ครอบคลุมรายการของรายการประเภทของวัตถุเหล่านี้ต้องการให้ทุกองค์ประกอบของรายการเป็นรายการย่อย คุณจะเป็นตัวแทนของ((1 2) 3 4)พวกเขาอย่างไร?

Lisp Conses ก่อตัวเป็นต้นไม้ไบนารีโดยมีใบไม้ (อะตอม) และกิ่งก้าน (conses) นอกจากนี้ใบของต้นไม้ดังกล่าวอาจมีประเภท Lisp ของอะตอม (ไม่ใช่ข้อเสีย) เลยก็ได้! ความยืดหยุ่นของโครงสร้างนี้คือสิ่งที่ทำให้ Lisp สามารถจัดการการคำนวณสัญลักษณ์ AST และการเปลี่ยนรหัส Lisp ได้เอง!

คุณจะสร้างโมเดลโครงสร้างดังกล่าวในภาษาที่พิมพ์แบบคงที่ได้อย่างไร? ลองใช้ใน Haskell ซึ่งมีระบบประเภทสถิตที่ทรงพลังและแม่นยำมาก:

type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons 
            | CAtom Atom

ปัญหาแรกของคุณคือขอบเขตของประเภท Atom เห็นได้ชัดว่าเรายังไม่ได้เลือกประเภท Atom ที่มีความยืดหยุ่นเพียงพอที่จะครอบคลุมวัตถุทุกประเภทที่เราต้องการจะโหนสลิง แทนที่จะพยายามขยายโครงสร้างข้อมูล Atom ตามที่ระบุไว้ด้านบน (ซึ่งคุณเห็นได้ชัดว่าเปราะ) สมมติว่าเรามีคลาสประเภทเวทย์มนตร์Atomicที่แยกแยะประเภททั้งหมดที่เราต้องการสร้างอะตอม จากนั้นเราอาจลอง:

class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons 
                          | CAtom a

แต่จะใช้ไม่ได้เพราะต้องใช้อะตอมทั้งหมดในต้นไม้เป็นประเภทเดียวกัน เราต้องการให้พวกมันสามารถแตกต่างกันไปในแต่ละใบไม้ แนวทางที่ดีกว่าต้องใช้ตัวระบุปริมาณที่มีอยู่ของ Haskell :

class Atomic a where ?????
data Cons = CCons Cons Cons 
            | forall a. Atomic a => CAtom a 

แต่ตอนนี้คุณมาถึงปมของเรื่องแล้ว อะตอมในโครงสร้างแบบนี้ทำอะไรได้บ้าง? โครงสร้างมีอะไรเหมือนกันที่สามารถจำลองได้ด้วยAtomic a? ประเภทดังกล่าวรับประกันความปลอดภัยในระดับใด โปรดทราบว่าเราไม่ได้เพิ่มฟังก์ชันใด ๆ ลงในคลาส type ของเราและมีเหตุผลที่ดี: อะตอมไม่มีอะไรเหมือนกันใน Lisp supertype ใน Lisp เรียกง่ายๆว่าt(คือด้านบน)

ในการใช้งานคุณจะต้องสร้างกลไกในการบังคับค่าของอะตอมแบบไดนามิกกับสิ่งที่คุณสามารถใช้ได้จริง และเมื่อถึงจุดนั้นคุณได้ติดตั้งระบบย่อยที่พิมพ์แบบไดนามิกภายในภาษาที่พิมพ์แบบคงที่! (เราไม่สามารถช่วยได้ แต่สังเกตข้อพิสูจน์ที่เป็นไปได้ของกฎการเขียนโปรแกรมที่สิบของ Greenspun )

โปรดทราบว่า Haskell ให้การสนับสนุนเฉพาะระบบย่อยแบบไดนามิกที่มีObjประเภทซึ่งใช้ร่วมกับDynamicประเภทและคลาส Typeableเพื่อแทนที่Atomicคลาสของเราซึ่งอนุญาตให้จัดเก็บค่าโดยพลการตามประเภทของพวกเขาและการบังคับกลับจากประเภทเหล่านั้นอย่างชัดเจน นั่นเป็นระบบที่คุณต้องใช้เพื่อทำงานกับโครงสร้าง Lisp cons ในลักษณะทั่วไปทั้งหมด

สิ่งที่คุณสามารถทำได้คือไปอีกทางหนึ่งและฝังระบบย่อยที่พิมพ์แบบคงที่ไว้ในภาษาที่พิมพ์แบบไดนามิกเป็นหลัก สิ่งนี้ช่วยให้คุณได้รับประโยชน์จากการตรวจสอบประเภทคงที่สำหรับส่วนต่างๆของโปรแกรมของคุณที่สามารถใช้ประโยชน์จากข้อกำหนดประเภทที่เข้มงวดมากขึ้น นี่ดูเหมือนจะเป็นแนวทางที่ใช้ในรูปแบบการตรวจสอบประเภทที่แม่นยำของ CMUCL แบบ จำกัดตัวอย่างเช่น

ในที่สุดมีความเป็นไปได้ที่จะมีระบบย่อยสองระบบแยกจากกันพิมพ์แบบไดนามิกและแบบคงที่ซึ่งใช้การเขียนโปรแกรมแบบสัญญาเพื่อช่วยนำทางในการเปลี่ยนระหว่างสองระบบ ด้วยวิธีนี้ภาษาสามารถรองรับการใช้งานของ Lisp ซึ่งการตรวจสอบประเภทคงที่จะเป็นอุปสรรคมากกว่าการช่วยเหลือเช่นเดียวกับการใช้ในกรณีที่การตรวจสอบประเภทคงที่จะเป็นประโยชน์ นี่เป็นแนวทางของTyped Racketดังที่คุณจะเห็นจากความคิดเห็นที่ตามมา


16
คำตอบนี้เกิดจากปัญหาพื้นฐาน: คุณสมมติว่าระบบประเภทคงที่ต้องเป็นแบบ HM แนวคิดพื้นฐานที่ไม่สามารถแสดงออกได้ที่นั่นและเป็นคุณสมบัติที่สำคัญของรหัส Lisp คือการพิมพ์ย่อย ถ้าคุณจะดูที่แร็กเกตพิมพ์คุณจะเห็นว่ามันสามารถแสดงชนิดของรายการใด ๆ - รวมทั้งสิ่งที่ชอบและ(Listof Integer) (Listof Any)เห็นได้ชัดว่าคุณสงสัยว่าหลังไม่มีประโยชน์เพราะคุณไม่รู้อะไรเกี่ยวกับประเภทนี้ แต่ใน TR คุณสามารถใช้งานได้ในภายหลัง(if (integer? x) ...)และระบบจะรู้ว่าxเป็นจำนวนเต็มในสาขาที่ 1
Eli Barzilay

5
โอ้และมันเป็นลักษณะที่ไม่ดีของแร็กเก็ตที่พิมพ์ (ซึ่งแตกต่างจากระบบประเภทที่ไม่ถูกต้องที่คุณพบในบางแห่ง) พิมพ์ไม้เป็นพิมพ์ staticallyภาษาโดยไม่มีค่าใช้จ่ายรันไทม์รหัสที่พิมพ์ แร็กเก็ตยังคงอนุญาตให้เขียนโค้ดบางส่วนใน TR และบางส่วนในภาษาที่ไม่ได้พิมพ์ตามปกติ - และสำหรับกรณีเหล่านี้สัญญา (การตรวจสอบแบบไดนามิก) จะใช้เพื่อป้องกันรหัสที่พิมพ์จากรหัสที่ไม่ได้พิมพ์ที่อาจทำงานผิดปกติ
Eli Barzilay

1
@Eli Barzilay: ฉันโกหกมีสี่ส่วน: 4. เป็นเรื่องที่น่าสนใจสำหรับฉันว่ารูปแบบการเข้ารหัส C ++ ที่ได้รับการยอมรับในอุตสาหกรรมนั้นค่อยๆห่างไกลจากการพิมพ์ย่อยไปสู่รูปแบบทั่วไปอย่างไร จุดอ่อนคือภาษาไม่ได้ให้ความช่วยเหลือในการประกาศอินเทอร์เฟซว่าฟังก์ชันทั่วไปกำลังจะใช้งานคลาสประเภทบางอย่างสามารถช่วยได้อย่างแน่นอน นอกจากนี้ C ++ 0x อาจเพิ่มการอนุมานประเภท ไม่ใช่ HM ฉันคิดว่า แต่กำลังคืบคลานไปในทิศทางนั้น?
Owen S.

1
โอเว่น: (1) ประเด็นหลักคือคุณต้องมีประเภทย่อยเพื่อแสดงประเภทของการเขียนโค้ดและคุณไม่สามารถทำได้ในระบบ HM ดังนั้นคุณจึงถูกบังคับให้ต้องกำหนดประเภทและตัวสร้างที่กำหนดเองสำหรับการใช้งานแต่ละครั้งซึ่ง ทำให้สิ่งทั้งหมดน่าอึดอัดมากขึ้นในการใช้ ในการพิมพ์แร็กเก็ตโดยใช้ระบบที่มีประเภทย่อยเป็นข้อพิสูจน์ของการตัดสินใจออกแบบโดยเจตนา: ผลลัพธ์ควรสามารถแสดงประเภทของรหัสดังกล่าวได้โดยไม่ต้องเปลี่ยนรหัสหรือสร้างประเภทที่กำหนดเอง
Eli Barzilay

1
(2) ใช่dynamicประเภทกำลังได้รับความนิยมในภาษาคงที่เป็นวิธีแก้ปัญหาชั่วคราวเพื่อให้ได้รับประโยชน์บางประการของภาษาที่พิมพ์แบบไดนามิกโดยการแลกเปลี่ยนตามปกติของค่าเหล่านี้จะถูกรวมไว้ในลักษณะที่ทำให้ประเภทต่างๆสามารถระบุตัวตนได้ แต่ที่นี่แร็กเก็ตที่พิมพ์ผิดก็ทำงานได้ดีมากในการทำให้สะดวกภายในภาษาตัวตรวจสอบประเภทใช้การเกิดขึ้นของเพรดิเคตเพื่อทราบข้อมูลเพิ่มเติมเกี่ยวกับประเภท ตัวอย่างเช่นดูตัวอย่างการพิมพ์ในหน้าแร็กเก็ตและดูว่าstring?"ลด" รายการสตริงและหมายเลขลงในรายการสตริงอย่างไร
Eli Barzilay

10

คำตอบของฉันโดยไม่ต้องมีระดับสูงของความเชื่อมั่นอาจจะ ตัวอย่างเช่นหากคุณดูภาษาเช่น SML และเปรียบเทียบกับ Lisp แกนการทำงานของแต่ละภาษาแทบจะเหมือนกัน ด้วยเหตุนี้ดูเหมือนว่าคุณจะไม่มีปัญหามากนักในการใช้การพิมพ์แบบคงที่กับแกนกลางของ Lisp (แอปพลิเคชันฟังก์ชันและค่าดั้งเดิม)

คำถามของคุณบอกได้เต็มปากเต็มคำและที่ฉันเห็นปัญหาบางอย่างเกิดขึ้นคือวิธีการโค้ดเป็นข้อมูล ประเภทมีอยู่ในระดับนามธรรมมากกว่านิพจน์ เสียงกระเพื่อมไม่มีความแตกต่างนี้ - โครงสร้างทุกอย่าง "แบน" ถ้าเราพิจารณานิพจน์บางอย่าง E: T (โดยที่ T เป็นตัวแทนของประเภทของมัน) จากนั้นเราจะถือว่านิพจน์นี้เป็นข้อมูล ol ธรรมดาแล้วประเภทของ T ตรงนี้คืออะไร? มันเป็นแบบ! ชนิดคือประเภทคำสั่งที่สูงกว่าดังนั้นเรามาพูดอะไรบางอย่างเกี่ยวกับสิ่งนั้นในรหัสของเรา:

E : T :: K

คุณอาจเห็นว่าฉันจะไปที่ไหนกับสิ่งนี้ ฉันแน่ใจว่าการแยกข้อมูลประเภทออกจากรหัสมันจะเป็นไปได้ที่จะหลีกเลี่ยงการอ้างอิงตัวเองประเภทนี้อย่างไรก็ตามจะทำให้ประเภทต่างๆไม่ "เสียงกระเพื่อม" ในรสชาติของมันมากนัก อาจมีหลายวิธีในการนี้แม้ว่าฉันจะไม่ชัดเจนว่าจะเป็นวิธีที่ดีที่สุด

แก้ไข: โอ้ด้วยการ googling เล็กน้อยฉันพบQiซึ่งดูเหมือนจะคล้ายกับ Lisp มากยกเว้นว่าจะพิมพ์แบบคงที่ บางทีอาจเป็นจุดเริ่มต้นที่ดีในการดูว่าพวกเขาทำการเปลี่ยนแปลงที่ใดเพื่อให้ได้การพิมพ์แบบคงที่


ดูเหมือนว่าการทำซ้ำครั้งต่อไปหลังจาก Qi คือShenซึ่งพัฒนาโดยบุคคลคนเดียวกัน
Diagon

4

ลิงค์ตายแล้ว แต่อย่างไรก็ตาม Dylan ไม่ได้พิมพ์แบบคงที่
Björn Lindqvist

@ BjörnLindqvist: ลิงก์นั้นไปยังวิทยานิพนธ์เกี่ยวกับการเพิ่มการพิมพ์ทีละน้อยให้กับ Dylan
Rainer Joswig

1
@ BjörnLindqvist: ฉันเชื่อมโยงกับกระดาษภาพรวม
Rainer Joswig

แต่การพิมพ์ทีละน้อยจะไม่นับเป็นการพิมพ์แบบคงที่ ถ้าเป็นเช่นนั้น Pypy จะพิมพ์ Python แบบคงที่เนื่องจากยังใช้การพิมพ์ทีละน้อย
Björn Lindqvist

2
@ BjörnLindqvist: ถ้าเราเพิ่มประเภทคงที่ผ่านการพิมพ์ทีละน้อยและสิ่งเหล่านี้จะถูกตรวจสอบในระหว่างการรวบรวมแสดงว่านี่คือการพิมพ์แบบคงที่ ไม่ใช่แค่ว่าทั้งโปรแกรมจะพิมพ์แบบคงที่ แต่เป็นส่วน / ภูมิภาค homes.sice.indiana.edu/jsiek/what-is- Gradual-typing 'การพิมพ์แบบค่อยเป็นค่อยไปเป็นระบบประเภทที่ฉันพัฒนาร่วมกับ Walid Taha ในปี 2549 ซึ่งอนุญาตให้พิมพ์บางส่วนของโปรแกรมแบบไดนามิกและส่วนอื่น ๆ สามารถพิมพ์แบบคงที่ได้'
Rainer Joswig
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.