เอะอะอะไรเกี่ยวกับ Haskell? [ปิด]


109

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

ใครช่วยยกตัวอย่างของ Haskell ที่แสดงให้เห็นว่าทำไมมันถึงดูสง่างาม / เหนือกว่า

คำตอบ:


134

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

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


47
ควรสังเกตว่าข้อความแสดงข้อผิดพลาดของ Haskell ไม่ได้ขาดหายไป ghc คือ มาตรฐาน Haskell ไม่ได้ระบุวิธีการทำข้อความแสดงข้อผิดพลาด
PyRulez

สำหรับคนที่ชอบฉัน GHC ย่อมาจาก Glasgow Haskell Compiler en.wikipedia.org/wiki/Glasgow_Haskell_Compiler
Lorem Ipsum

137

นี่คือตัวอย่างที่เชื่อฉันจะเรียนรู้ Haskell (และเด็กชายนฉันดีใจที่ฉันไม่)

-- program to copy a file --
import System.Environment

main = do
         --read command-line arguments
         [file1, file2] <- getArgs

         --copy file contents
         str <- readFile file1
         writeFile file2 str

ตกลงมันเป็นโปรแกรมสั้น ๆ ที่อ่านได้ ในแง่นั้นมันดีกว่าโปรแกรม C แต่สิ่งนี้แตกต่างจาก (พูด) โปรแกรม Python ที่มีโครงสร้างคล้ายกันมากอย่างไร?

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

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

ตามที่เป็นอยู่ Haskell ตระหนักดีว่าwriteFileขึ้นอยู่กับreadFileและสามารถเพิ่มประสิทธิภาพเส้นทางข้อมูลนี้ได้

แม้ว่าผลลัพธ์จะขึ้นอยู่กับคอมไพเลอร์ แต่โดยทั่วไปสิ่งที่จะเกิดขึ้นเมื่อคุณเรียกใช้โปรแกรมข้างต้นคือสิ่งนี้: โปรแกรมอ่านบล็อก (พูด 8KB) ของไฟล์แรกจากนั้นเขียนลงในไฟล์ที่สองจากนั้นอ่านบล็อกอื่นจากไฟล์แรก ไฟล์และเขียนลงในไฟล์ที่สองและอื่น ๆ (ลองวิ่งstraceดูสิ!)

... ซึ่งดูเหมือนมากว่าการใช้ C อย่างมีประสิทธิภาพของการคัดลอกไฟล์จะทำอย่างไร

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

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

  1. การออกแบบโปรแกรมที่ดีขึ้น ความซับซ้อนที่ลดลงทำให้เกิดข้อผิดพลาดทางตรรกะน้อยลง

  2. รหัสขนาดกะทัดรัด มีข้อบกพร่องน้อยลง

  3. ข้อผิดพลาดในการรวบรวม จำนวนของข้อบกพร่องเพียงแค่ไม่ถูกต้อง Haskell

Haskell ไม่ใช่สำหรับทุกคน แต่ทุกคนควรลองดู


คุณจะเปลี่ยนค่าคงที่ 8KB ได้อย่างไร (หรืออะไรก็ตาม)? เพราะฉันพนันได้เลยว่าการใช้งาน Haskell จะช้ากว่ารุ่น C เป็นอย่างอื่นโดยเฉพาะอย่างยิ่งหากไม่มีการดึงข้อมูลล่วงหน้า ...
user541686

1
@Mehrdad คุณสามารถเปลี่ยนขนาดบัฟเฟอร์ด้วยhSetBuffering handle (BlockBuffering (Just bufferSize))ไฟล์.
เดวิด

3
เป็นที่น่าอัศจรรย์ที่คำตอบนี้มี 116 upvotes แต่สิ่งที่อยู่ในนั้นไม่ถูกต้อง โปรแกรมนี้จะอ่านไฟล์ทั้งหมดเว้นแต่คุณจะใช้ Bytestrings ที่ขี้เกียจ (ซึ่งคุณสามารถทำได้Data.Bytestring.Lazy.readFile) ซึ่งไม่เกี่ยวข้องกับการที่ Haskell เป็นภาษาที่ขี้เกียจ (ไม่เข้มงวด) Monads กำลังจัดลำดับซึ่งหมายความว่า "ผลข้างเคียงทั้งหมดจะเกิดขึ้นเมื่อคุณนำผลลัพธ์ออกมา" สำหรับเวทมนตร์ "lazy Bytestring": สิ่งนี้อันตรายและคุณสามารถทำได้ด้วยไวยากรณ์ที่คล้ายกันหรือง่ายกว่าในภาษาอื่น ๆ ส่วนใหญ่
Jo So

