December 21, 2012

Haskell: กรองและแปลงค่าสมาชิกใน List

โดยทั่วไปแล้ว List ตั้งต้นมักไม่ค่อยมีประโยชน์ที่จะเอาไปใช้ต่อเท่าไหร่ จนกว่าเราจะเปลี่ยนค่าของสมาชิกบางตัวไปตามเราที่ต้องการ

เราสามารถสร้าง List ใหม่จากของเดิมได้ โดยคำนวณค่าสมาชิกเก่าทุกตัวภายใต้ฟังก์ชันเดียวกัน วิธีการนี้เรียกว่า list comprehension และสามารถทำได้ดังนี้


นอกจากนี้ เรายังสามารถกรองสมาชิกตัวที่ไม่ต้องการทิ้งก่อนเอาไปคำนวณไปได้ เช่น


อย่างไรก็ตาม ถ้าต้องทำงานกับ List อนันต์ (อย่าง [1..]) การกรองสมาชิกด้วยวิธีการข้างต้นนี้ไม่พอ ต้องใช้งานร่วมกับ take, takeWhile เช่นเดิมครับ



นอกจาก list comprehension ยังมีทางเลือกที่ช่วยให้ทำงานได้เช่นเดียวกันคือ map กับ filter ครับ


ข้อแตกต่างหลักๆ คือ list comprehension จะทำงานกับ List หลายๆ อัน (มองในรูป Cartesian product) ได้ง่ายกว่า แต่การใช้ฟังก์ชัน map, filter แยกกัน จะเป็นธรรมชาติกว่าเมื่อทำ filter หลังจาก map เช่น




ตัวอย่างเลื่องชื่อของ Haskell อย่าง quicksort ก็สามารถทำโดยใช้เทคนิค filter ร่วมกับการทำ pattern matching บน List ดังนี้ครับ

December 13, 2012

Haskell: ทำงานกับ List ที่ยาวเป็นอนันต์

การประกาศ List นอกจากจะทำได้ตามวิธีปรกติแล้ว Haskell ยังทำท่านี้ได้อีกด้วย


ค่าที่เพิ่มจะเพิ่มขึ้นครั้งละ 1 หน่วย (ถ้าตัวเลขก็คือ +1 ถ้าเป็นตัวอักษรก็คือตัวอักษรถัดไป) เราสามารถแก้ไขค่าที่จะเพิ่มขึ้นได้โดยบอกสมาชิกตัวถัดไปใน List นี้แทน


สมาชิกตัวสุดท้ายที่บอกขอบเขตบนนั้น อาจอยู่หรือไม่อยู่ใน List ที่สร้างเสร็จก็ได้ แต่จะไม่มีสมาชิกที่มีค่าใหญ่กว่านี้ครับ

ส่วนการประกาศค่าแบบลดลงนั้น ไม่สามารถบอกแค่ [10..1] ได้ ต้องบอกสมาชิกตัวถัดมาด้วย


หมายเหตุว่าให้ระวังการประกาศ List แบบนี้กับเลขทศนิยม เพราะ bug ของตัว Haskell เองทำให้อาจมีสมาชิกที่ใหญ่กว่าขอบเขตบนโผล่มาได้ครับ

อนึ่ง เราสามารถละส่วนขอบเขนบนได้ โดย Haskell จะเอาขอบเขตบนของ type นั้นมาเติมให้เอง เช่น


ปล่อยไว้ซักพัก จะเห็นว่า List ที่สร้างขึ้นนี้วิ่งไปจบที่ตัวอักษรซักตัวเอง และถ้าสงสัยว่าขอบเขตบนนั้นคืออะไร ก็สามารถหาได้โดย


แต่สำหรับข้อมูลบาง type ที่ไม่มีขอบเขตบน (เช่น Integer) เมื่อสั่ง [1..] ตัวเลขจะวิ่งขี้นไปเรื่อยๆ ไม่มีที่สิ้นสุดครับ (เป็น List ที่ยาวเป็นอนันต์)



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

ฟังก์ชันที่ใช้เพื่อขอสมาชิกเพียงไม่กี่ตัวแรกของ List จากทางด้านหน้าคือ take


ส่วน drop นั้นจะใช้เพื่อทิ้งสมาชิกตัวหน้าออกไป


(drop เพียงอย่างเดียวนั้นไม่ค่อยมีประโยชน์กับ List ที่ยาวเป็นอนันต์ครับ)

