วิธีทำ homoiconic ภาษา


16

ตามบทความนี้บรรทัดของเสียงกระเพื่อมพิมพ์ "Hello world" ไปยังเอาต์พุตมาตรฐาน

(format t "hello, world")

เสียงกระเพื่อมซึ่งเป็นภาษา homoiconicสามารถรักษารหัสเป็นข้อมูลด้วยวิธีนี้:

ตอนนี้จินตนาการว่าเราเขียนมาโครต่อไปนี้:

(defmacro backwards (expr) (reverse expr))

ย้อนกลับเป็นชื่อของแมโครซึ่งใช้นิพจน์ (แสดงเป็นรายการ) และย้อนกลับ นี่คือ "สวัสดีโลก" อีกครั้งคราวนี้ใช้มาโคร:

(backwards ("hello, world" t format))

เมื่อคอมไพเลอร์เสียงกระเพื่อมเห็นบรรทัดของรหัสนั้นมันจะดูที่อะตอมแรกในรายการ ( backwards) และสังเกตว่ามันตั้งชื่อแมโคร มันจะส่งรายการที่ไม่ได้ประเมินค่า("hello, world" t format)ไปยังแมโครซึ่งจัดเรียงรายการ(format t "hello, world")ใหม่เป็น รายการผลลัพธ์แทนที่นิพจน์แมโครและเป็นสิ่งที่จะถูกประเมินในเวลาทำงาน สภาพแวดล้อมเสียงกระเพื่อมจะเห็นว่าอะตอมแรก ( format) เป็นฟังก์ชั่นและประเมินมันส่งผ่านข้อโต้แย้งที่เหลือ

ในเสียงกระเพื่อมบรรลุภารกิจนี้เป็นเรื่องง่าย (แก้ไขฉันถ้าฉันผิด) เพราะรหัสจะดำเนินการตามรายการ ( s- นิพจน์ ?)

ตอนนี้ลองดูตัวอย่างโค้ด OCaml นี้ (ซึ่งไม่ใช่ภาษา homoiconic):

let print () =
    let message = "Hello world" in
    print_endline message
;;

ลองนึกภาพคุณต้องการเพิ่ม homoiconicity ให้กับ OCaml ซึ่งใช้ไวยากรณ์ที่ซับซ้อนกว่าเมื่อเปรียบเทียบกับ Lisp คุณจะทำอย่างไร ภาษาต้องมีไวยากรณ์ที่ง่ายเป็นพิเศษหรือไม่

แก้ไข : จากหัวข้อนี้ผมพบว่าวิธีการที่จะบรรลุ homoiconicity อื่นที่แตกต่างจากเสียงกระเพื่อมของ: หนึ่งนำมาใช้ในภาษา io มันอาจตอบคำถามนี้บางส่วน

ที่นี่เรามาเริ่มด้วยบล็อกง่ายๆ

Io> plus := block(a, b, a + b)
==> method(a, b, 
        a + b
    )
Io> plus call(2, 3)
==> 5

โอเคบล็อกทำงาน บล็อกบวกเพิ่มตัวเลขสองตัว

ทีนี้ลองใคร่ครวญกับเพื่อนตัวน้อยคนนี้

Io> plus argumentNames
==> list("a", "b")
Io> plus code
==> block(a, b, a +(b))
Io> plus message name
==> a
Io> plus message next
==> +(b)
Io> plus message next name
==> +

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

แต่เดี๋ยวก่อนเดี๋ยวก่อนเดี๋ยวก่อนอย่าแตะต้องนั้น

Io> plus message next setName("-")
==> -(b)
Io> plus
==> method(a, b, 
        a - b
    )
Io> plus call(2, 3)
==> -1

1
คุณอาจต้องการดูว่าScala ทำมาโครได้อย่างไร
Bergi

1
@Bergi Scala มีแนวทางใหม่ในการแมโคร: scala.meta
Martin Berger

ฉันมักจะพูดถึงเรื่อง homoiconicity ในภาษาที่ทรงพลังอย่างเพียงพอคุณสามารถกำหนดโครงสร้างแบบต้นไม้ที่สะท้อนโครงสร้างของภาษานั้น ๆ ได้และสามารถเขียนฟังก์ชันอรรถประโยชน์เพื่อแปลไปยังและจากภาษาต้นฉบับ (และ / หรือแบบฟอร์มที่คอมไพล์) ได้ตามต้องการ ใช่มันง่ายขึ้นเล็กน้อยใน LISP แต่เนื่องจาก (ก) งานเขียนโปรแกรมส่วนใหญ่ไม่ควรใช้โปรแกรมเปรียบเทียบและ (b) LISP ได้เสียสละความชัดเจนของภาษาเพื่อทำให้สิ่งนี้เป็นไปได้ฉันไม่คิดว่าการแลกเปลี่ยนนั้นคุ้มค่า
Periata Breatta

