ทุกสิ่งล้วนเป็นฟังก์ชัน: บันทึกหลังเรียนคอร์ส SICP กับ David Beazley
(ezzeriesa.notion.site)บันทึกหลังเรียนคอร์ส SICP กับ David Beazley: ประสบการณ์ 1 สัปดาห์
ขอแบ่งปันประสบการณ์จากการเข้าร่วมคอร์ส SICP ของ David Beazley ช่วงปลายปี 2022 แม้จะมีสื่อฟรีอยู่มากมาย แต่คอร์สของ Dave มีประสิทธิภาพมากเพราะเลือกหัวข้อเฉพาะมาอธิบายอย่างลึกซึ้ง
จุดเริ่มต้น
คอร์ส SICP ใช้ภาษา Scheme และในที่นี้ได้อธิบายแนวคิดพื้นฐานอย่างโมเดล การแทนที่ค่า (substitution) ผ่านการสร้างตัวแปล Scheme แบบง่าย ๆ ด้วย Python
พื้นฐานของภาษา Scheme
- Primitive: ค่าพื้นฐาน (เช่น จำนวนเต็ม)
- Operator: ใช้การดำเนินการพื้นฐานอย่าง
+,-,*,/ในรูปแบบ prefix notation - define: นิยามตัวแปร
> (define x 2)
> (+ x 3) ; 결과: 5
- if: คำสั่งเงื่อนไข
- lambda: นิยามฟังก์ชันนิรนาม
> ((lambda (x) (* x x)) 3) ; 결과: 9
ตัวแปล Scheme ใน Python
มีการสร้างตัวแปลอย่างง่ายสำหรับประเมินโค้ด Scheme โดยใช้ Python และนิยามการดำเนินการพื้นฐานด้วยฟังก์ชัน Python
definitions = {
"+": lambda x, y: x + y,
"*": lambda x, y: x * y,
}
ตัวอย่าง:
> evaluate(("+", 2, 3)) # 결과: 5
รวมถึงการทำงานของ define และ lambda ตลอดจนการจัดการคำสั่งเงื่อนไข if
โมเดลการแทนที่ค่า (Substitution Model)
โมเดลการแทนที่ค่าเป็นวิธีตีความโปรแกรมแบบง่าย โดยประเมินโปรแกรมผ่านการแทนตัวแปรด้วยค่า แต่เมื่อมี assignment โมเดลนี้จะใช้ไม่ได้
สถานะ (State)
ตัวอย่างที่ทำให้โมเดลการแทนที่ค่าใช้ไม่ได้คือ assignment เช่น เมื่อต้องจำลองยอดเงินคงเหลือในบัญชีธนาคาร จะใช้ set! เพื่ออัปเดตตัวแปร
(define balance 100)
(define (withdraw amount)
(set! balance (- balance amount))
balance)
ในกรณีนี้ โมเดลการแทนที่ค่าไม่สามารถแยกความต่างของสถานะยอดคงเหลือก่อนและหลังได้
จึงจำเป็นต้องใช้โมเดล Environment ตัวแปรจะถูกนิยามภายใน environment และแต่ละ procedure จะมี environment ของตัวเอง
สตรีม (Streams)
อีกวิธีหนึ่งในการจำลองสถานะคือ stream ซึ่งสามารถจำลองค่าของอนาคตได้ผ่านการประเมินค่าแบบหน่วงเวลา (lazy evaluation)
ลูปไม่สิ้นสุดและลำดับการประเมินค่า
ความต่างของลำดับการประเมินค่า: ภาษาส่วนใหญ่ใช้ applicative-order evaluation ซึ่งจะประเมินอาร์กิวเมนต์ก่อน
> (square (+ 1 2)) ; 결과: 9
แต่ normal-order evaluation จะเลื่อนการประเมินอาร์กิวเมนต์ออกไปจนกว่าจะจำเป็นจริง ๆ ทำให้หลีกเลี่ยงลูปไม่สิ้นสุดได้
> (define (p) (p))
> (define (test x y) (if (= x 0) 0 y))
> (test 0 (p)) ; normal order จะคืนค่า 0 แต่ applicative order จะเข้าสู่ลูปไม่สิ้นสุด
Lambda Calculus และเลข Church
สามารถใช้ Church encoding เพื่อแทนตัวเลขเป็น procedure ได้ ซึ่งเป็นแนวคิดสำคัญของการเขียนโปรแกรมเชิงฟังก์ชัน
(define (zero f) (lambda (x) x))
(define (increment n) (lambda (f) (lambda (x) (f ((n f) x)))))
zeroคือฟังก์ชันที่คืนอาร์กิวเมนต์เดิม (identityfunction)incrementคือการเพิ่มการเรียกใช้ฟังก์ชันอีกหนึ่งครั้ง
ตัวอย่าง
> ((zero (lambda (x) (+ x 1))) 0) ; 결과: 0
> (((increment zero) (lambda (x) (+ x 1))) 0) ; 결과: 1
การวนซ้ำ vs การเรียกซ้ำ
Scheme ใช้ recursion แทน for loop ในการทำงานซ้ำ
ตัวอย่างการเรียกซ้ำ: แฟกทอเรียล
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
การเรียกซ้ำลักษณะนี้อาจใช้หน่วยความจำมากเพราะต้องอาศัยสแตก
การเพิ่มประสิทธิภาพ tail-call
Scheme ลดการใช้หน่วยความจำผ่าน tail-call optimization ทำให้การทำงานมีลักษณะเหมือนกระบวนการแบบวนซ้ำ (iterative)
(define (factorial n)
(define (iter product counter)
(if (> counter n)
product
(iter (* product counter) (+ counter 1))))
(iter 1 1))
สรุป
คอร์สของ David Beazley คัดเลือกแนวคิดสำคัญของ SICP มานำเสนออย่างลึกซึ้ง โดยเฉพาะเรื่องการเขียนโปรแกรมเชิงฟังก์ชัน Lambda Calculus และลำดับการประเมินค่า ซึ่งช่วยให้เข้าใจพาราไดม์การเขียนโปรแกรมที่หลากหลายมากขึ้น
คำคมของ Knuth
ถ้าคุณเรียนแต่ทฤษฎี นั่นหมายความว่าถึงเวลาที่ควรหันไปโฟกัสด้านปฏิบัติ และถ้าคุณทำแต่ภาคปฏิบัติ นั่นหมายความว่าถึงเวลาที่ควรหันไปโฟกัสด้านทฤษฎี
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ขอเตือนไว้ก่อนว่า ถ้าคุณลงลึกกับ SICP/Lisp/Scheme มากพอ วิธีคิดเรื่องการเขียนโปรแกรมของคุณอาจเปลี่ยนไปได้ และแรงกระตุ้นทางปัญญาแบบนั้นก็น่ายินดีเสมอ
แต่ถ้านำแนวคิดเหล่านั้นไปใช้ทั้งดุ้นกับโค้ดเบสเชิงวัตถุ ก็มักจะให้ผลย้อนกลับหรือทำให้เพื่อนร่วมทีมต่อต้าน
ตัวอย่างเช่น หลังจากเจอ Lisp แล้ว คุณอาจอยากเปลี่ยน
forloop ทั้งหมดเป็นforEachหรือทำทุกอย่างให้กลายเป็นสายmap/reduceแต่ถ้าภาษาไม่ได้รองรับการเขียนโปรแกรมเชิงฟังก์ชันอย่างเต็มตัว ก็อาจทำลายทั้ง ความอ่านง่ายและประสิทธิภาพ ได้ท้ายที่สุด การจำไว้ว่าโค้ดถูกรันโดยหน่วยความจำที่เปลี่ยนแปลงได้และ CPU จะช่วยยึดโยงพื้นฐานเอาไว้ และทุกวันนี้แนวคิดนามธรรมอย่าง Church numerals ก็ดูจะใช้งานจริงในชีวิตประจำวันได้น้อยกว่าการออกแบบที่ยึดข้อมูลเป็นศูนย์กลางและ “mechanical sympathy” ที่สอดคล้องกับความเป็นจริงของฮาร์ดแวร์
ยิ่งมีผลข้างเคียงน้อยก็ยิ่งคาดเดาได้ง่ายกว่า และการเขียนเชิงวัตถุก็มักถูกใช้แม้ในจุดที่แค่ฟังก์ชันก็เพียงพอ
ในการพัฒนาเกม การเขียนเชิงวัตถุเหมาะพอสมควรเพราะมีทั้งการคำนวณและโค้ดที่เสี่ยงอันตรายจำนวนมาก แต่ในการพัฒนาเว็บ การเขียนเชิงฟังก์ชันเป็นธรรมชาติกว่ามาก และใน SaaS ภาษาตระกูลอย่าง Elixir ก็ช่วยให้เขียนโค้ดที่เชื่อถือได้มากกว่า บั๊กน้อยกว่า และทดสอบได้ง่ายกว่า
อีกอย่าง
forEach,map/reduceแบบนี้ก็มีอยู่แล้วในคอลเลกชันของ Smalltalk และถึงจะไม่ใช่ไวยากรณ์ชั้นหนึ่งก็ยังถูกคัดลอกไปยัง Object Pascal และ C++และเพราะหน่วยความจำชั้นล่างยังเปลี่ยนแปลงได้อยู่ เวลาจำเป็นจริง ๆ ภาษาตระกูล ML ก็ยังมีกลไกสำหรับจัดการ mutation เช่นกัน
gotoแต่gotoก็ยิ่งไม่เป็นที่นิยมกว่า Lisp เสียอีกที่แปลกยิ่งกว่าคือ แม้ว่า cache จะจำเป็นต่อประสิทธิภาพบน CPU สมัยใหม่ แต่ก็ยังไม่มีภาษาที่ปฏิบัติต่อชั้นลำดับหน่วยความจำเสมือนเป็นพลเมืองชั้นหนึ่ง
สิ่งที่ใกล้ที่สุดดูจะเป็น C แบบสาย Linux ที่มีขีดล่างเยอะราวกับทะเล
เป็นบทนำที่ดีในการ เข้ารหัสสถานะ ด้วยฟังก์ชันบริสุทธิ์
ในทางปฏิบัติยังมีการเข้ารหัสเชิงฟังก์ชันแบบบริสุทธิ์สำหรับข้อมูลแทบทุกชนิดอีกมาก ทั้ง tree, integer, sum/product type, image, monad และอื่น ๆ
การเข้ารหัสอาจชวนสับสนอยู่บ้าง แต่ก็ทั้งสง่างามและกะทัดรัดในเวลาเดียวกัน
ตัวอย่างเช่น ถ้าจะอิมพลีเมนต์ Maybe monad แบบเชิงฟังก์ชันใน JavaScript ก็จะได้ประมาณนี้:
Nothing = nothing => just => nothingJust = v => nothing => just => just(v)pure = Justbind = mx => f => mx(mx)(f)evalMaybe = maybe => maybe("Nothing")(v => "Just " + v)console.log(evalMaybe(bind(Nothing)(n => pure(n + 1)))) // Nothingconsole.log(evalMaybe(bind(Just(42))(n => pure(n + 1)))) // Just 43data Maybe a = Nothing | Just afoldMaybe :: (Unit -> r) -> (a -> r) -> Maybe a -> rฟังก์ชันระดับสูงสองตัวที่ส่งให้
foldMaybeนั้นสอดคล้องกับNothingและJustตามลำดับเพียงแต่ฝั่ง
Nothingเพิ่มพารามิเตอร์Unitเข้าไปเพื่อให้แม่นยำขึ้นเล็กน้อยในทฤษฎีชนิดมันค่อนข้างเท่มาก แต่กับการเขียนโปรแกรมจริงกลับไม่ค่อยดีนัก
ก่อนหน้านี้เคยดูคอร์ส SICP ของจริง คือวิดีโอบันทึกการสอนของ MIT OCW จากปี 1986
คนมักชมว่ามีความหนาแน่นของข้อมูลสูง แต่เอาเข้าจริงกลับเสียเวลาไปมากพอสมควรกับช่วงถามตอบนักศึกษา, ช่วงที่ผู้สอนต้องดึงความสนใจกลับมาระหว่างที่มีการพยายามนำเสนอ “multimedia” ในห้องเรียน, และช่วงที่ยังป้องกันคำถามล่วงหน้าไม่ได้เพราะแผนการสอนทั้งคอร์สยังไม่ได้จัดวางจนสมบูรณ์ตั้งแต่แรก
เวลาที่ใช้เขียนบนกระดานเมื่อสะสมรวมกันก็ไม่น้อย
แน่นอนว่าลำดับเนื้อหานั้นถกเถียงและจัดใหม่ได้เสมอ และสักวันหนึ่งก็มีแผนจะทำวิดีโอชุดที่อธิบายเนื้อหานี้ในแบบที่เข้ากับสไตล์ของตัวเอง
รู้สึกยินดีที่คอร์สนี้ดูเหมือนจะยังรักษารากเดิมไว้ แม้จะใช้ภาษาใหม่กว่าอย่าง Python และคิดว่าผู้คนยังประเมินความสามารถในการแสดงออกผ่าน สำนวนแบบฟังก์ชันนัล ของ Python ต่ำเกินไป แม้มันจะไม่บริสุทธิ์เต็มตัวก็ตาม เพราะ Python เป็นภาษาหลายกระบวนทัศน์ที่ใช้งานได้จริง
มองว่า Python ตัดออกจากคอร์สนี้ได้ และควรถูกตัดออกด้วย
Python รองรับการเขียนโปรแกรมเชิงฟังก์ชันนัลได้อ่อนมาก
ลิสต์ไม่ได้อิง
cons,lambdaก็มีข้อจำกัดมาก, pattern matching ก็แย่มาก แถมยังไม่เป็น expression-based และระบบ namespace ก็ประหลาดจะเรียก Python ว่าเป็นภาษาสมัยใหม่ก็ยาก มันเหมือนยังติดอยู่ในยุค 1990s และเติบโตขึ้นมาได้โดยน่าเสียดายที่เบียดบังภาษาอื่นที่ดีกว่า เพียงเพราะมี C-API ที่ดี
เพราะไม่มี tail call optimization แบบสมบูรณ์ แบบฝึกหัดโค้ดบางข้อจึงต้องใช้วิธีแก้ที่ต่างจาก Scheme โดยสิ้นเชิง
ถ้าโค้ดที่แปลแบบ 1:1 ใช้ไม่ได้ ผู้สอนควรบอกนักศึกษาว่าให้เชื่อไว้ก่อนเพราะมันทำงานแบบนั้นจากภาษาที่เลือกใช้ หรือควรมองทุกอย่างเป็นปัญหาของการย้ายสแตกออกมาภายนอกแล้วแก้แบบนั้นกันแน่
การยัด SICP ให้เข้ากับ Python แบบฝืน ๆ ดูค่อนข้างโง่เขลา
ตอนนี้เว็บไซต์ออฟไลน์ไปแล้ว แต่ยังดูคอร์สได้บน archive.org
https://en.m.wikipedia.org/wiki/ArsDigita#ArsDigita_Foundati...
https://archive.org/details/arsdigita_01_sicp/
พวกเขาเคยขาย USB key ที่บรรจุหลักสูตรทั้งหมดไว้ ถ้ามีใครอัป ISO ขึ้นมาจะดีมากจริง ๆ
https://web.archive.org/web/20190222145553/aduni.org/drives/
สิ่งที่เขียนได้ง่ายใน Scheme กลายเป็นแบบฝึกหัดซับซ้อนในภาษาอื่น
นักศึกษาจึงไปโฟกัสกับรายละเอียดของภาษา implementation ที่ใน Scheme ไม่ต้องสนใจ แทนที่จะโฟกัสกับแนวคิดพื้นฐาน
ตอนที่เห็นการ implement
cons/car/cdrด้วย lambda ครั้งแรก รู้สึกเหมือนเวทมนตร์แต่สุดท้ายก็มองว่ามันเป็นการแสดงให้เห็นว่า language runtime เองกำลัง implement dictionary แบบคีย์/ค่า อยู่ และเราสามารถอาศัย implementation นั้นมาสร้างโครงสร้างข้อมูลอื่นได้
ใน Elixir สามารถดึงค่าจากด้านหน้าออกมาได้มากเท่าที่ต้องการ
closure ที่ไม่มีพฤติกรรมก็เป็นแค่ pointer ไปยังตัวแปรที่ปิดไว้ และ closure ที่มี pointer 2 ตัวก็คือ pair ที่ใช้เอา
carกับcdrออกมาได้runtime ต้องทำให้สิ่งที่ pointer ชี้อยู่ยังใช้งานได้นอกขอบเขตที่ประกาศไว้ จึงต้องมี escape analysis, garbage collection ฯลฯ แต่ไม่จำเป็นต้องมี dictionary
ไม่นานมานี้ได้เจอแนวคิดว่าในการพิสูจน์ทฤษฎีบท ถ้าจะพิสูจน์อะไรอย่าง
0 != 1จะใช้แค่ Church encoding ไม่พอ แต่ต้องมี inductive data typeเคยเขียนเรื่องที่เกี่ยวข้องไว้คร่าว ๆ ตรงนี้ และมีคำวิจารณ์ต่อ SICP อีกประเด็นหนึ่งรวมอยู่ด้วย: https://intellec7.notion.site/Drinking-SICP-hatorade-and-why...
อยากเข้าใจข้อจำกัดของมุมมองแบบ “ทุกอย่างเป็นแค่ฟังก์ชัน” ให้ดีขึ้น
0 ≠ 1ได้สามารถอนุมานจากทฤษฎีบท
f = g -> f x = g xเพื่อสร้างข้อเท็จจริงเรื่องอสมการทางด้านขวา แล้วค่อยใช้ contrapositive ของมันคำกล่าวที่ว่าอสมการระหว่าง Church numeral พิสูจน์ตรง ๆ ไม่ได้หากไม่มีข้อเท็จจริงอื่นเกี่ยวกับอสมการ ก็ดูจะถูกต้อง
ในทางกลับกัน สำหรับ inductive data type ระบบพิสูจน์สามารถ “สังเกต” ความเท่ากันหรือความไม่เท่ากัน ได้โดยตรง ผ่านการลอกการใช้ constructor ชั้นนอกสุดของอินสแตนซ์รูปธรรม 2 ตัวที่อยู่ใน inductive type เดียวกันออกแบบเวียนเกิด
สงสัยว่าสามารถทำสิ่งนั้นได้ด้วย Scott encoding หรือ Church encoding หรือไม่
ตัวหนังสือเองกำลังมีการคุยกันอยู่ที่นี่แล้ว: https://news.ycombinator.com/item?id=42157558
สงสัยว่ามีเหตุผลอะไรหรือไม่ที่ลิงก์พาไปยังบทสนทนาช่วงล่างแทนที่จะเป็นต้นหน้าของหน้านั้น
รู้สึกว่าโพสต์นี้น่าจะรวมเข้ากับบทสนทนาที่มีอยู่เดิมได้
David Beazley เป็นบุคคลระดับตำนานพอตัวในโลก Python และคอร์สนี้ตอนแรกดูเหมือนไอเดียที่น่าทึ่ง แต่พอคิดดูสัก 2 วินาทีกลับรู้สึกว่าเป็นการจับคู่ที่ลงตัวมาก เลยไปลงเรียนคอร์สถัดไป
ประเด็นสำคัญคือ รูปแบบแบบนี้น่าจะเป็นหน้าตาของ การศึกษาต่อเนื่องสำหรับวิศวกรรมซอฟต์แวร์ ในอนาคต
มีพิมพ์ผิดในโค้ดของส่วน “the substitution model”
("+", ("fib", ("-", "n", 2)), ("fib", ("-", "n", 1))),ที่นิยามไว้คือ
fibonacciและไม่ได้มีการนิยามfibดังนั้นการเรียกfibทั้งสองจุดควรเป็นfibonacciอย่างชัดเจนโค้ดใน GitHub repository จริงถูกต้อง: https://github.com/savarin/pyscheme/blob/0f47292c8e5112425b5...
คิดว่า SICP ยอดเยี่ยมมาก
แต่ยิ่งเรียนรู้มากขึ้นและศึกษาคณิตศาสตร์มากขึ้น ก็ยิ่งโน้มเอียงไปสู่ข้อสรุปว่า ความสัมพันธ์ เป็นแนวคิดปฐมฐานที่พื้นฐานยิ่งกว่า
ฟังก์ชันทั้งหมดสามารถแสดงเป็นความสัมพันธ์แบบจำกัดรูปแบบได้ แต่ในทางกลับกันจะทำแบบนั้นไม่ได้หากไม่เพิ่มกลไกเสริมเข้าไปมากพอสมควร
แม้ฐานข้อมูลเชิงสัมพันธ์และ SQL จะเป็นตัวอย่างของการเขียนโปรแกรมเชิงสัมพันธ์ที่เป็นที่รู้จักและประสบความสำเร็จมากที่สุด แต่ก็ยังมองว่าพื้นที่นี้แทบยังไม่ได้รับการสำรวจอีกมาก
ตอนนี้ความสนใจใกล้เคียงกับการสอนพื้นฐานคณิตศาสตร์ให้เด็กเล็กมากกว่าการออกแบบภาษาโปรแกรม
น่าแปลกที่การสอนว่า “ใหญ่” เป็นความสัมพันธ์หนึ่งอาร์กิวเมนต์ และ “ใหญ่กว่า” เป็นความสัมพันธ์สองอาร์กิวเมนต์นั้นง่ายกว่าการพยายามอธิบายแนวคิดเดียวกันในฐานะฟังก์ชันมาก
ไม่ค่อยชอบมุมมองแบบ “ทุกอย่างคือฟังก์ชัน” เพราะมันลดทอนความซับซ้อนมากเกินไปและมักไม่ได้ช่วยอะไรนัก
ตัวอย่างเช่น มีฟังก์ชันที่ไม่สามารถใส่ไว้ในแคช, RAM หรือดิสก์ได้, มีฟังก์ชันที่ Big-O ระเบิด อย่าง N-way
JOINหรือการค้นหา/การจับคู่ และมีฟังก์ชันที่มีผลข้างเคียงรวมถึงความไม่ไอดีมโพเทนต์แทบไม่มีใครคิดถึงการโจมตีแบบ side-channel ต่อฟังก์ชัน
ยังมีฟังก์ชันไม่กำหนดแน่ชัดที่ขึ้นอยู่กับวันที่ เวลา และระยะเวลา และฟังก์ชันก็อาจล้มเหลวกลางทางหรือไม่ล้มเหลวอย่างสวยงามก็ได้
และก็ยากที่จะสมมติว่ามันไม่ใช้ทรัพยากรจนไปกระทบ “ฟังก์ชัน” อื่นที่ใช้พูลทรัพยากรร่วมกัน
อาร์กิวเมนต์ของฟังก์ชันอาจมีขนาดใหญ่หรือซับซ้อนได้ไม่จำกัด แต่ในโลกจริงมีขีดจำกัด และเมื่อนั้นก็ต้องใช้พอยน์เตอร์ แล้วสุดท้ายก็ต้องมีการอ้างอิงระยะไกลอย่างเว็บหรือดิสก์อีก
อยากให้มีคนบอกให้หยุดได้แล้ว เพราะพูดต่อได้เรื่อย ๆ
ในกรณีนี้ แบบจำลองแลมบ์ดาแคลคูลัส คือรากฐานของแนวทาง “ทุกอย่างก็แค่ฟังก์ชัน” และเป็นแบบจำลองการคำนวณที่ดี เพราะเรียบง่ายมากและจัดการรวมถึงให้เหตุผลได้ง่ายกว่าเมื่อเทียบกับเครื่องทัวริงและสิ่งอื่น ๆ
จึงเป็นรากฐานของตรรกะคอมพิวเตอร์และระบบพิสูจน์ส่วนใหญ่
ถ้าฟังก์ชันใดต้องใช้ทรัพยากร ก็ทำให้มันต้องรับทรัพยากรนั้นเข้าไป ถ้ามันขึ้นกับวันที่/เวลา ก็ทำให้มันขึ้นกับวันที่/เวลา และถ้ามันคืนค่า ค่าที่ไม่กำหนดแน่ชัด ก็ทำให้มันคืนค่าที่ไม่กำหนดแน่ชัดไปเลย
หนึ่งในเหตุผลที่แนวทางการเขียนโปรแกรมเชิงฟังก์ชันโดดเด่นก็คือ มันบังคับให้ต้องจัดการเรื่องพวกนี้อย่างจริงจัง
ถ้าอยากใช้ทรัพยากรที่ถูกแชร์แบบแฝง ก็ต้องจำลองมันขึ้นมา และ “ฟังก์ชัน” ที่พึ่งพาทรัพยากรแชร์แบบแฝงก็จะแตกต่างจากฟังก์ชันจริงอย่างชัดแจ้ง
มันใกล้เคียงกับ กระบวนวิธี มากกว่า
ฟังก์ชันเป็นกระบวนวิธีชนิดหนึ่ง แต่ไม่ใช่ว่ากระบวนวิธีทุกอย่างจะเป็นฟังก์ชัน