ฟังก์ชันอีกกลุ่มที่ทำหน้าที่เช่นเดียวกันนี้ เพียงแต่เปลี่ยนจากจำนวนสมาชิกที่ต้องการแน่นอน ไปเป็นการตรวจสอบค่าสมาชิกว่าตรงเงื่อนไขหรือยัง คือ takeWhile และ dropWhile


ย้ำอีกทีว่าฟังก์ชันพวกนี้จะเริ่มทำงานจากด้านหัว และคืนค่าทันทีเมื่อเงื่อนไขครบ เห็นได้จาก dropWhile (/=3) ที่ทิ้งเลข [1,2] เฉพาะทางด้านหัวเท่านั้น



นอกจากจะใช้การประกาศ List ตามด้านบนเพื่อสร้าง List ที่ยาวอนันต์ได้แล้ว ยังสามารถสร้าง List ที่ยาวเป็นอนันต์โดยใช้ฟังก์ชันเหล่านี้

cycle รับตัวแปรเป็น List ที่จะเอามาทำซ้ำเรื่อยๆ


ส่วน repeat จะรับตัวแปรคือสมาชิกตัวเดียวของ List ที่จะเอาไปทำซ้ำๆ แต่ถ้ารู้ขนาดที่แน่นอนอยู่แล้ว สามารถใช้ฟังก์ชัน replicate แทนได้

December 12, 2012

Haskell: เก็บข้อมูลชุดด้วย List

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


อย่างไรก็ตาม สมาชิกใน List ทุกตัวต้องเป็น type เดียวกันเท่านั้น เช่นเดียวกับ String ที่มีสมาชิกทุกตัวเป็น Char นั่นเอง

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


List ใน Haskell นั้น แท้จริงแล้วถูกออกแบบมาเป็น stack โดยด้านที่รับ-ส่งสมาชิกคือด้านหัว (ฝั่งซ้ายมือ) ดังนั้นเรามักจะเพิ่มสมาชิกตัวใหม่ๆ โดย : มากกว่าที่จะใช้ ++ เพิ่มสมาชิกต่อหลังครับ (มีผลต่อความเร็ว)



เมื่อต้องการดึงหัว List ออกมาใช้ ทำได้โดยฟังก์ชัน head หรือเก็บสมาชิกทุกตัวยกเว้นหัวก็ใช้ tail


แม้เราจะไม่ค่อยยุ่งกับสมาชิกตัวท้ายสุดซักเท่าไหร่ แต่ถ้าต้องการสมาชิกตัวสุดท้าย หรือต้องการสมาชิกทุกตัวยกเว้นท้ายสุด ก็สามาถทำได้ผ่าน last, init ตามลำดับ


ข้อควรระวังคือการดำเนินการพวกนี้ ไม่สามารถทำได้กับ List ที่ไม่มีสมาชิกเลย ซึ่งเราสามารถตรวจว่ามีสมาชิกหรือไม่ได้โดยคำสั่ง null


ส่วนการดึงสมาชิก ณ ตำแหน่งใดๆ ออกมา ทำได้ผ่านเครื่องหมาย !! เช่นนี้ครับ


แน่นอนว่ามันจะให้ error ถ้าส่วน index นั้นใหญ่เกินกว่า List เราสามารถหาขนาดได้โดยฟังก์ชัน length


ส่วนถ้าจะตรวจสอบว่ามีสมาชิกที่เราอยากรู้อยู่ใน List นั้นหรือเปล่า ก็ทำได้ผ่านฟังก์ชัน elem

December 11, 2012

Haskell: ข้อความและ IO เบื้องต้น

ใน Haskell จะแบ่งข้อมูลตัวอักษรเป็น 2 แบบ ได้แก่ Char (อักษร 1 ตัว) ที่ประกาศโดยใช้ single quote ส่วน String (อักษรหลายตัว) ประกาศโดยใช้ double quote


อย่างไรก็ตาม ถ้าลองตรวจสอบ type ของ String จะเห็นแบบนี้


วงเล็บปีกแข็งที่ครอบ Char นั้นบอกว่า String เป็นข้อมูลแบบ List ของ Char (คือการเอา Char หลายๆ ตัวมาต่อกัน)

การเชื่อม String เข้าด้วยกัน ทำได้โดยวางเครื่องหมาย ++ ไว้ระหว่างข้อความ 2 ข้อความ


อย่างไรก็ตาม การเชื่อม String กับ Char ต้องแปลง type ของ Char ให้เป็น String เสียก่อน


