ปีใหม่นี้ผมให้ของขวัญตัวเองเป็นปากกาหมึกซึมยี่ห้อ LAMY รุ่น safari ราคา 700 กว่าบาท, ซึ่งแท่งเก่าที่มีก็ใช้ยี่ห้อนี้อยู่แล้ว ใช้มาเกือบ 10 ปี หัวปากกามันหลวมใช้ไม่ค่อยดีเสียแล้ว
requirement ในการเลือกปากกาของผมก็คือ ผมใช้วาดรูปหรือ sketch เป็นหลัก เรื่องเขียนตัวหนังสือเป็นเรื่องเป็นราวนี่น้อยมาก ก็เลยเลือกขนาดของหัวเป็น medium (M)
สมัยที่เข้าทำงานใหม่ๆ เจ้านายและอาจารย์ผม(สองบทบาทในร่างเดียว) ก็พร่ำสอนว่า ต้องมีสมุดจดและต้องรู้จักจด ซึ่งผมก็ทำตาม แต่ไม่ร้อยเปอร์เซนต์เพราะสันดานออกจะเป็นคนขี้เกียจ แต่ก็จดมาได้เกือบสิบปี พอมาถึงยุค notebook ครองโลก ด้วยความที่มันพกสะดวกก็เลยทอดทิ้งสมุดโน้ตไปเสีย (จริงๆไม่เกี่ยวกันหรอก แค่อ้างให้ดูมีเหตุผล)
เมื่อสัปดาห์ก่อนผมได้อ่านหนังสือ Pragmatic Thinking and Learning ซึ่งเขาก็ชักจูงให้เห็นความสำคัญของการจดบันทึก ก็เลยเกิดอาการกลับตัวกลับใจ รื้อฟื้นสมุดโน้ตขึ้นมาใช้อีกครั้ง ส่งผลให้มีสาเหตุที่ต้องซื้อปากกาแท่งใหม่ ด้วยความเชื่อที่ว่า การมีปากกาที่ดีจะทำให้เราสนุกกับการเขียน (เหตุผลเดียวกับการใช้ mac)
Sunday, January 04, 2009
Tuesday, December 23, 2008
Eureka
เมื่อคืนก่อนตอนเที่ยงคืน ผมสะดุ้งตื่นเพราะได้ยินเสียงสัญญาณกันขโมย ก็เลยลุกงัวเงียเดินไปแง้มหน้าต่างเพื่อดูว่ารถตูหรือเปล่าวะ, อ้าวฉิบหายแล้ว รถเรานี่เอง ตอนนั้นสิ่งที่นึกในหัวก็คือ "เอาอีกแล้วไอ้เจ้าสัญญาณกันขโมยเฮงซวย" ที่คิดอย่างนั้นก็เพราะช่วงหลังๆ สัญญาณกันขโมยผมมันชอบร้องโวยวายโดยไม่มีสาเหตุ ร้องจนผมเลิกล็อครถตอนจอดอยู่ในบ้านแล้ว
หลังจากรู้ว่ารถตัวเองแน่นอน ก็เลยรีบวิ่งลงไปข้างล่างเพื่อไปหากุญแจมาปิดสัญญาณ(ไม่อยากให้เพื่อนบ้านหนวกหู) แต่ค้นเท่าไรก็ไม่เจอ อารมณ์โมโหเริ่มครอบงำ นึกกล่าวโทษภรรเมียว่าเก็บกุญแจไม่เป็นที่เป็นทาง
ตอนที่เดินหากุญแจในอยู่ ก็พบว่า อ้าวตู้เนี่ยทำไมเปิดอยู่ จัดการปิดซะเดินไปหาถึงประตูหลัง อ้าวประตูเปิดอยู่ นึกในใจ เด็กทำงานบ้านเราทำไมมันชุ่ยอย่างนี้ ไม่ยอมปิดประตูก่อนนอน ว่าแล้วก็ปิดประตูซะ ผ่านไปพักใหญ่สัญญาณก็หยุดทำงาน ก็เลยหยุดหาแล้วก็เปิดประตูหน้าบ้านออกไปดูว่าเกิดอะไรขึ้น พอหมุนลูกบิดก็รู้สึกว่า อ้าวประตูไม่ได้ล็อก ดูเสร็จไม่พบอะไรก็ล็อกซะ
ตอนนั้นก็ไม่เอะใจอะไร เดินไปปลุกแฟน เอากุญแจไปซ่อนไว้ที่ไหนหามาให้เดี๋ยวนี้นะ
แฟนก็บอกว่าไม่ได้เอาไป แล้วก็เดินลงมาช่วยหา ซึ่งก็หาไม่เจออีก แฟนก็เลยเดินขึ้นไปปลุกเด็กที่ทำงานบ้าน เด็กก็บอกว่าไม่ได้เอาไป
เท่านั้นเอง ผมก็ตาสว่างร้อง Eureka!! ขโมยขึ้นบ้านกูนี่หว่า ว่าแล้วก็ลงไปสำรวจหลังบ้าน ก็ได้พบกุญแจรถยนต์สมใจ (ขโมยมันโยนทิ้งไว้)
เรื่องที่อยากเล่าให้ฟังจริงๆก็คือ วิธีการทำงานของสมองเรา สิ่งที่เราคิดเราเชื่อจะเป็นตัวกำหนดกรอบความคิดของเรา เมื่อเกิด event หนึ่งๆขึ้น สมองเราก็จะใช้เส้นทางที่มันถนัดที่สุดในการตีความ event นั้นๆ
ซึ่งการด่วนตัดสิน(ฟันธง, confirm) ก็จะทำให้เราพลาดการมองเห็นสิ่งที่เกิดขึ้นจริงๆ
ยิ่งเราแก่ตัวลง ถ้าเรายึดมั่นในชุดความคิดใดความคิดหนึ่งมากๆ การคิดบ่อยๆเชื่อบ่อยๆ ก็จะยิ่งย้ำเส้นทางของ Neural network ในสมองเราให้เชื่อมกันแนบแน่นยิ่งขึ้น นี่เป็นสาเหตุที่เรามักพบว่า คนแก่มักจะหัวดื้อและไม่ยอมฟัง
นึกเปรียบเทียบกับสภาพบ้านเมืองตอนนี้ ขโมยขึ้นบ้าน(ภาวะเศรษฐกิจโลก) แต่เรายังหมกมุ่นกับเรื่องกุญแจไม่เลิก (แทนคำว่ากุญแจกันเอาเองนะ แทนได้หลายคำเลย)
หลังจากรู้ว่ารถตัวเองแน่นอน ก็เลยรีบวิ่งลงไปข้างล่างเพื่อไปหากุญแจมาปิดสัญญาณ(ไม่อยากให้เพื่อนบ้านหนวกหู) แต่ค้นเท่าไรก็ไม่เจอ อารมณ์โมโหเริ่มครอบงำ นึกกล่าวโทษภรรเมียว่าเก็บกุญแจไม่เป็นที่เป็นทาง
ตอนที่เดินหากุญแจในอยู่ ก็พบว่า อ้าวตู้เนี่ยทำไมเปิดอยู่ จัดการปิดซะเดินไปหาถึงประตูหลัง อ้าวประตูเปิดอยู่ นึกในใจ เด็กทำงานบ้านเราทำไมมันชุ่ยอย่างนี้ ไม่ยอมปิดประตูก่อนนอน ว่าแล้วก็ปิดประตูซะ ผ่านไปพักใหญ่สัญญาณก็หยุดทำงาน ก็เลยหยุดหาแล้วก็เปิดประตูหน้าบ้านออกไปดูว่าเกิดอะไรขึ้น พอหมุนลูกบิดก็รู้สึกว่า อ้าวประตูไม่ได้ล็อก ดูเสร็จไม่พบอะไรก็ล็อกซะ
ตอนนั้นก็ไม่เอะใจอะไร เดินไปปลุกแฟน เอากุญแจไปซ่อนไว้ที่ไหนหามาให้เดี๋ยวนี้นะ
แฟนก็บอกว่าไม่ได้เอาไป แล้วก็เดินลงมาช่วยหา ซึ่งก็หาไม่เจออีก แฟนก็เลยเดินขึ้นไปปลุกเด็กที่ทำงานบ้าน เด็กก็บอกว่าไม่ได้เอาไป
เท่านั้นเอง ผมก็ตาสว่างร้อง Eureka!! ขโมยขึ้นบ้านกูนี่หว่า ว่าแล้วก็ลงไปสำรวจหลังบ้าน ก็ได้พบกุญแจรถยนต์สมใจ (ขโมยมันโยนทิ้งไว้)
เรื่องที่อยากเล่าให้ฟังจริงๆก็คือ วิธีการทำงานของสมองเรา สิ่งที่เราคิดเราเชื่อจะเป็นตัวกำหนดกรอบความคิดของเรา เมื่อเกิด event หนึ่งๆขึ้น สมองเราก็จะใช้เส้นทางที่มันถนัดที่สุดในการตีความ event นั้นๆ
ซึ่งการด่วนตัดสิน(ฟันธง, confirm) ก็จะทำให้เราพลาดการมองเห็นสิ่งที่เกิดขึ้นจริงๆ
ยิ่งเราแก่ตัวลง ถ้าเรายึดมั่นในชุดความคิดใดความคิดหนึ่งมากๆ การคิดบ่อยๆเชื่อบ่อยๆ ก็จะยิ่งย้ำเส้นทางของ Neural network ในสมองเราให้เชื่อมกันแนบแน่นยิ่งขึ้น นี่เป็นสาเหตุที่เรามักพบว่า คนแก่มักจะหัวดื้อและไม่ยอมฟัง
นึกเปรียบเทียบกับสภาพบ้านเมืองตอนนี้ ขโมยขึ้นบ้าน(ภาวะเศรษฐกิจโลก) แต่เรายังหมกมุ่นกับเรื่องกุญแจไม่เลิก (แทนคำว่ากุญแจกันเอาเองนะ แทนได้หลายคำเลย)
Related link from Roti
Monday, December 08, 2008
random เรียกชื่อคน
เมื่อสุดสัปดาห์ที่ผ่านมาผมมีภาระกิจต้องไปสัมมนากับบริษัทฯ โดยในสัมมนาผมต้องนำคุยทำความรู้จักกับนิยามของ Knowledge แต่เนื่องจากเรื่องนี้มันคลุมเครือและออกนามธรรมมาก การที่น้องๆจะสมัครใจแสดงความคิดเห็น คงจะยากมาก ประกอบกับพึ่งอ่าน blog ของอาจารย์ cholwich ซึ่งเขียนโปรแกรม random ชื่อนักเรียนด้วย java ก็เลยลอกวิธีการมาใช้บ้าง แต่เนื่องจากช่วงนี้กำลังอินกับ clojure ก็เลย implement ด้วย clisp
ลองมาดูตัวโปรแกรมกัน
เริ่มจากการกำหนด global variable สำหรับเก็บชื่อคน
ตามด้วยการกำหนดชื่อคน
Note: global variable ใน clisp มี scope แบบ dynamic, การตั้งชื่อนิยมตั้งด้วย * นำหน้า เพื่อให้แยกความแตกต่างจาก local variable เพราะถ้าตั้งชื่อตรงกันอาจจะเจอปัญหาการ binding ได้
จากนั้นก็สร้าง function ที่ random ชื่อออกมา
ส่วน
โปรแกรมก็ทำงานได้ดี แต่ใช้ไปนานๆ มัน random คนออกมาซ้ำๆกันมากไปหน่อย
กลับมาจากสัมมนาแล้ว ก็เลยมานั่งปรับปรุงให้มันเอาชื่อคนที่เคยเรียกแล้วออกไปจาก list ให้ด้วย (จะได้ไม่เรียกซ้ำ)
สุดท้ายก็ปรับปรุงว่า ถ้าเอาชื่อออกจนหมดแล้ว ก็ให้ reset รายชื่อใหม่
ลองมาดูตัวโปรแกรมกัน
เริ่มจากการกำหนด global variable สำหรับเก็บชื่อคน
(defvar *names*)
ตามด้วยการกำหนดชื่อคน
(setq *names* '(pok bunny pann pune))
Note: global variable ใน clisp มี scope แบบ dynamic, การตั้งชื่อนิยมตั้งด้วย * นำหน้า เพื่อให้แยกความแตกต่างจาก local variable เพราะถ้าตั้งชื่อตรงกันอาจจะเจอปัญหาการ binding ได้
จากนั้นก็สร้าง function ที่ random ชื่อออกมา
(defun next ()
(nth (random (length *names*)) *names*))
nth คือ function ที่ใช้ access list ที่ตำแหน่ง index ที่ต้องการส่วน
(random N) จะได้ค่า integer ระหว่าง 0 ถึง (N-1)โปรแกรมก็ทำงานได้ดี แต่ใช้ไปนานๆ มัน random คนออกมาซ้ำๆกันมากไปหน่อย
กลับมาจากสัมมนาแล้ว ก็เลยมานั่งปรับปรุงให้มันเอาชื่อคนที่เคยเรียกแล้วออกไปจาก list ให้ด้วย (จะได้ไม่เรียกซ้ำ)
(defun next ()
(let ((name (nth (random (length *names*)) *names*)))
(setq *names* (remove name *names*))
name))
สุดท้ายก็ปรับปรุงว่า ถ้าเอาชื่อออกจนหมดแล้ว ก็ให้ reset รายชื่อใหม่
(defun next ()
(if (null *names*)
(setq *names* '(pok bunny pann pune)))
(let ((name (nth (random (length *names*)) *names*)))
(setq *names* (remove name *names*))
name))
Related link from Roti
Labels:
lisp
Tuesday, December 02, 2008
เรียนรู้ Clojure ตอน tail recusive
recursive คือหัวใจหลักของภาษาตระกูล functional, ตัว runtime ของภาษาตระกูลนี้ต้องออกแบบมาให้สามารถทำ optimize tail recursive เพื่อหลีกเลี่ยง stack overflow. โชคร้ายที่ jvm ไม่ได้ออกแบบมาเพื่อทำ optimize แบบนั้น (ยกเว้น IBM JVM - Java กับ Tail-Recursive)
เจ้า closure ก็เลยต้องอาศัย syntax พิเศษเพื่อเลียนการทำงานแบบ tail recursive
ลองดูตัวอย่าง code จริง
keyword จะอยู่ที่ loop กับ recur นี่แหล่ะ
loop เป็น syntax แบบเดียวกับ let แต่มันจะกำหนดจุดที่จะเกิดการ recursion
ส่วน recur เป็นการสั่งให้กระโดดกลับไปทำงานที่ loop อีกครั้ง แต่จะมีการเปลี่ยน binding ของตัวแปรที่กำหนดไว้ตอนประกาศ loop
ลองดูตัวอย่างข้างล่าง ซึ่งเป็นการหาค่า factorial โดยใช้วิธี recursive แบบ pass ค่า accumulator
จะเห็นว่าที่ตำแหน่ง loop มีการกำหนด binding ตัวแปร cnt กับ acc
ดังนั้นเมื่อถึงตอนสั่ง recur ก็จะต้องส่ง parameter ที่จะเป็นค่าใหม่ของ cnt และ acc กลับมาด้วย
เปรียบเทียบกับการเขียนแบบ recursive ใน clisp ก็จะเป็นแบบนี้
หลังจากที่ Clojure ออกมา ก็มีคนถามประเด็น mutual recursive ว่าจะเขียนอย่างไร
ลองดูตัวอย่าง mutual recursive ก่อน
Note: code แบบข้างบนนี้ ถ้าไปเขียนใน clisp จะ run ได้โดย stack ไม่ overflow
Rich Hickey ก็เลยแก้ code ของ clojure โดยใช้ technique ที่เรียกว่า trampoline
syntax ของ foo bar ข้างบนก็เลยต้องเขียนแบบนี้แทน
เจ้า closure ก็เลยต้องอาศัย syntax พิเศษเพื่อเลียนการทำงานแบบ tail recursive
ลองดูตัวอย่าง code จริง
(defn render-level-layer
"my, what an insane level representation i have here"
[target items if-empty if-full]
(loop [target target
remain (seq items)]
(if remain
(let [x (:x (first remain))
y (:y (first remain))
i (level-index x y)
at (target i)]
(recur (assoc target i (if (= at \space) if-empty if-full))
(rest remain)))
target)))
keyword จะอยู่ที่ loop กับ recur นี่แหล่ะ
loop เป็น syntax แบบเดียวกับ let แต่มันจะกำหนดจุดที่จะเกิดการ recursion
ส่วน recur เป็นการสั่งให้กระโดดกลับไปทำงานที่ loop อีกครั้ง แต่จะมีการเปลี่ยน binding ของตัวแปรที่กำหนดไว้ตอนประกาศ loop
ลองดูตัวอย่างข้างล่าง ซึ่งเป็นการหาค่า factorial โดยใช้วิธี recursive แบบ pass ค่า accumulator
จะเห็นว่าที่ตำแหน่ง loop มีการกำหนด binding ตัวแปร cnt กับ acc
ดังนั้นเมื่อถึงตอนสั่ง recur ก็จะต้องส่ง parameter ที่จะเป็นค่าใหม่ของ cnt และ acc กลับมาด้วย
(def factorial
(fn [n]
(loop [cnt n
acc 1]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt))))))
เปรียบเทียบกับการเขียนแบบ recursive ใน clisp ก็จะเป็นแบบนี้
(defun factorial (n &optional (acc 1))
(if (<= n 1)
acc
(factorial (- n 1) (* acc n))))
หลังจากที่ Clojure ออกมา ก็มีคนถามประเด็น mutual recursive ว่าจะเขียนอย่างไร
ลองดูตัวอย่าง mutual recursive ก่อน
(declare bar)
(defn foo [n]
(if (pos? n)
(bar (dec n))
:done-foo))
(defn bar [n]
(if (pos? n)
(foo (dec n))
:done-bar))
(foo 1000000)
-> java.lang.StackOverflowError
Note: code แบบข้างบนนี้ ถ้าไปเขียนใน clisp จะ run ได้โดย stack ไม่ overflow
Rich Hickey ก็เลยแก้ code ของ clojure โดยใช้ technique ที่เรียกว่า trampoline
instead of actually making a call in the tail, a function returns the function to be called, and a controlling loop calls it
syntax ของ foo bar ข้างบนก็เลยต้องเขียนแบบนี้แทน
(declare bar)
(defn foo [n]
(if (pos? n)
#(bar (dec n))
:done-foo))
(defn bar [n]
(if (pos? n)
#(foo (dec n))
:done-bar))
Related link from Roti
Labels:
clojure
Monday, November 24, 2008
google กับ *
วันนี้เล่น clojure แล้ว search หาตัวอย่างการใช้ fn* ใน google
ปรากฎว่าการใส่ * ในช่อง text ที่ใช้ search กลายเป็นการ enable feature searchwiki ไป
ดูตัวอย่าง