14
มาตรฐานเดิมน่าเบื่อreadFileยังไม่ขี้เกียจ IO ในทางเดียวกันData.ByteString.Lazy.readFileไม่ ดังนั้นคำตอบจึงไม่ผิดและไม่ใช่แค่การเพิ่มประสิทธิภาพคอมไพเลอร์เท่านั้น อันที่จริงนี่เป็นส่วนหนึ่งของข้อมูลจำเพาะของ Haskell : " readFileฟังก์ชันอ่านไฟล์และส่งคืนเนื้อหาของไฟล์เป็นสตริงไฟล์จะถูกอ่านอย่างเฉื่อยชาตามความต้องการเช่นเดียวกับgetContents"
Daniel Wagner

1
ฉันคิดว่าคำตอบอื่น ๆ ชี้ไปที่สิ่งที่พิเศษกว่าเกี่ยวกับ Haskell หลายภาษา / const fs = require('fs'); const [file1, file2] = process.argv.slice(2); fs.createReadStream(file1).pipe(fs.createWriteStream(file2))สภาพแวดล้อมที่มีลำธารที่คุณสามารถทำบางสิ่งบางอย่างที่คล้ายกันในโหนด: Bash ก็มีบางอย่างที่คล้ายกันเช่นกัน:cat $1 > $2
Max Heiber

64

คุณเป็นคนถามคำถามผิด

Haskell ไม่ใช่ภาษาที่คุณจะไปดูตัวอย่างเจ๋ง ๆ สักสองสามตัวอย่างแล้วไป "aha ฉันเห็นแล้วนั่นคือสิ่งที่ทำให้มันดี!"

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

  • ขี้เกียจประเมิน
  • ไม่มีผลข้างเคียง (ทุกอย่างบริสุทธิ์ IO / etc เกิดขึ้นผ่าน monads)
  • ระบบประเภทคงที่ที่แสดงออกอย่างไม่น่าเชื่อ

เช่นเดียวกับแง่มุมอื่น ๆ ที่แตกต่างจากภาษากระแสหลักหลายภาษา (แต่บางส่วนใช้ร่วมกัน)

  • การทำงาน
  • ช่องว่างที่สำคัญ
  • พิมพ์อนุมาน

ตามที่ผู้โพสต์คนอื่น ๆ ตอบไว้การรวมคุณสมบัติทั้งหมดนี้หมายความว่าคุณคิดเกี่ยวกับการเขียนโปรแกรมในลักษณะที่แตกต่างไปจากเดิมอย่างสิ้นเชิง ดังนั้นจึงยากที่จะสร้างตัวอย่าง (หรือชุดตัวอย่าง) ที่สื่อสารสิ่งนี้กับ Joe-mainstream-programmer ได้อย่างเพียงพอ มันเป็นสิ่งที่เป็นประสบการณ์ (เพื่อเป็นการเปรียบเทียบฉันสามารถแสดงรูปถ่ายของฉันที่เดินทางไปประเทศจีนในปี 1970 แต่หลังจากดูรูปถ่ายแล้วคุณยังไม่รู้ว่าการใช้ชีวิตที่นั่นเป็นอย่างไรในช่วงเวลานั้นในทำนองเดียวกันฉันสามารถแสดง Haskell ให้คุณดูได้ 'quicksort' แต่คุณยังไม่รู้ว่าการเป็น Haskeller หมายถึงอะไร)


