เขียนล่าม Haskell ใน Haskell


90

แบบฝึกหัดการเขียนโปรแกรมแบบคลาสสิกคือการเขียนล่าม Lisp / Scheme ใน Lisp / Scheme พลังของภาษาเต็มสามารถใช้เพื่อสร้างล่ามสำหรับส่วนย่อยของภาษา

มีการออกกำลังกายที่คล้ายกันสำหรับ Haskell หรือไม่? ฉันต้องการติดตั้งชุดย่อยของ Haskell โดยใช้ Haskell เป็นเครื่องยนต์ แน่นอนว่าสามารถทำได้ แต่มีแหล่งข้อมูลออนไลน์ให้ดูหรือไม่?


นี่คือเรื่องราวเบื้องหลัง

ฉันกำลังสำรวจแนวคิดในการใช้ Haskell เป็นภาษาเพื่อสำรวจแนวคิดบางอย่างในหลักสูตรโครงสร้างไม่ต่อเนื่องที่ฉันกำลังสอน สำหรับภาคการศึกษานี้ฉันได้ใช้มิแรนดาซึ่งเป็นภาษาเล็ก ๆ ที่เป็นแรงบันดาลใจให้ฮัสเคลล์ มิแรนดาทำประมาณ 90% ในสิ่งที่ฉันอยากทำ แต่ Haskell ทำประมาณ 2,000% :)

ดังนั้นความคิดของฉันคือการสร้างภาษาที่มีคุณสมบัติของ Haskell ที่ฉันต้องการและไม่อนุญาตอย่างอื่น ในขณะที่นักเรียนก้าวหน้าขึ้นฉันสามารถเลือก "เปิด" คุณสมบัติต่างๆได้เมื่อพวกเขาเข้าใจพื้นฐานแล้ว

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


ฉันมีภาษาถิ่น WIP Haskell ที่ใช้กับ Typing Haskell ใน Haskell เป็นฐาน มีการสาธิตอยู่ที่นี่chrisdone.com/toys/duet-deltaยังไม่พร้อมสำหรับการเปิดตัวโอเพ่นซอร์สสาธารณะ แต่ฉันสามารถแบ่งปันแหล่งที่มากับคุณได้หากสนใจ
Christopher Done

คำตอบ:


76

ฉันรักเป้าหมายของคุณ แต่มันเป็นงานใหญ่ คำแนะนำสองสามข้อ:

  • ฉันทำงานเกี่ยวกับ GHC และคุณไม่ต้องการส่วนใดส่วนหนึ่งของแหล่งที่มา Hugsเป็นการใช้งานที่ง่ายและสะอาดกว่ามาก แต่น่าเสียดายที่อยู่ในภาษา C

  • มันเป็นปริศนาชิ้นเล็ก ๆ แต่ Mark Jones เขียนกระดาษสวย ๆ ชื่อTyping Haskell ใน Haskellซึ่งจะเป็นจุดเริ่มต้นที่ดีสำหรับส่วนหน้าของคุณ

โชคดี! การระบุระดับภาษาสำหรับ Haskell โดยมีหลักฐานสนับสนุนจากห้องเรียนจะเป็นประโยชน์อย่างยิ่งต่อชุมชนและผลลัพธ์ที่เผยแพร่ได้อย่างแน่นอน!


2
ฉันสงสัยว่าความคิดเห็นเกี่ยวกับ GHC ยังคงถูกต้องหรือไม่ GHC มีความซับซ้อน แต่มีเอกสารค่อนข้างดี โดยเฉพาะอย่างยิ่งภายในNotesมีประโยชน์ในการทำความเข้าใจรายละเอียดระดับต่ำและบทที่เกี่ยวกับ GHC ในThe Architecture of Open-Source Applicationsจะให้ภาพรวมระดับสูงที่ยอดเยี่ยม
sjy

37

มีตัวแยกวิเคราะห์ Haskell ที่สมบูรณ์: http://hackage.haskell.org/package/haskell-src-exts

เมื่อคุณแยกวิเคราะห์แล้วการลอกออกหรือไม่อนุญาตบางสิ่งก็ทำได้ง่าย ฉันทำสิ่งนี้เพื่อให้ tryhaskell.org ไม่อนุญาตให้นำเข้าคำสั่งเพื่อรองรับคำจำกัดความระดับบนสุด ฯลฯ

เพียงแยกวิเคราะห์โมดูล:

parseModule :: String -> ParseResult Module

จากนั้นคุณมี AST สำหรับโมดูล:

Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]    