ปรากฎว่าการใส่ * ในช่อง text ที่ใช้ search กลายเป็นการ enable feature searchwiki ไป
ดูตัวอย่าง

Related link from Roti
เรียนรู้ Clojure ตอนที่ 3
Clojure ประกาศตัวชัดเจนว่า มันไม่ได้เป็น OOP (ถึงแม้จะ run อยู่บน platform ที่เป็น OOP จ๋าแบบ Java)
และเลือกข้าง functional อย่างชัดเจน (ไม่เหมือน scala ที่เลือกมันสองข้างเลย)
มาลองดู code ที่ตัดมาจากตัวอย่าง code ในตอนที่ 1
เริ่มด้วยบรรทัดบนสุด เป็นการระบุ namespace (ถ้าเทียบกับ java ก็คือ package)
บรรทัดที่สองบอกด้วยว่าให้ import class java.util.regex.Pattern เข้ามาด้วย
(note: ใน clojure มันมี syntax สำหรับ regex ให้ใช้เหมือนกัน แต่คนเขียนเลือกใช้แบบเรียกผ่าน java)
มาดู function line-explode ก่อน
จะเห็นเขาประกาศ function ด้วย form แบบนี้
โปรแกรมตระกูล lisp นี่, ตัว code ก็เป็น data structure ด้วย
ดังนั้นจะเห็นว่า params ก็คือ vector type, ส่วน body เป็น list type
จาก code จะเห็นว่า function explode รับ parameter ตัวเดียว ก็คือ lines
ตัว body ของ function line-explode จะอยู่ในรูป let syntax
ลองดู bindings ของโปรแกรมตัวอย่าง
ถ้าเทียบกับ java แล้วบรรทัดข้างบนก็คือ
ที่นี้ก็มาถึง body ของ function line-explode
เวลาอ่านก็ให้ไล่จากในออกมาข้างนอก
เริ่มจากในสุด
ก็คือการเรียกใช้ method split บน Pattern instance (ในที่นี้ก็คือการตัดที่ตำแหน่ง new-line)
ตรงนี้เขา show ความสามารถของ infinite-sequence และ lazy behavior ของ Clojure
โดยผลลัพท์ที่ได้จะ เป็นไปตามกฎนี้
สมาชิกตัวแรกของผลลัพท์เกิดจากการเรียกใช้ (inc 0) ซึ่งจะได้ผลลัพท์เป็น 1
ผลลัพท์ตัวที่สองเกิดจากการเรียกใช้ (inc ผลลัพท์ตัวแรก) ซึ่งก็คือ (inc 1)
ผลลัพท์ตัวถัดๆไปก็คือ (inc ผลลัพท์ตัวก่อนหน้ามัน)
ดังนั้น (iterate inc 0) จะได้ผลลัพท์เป็น (1 2 3 4 5 6 ... infinity)
ส่วน function map นี้ เดี๋ยวนี้นิยมใช้กันมากแล้ว คงไม่ต้องอธิบาย
แต่ว่า map ของพวก lisp นี้เขารับ collection ได้หลาย collection พร้อมๆกัน
ลองดูตัวอย่างนี้
เนื่องจากเจ้า map function มันไปเรียกใช้ function
ใน code ข้างบนจะเห็นว่ามี
นั่นคือมันจะจับ ตัวแปร ณ scope ที่มันถูกประกาศ
ตัว
ดูจาก code แล้วจะเห็นว่า function
ทดลอง run ดู
กลับมาที่ line-explode, function ตัวสุดท้ายก็คือ
reduce รับ parameter เป็น function และ list
โดยมันจะเรียก function โดยมี parameter เป็นสมาชิกตัวที่ 1 และ 2 ของ list
จากนั้นก็จะเรียก function โดยมี parameter ตัวที่เป็นผลลัพท์ที่ได้จากครั้งก่อน กับ สมาชิกตัวที่ 3
ทำเช่นนี้ไปเรื่อยๆจนกว่าจะหมด
ส่วน
มันคือ operation ที่จับ array บวกกันนั่นเอง
สรุปแล้วจะเห็นว่าการ explode ก็คือการเปลี่ยน multi-line string ให้อยู่ในรูปของ [(pos x y) char] นั่นเอง
และเลือกข้าง functional อย่างชัดเจน (ไม่เหมือน scala ที่เลือกมันสองข้างเลย)
มาลองดู code ที่ตัดมาจากตัวอย่าง code ในตอนที่ 1
(ns test
(:import (java.util.regex Pattern)))
(defstruct pos :x :y)
(defn explode
"return a pos/type seq for each char in the line"
[y line]
(map (fn [x y type] (vector (struct pos x y) type))
(iterate inc 0) (repeat y) line))
(defn line-explode
"call explode on each line with the appropriate y"
[lines]
(let [linesplit (. Pattern compile "\n" (. Pattern MULTILINE))] ; must be a better way?
(reduce into
(map explode
(iterate inc 0)
(. linesplit split lines)))))
เริ่มด้วยบรรทัดบนสุด เป็นการระบุ namespace (ถ้าเทียบกับ java ก็คือ package)
(ns test
(:import (java.util.regex Pattern)))
บรรทัดที่สองบอกด้วยว่าให้ import class java.util.regex.Pattern เข้ามาด้วย
(note: ใน clojure มันมี syntax สำหรับ regex ให้ใช้เหมือนกัน แต่คนเขียนเลือกใช้แบบเรียกผ่าน java)
มาดู function line-explode ก่อน
จะเห็นเขาประกาศ function ด้วย form แบบนี้
(defn function-name description params body)
เข่น
(defn plus-9 "plus with 9" [x] (+ x 9))
โปรแกรมตระกูล lisp นี่, ตัว code ก็เป็น data structure ด้วย
ดังนั้นจะเห็นว่า params ก็คือ vector type, ส่วน body เป็น list type
จาก code จะเห็นว่า function explode รับ parameter ตัวเดียว ก็คือ lines
(defn line-explode
"call explode on each line with the appropriate y"
[lines]
ตัว body ของ function line-explode จะอยู่ในรูป let syntax
(let bindings body)
เช่น
(let [x 9, y 10] (+ x y))
run แล้วได้ผลลัพท์เป็น 19
ใน bindings ของ let นั้น, variable ที่ถูก bind ก่อน สามารถถูกอ้างถึงโดย variable ที่ bind ทีหลังได้
(let [x 9, y (+ x 7)] (+ x y))
run แล้วได้ผลลัพท์เป็น 25
ลองดู bindings ของโปรแกรมตัวอย่าง
(let [linesplit (. Pattern compile "\n" (. Pattern MULTILINE))] ; must be a better way?
ถ้าเทียบกับ java แล้วบรรทัดข้างบนก็คือ
Pattern linesplit = Pattern.compie("\n", Pattern.MULTILINE);
ที่นี้ก็มาถึง body ของ function line-explode
(reduce into
(map explode
(iterate inc 0)
(. linesplit split lines)))
เวลาอ่านก็ให้ไล่จากในออกมาข้างนอก
เริ่มจากในสุด
(. linesplit split lines)ก็คือการเรียกใช้ method split บน Pattern instance (ในที่นี้ก็คือการตัดที่ตำแหน่ง new-line)
(iterate inc 0)ตรงนี้เขา show ความสามารถของ infinite-sequence และ lazy behavior ของ Clojure
โดยผลลัพท์ที่ได้จะ เป็นไปตามกฎนี้
สมาชิกตัวแรกของผลลัพท์เกิดจากการเรียกใช้ (inc 0) ซึ่งจะได้ผลลัพท์เป็น 1
ผลลัพท์ตัวที่สองเกิดจากการเรียกใช้ (inc ผลลัพท์ตัวแรก) ซึ่งก็คือ (inc 1)
ผลลัพท์ตัวถัดๆไปก็คือ (inc ผลลัพท์ตัวก่อนหน้ามัน)
ดังนั้น (iterate inc 0) จะได้ผลลัพท์เป็น (1 2 3 4 5 6 ... infinity)
ส่วน function map นี้ เดี๋ยวนี้นิยมใช้กันมากแล้ว คงไม่ต้องอธิบาย
แต่ว่า map ของพวก lisp นี้เขารับ collection ได้หลาย collection พร้อมๆกัน
ลองดูตัวอย่างนี้
(map + '(1 2 3) '(3 4 5))
=> (4 6 8)
(map + '(1 2 3) '(3 4 5 6 7 8))
=> (4 6 8)
เนื่องจากเจ้า map function มันไปเรียกใช้ function
explode ดังนั้นเราต้องตามไปดูว่ามันทำอะไร
(defn explode
"return a pos/type seq for each char in the line"
[y line]
(map (fn [x y type] (vector (struct pos x y) type))
(iterate inc 0) (repeat y) line))
ใน code ข้างบนจะเห็นว่ามี
fn ในบรรทัดที่ 4 โดยมันคือการ define function อีกแบบหนึ่ง ซึ่งมีคุณสมบัติแบบ closure นั่นคือมันจะจับ ตัวแปร ณ scope ที่มันถูกประกาศ
test> (def foo
(let [y 10]
(fn [x] (+ x y))))
#=(var test/foo)
test> (foo 7)
17
ลองเปลี่ยน y ให้เป็น 12 ณ ขณะที่ run
test> (let [y 12] (foo 7))
17
ตัว
repeat นั่นเป็น infinite list อีกตัวหนึ่ง(repeat 7)
=> (7 7 7 7 7 7 7 7 7 .....)
ดูจาก code แล้วจะเห็นว่า function
explode มันก็คือการสร้าง list ของ (pos x y) char นั่นเองทดลอง run ดู
test> (explode 0 "hello")
([{:x 0, :y 0} \h] [{:x 1, :y 0} \e] [{:x 2, :y 0} \l] [{:x 3, :y 0} \l] [{:x 4, :y 0} \o])
กลับมาที่ line-explode, function ตัวสุดท้ายก็คือ
(reduce into (map ..))reduce รับ parameter เป็น function และ list
โดยมันจะเรียก function โดยมี parameter เป็นสมาชิกตัวที่ 1 และ 2 ของ list
จากนั้นก็จะเรียก function โดยมี parameter ตัวที่เป็นผลลัพท์ที่ได้จากครั้งก่อน กับ สมาชิกตัวที่ 3
ทำเช่นนี้ไปเรื่อยๆจนกว่าจะหมด
(reduce + [1 2 3 4 5])
=> 15
เขียนอีกอย่างก็คือ
(+ (+ (+ (+ 1 2) 3) 4) 5)
ส่วน
into นั้นทำงานแบบนี้(into [1] [2])
=> [1 2]
(into [1 2 3] [2])
=> [1 2 3 2]
(into [1] [1 2 3])
=>[1 1 2 3]
มันคือ operation ที่จับ array บวกกันนั่นเอง
สรุปแล้วจะเห็นว่าการ explode ก็คือการเปลี่ยน multi-line string ให้อยู่ในรูปของ [(pos x y) char] นั่นเอง
test> (line-explode "hello\nworld\n")
([{:x 4, :y 1} \d] [{:x 3, :y 1} \l] [{:x 2, :y 1} \r] [{:x 1, :y 1} \o] [{:x 0, :y 1} \w] [{:x 0, :y 0} \h] [{:x 1, :y 0} \e] [{:x 2, :y 0} \l] [{:x 3, :y 0} \l] [{:x 4, :y 0} \o])
Related link from Roti
Labels:
clojure
Friday, November 21, 2008
เรียนรู้ clojure - data structure ตอน 2
ต่อจากเมื่อวาน
data structure ตัวถัดไปก็คือ map
ลองดูคำสั่งที่เขาใช้ (เขาใช้ map keyboard code เข้ากับ ทิศทาง)
เจ้าเครื่องหมาย { เป็น syntax อย่างย่อ
ถ้าจะสั่งแบบยาวหน่อย ก็ให้สั่งแบบนี้
เครื่องหมาย comma นั้นจะใส่หรือไม่ใส่ก็ได้
การ access ค่า สามารถทำได้โดยคำสั่ง
ถัดจาก map ก็คือ vector และ list
ทั้ง vector และ list จะใช้คำสั่งเหมือนกันอย่างกับแกะ ต่างกันตรง data structure ภายในที่เก็บ
เจ้า vector เวลาเรา add item เข้าไป, item นั้นจะไปต่อท้าย
ส่วน list นั้น ถ้าเรา add item เข้าไป, item จะไปอยู่ที่หัว
Note: จะเห็นเครื่องหมาย
เครื่องหมายนั้นจะบอก clojure ว่า (1 2 3) เป็น data นะ ไม่ต้องไป evaluate มัน
เวลาดูโปรแกรม clisp จะเห็นว่ามีเครื่องหมาย
แต่เจ้า clojure ที่ลอก syntax มา มีการดัดแปลง syntax ไปเล็กน้อย
โดยจะเห็นว่าเขาเอา vector มาใช้ define พวก parameter list, binding list
ซึ่งก็น่าจะทำให้ programmer recognise code ได้ไวขึ้น
data structure ตัวถัดไปก็คือ map
ลองดูคำสั่งที่เขาใช้ (เขาใช้ map keyboard code เข้ากับ ทิศทาง)
(def AsciiToDx {72 -1,
76 1,
75 0,
74 0})
เจ้าเครื่องหมาย { เป็น syntax อย่างย่อ
ถ้าจะสั่งแบบยาวหน่อย ก็ให้สั่งแบบนี้
(def AsciiToDx (hash-map 72 -1 76 1 75 0 74 0))
เครื่องหมาย comma นั้นจะใส่หรือไม่ใส่ก็ได้
(def AsciiToDx (hash-map 72 -1, 76 1, 75 0, 74 0))
การ access ค่า สามารถทำได้โดยคำสั่ง
getuser> (get AsciiToDx 72)
-1
ถัดจาก map ก็คือ vector และ list
ทั้ง vector และ list จะใช้คำสั่งเหมือนกันอย่างกับแกะ ต่างกันตรง data structure ภายในที่เก็บ
เจ้า vector เวลาเรา add item เข้าไป, item นั้นจะไปต่อท้าย
ส่วน list นั้น ถ้าเรา add item เข้าไป, item จะไปอยู่ที่หัว
user> (conj '(1 2 3) 4)
(4 1 2 3)
user> (conj [1 2 3] 4)
[1 2 3 4]
Note: จะเห็นเครื่องหมาย
' ที่หน้า list (1 2 3)เครื่องหมายนั้นจะบอก clojure ว่า (1 2 3) เป็น data นะ ไม่ต้องไป evaluate มัน
เวลาดูโปรแกรม clisp จะเห็นว่ามีเครื่องหมาย
() อยู่เต็มไปหมด (ชื่อภาษามันบอกอยู่แล้วว่ามันคือ list)แต่เจ้า clojure ที่ลอก syntax มา มีการดัดแปลง syntax ไปเล็กน้อย
โดยจะเห็นว่าเขาเอา vector มาใช้ define พวก parameter list, binding list
ซึ่งก็น่าจะทำให้ programmer recognise code ได้ไวขึ้น
Related link from Roti
Labels:
clojure
Thursday, November 20, 2008
เรียนรู้ clojure - data structure
เมื่อวาน @somkiat post link http://blog.learnr.org/post/59883018/first-clojure-program ใน twiiter
ผมตามไปอ่านแล้ว รู้สึกถูกใจในขนาดและความยากที่ดูจะเหมาะแก่การเริ่มต้นเรียนรู้ Clojure
เริ่มกันที่ data structure ก่อน
ในโปรแกรมนี้เขาใช้ StructMaps ในการเก็บค่าต่างๆเช่น position ของ กำแพง,ผู้เล่น
โดย StructMaps นั้นสามารถสร้างได้โดยใช้คำสั่งู่แบบนี้
Note: เจ้าคำสั่ง defstruct จริงๆมันเป็นแค่ macro
สิ่งที่เกิดขึ้นจริงๆ ก็คือมันจะแปลงคำสั่ง
เวลาที่เราต้องการสร้าง data ก็ให้สั่งแบบนี้
ถ้าต้องการ get ค่า x ก็ให้สั่ง
เนื่องจาก Clojure พยายามเน้นที่ Parallel programming
ดังนั้น struct เมื่อสร้างขึ้นมาแล้ว ก็ไม่สามารถเปลี่ยนค่าภายในได้ (immutable)
ถ้าเราต้องการเปลี่ยนค่าภายใน ก็หมายถึงว่าเราต้องสร้าง data ใหม่ขึ้นมา
ดูตัวอย่างนี้
เจ้า StructMaps จริงๆแล้วก็คือ Map ที่ define keys ที่เป็นไปได้ไว้ (รวมทั้งลำดับด้วย)
เราสามารถใช้ คำสั่งต่างๆที่เกี่ยวกับ Map มาทำ operation บน StructMaps ได้หมด
เช่น
คำสั่งที่น่าสนใจ ก็คือ accessor
ที่ return function ที่ใช้ access value ของ StructMaps นั้นๆ
ในโปรแกรมจะเห็นตัวอย่างหนึ่งที่น่าสนใจก็คือ
เขาประกาศ struct ของ level ไว้ดังนี้
ที่น่าสนใจก็คือ ปกติถ้าเราต้องการสร้าง data ของ level เราก็ใช้คำสั่ง
แต่การใช้คำสั่งแบบนี้ จะทำให้สับสนภายหลังได้ง่าย ว่า ตัวแปร a,b,c,d หมายถึงอะไร
ดังนั้นวิธีการที่เหมาะก็คือ ใช้คำสั่ง struct-map ในการสร้าง data แทน
ผมตามไปอ่านแล้ว รู้สึกถูกใจในขนาดและความยากที่ดูจะเหมาะแก่การเริ่มต้นเรียนรู้ Clojure
เริ่มกันที่ data structure ก่อน
ในโปรแกรมนี้เขาใช้ StructMaps ในการเก็บค่าต่างๆเช่น position ของ กำแพง,ผู้เล่น
โดย StructMaps นั้นสามารถสร้างได้โดยใช้คำสั่งู่แบบนี้
(defstruct pos :x :y)
Note: เจ้าคำสั่ง defstruct จริงๆมันเป็นแค่ macro
สิ่งที่เกิดขึ้นจริงๆ ก็คือมันจะแปลงคำสั่ง
(defstruct pos :x :y) ให้เป็น (def pos (create-struct :x :y))เวลาที่เราต้องการสร้าง data ก็ให้สั่งแบบนี้
(struct pos 1 2)
ถ้าต้องการ get ค่า x ก็ให้สั่ง
(:x (struct pos 1 2))เนื่องจาก Clojure พยายามเน้นที่ Parallel programming
ดังนั้น struct เมื่อสร้างขึ้นมาแล้ว ก็ไม่สามารถเปลี่ยนค่าภายในได้ (immutable)
ถ้าเราต้องการเปลี่ยนค่าภายใน ก็หมายถึงว่าเราต้องสร้าง data ใหม่ขึ้นมา
ดูตัวอย่างนี้
user> (def mypos (struct pos 1 2)) ;; สร้างตัวแปร mypos ที่เก็บตำแหน่ง (1,2)
#=(var user/mypos)
user> (assoc mypos :x 10) ;; เปลี่ยนค่า x ให้เป็น 10
{:x 10, :y 2}
user> (:x mypos) ;; แสดงค่า x
1
user> (def mypos (assoc mypos :x 10)) ;; ถ้าต้องการเปลี่ยนก็หมายถึงว่าต้อง set mypos ให้ชี้ไปที่ StructMap ตัวใหม่ที่ return จาก assoc
#=(var user/mypos)
user> (:x mypos)
10
เจ้า StructMaps จริงๆแล้วก็คือ Map ที่ define keys ที่เป็นไปได้ไว้ (รวมทั้งลำดับด้วย)
เราสามารถใช้ คำสั่งต่างๆที่เกี่ยวกับ Map มาทำ operation บน StructMaps ได้หมด
เช่น
user> (vals mypos) ;; return values ของ mypos
(10 2)
user> (keys mypos) ;; return keys ของ mypos
(:x :y)
คำสั่งที่น่าสนใจ ก็คือ accessor
ที่ return function ที่ใช้ access value ของ StructMaps นั้นๆ
user> (def get-x (accessor pos :x))
#=(var user/get-x)
user> (get-x mypos)
10
ในโปรแกรมจะเห็นตัวอย่างหนึ่งที่น่าสนใจก็คือ
เขาประกาศ struct ของ level ไว้ดังนี้
(defstruct level
:player ; pos
:boxes ; set-of-pos
:goals ; set-of-pos
:walls ; set-of-pos
)
ที่น่าสนใจก็คือ ปกติถ้าเราต้องการสร้าง data ของ level เราก็ใช้คำสั่ง
(struct level a b c d) ได้แต่การใช้คำสั่งแบบนี้ จะทำให้สับสนภายหลังได้ง่าย ว่า ตัวแปร a,b,c,d หมายถึงอะไร
ดังนั้นวิธีการที่เหมาะก็คือ ใช้คำสั่ง struct-map ในการสร้าง data แทน
(struct-map level
:player (first (filter-level expl-level \@ \+))
:boxes (filter-level expl-level \o \*)
:goals (filter-level expl-level \. \* \+)
:walls (filter-level expl-level \# \#))))
Related link from Roti
Labels:
clojure
Friday, November 14, 2008
fast-forward ใน git-merge
เห็นคำว่า Fast forward เวลาสั่ง git pull มานานแล้ว, มาอ่าน git-doc ก็เลยเข้าใจขึ้น
เวลาเราสั่ง git merge, การทำงานของมันสามารถแบ่งได้เป็น 3 ประเภท
เวลาเราสั่ง git merge, การทำงานของมันสามารถแบ่งได้เป็น 3 ประเภท
- ถ้า merged commit ที่เราดึงมา อยู่ใน Head(current tree ของเรา) ของเราแล้ว, ก็จะแสดงผลลัพท์ "Already up-to-date." แล้วก็จบการทำงาน
- ถ้า Head ของเราอยู่ใน commits ที่ดึงมา, case นี้มักเกิดจากคำสั่ง "git pull" เพื่อดึง code จากต้นน้ำมา update code(ที่ไม่มีการเปลี่ยนแปลง) ของเรา, สิ่งที่เกิดขึ้นก็คือ git จะ update HEAD ของเราให้ตรงตาม HEAD ของ merged commit (โดยไม่มีการสร้าง commit object ใหม่ขึ้นมา) มีศัพท์เฉพาะสำหรับกรณีนี้ว่า "Fast-forward"
- สุดท้ายเป็นกรณีที่เกิดการ merge จริงๆ นั่นคือ ตัว HEAD ของเรา independent กับ merged commit, ดังนั้นกรณนี้จะเกิดการ merge จริง และมีการสร้าง commmit object ใหม่ขึ้นมา
Related link from Roti
Labels:
git
Thursday, November 13, 2008
config syntax ใน grails
ผมสงสัยเรื่อง configuration ใน grails มาได้พักใหญ่แล้ว
เพราะเห็น syntax มันแปลกประหลาดเหลือเกิน ลองดูตัวอย่างข้างล่างนี้
ทำไมบรรทัดที่สองต้องมี quote ครอบด้วยหล่ะ?
class ที่ทำหน้าที่อ่าน file Config.groovy ของ grails ก็คือ ConfigSlurper
โดยวิธีการใช้งานเจ้า ConfigSlurper ก็คือแบบนี้
สิ่งที่เจ้า ConfigSlurper return มาก็คือ Map ตัวหนึ่ง (มันคือ class ConfigObject ที่ extends จาก LinkedHashMap)
ที่นี้มาลองดู nature ของมันบ้าง
เริ่มจาก config ง่ายๆ บรรทัดเดียว
ผลลัพท์ที่ได้จากการ parse จะเป็น map หน้าตาแบบนี้
ที่นี้ถ้าลองเพิ่ม sub node เข้าไปใต้ subtopic แบบนี้
เมื่อลอง parse ดู แทนที่จะได้ผลลัพท์ เรากลับได้ exception แบบนี้แทน
ดูเหมือนว่าการทำงานภายในของมันก็คือ เมื่อมันต้องการ assign ค่า
ที่นี้ลอง config แบบนี้บ้าง
ผลลัพท์ที่ได้คือ
อ้าว แล้ว subofsubtopic1 ของเราหายไปไหนหล่ะ
กลายเป็นว่า subofsubtopic1 ของเราถูก เจ้า topic1.subtopic1 override ค่าไปเรียบร้อยแล้ว
ลองให้เหลือ บรรทัดเดียวแบบนี้ ดูบ้าง
ผลลัพท์ที่ได้ จะเป็นแบบนี้
สรุปได้ว่า เนื่องจาก ConfigSlurperใช้ nested map เป็น internal structure ในการเก็บผลลัพท์จากการ parse
ทำให้เกิดข้อจำกัดในการเก็บขึ้นมา ดังนั้นถ้าเราต้องการเก็บค่าแบบบ้างบนให้ได้ เราก็เลยต้อง hack มัน โดยการห่อค่าให้เป็น string แบบนี้แทน
ผลลัพท์ที่ได้จากการ map ก็จะเป็นแบบนี้
อืมม์ไม่สวยเป็นอย่างยิ่ง น่าจะมีคน refactor นะ
สรุปได้ว่า ถ้าเรานำไปใช้งาน configuration ของเรา (ถ้าใช้ grails ก็ต้องเจอแน่ๆ)
เราก็ควรจะออกแบบ tree ของเราดีๆ อย่าให้เกิดกรณีที่ parent node มีค่า value attach อยู่
(ให้มี valueได้เฉพาะ leaf node)
ไม่งั้นอาจจะปวดหัวว่า ทำไมค่า config ถึงหายไป หรือไม่ก็ ทำไมถึง get config ที่ต้องการไม่เจอ
เพราะเห็น syntax มันแปลกประหลาดเหลือเกิน ลองดูตัวอย่างข้างล่างนี้
log4j.appender.stdout = "org.apache.log4j.ConsoleAppender"
log4j.appender."stdout.layout"="org.apache.log4j.PatternLayout"
ทำไมบรรทัดที่สองต้องมี quote ครอบด้วยหล่ะ?
class ที่ทำหน้าที่อ่าน file Config.groovy ของ grails ก็คือ ConfigSlurper
โดยวิธีการใช้งานเจ้า ConfigSlurper ก็คือแบบนี้
def config = new ConfigSlurper().parse(new File('/tmp/x.groovy').toURL())
สิ่งที่เจ้า ConfigSlurper return มาก็คือ Map ตัวหนึ่ง (มันคือ class ConfigObject ที่ extends จาก LinkedHashMap)
ที่นี้มาลองดู nature ของมันบ้าง
เริ่มจาก config ง่ายๆ บรรทัดเดียว
topic1.subtopic1=1
ผลลัพท์ที่ได้จากการ parse จะเป็น map หน้าตาแบบนี้
["topic1":["subtopic1":1]]
ที่นี้ถ้าลองเพิ่ม sub node เข้าไปใต้ subtopic แบบนี้
topic1.subtopic1=1
topic1.subtopic1.subofsubtopic1="foo"
เมื่อลอง parse ดู แทนที่จะได้ผลลัพท์ เรากลับได้ exception แบบนี้แทน
groovy.lang.MissingPropertyException: No such property: subofsubtopic1 for class: java.lang.Integer
at script1226551599145.run(script1226551599145.groovy:2)
at Script9.run(Script9:1)
ดูเหมือนว่าการทำงานภายในของมันก็คือ เมื่อมันต้องการ assign ค่า
topic1.subtopic1.subofsubtopic1 มันจะไป get value โดยใช้ key topic1.subtopic1 จากนั้นก็พยายามจะ set property subofsubtopic1ลงใน value ที่ได้มาก ซึ่งไม่สำเร็จแน่ๆเพราะ value ที่ได้มันไม่ใช่ map อย่างที่ควรจะเป็นที่นี้ลอง config แบบนี้บ้าง
topic1.subtopic1.subofsubtopic1="foo"
topic1.subtopic1=1
ผลลัพท์ที่ได้คือ
["topic1":["subtopic1":1]]
อ้าว แล้ว subofsubtopic1 ของเราหายไปไหนหล่ะ
กลายเป็นว่า subofsubtopic1 ของเราถูก เจ้า topic1.subtopic1 override ค่าไปเรียบร้อยแล้ว
ลองให้เหลือ บรรทัดเดียวแบบนี้ ดูบ้าง
topic1.subtopic1.subofsubtopic1="foo"
ผลลัพท์ที่ได้ จะเป็นแบบนี้
["topic1":["subtopic1":["subofsubtopic1":"foo"]]]
สรุปได้ว่า เนื่องจาก ConfigSlurperใช้ nested map เป็น internal structure ในการเก็บผลลัพท์จากการ parse
ทำให้เกิดข้อจำกัดในการเก็บขึ้นมา ดังนั้นถ้าเราต้องการเก็บค่าแบบบ้างบนให้ได้ เราก็เลยต้อง hack มัน โดยการห่อค่าให้เป็น string แบบนี้แทน
topic1.subtopic1=1
topic1."subtopic1.subofsubtopic1"="foo"
ผลลัพท์ที่ได้จากการ map ก็จะเป็นแบบนี้
["topic1":["subtopic1":1, "subtopic1.subofsubtopic1":"foo"]]
อืมม์ไม่สวยเป็นอย่างยิ่ง น่าจะมีคน refactor นะ
สรุปได้ว่า ถ้าเรานำไปใช้งาน configuration ของเรา (ถ้าใช้ grails ก็ต้องเจอแน่ๆ)
เราก็ควรจะออกแบบ tree ของเราดีๆ อย่าให้เกิดกรณีที่ parent node มีค่า value attach อยู่
(ให้มี valueได้เฉพาะ leaf node)
ไม่งั้นอาจจะปวดหัวว่า ทำไมค่า config ถึงหายไป หรือไม่ก็ ทำไมถึง get config ที่ต้องการไม่เจอ
Related link from Roti
Tuesday, October 28, 2008
Change blindness
ในปี 1998 Simons, D., & Levin, D. ได้เขียน paper ที่น่าสนใจ ชื่อว่า
Failure to Detect Changes to People During a Real-World Interaction
เขาทดลองให้ หน้าม้าปลอมตัวเป็นนักท่องเที่ยว จากนั้นก็ให้หน้าม้าทำทีกางแผนที่เข้าไปสอบถามเส้นทาง กับ เป้าหมาย
จังหวะที่กำลังสอบถามนั้น ก็ให้หน้าม้าอีกสองคน ถือประตู(กำลังขนประตูไปที่ไหนสักแห่ง) เดินแทรกกลางเข้ามาระหว่าง หน้าม้านักท่องเที่ยว กับเป้าหมาย
จังหวะที่ประตูกำลังบังเป้าหมายอยู่นั้น ก็ถือโอกาสสลับเปลี่ยนตัวหน้าม้าที่ทำตัวเป็นนักท่องเที่ยว
คำถามของเขาก็คือ เป้าหมายจะรู้หรือไม่ว่า มีการสลับตัวเกิดขึ้น
ครั้งที่ 1 เขาทดลอง 15 ครั้ง 7 ใน 15 บอกว่าสังเกตเห็นการเปลี่ยนแปลง ที่น่าสนใจก็คือ 7 คน นั้นอยู่ในช่วงอายุ 20-30 ปี ซึ่งเป็นช่วงอายุเดียวกับหน้าม้า
ส่วนเป้าหมายที่ไม่เห็นการเปลี่ยนแปลง จะอยู่ในช่วงอายุ 30-60 ปี
เขาตั้งข้อสงสัยว่า social group มีส่วนเกี่ยวข้องด้วยหรือไม่? (สมมติฐานมีอยู่ว่า เรา treat คนไม่เท่าเทียมกัน โดยให้ความสำคัญกับกลุ่มที่เป็นพวกเดียวกับเรามากกว่า) เขาก็เลยทำการทดลองใหม่อีกครั้ง คราวนี้เปลี่ยนรูปลักษณ์หน้าม้า โดยแปลงร่างเป็นช่างก่อสร้าง
(ที่พิเศษว่าครั้งแรก ก็คือ หน้าม้าใส่เสื้อคนละสีเลย เมื่อเทียบกับครั้งแรกที่ยังแต่งตัวเหมือนๆกันอยู่)
จากการทดลอง 12 ครั้ง กับเป้าหมายที่อายุ 20-30 ปี พบว่า มีแค่ 4 คนเท่านั้นที่สังเกตุเห็นการเปลี่ยนแปลง
? blindness นั้นเกิดจากอะไร, เป็นข้อจำกัดทางกายภาพของเรา
หรือเกิดจากการที่เราหมกมุ่นกับความคิดที่อยู่ใน "หัว" ของเรามากเกินไป
ตามไปดูรูปและรายละเอียดได้ที่นี่
Failure to Detect Changes to People During a Real-World Interaction
เขาทดลองให้ หน้าม้าปลอมตัวเป็นนักท่องเที่ยว จากนั้นก็ให้หน้าม้าทำทีกางแผนที่เข้าไปสอบถามเส้นทาง กับ เป้าหมาย
จังหวะที่กำลังสอบถามนั้น ก็ให้หน้าม้าอีกสองคน ถือประตู(กำลังขนประตูไปที่ไหนสักแห่ง) เดินแทรกกลางเข้ามาระหว่าง หน้าม้านักท่องเที่ยว กับเป้าหมาย
จังหวะที่ประตูกำลังบังเป้าหมายอยู่นั้น ก็ถือโอกาสสลับเปลี่ยนตัวหน้าม้าที่ทำตัวเป็นนักท่องเที่ยว
คำถามของเขาก็คือ เป้าหมายจะรู้หรือไม่ว่า มีการสลับตัวเกิดขึ้น
ครั้งที่ 1 เขาทดลอง 15 ครั้ง 7 ใน 15 บอกว่าสังเกตเห็นการเปลี่ยนแปลง ที่น่าสนใจก็คือ 7 คน นั้นอยู่ในช่วงอายุ 20-30 ปี ซึ่งเป็นช่วงอายุเดียวกับหน้าม้า
ส่วนเป้าหมายที่ไม่เห็นการเปลี่ยนแปลง จะอยู่ในช่วงอายุ 30-60 ปี
เขาตั้งข้อสงสัยว่า social group มีส่วนเกี่ยวข้องด้วยหรือไม่? (สมมติฐานมีอยู่ว่า เรา treat คนไม่เท่าเทียมกัน โดยให้ความสำคัญกับกลุ่มที่เป็นพวกเดียวกับเรามากกว่า) เขาก็เลยทำการทดลองใหม่อีกครั้ง คราวนี้เปลี่ยนรูปลักษณ์หน้าม้า โดยแปลงร่างเป็นช่างก่อสร้าง
(ที่พิเศษว่าครั้งแรก ก็คือ หน้าม้าใส่เสื้อคนละสีเลย เมื่อเทียบกับครั้งแรกที่ยังแต่งตัวเหมือนๆกันอยู่)
จากการทดลอง 12 ครั้ง กับเป้าหมายที่อายุ 20-30 ปี พบว่า มีแค่ 4 คนเท่านั้นที่สังเกตุเห็นการเปลี่ยนแปลง
? blindness นั้นเกิดจากอะไร, เป็นข้อจำกัดทางกายภาพของเรา
หรือเกิดจากการที่เราหมกมุ่นกับความคิดที่อยู่ใน "หัว" ของเรามากเกินไป
ตามไปดูรูปและรายละเอียดได้ที่นี่
Related link from Roti
Friday, October 17, 2008
Local Loop Expression ภาค 2
วันนี้เห็น code การบ้าน haskell ของ Porges
ตรงส่วนของการวน loop รับ entry ของ user
โดย user ต้องเลือก choice ว่าอยากให้โปรแกรมเป็น c(client) หรือ s(server)
โดย code มีหน้าตาแบบนี้
ประเด็นที่ผมสนใจ ก็คือ การวนลูป รับค่าจาก user และตรวจสอบว่าเป็นค่าที่ถูกต้องไหม
ตรงนี้เขา define function ที่ชื่อ untilM เข้ามาช่วย
หน้าตาของ untilM
จะเห็นว่า untilM รับ arguments 2 ตัวคือ p(predicate) กับ x
โดยมันจะทำ x ก่อน
ผลลัพท์ที่ได้จะถูก test ด้วย p
ถ้าผ่าน ก็จะ return กลับมา ถ้าไม่ผ่านก็ recursive รับค่าต่อไป
เห็นแล้วก็นึกถึง Local loop expression ที่เคยเขียนถึง
ก็เลยลองจับ untilM มาเขียนใหม่ด้วย fix ได้หน้าตาแบบนี้ออกมา
ดูแล้วก็ยังไม่สวยถูกใจเท่าไรนัก
ตรงส่วนของการวน loop รับ entry ของ user
โดย user ต้องเลือก choice ว่าอยากให้โปรแกรมเป็น c(client) หรือ s(server)
โดย code มีหน้าตาแบบนี้
main = withSocketsDo $ do -- enable sockets under windows
putStrLn "Welcome to One-Way Chat version 1.0"
...
input <- untilM -- get input of 'c' or 's'
(\x -> (not $ null x) && toLower (head x) `elem` "cs")
(putStr "Client or server? " >> getLine)
...
ประเด็นที่ผมสนใจ ก็คือ การวนลูป รับค่าจาก user และตรวจสอบว่าเป็นค่าที่ถูกต้องไหม
ตรงนี้เขา define function ที่ชื่อ untilM เข้ามาช่วย
หน้าตาของ untilM
-- monadic `until`
untilM p x = x >>= (\y -> if p y then return y else untilM p x)
จะเห็นว่า untilM รับ arguments 2 ตัวคือ p(predicate) กับ x
โดยมันจะทำ x ก่อน
ผลลัพท์ที่ได้จะถูก test ด้วย p
ถ้าผ่าน ก็จะ return กลับมา ถ้าไม่ผ่านก็ recursive รับค่าต่อไป
เห็นแล้วก็นึกถึง Local loop expression ที่เคยเขียนถึง
ก็เลยลองจับ untilM มาเขียนใหม่ด้วย fix ได้หน้าตาแบบนี้ออกมา
import Control.Monad.Fix
main = do ...
...
input <- fix (\loop ->
do putStr "Client or server? "
l <- getLine
if head(l) `elem` "cs" then return l else loop)
...
ดูแล้วก็ยังไม่สวยถูกใจเท่าไรนัก
Related link from Roti
Labels:
haskell
Saturday, October 04, 2008
thunks
พึ่งได้เห็นคำว่า
ซึ่งเขาบอกว่าการเขียน JQuery นั้น, มันบังคับให้ใช้ form แบบ thunks เยอะมาก
และเขาก็ยกตัวอย่าง code มาให้ดูด้วย
ใน code ข้างบนบั้น, thunks ก็คือ function ที่ไม่มี parameter
และวัตถุประสงค์การใช้มัน ก็คือ delay การทำงานของ code ในส่วนที่เราต้องการส่งไปเป็น argument
ต้องท้าวความนิดหนึ่ง สำหรับคนที่ตามไม่ทัน
ใน programming language ส่วนใหญ่
argument ที่เราส่งไปให้ function หรือ procedure
จะถูก evulate ก่อนที่จะถูกส่งเข้าไปใน function
ยกตัวอย่าง syntax แบบนี้
ในหลายๆภาษาเราไม่สามารถสร้าง function ให้เรียนแบบ syntax ข้างบนได้
เพราะถ้าเราลองเขียนแบบข้างล่างนี้ดู เราจะพบว่า t กับ f จะถูก evaluate ก่อนที่จะ check if เสมอ
สำหรับผมหลังจากนั่งงงดู code สักพักแล้วก็ร้องอ๋อ ว่า มันคาบเกี่ยวกับเรื่องของ higher order function
แล้วก็ closure ด้วย (คิดว่า thunk จริงๆใน lisp ซึ่งเป็นต้นกำเนิดของคำนี้ น่าจะมีความหมายมากกว่านี้)
อ่านรายละเอียดเพิ่มเติมใน wikipedia-thunk
thunks จาก blog ของ Phil Windleyซึ่งเขาบอกว่าการเขียน JQuery นั้น, มันบังคับให้ใช้ form แบบ thunks เยอะมาก
และเขาก็ยกตัวอย่าง code มาให้ดูด้วย
$(document).ready(function() {
$("#orderedlist li:last").hover(function() {
$(this).addClass("green");
},function(){
$(this).removeClass("green");
});
});
ใน code ข้างบนบั้น, thunks ก็คือ function ที่ไม่มี parameter
และวัตถุประสงค์การใช้มัน ก็คือ delay การทำงานของ code ในส่วนที่เราต้องการส่งไปเป็น argument
ต้องท้าวความนิดหนึ่ง สำหรับคนที่ตามไม่ทัน
ใน programming language ส่วนใหญ่
argument ที่เราส่งไปให้ function หรือ procedure
จะถูก evulate ก่อนที่จะถูกส่งเข้าไปใน function
ยกตัวอย่าง syntax แบบนี้
expr ? Texpr: Fexpr
ในหลายๆภาษาเราไม่สามารถสร้าง function ให้เรียนแบบ syntax ข้างบนได้
เพราะถ้าเราลองเขียนแบบข้างล่างนี้ดู เราจะพบว่า t กับ f จะถูก evaluate ก่อนที่จะ check if เสมอ
int ifexpr( int x, int t, int f )
{
if( x ) return t;
return f;
}
สำหรับผมหลังจากนั่งงงดู code สักพักแล้วก็ร้องอ๋อ ว่า มันคาบเกี่ยวกับเรื่องของ higher order function
แล้วก็ closure ด้วย (คิดว่า thunk จริงๆใน lisp ซึ่งเป็นต้นกำเนิดของคำนี้ น่าจะมีความหมายมากกว่านี้)
อ่านรายละเอียดเพิ่มเติมใน wikipedia-thunk
Related link from Roti
Labels:
programming
Monday, September 22, 2008
Local Loop Expression
เคยเขียนถึง Y combinator ไปที
ตอนนั้นก็ไม่เคยคิดว่า มันจะมีตัวอย่างการใช้งานในชีวิตจริง
วันนี้ได้เห็นตัวอย่างการนำไปใช้ ที่น่าสนใจ
โดยเขาเอามันมาทำ local loop expression
ในตัวอย่างข้างบน forkIO คือการแตก thread ออกไปทำงาน,
fix คือ function ที่ทำให้เกิด magic
โดยมันทำให้เราทำ recursive แบบไม่ต้องประกาศชื่อ function ได้
(ในตัวอย่าง loop ก็คือ function ที่ pass เข้ามา, ถ้าเราอยากให้มัน recursive ทำ ก็ให้เรียกมันซ้ำ)
ใน xmonad ที่เป็น window manager ที่เขียนด้วย haskell ก็มีใช้แบบนี้
โดยการทำงานก็คือการวน loop ตรวจสอบ windows event
เจ้า function fix มันอยู่ใน module Control.Monad.Fix
ซึ่งเกิดจาก paper นี้
ใครธาตุไฟแข็งแรงก็เข้าไปอ่านได้ครับ (ผมเปิดดูแล้วก็ปิดทันที)
ตอนนั้นก็ไม่เคยคิดว่า มันจะมีตัวอย่างการใช้งานในชีวิตจริง
วันนี้ได้เห็นตัวอย่างการนำไปใช้ ที่น่าสนใจ
โดยเขาเอามันมาทำ local loop expression
reader <- forkIO $ fix $ \loop -> do
(nr', line) <- readChan chan'
when (nr /= nr') $ hPutStrLn hdl line
loop
ในตัวอย่างข้างบน forkIO คือการแตก thread ออกไปทำงาน,
fix คือ function ที่ทำให้เกิด magic
โดยมันทำให้เราทำ recursive แบบไม่ต้องประกาศชื่อ function ได้
(ในตัวอย่าง loop ก็คือ function ที่ pass เข้ามา, ถ้าเราอยากให้มัน recursive ทำ ก็ให้เรียกมันซ้ำ)
ใน xmonad ที่เป็น window manager ที่เขียนด้วย haskell ก็มีใช้แบบนี้
โดยการทำงานก็คือการวน loop ตรวจสอบ windows event
f = fix $ \again -> do
more <- checkMaskEvent d enterWindowMask ev
when more again
เจ้า function fix มันอยู่ใน module Control.Monad.Fix
ซึ่งเกิดจาก paper นี้
ใครธาตุไฟแข็งแรงก็เข้าไปอ่านได้ครับ (ผมเปิดดูแล้วก็ปิดทันที)
Related link from Roti
Labels:
haskell
Friday, September 12, 2008
เขียน Rome 's plugin
ถ้าจะต้องเขียนโปรแกรม parse feed, เราสามารถเลือกวิธีการได้หลายวิธี
แบบที่ง่ายที่สุดก็คือ ใช้พวก xml parser library ซึ่งมีอยู่มากมาย
เรื่องกวนใจที่ตามมาก็คือ feed มันมี format หลักๆอยู่ 2 แบบ คือ rss และ atom
โปรแกรม parser เราก็เลยฉลาดขึ้นมาอีกนิดหนึ่ง (จะเขียนโดยใช้ if หรือแยก function หรือแนว OOP ก็ว่าไป)
ในส่วนตัวผม ผมเลือกใช้ Rome (มันเป็น Java !!!)
ซึ่งเป็น api ในการ parse feed
โดยมันจะซ่อนความแตกต่างของโครงสร้างระหว่าง RSS และ ATOM ไว้จากเรา
ปัญหาที่เจอก็คือ กรณีที่เราไปดึง feed จาก FeedBurner
มันจะมี extension element จำนวนหนึ่งที่ Rome ไม่รู้จัก (ทำให้ไม่มี method ให้ get ค่าเหล่านั้น)
ยกตัวอย่าง Feed ของ Feedburner มันจะมีหน้าตาแบบนี้
สิ่งที่เราต้องการก็คือ
แต่โชคดีที่คนออกแบบ Rome เขามองการณ์ไกล เขาก็เลยเตรียมช่อง plugin ให้เราไว้แล้ว
วิธีการก็คือ เราต้องสร้าง (High Level) Parser ของเราขึ้นมาตัวหนึ่ง
และทำการ register มันเข้ากับ Rome
เจ้า Parser ตัวนี้ จะถูกเรียกใช้หลังจากที่ Rome สร้าง DOM structure ขึ้นมาแล้ว
หน้าที่ของ parser ก็คือ return Object ที่ wrap ค่าที่เราต้องการไว้
ตัวอย่าง code
ในฝั่งคนใช้งาน ถ้าเขียนเป็น groovy ก็เขียนง่ายๆแบบนี้
แบบที่ง่ายที่สุดก็คือ ใช้พวก xml parser library ซึ่งมีอยู่มากมาย
เรื่องกวนใจที่ตามมาก็คือ feed มันมี format หลักๆอยู่ 2 แบบ คือ rss และ atom
โปรแกรม parser เราก็เลยฉลาดขึ้นมาอีกนิดหนึ่ง (จะเขียนโดยใช้ if หรือแยก function หรือแนว OOP ก็ว่าไป)
ในส่วนตัวผม ผมเลือกใช้ Rome (มันเป็น Java !!!)
ซึ่งเป็น api ในการ parse feed
โดยมันจะซ่อนความแตกต่างของโครงสร้างระหว่าง RSS และ ATOM ไว้จากเรา
ปัญหาที่เจอก็คือ กรณีที่เราไปดึง feed จาก FeedBurner
มันจะมี extension element จำนวนหนึ่งที่ Rome ไม่รู้จัก (ทำให้ไม่มี method ให้ get ค่าเหล่านั้น)
ยกตัวอย่าง Feed ของ Feedburner มันจะมีหน้าตาแบบนี้
<feed xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
...
<entry>
<title>xxx</title>
<content>yyyy<content>
<author>bact'</author>
<link href="http://feeds.feedburner.com/~r/bact/~3/326895937/hauntedness-management.html"/>
<feedburner:origLink href="http://bact.blogspot.com/2008/07/hauntedness-management.html"/>
</entry>
...
</feed>
สิ่งที่เราต้องการก็คือ
origLink ซึ่ง ROME ไม่มี api ให้ใช้แต่โชคดีที่คนออกแบบ Rome เขามองการณ์ไกล เขาก็เลยเตรียมช่อง plugin ให้เราไว้แล้ว
วิธีการก็คือ เราต้องสร้าง (High Level) Parser ของเราขึ้นมาตัวหนึ่ง
และทำการ register มันเข้ากับ Rome
เจ้า Parser ตัวนี้ จะถูกเรียกใช้หลังจากที่ Rome สร้าง DOM structure ขึ้นมาแล้ว
หน้าที่ของ parser ก็คือ return Object ที่ wrap ค่าที่เราต้องการไว้
ตัวอย่าง code
public class Parser implements ModuleParser {
public String getNamespaceUri() {
return FeedburnerModule.FEEDBURNER_URI;
}
public Module parse(Element element) {
Element origLink = element.getChild("origLink", FeedburnerModule.FEEDBURNER_NS);
if (origLink != null) {
FeedburnerModuleImpl impl = new FeedburnerModuleImpl(FeedburnerModule.class, FeedburnerModule.FEEDBURNER_URI);
impl.setOrigLink(origLink.getTextTrim());
return impl;
}
return null;
}
}
ในฝั่งคนใช้งาน ถ้าเขียนเป็น groovy ก็เขียนง่ายๆแบบนี้
def entry0 = feed.entries[0]
def module = entry0.getModule('http://rssnamespace.org/feedburner/ext/1.0')
assertEquals 'http://bact.blogspot.com/2008/07/hauntedness-management.html', module.origLink
Related link from Roti
Labels:
java
Sunday, August 31, 2008
barcampbangkok2
ในงานผมได้พูดเรื่อง "25 วัน เชียงใหม่-กรุงเทพฯ โดยเรือแคนนู"
แต่ตอน present ผมเปิด google map ไม่ได้
เลยขอลง map ให้ดูในนี้แล้วกัน
View Larger Map
ส่วนรูปดูได้ที่นี่
เรื่องประทับใจในงาน barcampbangkok2 ของผม
ข้อสังเกต
แต่ตอน present ผมเปิด google map ไม่ได้
เลยขอลง map ให้ดูในนี้แล้วกัน
View Larger Map
ส่วนรูปดูได้ที่นี่
![]() |
| 25days |
เรื่องประทับใจในงาน barcampbangkok2 ของผม
- เนื้อหาที่ประทับใจมากที่สุด เรื่อง Riding on Fans’ Energy: Touhou, Fan Culture, and Grassroot Entertainment ของอาจารย์โป้ง ที่พูดเกี่ยวกับปรากฎการณ์ของเกมส์ Touhou
เกมส์ที่สร้างโดยคนคนเดียว แต่สามารถจัด event ที่บรรดาแฟนๆ 47,000 คนมาร่วมงานได้
ใครสนใจตามไปอ่านบทความของอาจารย์โป้งได้ที่นี่ Link
ไม่ใช่แต่เนื้อหาเท่านั้นที่น่าสนใจ ประเด็นที่น่าสนใจอีกอย่างก็คือ
ตอนที่อาจารย์โป้งพูดนั้น เวลาดันหมดเสียก่อน
แต่ด้วย spirit ของ barcamp, คนฟังก็เลยรวมตัวกันเปิดห้องขึ้นใหม่อีกห้องหนึ่ง เพื่อฟังต่อให้จบ
ข้อสังเกต
- จำนวนห้องที่มากถึง 8 ห้อง ทำให้เกิด paradox of choice เล็กๆขึ้นมา
- 2 วันนี่เหนื่อยไปหน่อย, ดูเหมือน climax จะไปอยู่ที่ beercamp ทำให้อีกวันดูเงียบๆไปหน่อย
- ขาดบริเวณพักผ่อนกลาง ที่เปิดโอกาสให้นั่งเล่น และ present งานสำหรับคนที่ไม่ได้ถูกเลือกหัวข้อ
Related link from Roti
Labels:
barcampbangkok
Tuesday, August 26, 2008
rabbiter
@sugree tweet ถามถึง Rabbiter, ด้วยความที่ไม่รู้ว่ามันคืออะไร ก็เลยต้องไปหาข้อมูลมาอ่านก่อน
เป้าหมายของเจ้า rabbiter ก็คือ
เจ้า rabbitter จริงๆแล้วก็คือ module extension ของ ejabberd โดยมันจะประพฤติตนเป็น bot คอยโต้ตอบกับเรา
หน้าที่ของมันก็คือการ relay message ของคนอื่นๆที่เราสนใจ (follow) มาให้เรา หรือ ส่งต่อข้อความของเรา ไปให้กับคนอื่นที่สนใจติดตามเรา (follower)
โดยการ relay นี้สามารถทำข้ามวง ejabberd server ได้
ในแง่ architecture ประเด็นที่น่าสนใจก็คือ มันใช้ messageing queue (rabbitmq) เข้ามา handle publisher-subscriber model.
อ่านเอกสารอธิบายอย่างละเอียดได้ที่นี่
https://dev.rabbitmq.com/wiki/RabbiterFederation
Note: ตอนทดสอบต้องระวังประเด็นที่ IM client แอบแก้ message ของเรา เช่น pidgin บน linux, ที่เวลาเราสั่ง
เป้าหมายของเจ้า rabbiter ก็คือ
using XMPP to build a decentralized microblogging platform (think Twitter busted apart to run as a distributed network of microblogging providers)
หลักการทำงาน
เจ้า rabbitter จริงๆแล้วก็คือ module extension ของ ejabberd โดยมันจะประพฤติตนเป็น bot คอยโต้ตอบกับเรา
หน้าที่ของมันก็คือการ relay message ของคนอื่นๆที่เราสนใจ (follow) มาให้เรา หรือ ส่งต่อข้อความของเรา ไปให้กับคนอื่นที่สนใจติดตามเรา (follower)
โดยการ relay นี้สามารถทำข้ามวง ejabberd server ได้
ในแง่ architecture ประเด็นที่น่าสนใจก็คือ มันใช้ messageing queue (rabbitmq) เข้ามา handle publisher-subscriber model.
อ่านเอกสารอธิบายอย่างละเอียดได้ที่นี่
https://dev.rabbitmq.com/wiki/RabbiterFederation
วิธีการ install
- ติดตั้ง erlang, เนื่องจาก erlang compile ได้ง่ายๆ ดังนั้นใช้วิธี download source code มา compile เองดีกว่า
version ที่ผมใช้ก็คือ r12-b3 - ติดตั้ง rabbitemq-server
ตัวนี้ให้ download source version 1.4.0 มา เวลา compile ก็แค่สั่ง make เป็นอันเสร็จพิธี - ทำให้ erlang มองเห็น rabbitmq-server module
ถ้าเราติดตั้ง erlang ด้วยการ compile code เอง, modules path ของ erlang จะอยู่ที่ /usr/local/lib/erlang/lib
โดยมีหน้าตาประมาณนี้
pphetra@[/usr/local/lib/erlang/lib]
$ ls
appmon-2.1.9 dialyzer-1.8.1 mnesia-4.4.3 ssl-3.9
asn1-1.5.2 docbuilder-0.9.8.4 observer-0.9.7.4 stdlib-1.15.3
common_test-1.3.2 edoc-0.7.6 orber-3.6.9 syntax_tools-1.5.5
...
ให้ทำ symbolic link กับ directory rabbitmq-server-1.4.0
Note: ตรงนี้ให้ระวังชื่อด้วย เพราะ code ของ rabbiter มันต้องการให้เป็นชื่อ rabbitmq_server เป๊ะๆ
$ ls -l rab*
lrwxr-xr-x 1 root pphetra 40 Aug 27 13:05 rabbitmq_server -> /Users/pphetra/dev/rabbitmq-server-1.4.0 - ขั้นถัดไปก็ clone เจ้า rabbiter จาก github
Note: ใครที่สั่ง clone แล้วเกิด error timeout ให้ลองเปลี่ยน url จาก git://github.com/tonyg/rabbiter.git
เป็น http://github.com/tonyg/rabbiter.git
เมื่อได้มาแล้ว ก็ทิ้งไว้ก่อน เดี๋ยวเราจะไปฝาก compile กับ ejabberd - ติดตั้ง ejabberd, ตัวนี้ให้ใช้ version 2.0.1
ก่อน compile ก็ให้ไปแอบทำ symbolic link เจ้า file mod_rabbiter.erl เข้ามาใน directory ejabberd-2.0.1/src เสียก่อน
(หรือจะเล่นลูกทุ่ง copy เอาดื้อๆ ก็ไม่ผิด) - config ejabberd ให้รู้จัก rabbiter
โดย edit /etc/ejabberd/ejabberd.cfg
เพิ่ม บรรทัดนี้ลงไป
{modules,
[
{mod_adhoc, []},
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
{mod_caps, []},
...
{mod_rabbiter, []},
...
การทดสอบ
- สั่ง start ejabberd ด้วยคำสั่ง
/sbin/ejabberdctl start
ตรวจสอบว่า start สำเร็จหรือไม่ ด้วยคำสั่ง/sbin/ejabberdctl status
$ ./ejabberdctl status
Node ejabberd@localhost is started. Status: started
ejabberd is running - add user
$ ./ejabberdctl register pphetra localhost mypassword
$ ./ejabberdclt register sugree localhost anypassword - ทดลองใช้ IM ต่อเข้ามา โดยระบุตัวตนเป็น pphetra
จากนั้นก็ add contact rabbiter@rabbiter.localhost
ลองสั่ง*helpก็จะได้ message แบบนี้
To give me a command, choose one from the list below. Make sure you prefix it with '*', otherwise I'll interpret it as something you want to send to your group of followers. You can say '*help (command)' to get details on a command you're interested in. If you want to actually say 'help', rather than asking for help, type '*say help' instead.
["*help","*follow","*unfollow","*following","*followers",
"*invite"]
ลองสั่ง*follow sugree@localhostก็จะได้รับ message แบบนี้
You are now following sugree@localhost.
Note: user sugree ต้องเคย logon ก่อนด้วย IM client ไม่งั้น จะได้ error เป็นแบบนี้
I don't know that person. Perhaps you could invite them to get in touch?
Note: ตอนทดสอบต้องระวังประเด็นที่ IM client แอบแก้ message ของเรา เช่น pidgin บน linux, ที่เวลาเราสั่ง
*follow x@y มันจะแอบเปลี่ยนเป็น *follow x@y <mailto:x@y> ซึ่งส่งผลให้ rabbiter ไม่รู้จักคำสั่งนี้
Related link from Roti
Friday, August 22, 2008
Specifying revisions
หลายคนคงเคยผ่านตากับการอ้างถึง parent ของ branch แบบนี้ (เครื่องหมาย ^)
ถ้าไปเปิด man page ของ git-rev-parse จะพบ diagram ที่ช่วยให้เราเข้าใจถึงการใช้
Note: tree มันกลับหัว, A คือ ยอดบนสุดของ ที่เกิดจากการ merge B กับ C
git checkout HEAD^^
ถ้าไปเปิด man page ของ git-rev-parse จะพบ diagram ที่ช่วยให้เราเข้าใจถึงการใช้
^
Here is an illustration, by Jon Loeliger. Both node B and C are a
commit parents of commit node A. Parent commits are ordered
left-to-right.
G H I J
\ / \ /
D E F
\ | / \
\ | / |
\|/ |
B C
\ /
\ /
A
A = = A^0
B = A^ = A^1 = A~1
C = A^2 = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2 = B^^2 = A^^^2 = A~2^2
I = F^ = B^3^ = A^^3^
J = F^2 = B^3^2 = A^^3^2
Note: tree มันกลับหัว, A คือ ยอดบนสุดของ ที่เกิดจากการ merge B กับ C
Related link from Roti
Labels:
git
Friday, August 15, 2008
Do anything at barcamp bangkok 2
keyword ของ barcamp ก็คือ "เนื้อหา เกิดจาก ผู้เข้าร่วม"
คนพูดไม่จำเป็นต้องเป็น expert ในสิ่งที่พูดหรอก แค่มีประสบการณ์ดีๆที่อยากแบ่งปัน ก็เพียงพอแล้ว
มาร่วมกันสร้างอนาคตใหม่ให้กับวงการ IT เมืองไทย ด้วยการเปลี่ยนจาก ผู้รับ(passive) เป็น ผู้ให้(active) กันเถอะ
credits
เด็กตีลังกา - เด็กชายปัณณ์
เด็กห้อยหัว - เด็กชายปุณณ์
เด็กที่คนหัวโล้นอุ้ม - เด็กหญิงนมนต์ (สะกดถูกหรือเปล่าเนี่ย)
ผู้ชายกางเกงลาย - ผมเอง
ผู้ชายหัวโล้น - roofimon
คนถ่าย - keng
สถานที่ - opendream.org
คนพูดไม่จำเป็นต้องเป็น expert ในสิ่งที่พูดหรอก แค่มีประสบการณ์ดีๆที่อยากแบ่งปัน ก็เพียงพอแล้ว
มาร่วมกันสร้างอนาคตใหม่ให้กับวงการ IT เมืองไทย ด้วยการเปลี่ยนจาก ผู้รับ(passive) เป็น ผู้ให้(active) กันเถอะ
credits
เด็กตีลังกา - เด็กชายปัณณ์
เด็กห้อยหัว - เด็กชายปุณณ์
เด็กที่คนหัวโล้นอุ้ม - เด็กหญิงนมนต์ (สะกดถูกหรือเปล่าเนี่ย)
ผู้ชายกางเกงลาย - ผมเอง
ผู้ชายหัวโล้น - roofimon
คนถ่าย - keng
สถานที่ - opendream.org
Related link from Roti
Labels:
barcampbangkok
Monday, August 04, 2008
code ของ grails 's plugin
คราวก่อนที่เจอปัญหา xml-rpc บน grails, ผมได้นั่งแกะโครงสร้าง plugins ของ grails ไปแวบหนึ่ง
รู้สึกติดใจว่า ถ้ามีเวลาต้องมานั่งแกะให้เป็นเรื่องเป็นราว
วันนี้ได้ที ใน mailing list ของ th-grails-user มีปัญหาซึ่งพาดพิงถึง mail-plugin
ก็เลยจัดการ install แล้วก็นั่งแกะ code ว่ามันทำอะไรบ้าง
function หลักๆที่ mail plugin ทำก็คือ
code ในส่วนของ function แรกที่น่าสนใจ ก็คือการ define bean 'mailSender'
ลองดู code ที่มันใช้
method นี้จะถูกเรียกใช้ในช่วง startup ของ grails
ซึ่งข้างในจะเห็นว่ามันใช้ DSL ในการ config spring
code ข้างบน ถ้าเขียนในแบบดั้งเดิมของ spring ก็จะออกมาทำนองนี้
ข้อได้เปรียบที่เห็นได้ชัด สำหรับการใช้ DSL ในการ config spring ก็คือ
เราสามารถใส่ logic ลงไปได้ (เพราะมันเป็น code ไม่ใช่ xml)
ในส่วน function ที่สอง ที่มีการ inject method ให้กับทุก controller
ถ้าดูตาม code แล้วจะเห็นว่ามันทำแบบนี้
code กระทัดรัดมาก มันแค่ส่งต่อ closure ที่รับเข้ามาให้กับ mailService
ถ้าเป็น java code ก็ต้องยืดยาวประมาณนี้
รู้สึกติดใจว่า ถ้ามีเวลาต้องมานั่งแกะให้เป็นเรื่องเป็นราว
วันนี้ได้ที ใน mailing list ของ th-grails-user มีปัญหาซึ่งพาดพิงถึง mail-plugin
ก็เลยจัดการ install แล้วก็นั่งแกะ code ว่ามันทำอะไรบ้าง
function หลักๆที่ mail plugin ทำก็คือ
- สร้าง service ที่ชื่อ mailService เพื่อให้ controller หรือ domain เรียกใช้ตามอัธยาศัย
โดย service นี้ จะทำหน้าที่สร้าง builder ที่จะใช้แปลง closure ที่ pass เข้ามา ให้กลายเป็น mail message
ก่อนจะ delegate ต่อให้ mailSender bean เป็นคนจัดการส่งออกไป
(mailSender แอบใช้ของที่ spring มีอยู่แล้ว นั่นคือ org.springframework.mail.javamail.JavaMailSenderImpl) - สร้าง method ที่ชื่อ sendMail ให้กับ controller ทุกตัวที่มี ผ่านทางกลไกของ metdata
code ในส่วนของ function แรกที่น่าสนใจ ก็คือการ define bean 'mailSender'
ลองดู code ที่มันใช้
def doWithSpring = {
def config = application.config.grails.mail
mailSender(JavaMailSenderImpl) {
host = config.host ?: "localhost"
defaultEncoding = config.encoding ?: "utf-8"
if(config.port)
port = config.port
if(config.username)
username = config.username
if(config.password)
password = config.password
if(config.protocol)
protocol = config.protocol
if(config.props instanceof Map && config.props)
javaMailProperties = config.props
}
}
method นี้จะถูกเรียกใช้ในช่วง startup ของ grails
ซึ่งข้างในจะเห็นว่ามันใช้ DSL ในการ config spring
code ข้างบน ถ้าเขียนในแบบดั้งเดิมของ spring ก็จะออกมาทำนองนี้
<bean id="mailSender"
class="org.springframework.mail.javamail.JavaMailSenderImpl">
<host>localhost</host>
<defaultEncoding>utf-8</defaultEncoding>
...
</bean>
ข้อได้เปรียบที่เห็นได้ชัด สำหรับการใช้ DSL ในการ config spring ก็คือ
เราสามารถใส่ logic ลงไปได้ (เพราะมันเป็น code ไม่ใช่ xml)
ในส่วน function ที่สอง ที่มีการ inject method ให้กับทุก controller
ถ้าดูตาม code แล้วจะเห็นว่ามันทำแบบนี้
def doWithApplicationContext = { applicationContext ->
configureSendMail(application, applicationContext)
}
def configureSendMail(application, applicationContext) {
application.controllerClasses*.metaClass*.sendMail = {Closure callable ->
applicationContext.mailService?.sendMail(callable)
}
}
code กระทัดรัดมาก มันแค่ส่งต่อ closure ที่รับเข้ามาให้กับ mailService
ถ้าเป็น java code ก็ต้องยืดยาวประมาณนี้
MailService svr = (MailService) ctx.getBean('mailService');
if (svr != null) {
svr.sendMail(...);
}
Related link from Roti
Subscribe to:
Posts (Atom)



