ผู้สนับสนุนการเขียนโปรแกรมเชิงฟังก์ชั่นจะตอบข้อความนี้ใน Code Complete อย่างไร


30

ในหน้า 839 ของรุ่นที่สอง Steve McConnell กำลังพูดถึงวิธีการทั้งหมดที่โปรแกรมเมอร์สามารถ "พิชิตความซับซ้อน" ในโปรแกรมขนาดใหญ่ เคล็ดลับของเขาถึงจุดสูงสุดด้วยข้อความนี้:

"การเขียนโปรแกรมเชิงวัตถุให้ระดับของนามธรรมที่ใช้กับอัลกอริธึมและข้อมูลในเวลาเดียวกันซึ่งเป็นนามธรรมชนิดหนึ่งที่การสลายตัวของฟังก์ชั่นเพียงอย่างเดียวไม่ได้ให้"

เมื่อรวมกับข้อสรุปของเขาว่า "การลดความซับซ้อนนั้นเป็นกุญแจสำคัญที่สุดในการเป็นโปรแกรมเมอร์ที่มีประสิทธิภาพ" (หน้าเดียวกัน) สิ่งนี้ดูเหมือนจะเป็นความท้าทายอย่างมากสำหรับการเขียนโปรแกรมเชิงหน้าที่

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

หากเราเปลี่ยนเงื่อนไขของการเปรียบเทียบระหว่าง FP และ OO จากประเด็นเฉพาะเช่นการเห็นพ้องด้วยหรือนำกลับมาใช้ใหม่เพื่อการจัดการความซับซ้อนระดับโลกการอภิปรายนั้นจะมีลักษณะอย่างไร

แก้ไข

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

ดูตัวอย่างเช่น Stuart Halloway (ผู้เสนอชื่อ Clojure FP) ที่นี่บอกว่า "ข้อมูลจำเพาะของประเภทข้อมูล" เป็น "ผลลัพธ์เชิงลบของลักษณะ OO แบบใช้สำนวน" และนิยมวางแนวคิด AddressBook เป็นเวกเตอร์หรือแผนที่ที่สมบูรณ์ยิ่งขึ้นแทนที่จะเป็นวัตถุ OO ที่สมบูรณ์ยิ่งขึ้น ที่มีคุณสมบัติและวิธีการเพิ่มเติม (นอกจากนี้ OO และผู้สนับสนุนการออกแบบโดเมนอาจบอกว่าการเปิดเผย AddressBook เป็นเวกเตอร์หรือแผนที่จะเปิดเผยข้อมูลที่ถูกห่อหุ้มไปยังวิธีที่ไม่เกี่ยวข้องหรืออันตรายจากจุดยืนของโดเมน)


3
+1 แม้คำถามจะถูกวางกรอบค่อนข้างเป็นปฏิปักษ์ แต่เป็นคำถามที่ดี
mattnz

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

5
ความรู้ของ McConnel ชัดเจนในระบบข้อมูลประเภทการทำงานที่ทันสมัยและโมดูลอันดับหนึ่งที่มีคุณภาพสูงนั้นค่อนข้างที่จะเห็นได้ชัด คำพูดของเขานั้นไร้สาระที่สุดเนื่องจากเรามีโมดูลและฟังก์ชั่นชั้นหนึ่ง (ดู SML), พิมพ์คลาส (ดู Haskell) มันเป็นเพียงตัวอย่างของวิธีการคิดของ OO เป็นศาสนามากกว่าวิธีการออกแบบที่น่านับถือ และโดยวิธีการที่คุณได้รับสิ่งนี้เกี่ยวกับการเกิดพร้อมกัน? โปรแกรมเมอร์ทำงานส่วนใหญ่ไม่สนใจเลยเกี่ยวกับความเท่าเทียม
SK-logic