ถ้าต้องการแสดงตัวเลข (และข้อมูลชนิดอื่นๆ) ให้ใช้ฟังก์ชัน show แทน


ส่วนการแปลง type จากข้อความไปเป็น type อื่นเพื่อนำไปคำนวณต่อ สามารถทำได้ผ่านฟังก์ชัน read เช่นนี้


แต่การสั่ง read "23" เพียงอย่างเดียวจะเกิด error เพราะตัวแปรนั้นจะหา type ไม่ได้ (เนื่องจากไม่รู้ว่าจะถูกเอาไปใช้ทำอะไรต่อ)

ทางออกคือถ้ารู้ type ที่จะเอาไปใช้ต่อแน่นอน ก็กำหนดลงไปได้เลย




เนื่องจากหลักการของภาษาเชิง functional นั้นบอกว่า ฟังก์ชันใดๆ ที่เรียกขึ้นมาโดยส่งผ่านตัวแปรเดิมเข้าไป ผลลัพท์ก็ต้องออกมาเหมือนเดิมเสมอ

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

เมื่อต้องการรับข้อความเป็นบรรทัดจากผู้ใช้ ทำได้โดยฟังก์ชัน getLine ดังนี้


ฟังก์ชันนี้จะรอเราพิมพ์ข้อความไปเรื่อยๆ จนกว่าจะป้อน EOL ให้ สังเกตว่าการเก็บผลลัพท์นั้นไม่ได้ใช้ let แล้วตามด้วย = แล้ว แต่เปลี่ยนมาใช้ <- แทน ซึ่งเป็นสัญลักษณ์พิเศษสำหรับเก็บค่า String ที่รับมาจาก IO ครับ

อนึ่ง การพิมพ์ค่าโดยฟังก์ชัน putStrLn นั้น ตัวแปรแบบต้องเป็นข้อความเท่านั้น ถ้าต้องการพิมพ์ค่าตัวแปรที่ไม่ใช่ข้อความ สามารถใช้ฟังก์ชัน print แทนได้

ตัวอย่างต่อไปนี้เป็นโปรแกรมแบบ IO คอยรับข้อมูลจากผู้ใช้ แล้วคำนวณด้านที่ยาวที่สุดของสามเหลี่ยมมุมฉากครับ

หมายเหตุว่าการ recursive main นั้นเป็นเรื่องปรกติใน Haskell ครับ

December 7, 2012

Haskell: ฟังก์ชันและขอบเขตของตัวแปร

นอกจากเราจะสร้างฟังก์ชันด้วย if-else แล้ว Haskell ยังมีความสามารถในการทำ pattern matching กับตัวแปรฟังก์ชันได้อีกด้วย

ตัวอย่างฟังก์ชันจากคราวก่อนในแบบ pattern matching


pattern matching จะช่วยให้อ่านฟังก์ชันที่อยู่ในรูป recursive ง่ายขึ้นมาก


แต่ฟังก์ชัน fact นี้ยังมีปัญหาอยู่ ตรงที่มันจะ terminate ไม่ได้ ถ้าใส่จำนวนเต็มลบเข้าไป

จากบรรทัดที่สอง x จะไป match กับค่าใดๆ ก็ตามที่ไม่ใช่ 0 เราอาจนึกถึงการใช้ if-else เพื่อดักตัวแปรเหมือนที่ผ่านมา อย่างไรก็ตาม Haskell มีสิ่งที่เรียกว่า guard ซึ่งเทียเท่าได้กับ switch-case ในภาษาอื่น โดยสามารถเรียกใช้งานได้เช่นนี้


นอกจากนี้ เรายังสามารถระบุ type ฟังก์ชันได้เช่นเดียวกับตัวแปร โดยเพิ่มบรรทัดนี้เข้าไปด้านบนสุดครับ




ด้านขอบเขตของตัวแปรนั้น เราเคยใช้ let เก็บค่าตัวแปรที่คำนวณเอาไว้ก่อนมาแล้ว ซึ่งนี่เป็นคำสั่งพิเศษเฉพาะบน ghci เท่านั้น เมื่อเขียนเป็นฟังก์ชันจะต้องมีส่วน in เพื่อบอกขอบเขตของตัวแปรที่โดนประกาศด้วย let เสมอ


เช่นตัวอย่างนี้จะประมาณค่า Fibonacci ในตำแหน่งที่ n โดยคำนวณค่า phi กับ psi เก็บไว้ก่อน ซึ่งมันจะถูกนำไปใช้ได้ในหลังจาก in ตรงบรรทัดที่ 3 เท่านั้นครับ