ประเภท Decl นั้นกว้างขวาง: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t%3ADecl

สิ่งที่คุณต้องทำคือกำหนด white-list - สิ่งที่มีการประกาศการนำเข้าสัญลักษณ์ไวยากรณ์จากนั้นเดิน AST และโยน "ข้อผิดพลาดในการแยกวิเคราะห์" ในสิ่งที่คุณไม่ต้องการให้พวกเขารับรู้ คุณสามารถใช้ค่า SrcLoc ที่แนบกับทุกโหนดใน AST:

data SrcLoc = SrcLoc
     { srcFilename :: String
     , srcLine :: Int
     , srcColumn :: Int
     }

ไม่จำเป็นต้องนำ Haskell มาใช้ใหม่ หากคุณต้องการจัดเตรียมข้อผิดพลาดในการคอมไพล์ที่เป็นมิตรมากขึ้นเพียงแค่แยกวิเคราะห์รหัสกรองส่งไปยังคอมไพเลอร์และแยกวิเคราะห์เอาต์พุตของคอมไพเลอร์ หากเป็น "ไม่สามารถจับคู่ประเภทที่คาดว่า a กับการอนุมานa -> b" ได้คุณก็จะรู้ว่าอาจมีอาร์กิวเมนต์น้อยเกินไปสำหรับฟังก์ชัน

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


24

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

การตีความ Haskell ด้วยมือจะซับซ้อนกว่ามากเนื่องจากคุณจะต้องจัดการกับคุณสมบัติที่ซับซ้อนสูงเช่นเครื่องพิมพ์ดีดระบบประเภทที่มีประสิทธิภาพมาก (การอนุมานประเภท!) และการประเมินแบบขี้เกียจ (เทคนิคการลด)

ดังนั้นคุณควรกำหนดส่วนย่อยของ Haskell ที่จะทำงานด้วยจากนั้นอาจเริ่มต้นด้วยการขยาย Scheme-example ทีละขั้นตอน

ส่วนที่เพิ่มเข้าไป:

โปรดทราบว่าใน Haskell คุณสามารถเข้าถึง API ของล่ามได้อย่างสมบูรณ์ (อย่างน้อยภายใต้ GHC) รวมถึงตัวแยกวิเคราะห์คอมไพเลอร์และล่ามแน่นอน

แพคเกจที่จะใช้เป็นคำใบ้ (Language.Haskell. *) ฉันไม่พบบทเรียนออนไลน์เกี่ยวกับเรื่องนี้และไม่ได้ลองใช้ด้วยตัวเอง แต่ดูเหมือนว่าจะมีแนวโน้มดี


12
โปรดทราบว่าการอนุมานประเภทเป็นอัลกอริทึม 20-30 บรรทัดที่ง่ายมาก มันสวยงามในความเรียบง่าย การประเมินความเกียจคร้านยังไม่ยากที่จะเข้ารหัส ฉันจะบอกว่าความยากอยู่ที่ไวยากรณ์ที่บ้าการจับคู่รูปแบบและเนื้อหาจำนวนมากในภาษา
Claudiu

น่าสนใจ - คุณสามารถโพสต์ลิงก์สำหรับการอนุมานประเภท algos ได้หรือไม่?
Dario

5
ใช่ลองดูหนังสือฟรีเล่มนี้ - cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26 - อยู่ในหน้า 273 (289 ของ pdf) alg pseudocode อยู่บน P296
Claudiu

1
นอกจากนี้ยังมีการใช้อัลกอริทึมการอนุมาน / การตรวจสอบประเภท (the?) ใน " การใช้งานภาษาเขียนโปรแกรมเชิงฟังก์ชัน "
Phil Armstrong

1
การอนุมานประเภทด้วยคลาสประเภทไม่ใช่เรื่องง่าย
Christopher Done

20

สร้างภาษาที่มีคุณลักษณะของ Haskell ที่ฉันต้องการและไม่อนุญาตอย่างอื่น ในขณะที่นักเรียนก้าวหน้าขึ้นฉันสามารถเลือก "เปิด" คุณสมบัติต่างๆได้เมื่อพวกเขาเข้าใจพื้นฐานแล้ว

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

นั่นจะคล้ายกับHLint (และตรงกันข้ามด้วย):