6
@ SK- ตรรกะทั้งหมด McConnell กล่าวว่าเป็น "การสลายตัวการทำงานเพียงอย่างเดียว" ไม่ได้ให้วิธีการที่เป็นนามธรรมของ OOP ซึ่งดูเหมือนจะเป็นคำสั่งที่ปลอดภัยสำหรับฉัน เขาไม่มีที่ไหนเลยที่พูดว่าภาษา FP ไม่ได้มี abstractions ที่ทรงพลังเท่ากับ OOP ในความเป็นจริงเขาไม่ได้พูดถึงภาษา FP เลย นั่นเป็นเพียงการตีความของ OP
sepp2k

2
@ sepp2k โอเคฉันเข้าใจแล้ว แต่ถึงกระนั้นระบบโครงสร้างข้อมูลและการประมวลผลที่ซับซ้อนและซับซ้อนมากก็สามารถสร้างขึ้นได้โดยไม่ต้องเสียอะไรเลยนอกจากการย่อยสลายการทำงานสำหรับแคลคูลัสแลมบ์ดาเกือบบริสุทธิ์ - ผ่านการจำลองพฤติกรรมของโมดูล ไม่จำเป็นต้องใช้ abstractions OO เลย
SK-logic

คำตอบ:


13

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

เราจำเป็นต้องวางกรอบ "การย่อยสลายตามหน้าที่" ในบริบทที่เหมาะสมของงาน ผู้เขียนไม่ได้อ้างถึงการเขียนโปรแกรมการทำงาน เราจำเป็นต้องผูกกลับไปที่ "การเขียนโปรแกรมที่มีโครงสร้าง" และGOTOระเบียบที่เต็มไปด้วยที่มาก่อนมัน หากจุดอ้างอิงของคุณคือ FORTRAN / COBOL / BASIC เก่าที่ไม่มีฟังก์ชั่น (บางทีถ้าคุณโชคดีคุณจะได้ระดับ GOSUB ในระดับเดียว) และตัวแปรทั้งหมดของคุณเป็นแบบโกลบอลสามารถทำลายโปรแกรมของคุณได้ ในชั้นของฟังก์ชั่นเป็นประโยชน์ที่สำคัญ

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


27

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

มันควรจะชี้ให้เห็นว่า FP และ OOP เป็นแนวคิดแบบฉากและไม่เป็นเอกสิทธิ์ของกันและกัน ดังนั้นจึงไม่สมเหตุสมผลที่จะเปรียบเทียบกัน คุณสามารถเปรียบเทียบ "imperative OOP" (เช่น Java) กับ "functional OOP" (เช่น Scala) ได้เป็นอย่างดี แต่ข้อความที่คุณยกมานั้นจะไม่มีผลกับการเปรียบเทียบนั้น


10
+1 "ฟังก์ชั่นการสลายตัว"! = "โปรแกรมมิงฟังก์ชัน" ครั้งแรกอาศัยการเข้ารหัสตามลำดับแบบคลาสสิกโดยใช้โครงสร้างข้อมูลวานิลลาที่ไม่มีการสืบทอด (หรือการรีดด้วยมือเท่านั้น) การห่อหุ้มและ polymorphism ข้อที่สองเป็นการแสดงออกถึงการแก้ปัญหาโดยใช้แคลคูลัสแลมบ์ดา สองสิ่งที่แตกต่างอย่างสิ้นเชิง
ไบนารี Worrier

4
ขอโทษ แต่วลี "การเขียนโปรแกรมตามขั้นตอน" ปฏิเสธอย่างดื้อรั้นที่จะนึกถึงก่อนหน้านี้ "ฟังก์ชั่นการสลายตัว" สำหรับฉันนั้นมีความหมายมากกว่าการเขียนโปรแกรมเชิงโพรซีเดอร์มากกว่าการเขียนโปรแกรมเชิงฟังก์ชัน
ไบนารี Worrier