@PerataBreatta คุณพูดถูก แต่ข้อดีหลัก ๆ ของ MP ก็คือ MP ช่วยให้รูปแบบนามธรรมโดยไม่มีบทลงโทษในเวลาทำงาน ดังนั้น MP จะช่วยลดความตึงเครียดระหว่างสิ่งที่เป็นนามธรรมและประสิทธิภาพแม้ว่าจะมีค่าใช้จ่ายในการเพิ่มความซับซ้อนทางภาษา มันคุ้มหรือไม่ ฉันว่าความจริงที่ว่าPLs รายใหญ่ทั้งหมดมีส่วนขยาย MP บ่งชี้ว่าโปรแกรมเมอร์ที่ทำงานจำนวนมากพบว่าข้อเสนอ MP มีประโยชน์
Martin Berger

คำตอบ:


10

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

เสียงกระเพื่อมนั้นเร็วเพราะไวยากรณ์ที่ง่าย แต่ตระกูล MetaML ของ W. Taha แสดงให้เห็นว่ามันเป็นไปได้ที่จะทำสำหรับภาษาใด ๆ

กระบวนการทั้งหมดจะระบุไว้ในการสร้างแบบจำลองที่เป็นเนื้อเดียวกัน meta-การเขียนโปรแกรมกำเนิด แนะนำที่มีน้ำหนักเบามากขึ้นเพื่อวัสดุเดียวกันคือที่นี่


1
ช่วยแก้ให้ด้วยนะถ้าฉันผิด. "การมิร์เรอร์" เกี่ยวข้องกับส่วนที่สองของคำถาม (homoiconicity ใน io lang) ใช่ไหม
incud

@Ignus ฉันไม่แน่ใจว่าฉันเข้าใจคำพูดของคุณอย่างเต็มที่ วัตถุประสงค์ของการ homoiconicity คือการเปิดใช้งานการรักษารหัสเป็นข้อมูล นั่นหมายความว่ารูปแบบของรหัสใด ๆ จะต้องมีการแสดงเป็นข้อมูล มีหลายวิธีในการทำเช่นนี้ (เช่น ASTs quasi-quotes โดยใช้ชนิดเพื่อแยกความแตกต่างของรหัสจากข้อมูลที่ทำโดยวิธีการจัดเตรียมแบบโมดูลาร์น้ำหนักเบา) แต่ทั้งหมดนั้นต้องการไวยากรณ์สองภาษา / การสะท้อนของภาษาในบางรูปแบบ
Martin Berger

ฉันคิดว่า @Ignus จะได้ประโยชน์จากการดู MetaOCaml หรือไม่? การเป็น "homoiconic" หมายถึงการอ้างถึงแล้วหรือไม่? ฉันคิดว่าภาษาแบบหลายขั้นตอนเช่น MetaML และ MetaOCaml จะไปไกลกว่านี้ไหม
สตีเวนชอว์

1
@StevenShaw MetaOCaml เป็นที่น่าสนใจมากโดยเฉพาะ Oleg ใหม่MetaOCaml BER อย่างไรก็ตามมันค่อนข้าง จำกัด ในการที่จะดำเนินการเฉพาะการเขียนโปรแกรมเมตาเวลาทำงานและแสดงรหัสเฉพาะผ่านทางคำพูดเสมือนซึ่งไม่ได้แสดงความเป็น AST
Martin Berger

7

คอมไพเลอร์ Ocaml ถูกเขียนใน Ocaml ตัวเองดังนั้นแน่นอนมีวิธีการจัดการกับ asts Ocaml ใน Ocaml

เราอาจจินตนาการว่าการเพิ่มประเภทบิวด์อินocaml_syntaxเข้ากับภาษาและมีdefmacroฟังก์ชั่นบิวท์อินซึ่งรับอินพุตเป็นประเภทพูด

f : ocaml_syntax -> ocaml_syntax

ตอนนี้ประเภทของdefmacroอะไร นั่นขึ้นอยู่กับอินพุตราวกับว่าfเป็นฟังก์ชั่นเอกลักษณ์ประเภทของชิ้นส่วนผลลัพธ์ของโค้ดจะขึ้นอยู่กับส่วนของไวยากรณ์ที่ส่งผ่าน

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

defmacro : (ocaml_syntax -> ocaml_syntax) -> 'a

ซึ่งจะอนุญาตให้ใช้แมโครในบริบทใด ๆ แต่มันไม่ปลอดภัยแน่นอนมันจะอนุญาตให้ a boolถูกใช้แทนการstringcrashing โปรแกรมในเวลาทำงาน

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