HLint (เดิมชื่อ Dr.Haskell) อ่านโปรแกรมของ Haskell และแนะนำการเปลี่ยนแปลงที่หวังว่าจะทำให้อ่านง่ายขึ้น นอกจากนี้ HLint ยังช่วยให้ปิดคำแนะนำที่ไม่ต้องการได้ง่ายและเพิ่มคำแนะนำที่คุณกำหนดเองได้

  • ใช้ "คำแนะนำ" HLint ของคุณเองเพื่อไม่ใช้คุณสมบัติที่คุณไม่อนุญาต
  • ปิดใช้งานคำแนะนำ HLint มาตรฐานทั้งหมด
  • ทำให้ Wrapper ของคุณเรียกใช้ HLint ที่แก้ไขแล้วเป็นขั้นตอนแรก
  • ถือว่าคำแนะนำ HLint เป็นข้อผิดพลาด นั่นคือถ้า HLint "บ่น" โปรแกรมจะไม่เข้าสู่ขั้นตอนการคอมไพล์


6

คอมไพเลอร์ EHC ซีรีส์น่าจะเป็นทางออกที่ดีที่สุด: ได้รับการพัฒนาอย่างแข็งขันและดูเหมือนว่าจะตรงกับที่คุณต้องการ - ชุดคอมไพเลอร์ / ล่ามแลมบ์ดาขนาดเล็กที่ปิดท้ายใน Haskell '98

แต่คุณยังสามารถดูภาษาต่างๆที่พัฒนาในประเภทและภาษาการเขียนโปรแกรมของเพียร์ซหรือล่ามฮีเลียม (Haskell ที่พิการมีไว้สำหรับนักเรียนhttp://en.wikipedia.org/wiki/Helium_(Haskell) )


6

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

ฉันเขียนคอมไพเลอร์ย่อย Haskell ที่รวบรวมด้วยตนเองสำหรับการท้าทาย Code Golf ใช้รหัสย่อยของ Haskell ในอินพุตและสร้างรหัส C บนเอาต์พุต ขออภัยไม่มีเวอร์ชันที่อ่านได้มากขึ้น ฉันยกคำจำกัดความที่ซ้อนกันด้วยมือในขั้นตอนการรวบรวมด้วยตนเอง

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

  • ขี้เกียจประเมิน. หากล่ามอยู่ใน Haskell คุณอาจไม่ต้องทำอะไรเลย

  • นิยามฟังก์ชันที่มีอาร์กิวเมนต์และยามที่ตรงกับรูปแบบ กังวลเกี่ยวกับตัวแปรข้อเสียศูนย์และ_รูปแบบเท่านั้น

  • ไวยากรณ์นิพจน์ทั่วไป:

    • ตัวอักษรจำนวนเต็ม

    • ตัวอักษรตามตัวอักษร

    • [] (ศูนย์)

    • แอปพลิเคชันฟังก์ชัน (เชื่อมโยงด้านซ้าย)

    • Infix :(ข้อเสียการเชื่อมโยงที่ถูกต้อง)

    • วงเล็บ

    • ชื่อตัวแปร

    • ชื่อฟังก์ชัน

เขียนล่ามที่สามารถรันสิ่งนี้ได้อย่างเป็นรูปธรรม:

-- tail :: [a] -> [a]
tail (_:xs) = xs

-- append :: [a] -> [a] -> [a]
append []     ys = ys
append (x:xs) ys = x : append xs ys

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
zipWith _ _      _      = []

-- showList :: (a -> String) -> [a] -> String
showList _    []     = '[' : ']' : []
showList show (x:xs) = '[' : append (show x) (showItems show xs)

-- showItems :: (a -> String) -> [a] -> String
showItems show []     = ']' : []
showItems show (x:xs) = ',' : append (show x) (showItems show xs)

-- fibs :: [Int]
fibs = 0 : 1 : zipWith add fibs (tail fibs)

-- main :: String
main = showList showInt (take 40 fibs)

การตรวจสอบประเภทเป็นคุณสมบัติที่สำคัญของ Haskell อย่างไรก็ตามการเปลี่ยนจากคอมไพเลอร์ Haskell การตรวจสอบประเภทเป็นเรื่องยากมาก หากคุณเริ่มต้นด้วยการเขียนล่ามสำหรับสิ่งที่กล่าวมาข้างต้นการเพิ่มการตรวจสอบประเภทลงไปก็น่าจะไม่ยุ่งยาก


"ขี้เกียจประเมินถ้าล่ามอยู่ใน Haskell คุณอาจไม่ต้องทำอะไรเลย" เรื่องนี้อาจไม่เป็นความจริง ดูบทความของ Naylor ในhaskell.org/wikiupload/0/0a/TMR-Issue10.pdfสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการใช้งานล่ามที่ขี้เกียจใน Haskell
Jared Updike


3

นี่อาจเป็นความคิดที่ดี - สร้าง NetLogo เวอร์ชันจิ๋วใน Haskell นี่คือล่ามตัวจิ๋ว


ลิงก์ตายแล้ว มีโอกาสที่เนื้อหานี้ยังคงมีอยู่ที่อื่นหรือไม่? ฉันอยากรู้อยากเห็น ...
Nicolas Payette

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

1
การค้นหา "netlogo haskell" ใน Google ปรากฏขึ้น ... คำถามนี้ อย่างไรก็ตามไม่มีเรื่องใหญ่ ขอบคุณ!
Nicolas Payette



2

ฉันได้รับแจ้งว่าIdrisมีตัวแยกวิเคราะห์ที่ค่อนข้างกะทัดรัดไม่แน่ใจว่าเหมาะสำหรับการดัดแปลงหรือไม่ แต่เขียนด้วย Haskell


2

สวนสัตว์ภาษาการเขียนโปรแกรมของ Andrej Bauerใช้งานได้เพียงเล็กน้อยซึ่งมีชื่อว่า "minihaskell" อย่างหน้าด้าน ๆ เป็น OCaml ประมาณ 700 เส้นย่อยง่ายมาก

ไซต์นี้ยังมีเวอร์ชันของเล่นของภาษาการเขียนโปรแกรม ML-style, Prolog-style และ OO


1

คุณไม่คิดว่าจะง่ายกว่าที่จะใช้แหล่งข้อมูล GHCและตัดสิ่งที่คุณไม่ต้องการออกไปมากกว่าที่จะเขียนล่าม Haskell ของคุณเองตั้งแต่เริ่มต้นหรือไม่? โดยทั่วไปควรมีความพยายามน้อยกว่ามากในการลบคุณลักษณะซึ่งต่างจากการสร้าง / เพิ่มคุณลักษณะ

GHC เขียนใน Haskell ดังนั้นในทางเทคนิคแล้วคำถามของคุณเกี่ยวกับล่าม Haskell ที่เขียนใน Haskell

อาจจะไม่ยากเกินไปที่จะทำให้สิ่งทั้งหมดเชื่อมโยงแบบคงที่แล้วแจกจ่ายเฉพาะ GHCi ที่กำหนดเองของคุณเพื่อให้นักเรียนไม่สามารถโหลดโมดูลซอร์ส Haskell อื่น ๆ ได้ เกี่ยวกับการป้องกันไม่ให้โหลดไฟล์อ็อบเจ็กต์ Haskell อื่น ๆ เท่าไหร่ฉันไม่รู้ คุณอาจต้องการปิด FFI ด้วยเช่นกันหากคุณมีคนขี้โกงในชั้นเรียนของคุณ :)