ใช่คุณพูดถูก. ฉันคิดว่าการเขียนโปรแกรมฟังก์ชั่นสนับสนุนฟังก์ชั่นที่ใช้ซ้ำได้ซึ่งทำงานบนโครงสร้างข้อมูลแบบธรรมดา (รายการต้นไม้แผนที่) ซ้ำแล้วซ้ำอีกและจริง ๆ แล้วอ้างว่านี่เป็นจุดขายเหนือ OO ดู Stuart Halloway (ผู้เสนอการ Clojure FP) ที่นี่บอกว่า "ข้อมูลจำเพาะของประเภทข้อมูล" เป็น "ผลลัพธ์เชิงลบของสไตล์ OO ที่ใช้สำนวน" และนิยมวางแนวคิดสมุดรายชื่อเป็นเวกเตอร์หรือแผนที่แทนที่จะเป็นวัตถุที่สมบูรณ์ยิ่งขึ้น OO คุณสมบัติ -vectorish & ไม่ใช่ maplike) และวิธีการ
แดน

การเชื่อมโยงสำหรับอ้าง Stuart Halloway: thinkrelevance.com/blog/2009/08/12/...
แดน

2
@dan นั่นอาจเป็นวิธีที่ทำในภาษาที่พิมพ์แบบไดนามิก Clojure (ฉันไม่รู้ฉันไม่ใช้ Clojure) แต่ฉันคิดว่ามันอันตรายที่จะสรุปจากนั้นนั่นเป็นวิธีที่ทำใน FP โดยทั่วไป ยกตัวอย่างเช่นคน Haskell ดูเหมือนจะมีขนาดใหญ่มากในรูปแบบนามธรรมและการซ่อนข้อมูล (อาจจะไม่มากเท่ากับคน Java)
sepp2k

7

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

ตัวอย่างเช่นฉันเพิ่งเขียนเกมใน Clojure และสถานะทั้งหมดของเกมถูกกำหนดไว้ในโครงสร้างข้อมูลเดียวที่ไม่เปลี่ยนรูป:

