Nattawut Phetmak
Jack of all Trades
นอกจากเราจะสร้างฟังก์ชันด้วย if-else แล้ว Haskell ยังมีความสามารถในการทำ pattern matching กับตัวแปรฟังก์ชันได้อีกด้วย
ตัวอย่างฟังก์ชันจากคราวก่อนในแบบ pattern matching
logAbs 0 = error "log zero!"
logAbs x = log (abs x)
pattern matching จะช่วยให้อ่านฟังก์ชันที่อยู่ในรูป recursive ง่ายขึ้นมาก
fact 0 = 1
fact x = x * fact (x - 1)
แต่ฟังก์ชัน fact
นี้ยังมีปัญหาอยู่ ตรงที่มันจะ terminate ไม่ได้ ถ้าใส่จำนวนเต็มลบเข้าไป
จากบรรทัดที่สอง x
จะไป match กับค่าใดๆ ก็ตามที่ไม่ใช่ 0
เราอาจนึกถึงการใช้ if-else เพื่อดักตัวแปรเหมือนที่ผ่านมา อย่างไรก็ตาม Haskell มีสิ่งที่เรียกว่า guard ซึ่งเทียเท่าได้กับ switch-case ในภาษาอื่น โดยสามารถเรียกใช้งานได้เช่นนี้
fact 0 = 1
fact x | x > 0 = x * fact (x - 1)
| otherwise = error "factorial negative number!"
นอกจากนี้ เรายังสามารถระบุ type ฟังก์ชันได้เช่นเดียวกับตัวแปร โดยเพิ่มบรรทัดนี้เข้าไปด้านบนสุดครับ
fact :: Integral n => n -> n
ด้านขอบเขตของตัวแปรนั้น เราเคยใช้ let
เก็บค่าตัวแปรที่คำนวณเอาไว้ก่อนมาแล้ว ซึ่งนี่เป็นคำสั่งพิเศษเฉพาะบน ghci
เท่านั้น เมื่อเขียนเป็นฟังก์ชันจะต้องมีส่วน in
เพื่อบอกขอบเขตของตัวแปรที่โดนประกาศด้วย let
เสมอ
fib n = let phi = (1 + sqrt 5) / 2
psi = (1 - sqrt 5) / 2
in (phi ** n - psi ** n) / sqrt 5
เช่นตัวอย่างนี้จะประมาณค่า Fibonacci ในตำแหน่งที่ n
โดยคำนวณค่า phi
กับ psi
เก็บไว้ก่อน ซึ่งมันจะถูกนำไปใช้ได้ในหลังจาก in
ตรงบรรทัดที่ 3 เท่านั้นครับ
นอกจากการประกาศด้วย let-in
แล้ว ยังมีอีกวิธีคือใช้ where
ซึ่งคราวนี้ตัวแปรที่ถูกประกาศจะเห็นได้ทั้งฟังก์ชัน รวมถึงส่วน guard เพื่อทำ switch-case อีกด้วย
grade mean sd point
| aboveMean > 2 * sd = "A"
| aboveMean > 1 * sd = "B"
| belowMean > 2 * sd = "F"
| belowMean > 1 * sd = "D"
| otherwise = "C"
where aboveMean = point - mean
belowMean = mean - point
สิ่งที่ต้องระวังในการเขียนหลายบรรทัดคือการ indent (จัดย่อหน้า) สังเกตว่าประโยคหลัง where
หรือ let
นั้นต้อง indent ให้เท่ากัน เช่นเดียวกับส่วน let
และ in
ครับ
ด้านการออกแบบลำดับตัวแปรของฟังก์ชันนั้น จะให้ตัวแปรแรกๆ เป็นตัวแปรที่เปลี่ยนค่าไม่บ่อย เนื่องจากเราสามารถทำ partial application เพื่อสร้างฟังก์ชันที่มี initial value ได้เช่นนี้
ghci> let gradeMath = grade 55 12.5
ghci> let gradeChem = grade 70 10.0
ghci> gradeMath 80
"B"
ghci> gradeChem 80
"C"