สรุป: มีไวยากรณ์ที่ซับซ้อนไม่เป็นปัญหาเนื่องจากมีหลายวิธีที่จะเป็นตัวแทนของไวยากรณ์ภาษาภายในและอาจใช้ meta-การเขียนโปรแกรมเช่นquoteการดำเนินการเพื่อฝัง "ง่าย" ocaml_syntaxไวยากรณ์เข้าสู่ภายใน

ปัญหานี้ทำให้พิมพ์ได้ดีโดยเฉพาะอย่างยิ่งมีกลไกแมโครแบบรันไทม์ที่ไม่อนุญาตให้มีข้อผิดพลาดประเภท

มีเวลารวบรวมกลไกสำหรับแมโครในภาษาเช่น Ocaml เป็นไปได้แน่นอนเห็นเช่นMetaOcaml

อาจเป็นประโยชน์ด้วย: ถนนเจนในการเขียนโปรแกรมเมตาใน Ocaml


2
MetaOCaml มีการเขียนโปรแกรมรันไทม์เมตาไม่ได้รวบรวมโปรแกรมเมตาเวลาคอมไพล์ นอกจากนี้ระบบการพิมพ์ของ MetaOCaml ก็ไม่มีประเภทที่ขึ้นอยู่กับ (MetaOCaml ก็พบว่าไม่ปลอดภัยต่อประเภท!) Template Haskell มีวิธีการที่น่าสนใจในระดับกลาง: ทุกด่านมีประเภทที่ปลอดภัย แต่เมื่อเข้าสู่เวทีใหม่เราจะต้องทำการตรวจสอบประเภทอีกครั้ง มันใช้งานได้ดีในทางปฏิบัติในประสบการณ์ของฉันและคุณไม่สูญเสียประโยชน์ของความปลอดภัยประเภทในขั้นตอนสุดท้าย (เวลาทำงาน)
Martin Berger

@ มันเป็นไปได้หรือไม่ที่จะมี metaprogramming ใน OCaml เช่นเดียวกันกับExtension Pointsใช่ไหม?
incud

@ ฉันกลัวว่าฉันไม่รู้เกี่ยวกับคะแนนส่วนขยายมากนักถึงแม้ว่าฉันจะอ้างอิงในลิงก์ไปยังบล็อกของ Jane Street
ดี้

1
คอมไพเลอร์ C ของฉันเขียนด้วย C แต่นั่นไม่ได้หมายความว่าคุณสามารถจัดการ AST ใน C ...
BlueRaja - Danny Pflughoeft

2
@immibis: เห็นได้ชัดว่า แต่ถ้านั่นคือสิ่งที่เขาหมายถึงว่าคำสั่งนั้นทั้งว่างและไม่เกี่ยวข้องกับคำถาม ...
BlueRaja - Danny Pflughoeft

1

เป็นตัวอย่างให้พิจารณา F # (ขึ้นอยู่กับ OCaml) F # ไม่เหมือนกันอย่างสมบูรณ์ แต่สนับสนุนการรับโค้ดของฟังก์ชันในฐานะ AST ภายใต้สถานการณ์บางอย่าง

ใน F # คุณprintจะได้เป็นตัวแทนExprที่ถูกพิมพ์เป็น:

Let (message, Value ("Hello world"), Call (None, print_endline, [message]))

หากต้องการเน้นโครงสร้างให้ดีขึ้นต่อไปนี้เป็นอีกทางเลือกวิธีที่คุณสามารถสร้างโครงสร้างเดิมได้Expr:

let messageVar = Var("message", typeof<string>)
let expr = Expr.Let(messageVar,
                    Expr.Value("Hello world"),
                    Expr.Call(print_endline_method, [Expr.Var(messageVar)]))

ฉันไม่เข้าใจ คุณหมายความว่า F # ให้คุณ "สร้าง" AST ของนิพจน์แล้วเรียกใช้งานได้หรือไม่ ถ้าใช่ภาษาต่างกันที่ให้คุณใช้eval(<string>)ฟังก์ชั่นอะไร? ( ตามแหล่งข้อมูลมากมายการมีฟังก์ชั่นการวิเคราะห์นั้นแตกต่างจากการมีความรักร่วมเพศ - เป็นเหตุผลที่คุณพูดว่า F # ไม่เหมือนกันทั้งหมดหรือไม่)
รวม

@Ignus คุณสามารถสร้าง AST ได้ด้วยตัวเองหรือให้คอมไพเลอร์ทำ Homoiconicity "ช่วยให้ทุกรหัสในภาษาที่จะเข้าถึงและเปลี่ยนเป็นข้อมูล" ใน F # คุณสามารถเข้าถึงบางรหัสเป็นข้อมูล (ตัวอย่างเช่นคุณต้องทำเครื่องหมายprintด้วยแอ[<ReflectedDefinition>]ททริบิว)
svick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.