(def starting-game-state {:map ....
                          :player ....
                          :weather ....
                          :other-stuff ....}

และสามารถกำหนดวงเกมหลักเป็นการใช้ฟังก์ชั่นบริสุทธิ์บางอย่างกับสถานะเกมในวง:

 (loop [initial-state starting-game-state]
   (let [user-input (get-user-input)
         game-state (update-game initial-state user-input)]
     (draw-screen game-state)
     (if-not (game-ended? game-state) (recur game-state))))

ฟังก์ชั่นหลักที่เรียกว่าคือupdate-gameซึ่งจะดำเนินการตามขั้นตอนการจำลองสถานการณ์ของเกมก่อนหน้านี้และการป้อนข้อมูลของผู้ใช้บางส่วน

ดังนั้นความซับซ้อนอยู่ที่ไหน ในมุมมองของฉันมันได้รับการจัดการค่อนข้างดี:

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

OOP สามารถจัดการความซับซ้อนผ่านการห่อหุ้ม แต่ถ้าคุณเปรียบเทียบสิ่งนี้กับ OOP ฟังก์ชันมีข้อดีที่สำคัญมากดังนี้:

  • โครงสร้างข้อมูลสถานะของเกมไม่เปลี่ยนรูปดังนั้นการประมวลผลจำนวนมากสามารถทำได้อย่างง่ายดายในแบบคู่ขนาน ตัวอย่างเช่นมันมีความปลอดภัยอย่างสมบูรณ์แบบที่จะมีการเรียกใช้การวาดหน้าจอในเธรดที่แตกต่างจากตรรกะของเกม - พวกเขาไม่สามารถส่งผลกระทบซึ่งกันและกันหรือดูสถานะที่ไม่สอดคล้องกัน นี่เป็นเรื่องยากอย่างน่าประหลาดใจกับกราฟวัตถุที่ไม่แน่นอนขนาดใหญ่ ......
  • คุณสามารถจับภาพสถานะเกมได้ตลอดเวลา ไกลเป็นเรื่องเล็กน้อย (ขอบคุณใด ๆ กับโครงสร้างข้อมูลถาวรของ Clojure สำเนาใช้หน่วยความจำใด ๆ แทบจะไม่ตั้งแต่ส่วนใหญ่ของข้อมูลที่ใช้ร่วมกัน) นอกจากนี้คุณยังสามารถเรียกใช้เกมอัปเดตเพื่อ "ทำนายอนาคต" เพื่อช่วย AI ประเมินการเคลื่อนไหวที่แตกต่างเช่น
  • ไม่มีที่ไหนเลยที่ฉันจะต้องทำให้การแลกเปลี่ยนยากลำบากใด ๆ เพื่อให้สอดคล้องกับกระบวนทัศน์ของ OOP เช่นการกำหนดลำดับชั้นของชนชั้นที่เข้มงวด ในแง่นี้โครงสร้างข้อมูลการทำงานจะทำงานเหมือนกับระบบที่มีต้นแบบที่ยืดหยุ่น

ในที่สุดสำหรับผู้ที่สนใจข้อมูลเชิงลึกเกี่ยวกับวิธีการจัดการความซับซ้อนในการทำงานกับภาษา OOP ฉันขอแนะนำวิดีโอคำปราศรัยของ Rich Hickey Simple Made Easy (ถ่ายทำที่การประชุมเทคโนโลยีStrange Loop )


2
ฉันคิดว่าเกมเป็นหนึ่งในตัวอย่างที่แย่ที่สุดที่จะแสดงให้เห็นถึง "ประโยชน์" ของการบังคับที่ไม่สามารถบังคับได้ สิ่งต่าง ๆ กำลังเคลื่อนไหวอยู่ตลอดเวลาในเกมซึ่งหมายความว่าคุณจะต้องสร้างเกมใหม่ตลอดเวลา และหากทุกอย่างไม่เปลี่ยนรูปนั่นหมายความว่าคุณไม่เพียง แต่ต้องสร้างสถานะของเกมใหม่ แต่ทุกอย่างในกราฟที่มีการอ้างอิงถึงมันหรือที่มีการอ้างอิงถึงมันและอื่น ๆ เรื่อย ๆ จนกว่าคุณจะรีไซเคิลทั้งหมด โปรแกรมที่ 30+ FPS พร้อม GC churn มากมายให้บู๊ต! ไม่มีทางที่คุณจะได้ประสิทธิภาพที่ดีจากสิ่งนั้น ...
Mason Wheeler

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

3
@Mason Wheeler: ที่จริงแล้วมันเป็นไปได้ที่จะได้รับประสิทธิภาพเท่ากัน (และการกลายพันธุ์) เท่ากันกับวัตถุที่ไม่เปลี่ยนรูปโดยไม่ต้องมี GC มากเลย เคล็ดลับใน Clojure ใช้โครงสร้างข้อมูลถาวร : พวกมันไม่เปลี่ยนรูปแบบไปยังโปรแกรมเมอร์ แต่จริง ๆ แล้วไม่แน่นอนภายใต้ประทุน สุดยอดของทั้งสองโลก
Joonas Pulakka

4
@quant_dev แกนเพิ่มเติมมีราคาถูกกว่าแกนที่ดีกว่า ... escapistmagazine.com/news/view/…
deworde

6
@quant_dev - ไม่ใช่ข้อแก้ตัวมันเป็นข้อเท็จจริงทางคณิตศาสตร์และสถาปัตยกรรมที่คุณควรมีค่าใช้จ่ายคงที่ถ้าคุณสามารถชดเชยได้โดยการปรับอัตราส่วนประสิทธิภาพของคุณให้ใกล้เคียงกับจำนวนแกน เหตุผลที่ภาษาที่ใช้งานได้ในที่สุดจะให้ประสิทธิภาพที่เหนือกว่าคือเราได้มาถึงจุดสิ้นสุดสำหรับการทำงานแบบแกนเดียวและมันจะเกี่ยวกับการทำงานพร้อมกันและขนานในอนาคต วิธีการทำงาน (และโดยเฉพาะอย่างยิ่งการเปลี่ยนแปลง) มีความสำคัญในการทำงานนี้
mikera

3

"การเขียนโปรแกรมเชิงวัตถุให้ระดับของนามธรรมที่ใช้กับอัลกอริธึมและข้อมูลในเวลาเดียวกันซึ่งเป็นนามธรรมชนิดหนึ่งที่การสลายตัวของฟังก์ชั่นเพียงอย่างเดียวไม่ได้ให้"

ฟังก์ชั่นการแยกย่อยฟังก์ชั่นเพียงอย่างเดียวไม่เพียงพอที่จะสร้างอัลกอริทึมหรือโปรแกรมใด ๆ : คุณต้องแสดงข้อมูลด้วย ฉันคิดว่าข้อความข้างต้นโดยปริยายถือว่า (หรืออย่างน้อยก็สามารถเข้าใจได้เช่น) ว่า "ข้อมูล" ในกรณีการทำงานเป็นประเภทพื้นฐานที่สุด: เพียงรายการสัญลักษณ์และไม่มีอะไรอื่น การเขียนโปรแกรมในภาษาดังกล่าวไม่สะดวกนัก อย่างไรก็ตามหลายคนโดยเฉพาะอย่างยิ่งภาษาใหม่และทันสมัย, การทำงาน (หรือหลายหลาก) เช่น Clojure เสนอโครงสร้างข้อมูลที่หลากหลาย: ไม่เพียง แต่รายการ แต่ยังสตริง, เวกเตอร์, แผนที่และชุด, บันทึก, structs - และวัตถุ! - มี metadata และ polymorphism

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


1
ฉันได้ยินวลี "OO ในวงกว้าง FP ในตัวเล็ก ๆ " ที่ไหนสักแห่ง - ฉันคิดว่า Michael Feathers อ้างถึง ความหมายว่า FP อาจดีสำหรับบางส่วนของโปรแกรมขนาดใหญ่ แต่โดยทั่วไปควรเป็น OO
แดน

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

@dan: ใช้เครื่องมืออะไรก็ได้ที่เหมาะกับงานที่สุด พูดได้หลายภาษาในการเขียนโปรแกรมเป็นปัจจัยที่สำคัญคือการสื่อสารที่สะดวกสบายระหว่างภาษาและClojure และ Java แทบจะไม่สามารถเล่นได้ดีขึ้นด้วยกัน ฉันเชื่อว่าโปรแกรม Clojure ที่สำคัญที่สุดใช้อย่างน้อยบางส่วนของห้องสมุด Java มาตรฐานของ JDK ที่นี่และที่นั่น
Joonas Pulakka

2

สถานะที่ไม่แน่นอนเป็นรากเหง้าของความซับซ้อนและปัญหาส่วนใหญ่ที่เกี่ยวข้องกับการเขียนโปรแกรมและการออกแบบซอฟต์แวร์ / ระบบ

OO รวบรวมสถานะที่ไม่แน่นอน ผู้เขียน FP สถานะที่ไม่แน่นอน

ทั้ง OO และ FP มีประโยชน์และจุดหวาน เลือกอย่างชาญฉลาด และจำไว้ว่าสุภาษิต: "ปิดเป็นวัตถุของคนยากจนวัตถุที่ปิดของชายยากจน"


3
ฉันไม่แน่ใจว่าการยืนยันการเปิดของคุณเป็นจริง รากของ "ส่วนใหญ่" ของความซับซ้อนหรือไม่ ในการเขียนโปรแกรมที่ฉันได้ทำหรือเห็นปัญหาไม่ได้เป็นรัฐที่ไม่แน่นอนมากนักเนื่องจากไม่มีนามธรรมและรายละเอียดมากเกินไปผ่านรหัส
แดน

1
@Dan: น่าสนใจ ฉันได้เห็นสิ่งที่ตรงกันข้ามกันมากมายจริง ๆ แล้ว: ปัญหาเกิดจากการใช้สิ่งที่เป็นนามธรรมทำให้ยากที่จะเข้าใจและเมื่อจำเป็นเพื่อแก้ไขรายละเอียดของสิ่งที่เกิดขึ้นจริง
Mason Wheeler

1

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

ลองมาเป็นตัวอย่างที่ง่ายมาก สมมติว่า Oracle ตัดสินใจว่า Java Strings ควรมีวิธีการย้อนกลับและคุณเขียนรหัสต่อไปนี้

String x = "abc";
StringBuffer y = new StringBuffer(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString());

บรรทัดสุดท้ายประเมินว่าอะไร คุณต้องการความรู้พิเศษของคลาส String เพื่อทราบว่าสิ่งนี้จะประเมินเป็นเท็จ

ถ้าฉันทำคลาสของตัวเอง WuHoString

String x = "abc";
WuHoString y = new WuHoString(x);
y.reverse();
x.reverse();
x.toString().equals(y.toString())

เป็นไปไม่ได้ที่จะรู้ว่าบรรทัดสุดท้ายประเมินค่าเป็นอย่างไร

ในรูปแบบการเขียนโปรแกรมฟังก์ชั่นมันจะถูกเขียนเพิ่มเติมดังนี้:

String x;
equals(toString(reverse(x)), toString(reverse(WuHoString(x))))

และมันควรจะเป็นจริง

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

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


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

@ sepp2k: คุ้นเคยกับมัน ผู้ให้การสนับสนุนของ FP มักจะนำพาตัวอย่างที่แปลกประหลาดมาประดิษฐ์ซึ่งไม่มีส่วนเกี่ยวข้องกับการเข้ารหัสในโลกแห่งความเป็นจริง เป็นวิธีเดียวที่จะสร้างแนวคิดหลักของ FP เช่นการบังคับให้เปลี่ยนแปลงไม่ได้ให้ดูดี
Mason Wheeler

1
@ ช่างก่ออิฐ: หือ? วิธีที่ดีที่สุดในการทำให้รูปลักษณ์ที่ไม่เปลี่ยนแปลงไม่น่าจะดีกว่าคือการพูดว่า "Java (และ C #, python และอื่น ๆ ) ใช้สตริงที่ไม่เปลี่ยนรูปแบบและใช้งานได้ดี"
sepp2k

1
@ sepp2k: ถ้าสตริงที่ไม่เปลี่ยนรูปแบบทำงานได้ดีมากทำไมคลาสสไตล์ StringBuilder / StringBuffer ถึงยังคงปรากฏขึ้นมา? มันเป็นเพียงอีกตัวอย่างหนึ่งของการกลับตัวที่เป็นนามธรรม
Mason Wheeler

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

0

ฉันคิดว่าในกรณีส่วนใหญ่นามธรรม OOP คลาสสิกไม่ครอบคลุมความซับซ้อนพร้อมกัน ดังนั้น OOP (ตามความหมายดั้งเดิม) ไม่ได้ยกเว้น FP และนั่นคือเหตุผลที่เราเห็นสิ่งต่าง ๆ เช่นสกาล่า


0

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

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

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

data TPLValue = Null
              | Number Integer
              | String String
              | List [TPLValue]
              | Function [TPLValue] TPLValue
              -- There's more in the real code...

สิ่งนี้บอกว่า - ในลักษณะที่เห็นได้ชัดเจน - คือ TPLValue (ค่าใด ๆ ในภาษาของฉัน) สามารถเป็น a Nullหรือ a ที่NumberมีIntegerค่าหรือแม้แต่Functionกับรายการของค่า (พารามิเตอร์) และค่าสุดท้าย (เนื้อความ )

ต่อไปฉันสามารถใช้คลาสคลาสเพื่อเข้ารหัสพฤติกรรมทั่วไปบางอย่าง ตัวอย่างเช่นฉันสามารถสร้างTPLValueและอินสแตนซ์Showซึ่งหมายความว่ามันสามารถแปลงเป็นสตริง

นอกจากนี้ฉันสามารถใช้คลาสประเภทของตัวเองเมื่อฉันต้องการระบุพฤติกรรมของบางประเภท (รวมถึงประเภทที่ฉันไม่ได้ใช้ด้วยตนเอง) ตัวอย่างเช่นฉันมีExtractableคลาสประเภทที่ให้ฉันเขียนฟังก์ชั่นที่ใช้TPLValueและส่งกลับค่าปกติที่เหมาะสม ดังนั้นจึงextractสามารถแปลงNumberไปยังIntegerหรือStringไปStringตราบเท่าที่IntegerและมีกรณีของStringExtractable

สุดท้ายตรรกะหลักของโปรแกรมของฉันอยู่ในฟังก์ชั่นหลายอย่างและeval applyสิ่งเหล่านี้เป็นแกนกลางจริงๆ - พวกมันใช้TPLValues และเปลี่ยนให้เป็นTPLValues มากขึ้นรวมถึงการจัดการสถานะและข้อผิดพลาด

โดยรวม, นามธรรมฉันใช้ในรหัส Haskell ของฉันเป็นจริงมากขึ้นมีประสิทธิภาพกว่าสิ่งที่ผมจะได้นำมาใช้ในภาษา OOP


evalใช่ความรักต้อง "เฮ้มองฉันสิฉันไม่ต้องเขียนช่องโหว่ความปลอดภัยของตัวเองฉันมีช่องโหว่ในการใช้รหัสโดยอำเภอใจในภาษาโปรแกรม!" การทำให้ข้อมูลที่เข้ารหัสด้วยรหัสเป็นสาเหตุหลักของช่องโหว่ความปลอดภัยที่ได้รับความนิยมสูงสุดหนึ่งในสองชั้นของเวลาทั้งหมด ทุกครั้งที่คุณเห็นใครบางคนถูกแฮ็คเนื่องจากการโจมตีของ SQL injection (เหนือสิ่งอื่นใด) นั้นเป็นเพราะโปรแกรมเมอร์บางคนไม่ทราบวิธีการแยกข้อมูลออกจากรหัสอย่างเหมาะสม
Mason Wheeler

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

3
ใช่ฉันได้ยินมาว่า "มาโครยอดเยี่ยม" พูดหลายครั้งก่อนหน้านี้ แต่ฉันไม่เคยเห็นตัวอย่างจริงของมาโคร Lisp ที่ทำสิ่งที่ 1) เป็นประโยชน์และสิ่งที่คุณอยากทำในรหัสโลกแห่งความจริงและ 2) ไม่สามารถทำได้อย่างง่ายดายในภาษาใด ๆ ที่สนับสนุนฟังก์ชั่น
Mason Wheeler