17
ฉันไม่เห็นด้วยกับประโยคแรกของคุณ ฉันประทับใจตัวอย่างของโค้ด Haskell ในตอนแรกและสิ่งที่ทำให้ฉันเชื่อว่ามันคุ้มค่าที่จะเรียนรู้คือบทความนี้: cs.dartmouth.edu/~doug/powser.html แต่แน่นอนว่านี่เป็นสิ่งที่น่าสนใจสำหรับนักคณิตศาสตร์ / นักฟิสิกส์ โปรแกรมเมอร์ที่มองโลกแห่งความเป็นจริงจะพบว่าตัวอย่างนี้ไร้สาระ
Rafael S. Calsaverini

2
@ ราฟาเอล: นั่นทำให้เกิดคำถามว่า "โปรแกรมเมอร์ที่มองโลกแห่งความเป็นจริงจะประทับใจอะไร"?
JD

คำถามที่ดี! ฉันไม่ใช่โปรแกรมเมอร์ "โลกแห่งความจริง" ดังนั้นฉันจึงไม่รู้ว่าพวกเขาชอบอะไร ฮ่า ๆ ๆ ... ฉันรู้ว่านักฟิสิกส์และนักคณิตศาสตร์ชอบอะไร : P
Rafael S. Calsaverini

27

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

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


1
มีความเป็นไปได้เสมอที่จะใช้ ST monad และ / หรือunsafePerformIOสำหรับผู้ที่ต้องการดูโลกมอดไหม้เท่านั้น)
sara

22

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

Haskell ช่วยให้คุณมีตัวเลือกในการใช้งานคู่ขนานมากกว่าภาษาที่ใช้งานทั่วไปพร้อมกับคอมไพเลอร์โค้ดเนทีฟที่รวดเร็ว ไม่มีการแข่งขันใด ๆ กับการสนับสนุนรูปแบบคู่ขนานแบบนี้:

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


"พร้อมกับคอมไพเลอร์โค้ดเนทีฟที่รวดเร็ว"?
JD

ฉันเชื่อว่า dons อ้างถึง GHCI
Gregory Higley

3
@Jon: shootout.alioth.debian.org/u32/… Haskell ทำได้ดีในการยิงเช่นกัน
Peaker

4
@ จอน: โค้ดการยิงนั้นเก่ามากและจากอดีตอันไกลโพ้นที่ GHC เป็นคอมไพเลอร์ที่ปรับแต่งได้น้อยกว่า ถึงกระนั้นก็ยังพิสูจน์ได้ว่ารหัส Haskell สามารถไปในระดับต่ำเพื่อให้ได้ประสิทธิภาพหากจำเป็น โซลูชันใหม่ในการยิงนั้นมีสำนวนมากกว่าและยังรวดเร็ว
Peaker

1
@GregoryHigley มีความแตกต่างระหว่าง GHCI และ GHC
Jeremy List

18

Software Transactional Memoryเป็นวิธีที่ยอดเยี่ยมในการจัดการกับภาวะพร้อมกัน มีความยืดหยุ่นมากกว่าการส่งข้อความและไม่เกิดการชะงักงันเหมือน mutexes การใช้งาน STM ของGHCถือเป็นหนึ่งในสิ่งที่ดีที่สุด


18

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

อาจเป็นชัยชนะที่ยิ่งใหญ่ที่สุดสำหรับฉันคือความสามารถในการควบคุมการไหลแบบแยกส่วนผ่านสิ่งต่าง ๆ เช่น monoids monads และอื่น ๆ ตัวอย่างง่ายๆคือ Ordering monoid; ในนิพจน์เช่น

c1 `mappend` c2 `mappend` c3

ที่c1และอื่น ๆ ในการกลับมาLT, EQหรือGT, c1กลับEQสาเหตุการแสดงออกที่จะดำเนินการประเมินผลc2; หากc2ส่งคืนLTหรือGTนั่นคือมูลค่าของทั้งหมดและc3ไม่ได้รับการประเมิน สิ่งเหล่านี้มีความซับซ้อนและซับซ้อนมากขึ้นในสิ่งต่างๆเช่นตัวสร้างข้อความ monadic และตัวแยกวิเคราะห์ซึ่งฉันอาจต้องแบกรับสถานะประเภทต่างๆมีเงื่อนไขการทำแท้งที่แตกต่างกันหรืออาจต้องการให้สามารถตัดสินใจสำหรับการเรียกใด ๆ โดยเฉพาะว่าการยกเลิกหมายถึงจริงหรือไม่ "ไม่มีการประมวลผลเพิ่มเติม" หรือหมายความว่า "ส่งคืนข้อผิดพลาดในตอนท้าย แต่ดำเนินการประมวลผลเพื่อรวบรวมข้อความแสดงข้อผิดพลาดเพิ่มเติม"

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

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