นอกจากการประกาศด้วย let-in แล้ว ยังมีอีกวิธีคือใช้ where ซึ่งคราวนี้ตัวแปรที่ถูกประกาศจะเห็นได้ทั้งฟังก์ชัน รวมถึงส่วน guard เพื่อทำ switch-case อีกด้วย


สิ่งที่ต้องระวังในการเขียนหลายบรรทัดคือการ indent (จัดย่อหน้า) สังเกตว่าประโยคหลัง where หรือ let นั้นต้อง indent ให้เท่ากัน เช่นเดียวกับส่วน let และ in ครับ

ด้านการออกแบบลำดับตัวแปรของฟังก์ชันนั้น จะให้ตัวแปรแรกๆ เป็นตัวแปรที่เปลี่ยนค่าไม่บ่อย เนื่องจากเราสามารถทำ partial application เพื่อสร้างฟังก์ชันที่มี initial value ได้เช่นนี้

December 6, 2012

PHP: ระบบสมาชิก - log in, log out

คราวก่อน เขียนถึงระบบสมาชิกส่วนแรกซึ่งก็คือ การสมัครสมาชิก คราวนี้มาดูการ log in กับ log out กันบ้าง เริ่มต้นจากสร้างแบบฟอร์มสำหรับ log in กันก่อน แบบฟอร์มก็แบบเดียวกับที่พบได้ตามเว็บไซต์ทั่วไป ส่วน .php ที่ใช้ในการ log in จะเริ่มต้นด้วยการตรวจสอบเบื้องต้นก่อนว่า ผู้ใช้ลืมกรอกข้อมูล หรือเปล่า ถ้าทุกอย่างไม่มีปัญหาอะไร ก็มาตรวจสอบบัญชีผู้ใช้ในฐานข้อมูล โดยใช้การเชื่อมต่อผ่าน PDO เหมือนเดิม ซึ่งตอนที่ตรวจสอบรหัสผ่านนั้น ในตอนสมัครสมาชิก เราใช้การ hash และ salt ในการเก็บข้อมูลของรหัสผ่าน ในขั้นตอน log in เราก็จะต้องแปลงรหัสผ่านให้กลายเป็น hash ที่ถูกต้องด้วย ตอนนี้เราสามารถ log in เข้ามาได้แล้ว แต่ผู้ใช้ต้อง log in ใหม่ทุกครั้งเวลาเปลี่ยนหน้า อันนี้ต้องใช้ session เข้ามาช่วยในการจดจำผู้ใช้

December 4, 2012

Haskell: Boolean และ Control Flow

ค่าความจริงใน Haskell มีเพียงแค่ True กับ False เท่านั้น (ไม่สามารถใช้ 0 หรือค่าว่างแบบอื่นๆ แทน False ได้)

ฟังก์ชันสำหรับตรวจสอบความเท่ากัน ใช้ == (เท่ากัน) กับ /= (ไม่เท่ากัน) ส่วนฟังก์ชันสำหรับการตรวจสอบลำดับก็ได้แก่ <= (น้อยกว่าเท่ากับ), < (น้อยกว่า), > (มากกว่า), >= (มากกว่าเท่ากับ)


logic สำหรับเชื่อมค่าความจริงเหล่านี้ได้แก่ && (และ), || (หรือ), not (นิเสธ)


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



ด้วยความที่เป็นภาษา functional ทุกๆ expression จะต้องคืนค่ากลับมาเสมอ ทำให้การใช้ if ใน Haskell ต้องมีส่วน else ตามท้ายตลอด


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


ตัวอย่างข้างต้นนี้สามารถแยกเขียนหลายบรรทัดให้อ่านง่ายขึ้นได้ แต่เนื่องจาก ghci ไม่สะดวกเมื่อต้องเขียนหลายบรรทัด จะย้ายไปเขียนเป็นฟังก์ชันในไฟล์แทน ดังนี้


สิ่งที่ต่างไปจากการประกาศฟังก์ชันบน ghci คือ ไม่ต้องมี let นำหน้า เนื่องจากฟังก์ชันนี้ไม่ได้ถูกประกาศใน main ครับ

เซฟเป็นไฟล์ func.hs แล้วกลับมาที่ ghci เราจะเรียกฟังก์ชันที่เขียนเก็บด้วยคำสั่ง :l หรือ :load ดังนี้