1
@MasonWheeler andลัดวงจร orลัดวงจร let. let-rec. cond. defn. สิ่งเหล่านี้ไม่สามารถนำไปใช้กับฟังก์ชั่นในภาษาเพื่อ for(รายการความเข้าใจ) dotimes. doto.

1
@ MattFenwick: ตกลงฉันควรเพิ่มจุดที่สามในสองของฉันด้านบน: 3) ไม่ได้มีอยู่แล้วในภาษาการเขียนโปรแกรมที่มีสติ เพราะนั่นเป็นเพียงตัวอย่างแมโครที่มีประโยชน์อย่างแท้จริงที่ฉันเคยเห็นและเมื่อคุณพูดว่า "เฮ้มองฉันภาษาของฉันยืดหยุ่นมากจนฉันสามารถใช้ไฟฟ้าลัดวงจรของตัวเองได้and!" ฉันได้ยิน "เฮ้มองมาที่ฉันภาษาของฉันง่อยมากจนไม่มากับการลัดวงจรandและฉันต้องบูรณาการล้อสำหรับทุกอย่าง !"
Mason Wheeler

0

ประโยคที่ยกมาไม่มีความถูกต้องอีกต่อไปเท่าที่ฉันเห็น

ภาษา OO ร่วมสมัยไม่สามารถแยกแยะความแตกต่างของประเภทที่ไม่ใช่ *, เช่นชนิดที่สูงกว่าไม่เป็นที่รู้จัก ระบบประเภทของพวกเขาไม่อนุญาตให้แสดงความคิดของ "คอนเทนเนอร์บางตัวที่มีองค์ประกอบ Int ที่ช่วยให้การแมปฟังก์ชั่นมากกว่าองค์ประกอบ"