2
น่าสนใจมาก! รหัส Haskell เข้าสู่ระบบการซื้อขายอัตโนมัติของคุณทั้งหมดกี่บรรทัด? คุณจัดการกับความทนทานต่อความผิดพลาดได้อย่างไรและคุณได้รับผลการปฏิบัติงานประเภทใด เมื่อเร็ว ๆ นี้ฉันคิดว่า Haskell มีศักยภาพที่จะดีสำหรับการเขียนโปรแกรมเวลาแฝงต่ำ ...
JD

12

สำหรับตัวอย่างที่น่าสนใจคุณสามารถดูได้ที่: http://en.literateprograms.org/Quicksort_(Haskell)

สิ่งที่น่าสนใจคือการดูการนำไปใช้งานในภาษาต่างๆ

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

ดังที่ได้กล่าวไว้ข้างต้น Haskell และภาษาที่ใช้งานได้อื่น ๆ มีความสามารถในการประมวลผลแบบขนานและการเขียนแอปพลิเคชันเพื่อทำงานบนมัลติคอร์


2
การเรียกซ้ำคือระเบิด ที่และรูปแบบที่ตรงกัน
Ellery Newcomer

1
การกำจัด for and while ลูปเป็นส่วนที่ยากที่สุดสำหรับฉันเมื่อเขียนด้วยภาษาที่ใช้งานได้ :)
James Black

4
การเรียนรู้ที่จะคิดแบบวนซ้ำแทนการวนซ้ำเป็นส่วนที่ยากที่สุดสำหรับฉันเช่นกัน เมื่อมันจมลงไปในที่สุดมันก็เป็นหนึ่งในความหมายของการเขียนโปรแกรมที่ยิ่งใหญ่ที่สุดที่ฉันเคยมีมา
Chris Connett

8
ยกเว้นว่าโปรแกรมเมอร์ Haskell ที่ใช้งานได้แทบจะไม่ใช้การเรียกซ้ำแบบดั้งเดิม ส่วนใหญ่คุณใช้ฟังก์ชันไลบรารีเช่นแผนที่และพับ
Paul Johnson

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

8

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


1
อย่าลืมอ่านซอร์สโค้ดของคอมไพเลอร์ นอกจากนี้ยังจะให้ข้อมูลที่มีค่ามากมาย
JD

7

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

ตัวอย่างเช่นหากคุณต้องการคำนวณราคาทั้งหมดคุณสามารถใช้

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]

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

foo = sum $ takeWhile (<100) primes

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

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


2
นี่ไม่เป็นความจริงทั้งหมด ด้วยพฤติกรรมผลตอบแทนของ C # (ภาษาเชิงวัตถุ) คุณยังสามารถประกาศรายการที่ไม่มีที่สิ้นสุดที่ประเมินตามความต้องการ
Jeff Yates

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

8
คุณอาจสนใจที่จะอ่านว่าทำไม "ตะแกรง" จึงไม่ใช่ Sieve of Eratosthenes: lambda-the-ultimate.org/node/3127
Chris Conway

@ คริส: ขอบคุณนั่นเป็นบทความที่น่าสนใจจริงๆ! ฟังก์ชัน primes ข้างต้นไม่ใช่ฟังก์ชันที่ฉันใช้ในการคำนวณของตัวเองเนื่องจากทำงานช้าอย่างเจ็บปวด อย่างไรก็ตามบทความนำเสนอประเด็นที่ดีว่าการตรวจสอบตัวเลขทั้งหมดสำหรับ mod เป็นอัลกอริทึมที่แตกต่างกันจริงๆ
waxwing

6

