การห่อ
คำสั่ง Scheme ทำงานในระดับต่ำ ซึ่งหมายความว่าแม้แต่งานง่ายๆ ก็อาจต้องใช้หลายขั้นตอน อย่างไรก็ตาม รายละเอียดนี้ให้ความยืดหยุ่น เราสามารถรวมคำสั่งออกเป็นฟังก์ชันเล็กๆ ที่สามารถนำกลับมาใช้ใหม่ได้ ซึ่งทำหน้าที่ตรงตามที่เราต้องการ การห่อไม่ใช่แนวคิดขาวดำ อาจมีตั้งแต่นามแฝงธรรมดาสำหรับคำสั่งที่ใช้บ่อยไปจนถึงฟังก์ชันที่ซับซ้อนมากขึ้นที่จัดการเวิร์กโฟลว์ทั้งหมด บางครั้ง wrapper เป็นเพียงฟังก์ชันอำนวยความสะดวกในการปรับปรุงความสามารถในการอ่าน ในขณะที่ในกรณีอื่นๆ มันพัฒนาเป็นยูทิลิตี้ที่มีคุณสมบัติครบถ้วนที่สรุปการดำเนินการหลายอย่าง
ทำไมต้อง Wrap ฟังก์ชั่น?
มีประโยชน์หลักหลายประการสำหรับฟังก์ชันการห่อ:
- ลดความซับซ้อนของงานซ้ำๆ – แทนที่จะทำซ้ำคำสั่งระดับต่ำ ให้รวมคำสั่งเหล่านั้นไว้ในฟังก์ชันตัวช่วยแล้วนำมาใช้ซ้ำ
- ปรับปรุงความสามารถในการอ่าน – การให้ฟังก์ชันที่ห่อไว้ของเรามีชื่อที่ชัดเจนและสื่อความหมาย ทำให้โค้ดของเราเข้าใจได้ง่ายขึ้นในทันที
- สรุปความซับซ้อน – แทนที่จะจัดการกับรายการคำสั่งที่ยาวและเป็นความลับ ลูปที่ซ้อนกันลึก หรือคำสั่งข้อความที่ซับซ้อน เราสามารถแยกย่อยออกเป็นฟังก์ชันตัวช่วยที่มีโครงสร้างเล็กลงได้
- ปรับปรุงการบำรุงรักษา – หากฟังก์ชันหลักของคำสั่งเปลี่ยนแปลง เราจำเป็นต้องอัปเดตฟังก์ชันที่รวมของเราเพียงครั้งเดียว เพื่อป้องกันปลั๊กอินของเราจากรายละเอียดของการเปลี่ยนแปลงเหล่านั้น
- สนับสนุนการใช้โค้ดซ้ำ – ผู้ช่วยเหลือแต่ละคนจะเป็นส่วนหนึ่งของไลบรารีของคุณ ทำให้สคริปต์ในอนาคตเขียนและแก้ไขจุดบกพร่องได้เร็วขึ้น
เมื่อปลั๊กอินของคุณเติบโตขึ้น Wrapper จะช่วยให้คุณรักษาตรรกะหลักให้อ่านได้และแยกรายละเอียดที่ซ้ำกัน
ข้อดีอีกประการหนึ่งของฟังก์ชันการตัดคำคือการรวมฟังก์ชันเหล่านั้นไว้ในตัวเน้นไวยากรณ์ เช่น Visual Studio Code สิ่งนี้จะปรับปรุงความสามารถในการอ่านและการนำทาง ทำให้สคริปต์ชัดเจนยิ่งขึ้น ในปลั๊กอินที่ใช้ฟังก์ชันแบบกำหนดเอง ฟังก์ชันที่เน้นสีเขียวจะช่วยยืนยันว่าได้รับการอ้างอิงจากไลบรารีของเราอย่างถูกต้อง
หากคุณดูแลไลบรารีตัวช่วยของคุณเอง ให้ลองเพิ่มชื่อฟังก์ชันของโปรเจ็กต์ของคุณลงในการเน้นไวยากรณ์ของตัวแก้ไข ทำให้การนำทางและการปรับโครงสร้างใหม่เร็วขึ้น
ตัวอย่าง:
Seed สุ่ม
;; วัตถุประสงค์: คืนค่าจำนวนเต็มสุ่มสำหรับ seed ฟิลเตอร์
(define (random-seed)
(msrg-rand))แม้ว่าเราจะสามารถใช้ msrg-rand ในโค้ดของเราได้โดยตรง แต่การใส่มันไว้ในฟังก์ชันที่เรียกว่า random-seed จะทำให้อ่านง่ายขึ้น การตั้งชื่อฟังก์ชันให้ชัดเจนและสื่อความหมายจะช่วยให้เข้าใจวัตถุประสงค์ได้ง่ายขึ้นเมื่อมองแวบเดียว
นอกจากนี้ การกำหนด random-seed ให้เป็นฟังก์ชันแบบสแตนด์อโลนช่วยให้เราสามารถใช้งานได้ทุกที่ในปลั๊กอินของเรา ในขณะเดียวกันก็รวมศูนย์การดำเนินการไว้ในที่เดียว หากเราจำเป็นต้องเปลี่ยนวิธีการสร้าง Seed เราเพียงแต่ต้องอัปเดตฟังก์ชันนี้ โดยปล่อยให้โค้ดที่เหลือของเราไม่ถูกแตะต้อง
ตัวอย่างเช่น หากเราตัดสินใจเปลี่ยนเป็น สุ่ม แทน:
;; วัตถุประสงค์: คืนค่าจำนวนเต็มสุ่มสำหรับ seed ฟิลเตอร์
(define (random-seed)
(random 1000))ชื่อฟังก์ชันยังคงเหมือนเดิม ทำให้มั่นใจได้ว่าสคริปต์ของเราจะทำงานต่อไปโดยไม่มีการแก้ไข แนวทางนี้ทำให้โค้ดของเรามีความยืดหยุ่น บำรุงรักษาได้ และอ่านง่าย
การส่งออก JPEG
ฟังก์ชันการส่งออก JPEG ใน Scheme มาพร้อมกับพารามิเตอร์มากมาย ช่วยให้สามารถควบคุมวิธีการบันทึกรูปภาพได้อย่างละเอียด อย่างไรก็ตาม ในกรณีส่วนใหญ่ เราสนใจเฉพาะการตั้งค่าหลักบางประการเท่านั้น เช่น ชื่อไฟล์และคุณภาพ เพื่อให้กระบวนการง่ายขึ้น เราสามารถสรุปฟังก์ชันได้
;; วัตถุประสงค์: บันทึกภาพเป็น JPEG ด้วยคุณภาพที่กำหนด
(define (file-jpg-save image file quality)
(let ((export-file (if (has-substring? file ".jpg")
file
(string-append file ".jpg")))) ;; หลีกเลี่ยง jpg.jpg
(debug-message "Exporting: " export-file)
(file-jpeg-export #:run-mode RUN-NONINTERACTIVE
#:image image
#:file export-file
#:options -1
#:quality (* 0.01 quality)
#:smoothing 0.0
#:optimize 1
#:progressive 1
#:cmyk 0
#:sub-sampling "sub-sampling-1x1"
#:baseline 1
#:restart 0
#:dct "integer")))ในฟังก์ชัน wrapper นี้ ตัวเลือกการส่งออกส่วนใหญ่จะฮาร์ดโค้ด โดยจะแสดงเฉพาะพารามิเตอร์ที่เรามีแนวโน้มจะปรับเปลี่ยน ได้แก่ ชื่อไฟล์และคุณภาพ วิธีการนี้ช่วยเพิ่มความสามารถในการอ่านและทำให้การบันทึกภาพง่ายขึ้นนอกจากนี้ หากผู้ส่งออกของ Lumi มีการเปลี่ยนแปลงในอนาคต เราจำเป็นต้องอัปเดตฟังก์ชันนี้เพียงฟังก์ชันเดียว แทนที่จะแก้ไขทุกสคริปต์ที่ส่งออก JPEG
การใช้ Wrapper
หากต้องการส่งออก JPEG ในปลั๊กอินของเรา เราเพียงรวมไลบรารีและเรียกใช้ฟังก์ชันที่กำหนดเองของเรา:
(file-jpg-save image "/home/mark/pictures/my-picture" 85)สิ่งนี้ทำให้โค้ดของเราสะอาด อ่านง่าย และปรับเปลี่ยนได้ ในขณะเดียวกันก็ช่วยให้เราส่งออก JPEG ได้อย่างมีประสิทธิภาพโดยใช้ความพยายามเพียงเล็กน้อย
แทนที่ car
ฟังก์ชัน car อาจเป็นความลับและมีแนวโน้มที่จะเกิดข้อผิดพลาดในการเขียนสคริปต์ เป็นเรื่องง่ายที่จะใส่ รถยนต์ กับเวกเตอร์หรือรายการที่ไม่ใช่รายการโดยไม่ตั้งใจ ซึ่งนำไปสู่พฤติกรรมที่ไม่คาดคิด เพื่อให้โค้ดของเราแข็งแกร่งและอ่านง่ายขึ้น เราสามารถรวมฟังก์ชันนี้ไว้ในฟังก์ชันที่ปลอดภัยยิ่งขึ้นได้
;; วัตถุประสงค์: คืนรายการแรกของ list หรือ vector
;; แจ้งเตือนหากอินพุตไม่ถูกต้องหรือว่าง
(define (first-item collection)
(cond
;; จัดการรายการที่ไม่ว่าง
((and (list? collection) (not (null? collection)))
(list-ref collection 0))
;; จัดการเวกเตอร์ที่ไม่ว่าง
((and (vector? collection) (> (vector-length collection) 0))
(vector-ref collection 0))
;; อินพุตไม่ถูกต้องหรือว่างเปล่า
(else
(begin
(warning-message "first-item: Expected a non-empty list or vector, but received: " collection)
#f))))ฟังก์ชันนี้จะเรียกข้อมูลรายการแรกของรายการหรือเวกเตอร์อย่างปลอดภัย พร้อมทั้งแจ้งเตือนที่เป็นประโยชน์เมื่อพบอินพุตที่ไม่ถูกต้องหรือว่างเปล่า ด้วยการใช้ รายการแรก แทน รถยนต์ เราจะลดความเสี่ยงของข้อผิดพลาดโดยไม่ตั้งใจ และปรับปรุงความชัดเจนของสคริปต์ของเรา
ทำไมต้องใช้กระดาษห่อนี้?
- ป้องกันการล่มของสคริปต์ – หลีกเลี่ยงข้อผิดพลาดที่เกิดจากการนำ car ไปใช้กับรายการที่ไม่ใช่รายการ
- รองรับทั้งรายการและเวกเตอร์ – ขยายการใช้งานให้มากกว่าแค่รายการ
- ให้คำเตือนที่มีความหมาย – ช่วยแก้ไขปัญหาอินพุตที่ไม่คาดคิด
- ปรับปรุงความสามารถในการอ่าน – ชื่อฟังก์ชันสื่อถึงวัตถุประสงค์ได้อย่างชัดเจน
ด้วยการสรุปตรรกะนี้ไว้ในรายการแรก เราทำให้ปลั๊กอินของเรามีประสิทธิภาพและบำรุงรักษาได้ง่ายขึ้น แน่นอนว่าสิ่งนี้ขึ้นอยู่กับความชอบส่วนบุคคล คุณอาจรู้สึกสบายใจอย่างยิ่งในการใช้ฟังก์ชั่นรถยนต์, Caar, Cadr และ Scheme ที่คล้ายกันโดยตรง
การห่อฟังก์ชันที่ห่อ
การรวมฟังก์ชันที่รวมไว้แล้วสามารถปรับปรุงความสามารถในการอ่านและการบำรุงรักษาได้ดียิ่งขึ้น ตัวอย่างเช่น เมื่อทำงานกับคู่พิกัด เช่น pixel-coords (รายการ 100 200) เราสามารถใช้:
(first-item pixel-coords)เพื่อดึงพิกัด x อย่างไรก็ตาม แม้ว่าการทำงานจะดูไม่ชัดเจนมากนัก แต่เราสามารถล้อม รายการแรก ไว้ในคำจำกัดความที่เหมาะสมกว่าเพื่อทำให้เจตนาของเราชัดเจนยิ่งขึ้น
;; วัตถุประสงค์: คืนค่าพิกัด x เพื่อความอ่านง่าย
(define (x-coord pixel-coords)
(first-item pixel-coords))
;; วัตถุประสงค์: คืนค่าพิกัด y เพื่อความอ่านง่าย
(define (y-coord pixel-coords)
(second-item pixel-coords))เหตุใดจึงใช้แนวทางนี้
- ปรับปรุงความชัดเจนของโค้ด – แทนที่จะใช้ฟังก์ชันการเข้าถึงรายการทั่วไป เราจะกำหนดฟังก์ชันที่อธิบายวัตถุประสงค์อย่างชัดเจน
- ปรับปรุงการบำรุงรักษา – หากการแสดงพิกัดของเราเปลี่ยนแปลง (เช่น การใช้เวกเตอร์แทนรายการ) เราจำเป็นต้องอัปเดตฟังก์ชันเล็กๆ เหล่านี้เท่านั้น
- ส่งเสริมความสอดคล้อง – การใช้ x-coord และ y-coord ช่วยให้อ่านและทำความเข้าใจสคริปต์ได้ง่ายขึ้นในทันที
ตอนนี้ แทนที่จะเขียนในรูปแบบทั่วไป:
(car pixel-coords) ;; ดึงพิกัด x
(cadr pixel-coords) ;; ดึงพิกัด yเราสามารถเขียนใน our Scheme:
(x-coord pixel-coords)
(y-coord pixel-coords)ด้วยการรวมฟังก์ชันระดับต่ำไว้ในชื่อที่มีความหมาย เราจึงสร้างวิธีการทำงานกับข้อมูลที่ใช้งานง่ายยิ่งขึ้น ลดความสับสนและข้อผิดพลาดที่อาจเกิดขึ้น
Wrappers ที่จัดส่งแล้ว: Utility Stdlib
Lumi จัดส่งชุดเครื่องห่อสำเร็จรูปที่โหลดโดยอัตโนมัติเมื่อเริ่มต้นระบบ ดังนั้นจึงพร้อมใช้งานในปลั๊กอินหรือใน Scheme Console โดยไม่ต้องมีการเรียก (load ...) ไลบรารีเหล่านี้ (common.scm, files.scm, gegl.scm, images.scm, layers.scm, parasites.scm และ paths.scm) สร้างขึ้นบนหลักการเดียวกันกับตัวอย่างด้านบน: โดยให้ชื่อที่ชัดเจนสำหรับการดำเนินการระดับต่ำ ซ่อนรูปแบบสำเร็จรูปที่ซ้ำกัน และจัดให้มีที่เดียวในการอัปเดตหากคำสั่งที่ซ่อนอยู่มีการเปลี่ยนแปลงตัวอย่างเช่น images.scm ให้ image-get-open-list เป็น wrapper ที่อ่านได้เกี่ยวกับการเรียก PDB แบบ Raw และ files.scm จะเปิดเผยตัวช่วยสร้างเส้นทางที่อาจต้องใช้ string-append chains ซ้ำ
คุณสามารถเรียกดูชื่อที่ส่งออกทั้งหมด อ่านเอกสารของชื่อ และดูว่ามาจากไลบรารีใดใน Utility Browser (ช่วยเหลือ → การเขียนโปรแกรม → เบราว์เซอร์ยูทิลิตี้) นี่เป็นการสาธิตการใช้งานจริงของการห่อตามขนาด และเป็นแหล่งรูปแบบที่มีประโยชน์ในการยืมเมื่อสร้างไลบรารีตัวช่วยของคุณเอง
บทสรุป
ฟังก์ชันการรวมเป็นวิธีที่มีประสิทธิภาพในการทำให้การพัฒนา Scheme ง่ายขึ้น ทำให้สคริปต์สามารถอ่าน บำรุงรักษาได้ และมีประสิทธิภาพมากขึ้น ด้วยการห่อหุ้มความซับซ้อนและเปิดเผยเฉพาะรายละเอียดที่จำเป็น เราจึงสร้างแนวทางการเขียนปลั๊กอินที่มีโครงสร้างมากขึ้น
ประเด็นสำคัญจากแนวทางนี้:
- ลดความซับซ้อนของงานซ้ำๆ – แทนที่จะทำซ้ำคำสั่งระดับต่ำด้วยตนเอง เราสร้างฟังก์ชันที่นำมาใช้ซ้ำได้
- ปรับปรุงความสามารถในการอ่านโค้ด – Wrapper ที่มีชื่อดีช่วยให้สคริปต์เข้าใจได้ง่ายขึ้น
- สรุปความซับซ้อน – รายละเอียดระดับต่ำได้รับการจัดการภายใน wrapper เพื่อรักษาสคริปต์หลักให้สะอาด
- ปรับปรุงการบำรุงรักษา – หากฟังก์ชันการทำงานหลักมีการเปลี่ยนแปลง เราจำเป็นต้องอัปเดต Wrapper เท่านั้น ไม่ใช่ทุกสคริปต์ที่ต้องอาศัยฟังก์ชันดังกล่าว
- สนับสนุนการใช้ซ้ำและความสม่ำเสมอ – ไลบรารีฟังก์ชันส่วนตัวของเราจะเติบโตขึ้นเมื่อเวลาผ่านไป ทำให้การพัฒนาเร็วขึ้นและมีประสิทธิภาพมากขึ้น
ด้วยการใช้การรวมฟังก์ชันอย่างสม่ำเสมอ เราสามารถเปลี่ยนวิธีการเขียนปลั๊กอิน Scheme เพื่อสร้างสภาพแวดล้อมการเขียนสคริปต์แบบโมดูลาร์และแสดงออกได้มากขึ้น ด้วยหลักการเหล่านี้ เราจึงสามารถปรับปรุงแนวทางของเราต่อไปได้ พัฒนา Scheme เวอร์ชันที่มีประสิทธิภาพมากขึ้นและปรับแต่งให้ตรงกับความต้องการเฉพาะของเรา
ขั้นตอนถัดไป: ระบุการบล็อกที่ซ้ำกันในสคริปต์ของคุณและแยกตัวช่วยเล็กๆ ที่มีชื่อที่ชัดเจน