ดังนั้นฟังก์ชั่นพื้นฐานเช่น Haskells

fmap :: Functor f => (a -> b) -> f a -> f b 

ไม่สามารถเขียนได้อย่างง่ายดายใน Java *) ตัวอย่างเช่นอย่างน้อยไม่ได้อยู่ในประเภทที่ปลอดภัย ดังนั้นเพื่อให้ได้ฟังก์ชั่นพื้นฐานคุณต้องเขียนแผ่นจำนวนมากเพราะคุณต้องการ

  1. วิธีการใช้ฟังก์ชั่นง่าย ๆ กับองค์ประกอบของรายการ
  2. วิธีการใช้ฟังก์ชั่นที่เรียบง่ายเช่นเดียวกันกับองค์ประกอบของอาร์เรย์
  3. วิธีการใช้ฟังก์ชั่นง่าย ๆ แบบเดียวกันกับค่าของแฮช
  4. .... ตั้งค่า
  5. ต้นไม้ ....
  6. ... 10. การทดสอบหน่วยสำหรับเดียวกัน

และยังห้าวิธีนั้นเป็นรหัสเดียวกันให้หรือใช้บางส่วน ในทางตรงกันข้ามใน Haskell ฉันต้องการ:

  1. อินสแตนซ์ Functor สำหรับรายการอาร์เรย์แผนที่ชุดและต้นไม้ (ส่วนใหญ่กำหนดไว้ล่วงหน้าหรือสามารถรวบรวมได้โดยอัตโนมัติ)
  2. ฟังก์ชั่นที่เรียบง่าย

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