ฉันเห็นด้วยกับคนอื่น ๆ ว่าการดูตัวอย่างเล็ก ๆ น้อย ๆ ไม่ใช่วิธีที่ดีที่สุดในการอวด Haskell แต่ฉันจะให้บ้าง นี่คือวิธีแก้ปัญหาของEuler Project ที่ 18 และ 67อย่างรวดเร็วซึ่งขอให้คุณหาเส้นทางผลรวมสูงสุดจากฐานถึงปลายสามเหลี่ยม:

bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
  where bu [bottom]     = bottom
        bu (row : base) = merge row $ bu base
        merge [] [_] = []
        merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)

นี่คือการใช้อัลกอริทึมBubbleSearch ที่สมบูรณ์และนำกลับมาใช้ใหม่ได้โดย Lesh และ Mitzenmacher ฉันใช้มันเพื่อแพ็คไฟล์มีเดียขนาดใหญ่สำหรับการจัดเก็บเอกสารในดีวีดีโดยไม่ต้องเสีย:

data BubbleResult i o = BubbleResult { bestResult :: o
                                     , result :: o
                                     , leftoverRandoms :: [Double]
                                     }
bubbleSearch :: (Ord result) =>
                ([a] -> result) ->       -- greedy search algorithm
                Double ->                -- probability
                [a] ->                   -- list of items to be searched
                [Double] ->              -- list of random numbers
                [BubbleResult a result]  -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
    where bubble order rs = BubbleResult answer answer rs : walk tries
            where answer = search order
                  tries  = perturbations p order rs
                  walk ((order, rs) : rest) =
                      if result > answer then bubble order rs
                      else BubbleResult answer result rs : walk rest
                    where result = search order

perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
    where xr' = perturb xs rs
          perturb :: [a] -> [Double] -> ([a], [Double])
          perturb xs rs = shift_all p [] xs rs

shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
  where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
        shift_one new' xs rs k = shift new' [] xs rs
          where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
                shift new' prev' (x:xs) (r:rs) 
                    | r <= p    = k (x:new') (prev' `revApp` xs) rs
                    | otherwise = shift new' (x:prev') xs rs
                revApp xs ys = foldl (flip (:)) ys xs

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

จากตัวอย่างที่คุณขอมาฉันจะบอกว่าวิธีที่ดีที่สุดในการเริ่มชื่นชม Haskellคือการอ่านบทความที่ให้แนวคิดที่ฉันต้องการในการเขียน DVD packer: Why Functional Programming Mattersโดย John Hughes กระดาษนี้มีมาก่อน Haskell แต่มันอธิบายแนวคิดบางอย่างที่ทำให้คนชอบ Haskell ได้อย่างยอดเยี่ยม


5

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

ฉันได้เขียนมากรหัสจำลองทางวิทยาศาสตร์และสงสัยดังนั้นหลายครั้งถ้ามีข้อผิดพลาดในรหัสของฉันก่อนซึ่งอาจทำให้การทำงานมากในปัจจุบัน


6
ไม่รับประกันความถูกต้องได้อย่างไร?
Jonathan Fischoff

ส่วนที่บริสุทธิ์ของโค้ดนั้นปลอดภัยกว่าส่วนที่ไม่บริสุทธิ์มาก ระดับความไว้วางใจ / ความพยายามลงทุนสูงขึ้น
สมมุติ

1
อะไรทำให้คุณประทับใจขนาดนั้น?
JD

5

ฉันพบว่าสำหรับงานบางอย่างฉันมีประสิทธิผลอย่างเหลือเชื่อกับ Haskell

สาเหตุเป็นเพราะไวยากรณ์ที่รวบรัดและความง่ายในการทดสอบ

นี่คือลักษณะของไวยากรณ์การประกาศฟังก์ชัน:

foo a = a + 5

นั่นเป็นวิธีที่ง่ายที่สุดที่ฉันคิดได้ในการกำหนดฟังก์ชัน

ถ้าฉันเขียนผกผัน

ผกผัน Foo a = a - 5

ฉันสามารถตรวจสอบได้ว่าเป็นอินเวอร์สสำหรับอินพุตแบบสุ่มโดยการเขียน

prop_IsInverse :: Double -> Bool
prop_IsInverse a = a == (inverseFoo $ foo a)