1
นี่ไม่ใช่เรื่องง่ายอย่างที่คิดเนื่องจากคุณสมบัติหลายอย่างขึ้นอยู่กับคุณสมบัติอื่น ๆ แต่บางที OP ก็แค่อยากจะไม่นำเข้า Prelude และให้ของเขาเองแทน Haskell ส่วนใหญ่ที่คุณเห็นเป็นฟังก์ชันปกติไม่ใช่คุณลักษณะเฉพาะของรันไทม์ ( แต่แน่นอนมากมี .)
jrockway

0

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

  • ตัวดำเนินการที่มีลำดับความสำคัญที่กำหนดได้การเชื่อมโยงและความมั่นคง
  • ความคิดเห็นที่ซ้อนกัน
  • กฎเค้าโครง
  • รูปแบบไวยากรณ์
  • do- บล็อกและ desugaring ไปยังรหัส monadic

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

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

นอกจากนี้คุณไม่ควรอายที่จะใช้ฟรอนต์เอนด์ของ GHC (หรือคอมไพเลอร์อื่น) ฉันไม่คิดว่าเป็นการโกงเนื่องจาก LISP ที่กำหนดเองใช้ตัวแยกวิเคราะห์ของระบบโฮสต์ LISP (อย่างน้อยในระหว่างการบูตเครื่อง) การล้างCoreข้อมูลโค้ดและนำเสนอให้นักเรียนพร้อมกับโค้ดต้นฉบับควรช่วยให้คุณสามารถให้ภาพรวมของสิ่งที่ส่วนหน้าทำและเหตุผลที่ไม่ควรนำมาใช้ซ้ำ

ลิงก์บางส่วนไปยังเอกสารCoreที่ใช้ใน GHC มีดังนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.