แม้แต่ภาษา OO ใหม่ ๆ เช่น Ceylon ก็ไม่มีประเภทที่สูงกว่า (ฉันได้ถามกาวินคิงเมื่อเร็ว ๆ นี้และเขาบอกฉันว่ามันไม่สำคัญในเวลานี้) ไม่ทราบเกี่ยวกับ Kotlin

*) เพื่อความเป็นธรรมคุณสามารถมีส่วนต่อประสาน Functor ที่มีเมธอด fmap สิ่งที่ไม่ดีคือคุณไม่สามารถพูดได้: เฮ้ฉันรู้วิธีการใช้ fmap สำหรับห้องสมุดคลาส SuperConcurrentBlockedDoublyLinkedDequeHasMap คอมไพเลอร์ที่รักโปรดยอมรับว่าจากนี้ไป SuperConcurrentBlockedDoublyLinkedDequeHasMaps ทั้งหมด


FTR: ศรีลังกา typechecker และ JavaScript ส่วนหลังตอนนี้ทำสนับสนุนประเภทมือที่สูงขึ้น (และยังสูงยศชนิด) มันถือเป็นคุณสมบัติ "ทดลอง" อย่างไรก็ตามชุมชนของเราพยายามหาแอปพลิเคชั่นที่ใช้งานได้จริงสำหรับฟังก์ชั่นนี้ดังนั้นจึงเป็นคำถามที่เปิดกว้างว่าจะเป็นส่วนหนึ่งของ "ทางการ" ของภาษาหรือไม่ ผมไม่คาดหวังว่ามันจะได้รับการสนับสนุนโดยแบ็กเอนด์ชวาที่บางขั้นตอน
Gavin King

-2

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

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

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


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

1
โปรแกรมเมอร์ใด ๆ ที่จะช่วยให้ข้อมูลของผู้ใช้จะกลายเป็นรหัสปฏิบัติการโดยอัตโนมัติจะเห็นได้ชัดไม่รู้ ไม่โง่ การทำอย่างนั้นมักจะง่ายและชัดเจนและหากพวกเขาไม่รู้ว่าทำไมมันเป็นความคิดที่ไม่ดีและมีวิธีแก้ปัญหาที่ดีกว่าอยู่คุณไม่สามารถตำหนิพวกเขาสำหรับการทำเช่นนั้นได้ (ใครก็ตามที่ยังคงทำเช่นนั้นหลังจากที่พวกเขาได้รับการสอนว่ามีวิธีที่ดีกว่า แต่ก็โง่เขลาอย่างเห็นได้ชัด)
Mason Wheeler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.