และการโทรจากบรรทัดคำสั่ง

jonny @ ubuntu: runhaskell quickCheck + ชื่อ fooFileName.hs

ซึ่งจะตรวจสอบว่าคุณสมบัติทั้งหมดในไฟล์ของฉันถูกเก็บไว้โดยการสุ่มทดสอบอินพุตเป็นร้อย ๆ ครั้ง

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


คุณกำลังแก้ปัญหาอะไรและคุณได้ลองใช้ภาษาใดบ้าง
JD

1
กราฟิก 3 มิติแบบเรียลไทม์สำหรับมือถือและ iPad
Jonathan Fischoff

3

ถ้าคุณสามารถห่อหัวของคุณรอบ ๆ ระบบประเภทใน Haskell ฉันคิดว่าในตัวมันเองก็ค่อนข้างประสบความสำเร็จ


1
จะได้อะไร? หากคุณต้องคิดว่า "data" == "class" และ "typeclass" = "interface" / "role" / "trait" มันไม่ง่ายไปกว่านี้แล้ว (ไม่มีแม้แต่ "โมฆะ" ที่จะทำให้คุณวุ่นวาย Null เป็นแนวคิดที่คุณสามารถสร้างเป็นประเภทของคุณเองได้)
jrockway

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

2

มันไม่มีโครงสร้างแบบวนซ้ำ มีภาษาไม่มากนักที่มีลักษณะนี้


17
ghci>: m + Control.Monad ghci> forM_ [1..3] พิมพ์ 1 2 3
sastanin

1

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


-1

ในการถ่ายทอดมุมมองที่ตรงกันข้าม: Steve Yegge เขียนว่าภาษา Hindely-Milner ขาดความยืดหยุ่นที่จำเป็นในการเขียนระบบที่ดี :

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

Haskell คุ้มค่ากับการเรียนรู้ แต่ก็มีจุดอ่อนในตัวเอง


5
ในขณะที่มันเป็นความจริงอย่างแน่นอนที่ระบบประเภทที่แข็งแกร่งโดยทั่วไปต้องการให้คุณปฏิบัติตาม (นั่นคือสิ่งที่ทำให้ความแข็งแกร่งของพวกเขามีประโยชน์) แต่ก็เป็นกรณีที่ระบบประเภทที่ใช้ HM ที่มีอยู่จำนวนมาก (ส่วนใหญ่?) ในความเป็นจริงมีบางประเภท ' Escape hatch 'ตามที่อธิบายไว้ในลิงค์ (ใช้ Obj.magic ใน O'Caml เป็นตัวอย่างแม้ว่าฉันจะไม่เคยใช้มันยกเว้นแฮ็ก) อย่างไรก็ตามในทางปฏิบัติสำหรับโปรแกรมหลายประเภทเราไม่จำเป็นต้องใช้อุปกรณ์ดังกล่าว
Zach Snow

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

5
-1: ข้อความของสตีฟบางส่วนล้าสมัย แต่ส่วนใหญ่ผิดจริงอย่างสิ้นเชิง การ จำกัด มูลค่าที่ผ่อนคลายของ OCaml และระบบประเภทของ. NET เป็นตัวอย่างที่เห็นได้ชัดสำหรับข้อความของเขา
JD

4
Steve Yegge มีผึ้งที่ไม่มีเหตุผลในฝากระโปรงของเขาเกี่ยวกับการพิมพ์แบบคงที่และไม่เพียง แต่เป็นสิ่งที่เขาพูดเกี่ยวกับเรื่องนี้ไม่ถูกต้องเท่านั้นเขายังนำมันมาใช้ในทุกโอกาสที่มีอยู่ (และแม้แต่บางคนที่ไม่พร้อมใช้งาน) คุณควรเชื่อเฉพาะประสบการณ์ของคุณเองในเรื่องนี้
ShreevatsaR

3
แม้ว่าฉันจะไม่เห็นด้วยกับ Yegge ในเรื่องการพิมพ์แบบคงที่และแบบไดนามิก แต่ Haskell ก็มีประเภท Data.Dynamic หากคุณต้องการพิมพ์แบบไดนามิกคุณสามารถมีได้!
jrockway
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.