Monday, December 23, 2013

Fizz Buzz in Haskell without If

เห็นน้องรูฟจัดกิจกรรม Kata เขียน FizzBuzz แบบไม่ให้ if แล้วคันมือบ้าง ก็เลยขุดเอา Haskell มาลองใช้

แนวคิดก็คือ เรารู้ว่าชุดของ fizzbuzz มันจะซ้ำเมื่อวนซ้ำเดิมทุกๆ 15 ค่า เราก็เลยสร้าง function ที่ทำหน้าที่แปลงตัวเลข เป็นข้อความ ขึ้นมา 15 ตัว แล้วใช้ function cycle ทำหน้าผลิตซ้ำมันให้กลายเป็น infinite array

fizz n     = "fizz"
buzz n     = "buzz"
fizzbuzz n = "fizzbuzz"
as_is n    = show n
 
fn_table = cycle [as_is, as_is, fizz, as_is, buzz, fizz, as_is, as_is, fizz, buzz, as_is, fizz, as_is, as_is, fizzbuzz] 

ตาราง fizz buzz ก็เกิดจากการเรียกใช้ function ด้วย ตัวเลข index ของ ตำแหน่งของมัน


table = zipWith ($) fn_table [1..]

ลองเรียก 100 ลำดับแรกขึ้นมาตรวจสอบ

take 100 table 
["1","2","fizz","4","buzz","fizz","7","8","fizz","buzz","11","fizz","13","14","fizzbuzz","16","17","fizz","19","buzz","fizz","22","23","fizz","buzz","26","fizz","28","29","fizzbuzz","31","32","fizz","34","buzz","fizz","37","38","fizz","buzz","41","fizz","43","44","fizzbuzz","46","47","fizz","49","buzz","fizz","52","53","fizz","buzz","56","fizz","58","59","fizzbuzz","61","62","fizz","64","buzz","fizz","67","68","fizz","buzz","71","fizz","73","74","fizzbuzz","76","77","fizz","79","buzz","fizz","82","83","fizz","buzz","86","fizz","88","89","fizzbuzz","91","92","fizz","94","buzz","fizz","97","98","fizz","buzz"]


น้องรูฟเห็น code แล้ว บอกว่า ทำไมไม่มี test หล่ะพี่ เราก็นึกในใจ code สั้นขนาดนี้ยังต้องมี test อีกหรือ แต่เพื่อให้เข้ากระแสจึงต้องไปค้นคว้าสักหน่อยว่า ถ้าต้องเขียน test ใน haskell เราจะใช้ท่าไหนดี
ใน haskell เขาไม่นิยมเขียน unit test แบบทั่วๆไป เขาบอกว่ามัน primitive มากที่ต้องเขียน test เพื่อ assert เงื่อนไขแบบ manual ทำไมเราไม่เขียนแค่ specification ของ function เรา แล้วเดี๋ยว haskell จะ random data เข้ามาทำทดสอบให้เราเอง

อันนี้คือ spec ของ fizzbuzz ของเรา

show_at n = table !! (n - 1)
test' n = fn n == show_at n
        where fn n
                | (n `rem` 15) == 0 = "fizzbuzz"
                | (n `rem` 5)  == 0 = "buzz"
                | (n `rem` 3)  == 0 = "fizz"
                | otherwise = show n

show_at ก็คือ ค่าที่ได้จากตาราง fizz buzz ที่เรา generate ขึ้นมาจาก code ข้างบน
ส่วน fn ก็คือ spec ที่เรากำหนดขึ้นมาทดสอบ

ลองทดสอบ run ดู

*Main> quickCheck test' 
*** Failed! (after 1 test and 1 shrink): Exception: Prelude.(!!): negative index 0

จะเห็นว่า Fail ตั้งแต่ค่าแรกสุด เลย นั่นคือค่า fizz buzz กรณี n = 0
แต่เราไม่อยากปวดหัวกับ fizzbuzz ที่ n <= 0 เราก็เลย กำหนด test ขึ้นมาใหม่ ให้ใช้ index ค่าระหว่าง 1 ถึง 10000 เท่านั้น

 test n = forAll (elements [1..10000]) $ \n -> test' n 

ลองทดสอบ run ดู จะเห็นว่า quickCheck จะ random ค่าระหว่าง 1 ถึง 10000 ขึ้นมา 100 ค่า แล้วทำการทดสอบให้เรา

*Main> quickCheck test 
+++ OK, passed 100 tests.

Related link from Roti

Saturday, May 14, 2011

เอาเข้าไป

Opendream ได้ Notice จาก web site ชื่อดัง

> ผมคงไม่สามารถร่วมงานกับ OD ที่สนิทกับ Roofimon และ Pphetra
> ได้อีกแล้วน่ะครับ สองคนนี้เกรียนเกินไป
>
> ผมไม่มีปัญหาอะไรกับ OD นะครับ และเคารพในการตัดสินใจของ OD
> ที่จะร่วมงานกับสาวกจาวาทั้งสอง (ว่าจะมีประโยชน์กับ OD เอง)
> แต่ Blognone คงจะไม่สามารถเอื้อประโยชน์กับ OD
> ที่มีสองคนนี้อยู่ร่วมงานด้วยได้อีกต่อไปครับ
> ทั้งในแง่การโพสต์รับสมัครงาน และกิจกรรมอื่นๆ ในอนาคต
>
> นอกจากนี้ งานส่วนตัวในอนาคตของผมก็คงจะไม่ร่วมงานที่มี OD
> เป็นพาร์ทเนอร์อีกเช่นกัน ไม่ว่าจะเป็น CC หรืออื่นๆ นะครับ

กลุ่ม CC ก็ได้กับเขาด้วย

> เรียนทุกท่าน

> ผมขอถอนตัวจากทีม CC Thailand ครับ ด้วยเหตุผลว่า CC Thailand มี
> OpenDream เป็นพาร์ทเนอร์ และผมมีนโยบายส่วนตัวที่จะไม่ทำงานร่วมกับ OD
> ซึ่งผมมีปัญหากับพนักงาน-สมาชิกบางคนของ OD ครับ
> (ไม่ได้มีปัญหาอะไรกับคุณเก่งนะครับ แต่เนื่องจากตัวองค์กร OD
> มีความสัมพันธ์กับพนักงานเหล่านี้ ซึ่งเคยว่าร้ายผม
> ก็คงต้องขอถอนตัวครับ)

> สุดท้ายขอบคุณทุกท่านที่ช่วยผลักดัน CC Thai
> ให้ประสบความสำเร็จขึ้นมาได้ครับ และอวยพรให้ก้าวหน้าต่อไป

คาดการณ์ว่าในอนาคต ถ้าท่านฯยังไม่หายแค้น
Opendream คงมีโอกาสโดนข่าวปล่อยเล่นงานแน่ๆ #สื่อเท่านั้นที่ครองโลก

Related link from Roti

Wednesday, February 02, 2011

git - อยาก commit รวมกับ commit ก่อนหน้านั้น

เรื่องมีอยู่ว่า หลังจากที่ผม commit code ไปเป็นชุด (แก้ feature A แต่พบว่า feature B มี bug ด้วย ก็เลยเกิด commit ไล่ๆกัน)
แล้วเกิดพบว่า ลืม commit file ไปหนึ่ง file
แต่อยากให้ commit นี้ไปรวมอยู่ใน commit ก่อนหน้านั้น
กรณีที่เป็น case พึ่ง commit ไปหยกๆ เราก็สามารถใช้ commit --amend เข้ามาช่วยได้
แต่ถ้าเป็นกรณีที่มี commit อื่นๆมาคั่นหล่ะ

From pphetra


Note: ใช้ได้กับกรณีที่ยังไม่ push ขึ้น repository เท่านั้น

พระเอกของเรื่องนี้ก็คือ git rebase --interaction
ขั้นตอนก็คือ

เนื่องจากเรามี code ที่ต้องการแก้ไข ค้างอยู่ ให้ทำการ stash เก็บไว้ก่อน

$ git stash
Saved working directory and index state WIP on (no branch): b1b2bd6 fix bug: บางครั้งก็กด zoom ผลลัพท์จากการ serach ได้ บางครั้งก็ไม่ได้
HEAD is now at b1b2bd6 fix bug: บางครั้งก็กด zoom ผลลัพท์จากการ serach ได้ บางครั้งก็ไม่ได้


จากนั้นก็สั่ง rebase โดยกำหนด commit id ตัวก่อนหน้าตัวที่เราจะแก้

$ git rebase -i d1b9dc72a4a8f57b1dcb43c704494d8fcb639fd2

pick 516712b ERP-1807 implement login panel.
pick 38d8bda fix bug: duplicate primary key constraint 's name.
pick d316ecd fix bug: บางครั้งก็กด zoom ผลลัพท์จากการ serach ได้ บางครั้งก็ไม่ได้

# Rebase d1b9dc7..d316ecd onto d1b9dc7
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

เลือกเปลี่ยน commit ที่เป็นเป้าหมายในการ amend ให้เป็น 'edit'

จากนั้นก็สั่ง git stash pop
แล้วก็เลือก commit --amend เข้าไป

สุดท้ายก็สั่ง git rebase --continue

Related link from Roti

Monday, November 22, 2010

javascript: undefined && null

Related link from Roti

Wednesday, November 10, 2010

ทบทวน drama เมื่อ 3 ปีก่อน

ขออนุญาติน้อง Deans4j นะ ที่ quote คำพูดบางอันมาออก
เพราะพี่ว่า "ความจริงมันเปิดเผยแล้ว ทุกคนเข้าใจแล้วว่าอะไรเป็นอะไร"

คุณ bow แกเขียน benchmark เทียบ Java, Python, Ruby
บังเอิญคุณ bow แก้มี domain ที่ใช้อยู่ในเรื่อง "scientific computation"
แล้วมีประสบการณ์ java ไม่พอที่จะ optimize
เจ้า deans4j ก็เลยเข้าไปจุดระเบิด

http://www.blognone.com/node/4385

มีเรื่องมีราว
เจ้าน้อง deans ก็ไปตั้งกระทู้ไว้ใน blog ตัวเอง

http://deans4j.wordpress.com/2007/04/12/against-idiot/

สมัยนั้นผมเป็นแฟน blognone และ markpeak
เห็นเจ้า deans4j aggressive ขนาดนั้น ทนไม่ได้ก็เลยไป comment ไว้แบบนี้


Dean4j, คุณมีความคิดด้านลบอยู่เต็มตัวเลยนะ

ผมลองเล่าเรื่องผมให้ฟังแล้วกัน
มีอยุ่ช่วงที่ผมรู้สึกว่า “คนเก่งๆหายไปไหนหมด”
มีอยู่วันหนึ่งมีคน post ในพันธ์ทิพย์ว่า
“ต้องการหาคนเป็น database ช่วยกันทำ project สนุกๆกัน”
ผมก็เลย mail เล่าไปว่าเคยทำอะไร อย่างไร
ปรากฎว่า mail ที่เขาตอบกลับมา นั้นทำให้ผมช็อค ไปเหมือนกัน
เขาบอกว่า เขาเกลียดคนชนิดผมมากเลย คนที่ภูมิใจในตัวเองสูง
ประโยคนั้นเป็นประโยคที่ดีมากเลย
ทำให้ต้องกลับมาทบทวนตัวเองใหม่
ซึ่งจากการทบทวนแล้ว ผมก็พบว่า สิ่งที่ผมเล่าอย่างธรรมดาๆนั้น
สำหรับคนอื่นแล้วมันดู arrogance เกินไป

ทำให้ผมต้องระมัดระวัง
วิธีการเขียนหรือเรื่องต่างๆ ที่อาจทำให้คนอื่นเข้าใจผิดในเจตนา
แต่ก็ไม่วายผิดผลาดอีก
อย่างเมื่อปลายปีก่อน apple เขาจัดอบรมฟรี
ผมก็ไปกับเขาด้วย
ก็ไปเจอพี่คนหนึ่ง อัธยาศัยดีมาก
กลับมาก็ mail คุยกัน
พี่เขาก็ถามว่า ปัจจุบัน ทำอะไรอยู่บ้าง
ผมก็เขียนเล่าเรื่องอย่างยาวเลย
ผลเป็นอย่างไรรุ้ไหม
พี่เขาเลิกติดต่อผมเลย
ซึ่งผมเดาได้ไม่ยากเลยว่า
มันต้องเกิดจากเนื้อความในจดหมาย
ซึ่งมันน่าจะมีส่วนผสมของความ Proud มากไปหน่อย
ทำให้คนอ่านรู้สึกว่าเราเป็นคน arrogance

บางทีความอัดอั้นตันใจของเรา, ความภูมิใจที่ถุกเก็บกดของเรา
ความรู้สึกไม่เต็มของเรา, ความรู้สึกไม่พอใจต่างๆ
มันเผลอสะท้อนออกไปทางสิ่งที่เราเขียน
โดยเราก็ไม่รู้ตัว

สำหรับ post ที่คุณตอบในบทความ “BowDerKliene”
ถ้ามองในแง่ fact แล้ว. เป็นการตอบที่ดีมาก
แต่ถ้ามองในแง่ของท่าทีแล้ว
มันมีน้ำหนักของการดูถูก และการท้าตีท้าต่อยมากไปหน่อย
ค่อนข้าง arrogance สูง

ส่วนเรื่อง FUD
อย่างไง FUD ก็เป็นสิ่งที่หลีกเลี่ยงไม่ได้
ตราบใดที่ คน ยังเป็น คน อยุ่
การชักจูงคนให้เห็นอีกมุม ไม่สามารถใช้วิธีปะทะได้หรอก
มันยิ่งทำให้เขาปฏิเสธมากขึ้นเท่านั้น


จากนั้น deans กับผม ก็เขียนจดหมายคุยกันหลังไมค์
นี่คือจดหมายที่ผมตอบเขา

ผมกับคุณมีส่วนหนึ่งที่เหมือนกัน ก็คือ เรามีความโกรธเป็นเจ้าเรือน
เรามีความคาดหวังสูงกับสิ่งรอบข้าง
ซึ่งมันมักจะทำให้เราผิดหวังกับโมโหอยู่บ่อยๆ

อย่าง mk บางทีตอนแรกคุณก็อาจจะรู้สึกว่า
เฮ้ย คนนี้ น่าจะเป็นเพื่อนกับเราได้ เขามีเป้าหมายบางอย่างที่ตรงกับเรา
แต่สุดท้ายคุณก็เริ่มพบว่า มันมีบางสิ่งที่มันไม่ตรงกันนัก
ความคิดเห็น อุดมการณ์ การแสดงออก

ส่วนเรื่องที่คุณผิดหวังกับวิธีคิดของเขา
ผมขอให้มองเขาเป็นคนธรรมดาเหมือนเรานี่แหล่ะ
มีการก้าวที่ผิดพลาด มี double standard (ทุกคนเป็นเหมือนกันหมด)
มีความขัดแย้งในสิ่งที่เชื่อกับสิ่งที่ทำ
สิ่งที่เขากำลังทำอยู่ ก็คือการเรียนรู้ชีิวิตของเขา

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

การปะทะเกิดขึ้นได้
แต่อย่าให้อารมณ์ร้ายๆ มามีผลต่อชีวิตเรา


อันนี้คือบางส่วนที่เขา ตอบกลับมา

ประเด็นคือสิ่งที่ mk ปฏิบัติต่อผมก็น่ารังเกียจจริงๆ แล้วเต็มไปด้วยอคติ ความต้องการเอาชนะ เค้ามีปัญหาอะไรกับผม เค้าไม่เคยคิดจะเคลียร์ ผมเคยพยายามที่จะเคลียร์หลังไมค์ แต่เค้าก็ได้แต่หนีไป แถมมีแต่คำพูดขู่ และดูถูกผมซะยิ่งกว่า
...
ผมอยากให้พี่รับรู้ไว้ว่าไม่ใช่ผมที่เริ่มก่อน แต่เป็นเค้าที่เล่นนอกเกมสารพัดจนทำให้ผมเก็บอาการไม่อยู่ เค้าพยายาม discredit ผมหลายทาง เค้าเลือกที่จะประณามผมในที่สาธารณะ space ตัวเอง ทั้งๆ ที่ผมพยายามจะเคลียร์กับเค้าในที่ส่วนตัว จนที่สุดมันลงเอยอย่างนี้ผมจึงค่อยออกมา


เมื่อก่อนผมไม่เข้าใจที่ deans พูดหรอกนะ แต่ปัจจุบัน พฤติกรรมของ markpeak มันยืนยันตามคำพูดน้อง deans ทุกอย่าง


ตัดภาพกลับมาปัจจุบัน
หลังจากเรามี party "java เกรียน day"
ทุกอย่างที่ผ่านมา 3 ปี ก็ชัดเจนแล้วสำหรับผม

สุดท้ายผมก็ทำในสิ่งที่ผมแนะนำคนอื่นไม่ได้
อารมณ์ร้ายๆ ก็มามีผลต่อชิวิตผม

ศาสดาเจ็บใจมาก ตามมาเหน็บผมอีกแล้ว, พวกเกรียนใน net นี่ คงรู้สึกว่าพูดอย่างไรก็ได้เพราะใน net มันไกลตีน หรือเราควรจะลดระยะดี

หวังว่าสุดท้าย ตีนผมคงจะไม่ปะทะปากใครนะ

Related link from Roti

Tuesday, November 09, 2010

funciton form ใน javascript

หลังๆมา style การเขียน javascript ของผมมักจะตกไปอยู่ใน form แบบนี้
(โลกของ javascript ไม่มี class, ทุกอย่างเป็น object)
myService = {
sayHi: function(msg) {
this.output("-" + msg + "-");
},

output: function(msg) {
alert(msg);
}
}


หลังๆ javascript code ที่เขียนมันเริ่มใหญ่ (ถึงตอนนี้ก็ราวๆ 70000 บรรทัดแล้ว)
เวลา debug ก็เริ่มงงๆว่า ใครมันเป้นคนเรียก function ที่กูเขียนวะ

ใน webkit มันมี function ที่ชื่อ console.trace()
funciton นี้จะช่วย dump stacktrace ณ จุดที่เราใส่
ลองทดลองใส่ console.dump ใน code ข้างบน ก่อนตำแหน่ง alert

ผลลัพท์ที่ได้

:( ได้ anonymous ออกมาหมดเลย ตกลงก็เลยไม่รู้เลยว่าใครเรียก

แต่ทุกปัญหามีทางออก วิธีแก้ก็คือ ปรับรูป form การประกาศ javascript เสียใหม่ เปลี่ยนเป็น
myService = {
sayHi: function sayHi(msg) {
this.output("-" + msg + "-");
},

output: function output(msg) {
console.trace();
alert(msg);
}
}


ทดลอง run ใหม่

ทีนี้ก็ต้องเลือกแล้ว จะเอา form เยิ่นเย้อ หรือเอาแบบที่เวลาไล่ stacktrace แล้วมันไล่ง่าย

Related link from Roti

Wednesday, October 27, 2010

ติดใจ repl ของ Clojure

ช่วงนี้ research หาลู่ทางรับงานจากอเมริกา
โจทย์ก็คือไป crawl ดึงข้อมูล จาก medicare.gov
ความยากอยู่ตรง site นั้นใช้ .net เขียนแบบ component based (stateful)
การจะได้ข้อมูลมา ต้องฝ่าด่านตอบคำถาม จำนวน 4-5 หน้าเข้าไปก่อน
ก็เลยต้องใช้ browser จริงๆเข้าไปดึงข้อมูล

หวยก็เลยออกมาที่ Selenium + WebDriver
แต่ครั้นจะเขียนด้วย java ก็ไม่สนุก
เขียนด้วย ruby/python ก็เคยเขียนแล้ว
ก็เลยเลือกใช้ Clojure

Lisp มันมีข้อดีอย่างหนึ่งก็คือ
มันมี REPL ทำให้เราสามารถเขียนโปรแกรมในลักษณะ incremental ได้
เขียนไป ทดลองไป ปรับแต่งไป

สภาพแวดล้อมที่เลือกใช้พัฒนาก็คือ
emacs + cake

ลองดูตัวอย่าง ตอนทดสอบได้ใน video นี้
(ดูช่วงต้นๆก็พอ, net/web มันช้า หลังๆหมดไปกับการรอมันโหลด
ใครว่า web ราชการไทยช้า web ราชการฝรั่งก็ช้าเหมือนกัน)



Updated: video upload ผ่านทาง blogger ได้ความละเอียดต่ำเหลือเกิน
ดูรูปประกอบแล้วกัน



ตัวอย่าง code ที่ใช้

(defn send-key [by txt]
(let [elm (.findElement *driver* by)]
(.sendKeys elm (into-array [txt]))))

(defn click [by]
(let [elm (.findElement *driver* by)]
(.click elm)))

(defn init-queue []
(def *queue* (LinkedBlockingQueue.)))

(defn start-thread [driver func]
(.start
(Thread.
(fn []
(loop []
(let [code (.take *queue*)]
(binding [*driver* driver]
(func code)))
(recur))))))

(defn fetch [code]
(go "https://www.medicare.gov/find-a-plan/questions/home.aspx")
(send-key (by-css-selector "div#zip-code-field > input") code)
(click (by-css-selector "input[type=submit][alternatetext=\"Find Plans\"]")))

Related link from Roti

Tuesday, October 26, 2010

เรียนรู้วิธีที่ ExtJS format date

ใน ExtJS เราใช้ function format ในการกำหนดรูปแบบการแสดงผลของ Date object
ลองดูตัวอย่าง

new Date().format("c")
"2010-10-26T08:35:30+07:00"

new Date().format("d/n/Y")
"26/10/2010"


คำถามที่น่าสนใจก็คือ เขาใช้วิธีไหนในการ implement function นี้

ลองเปิดไล่ดู code ใน file src/util/Date.js
เริ่มจาก object ที่ชื่อ formatCodes
formatCodes : {
d: "String.leftPad(this.getDate(), 2, '0')",
D: "Date.getShortDayName(this.getDay())", // get localised short day name
j: "this.getDate()",
l: "Date.dayNames[this.getDay()]",
N: "(this.getDay() ? this.getDay() : 7)",
S: "this.getSuffix()",
w: "this.getDay()",
z: "this.getDayOfYear()",
W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
F: "Date.monthNames[this.getMonth()]",
m: "String.leftPad(this.getMonth() + 1, 2, '0')",
M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
n: "(this.getMonth() + 1)",
....
}

คำถามที่ตามมาก็คือ ทำไมถึงเก็บ code ฝั่งขวาเป็น String

ลองไล่จาก top down บ้าง, โดยย้อยกลับไปดู function format ว่า code ข้างในเป็นอย่างไร
Date.prototype.format = Date.prototype.dateFormat;

dateFormat : function(format) {
if (Date.formatFunctions[format] == null) {
Date.createFormat(format);
}
return Date.formatFunctions[format].call(this);
},


จะเห็นว่า เมื่อได้รับ parameter เป็น format แล้ว, function มันจะพยายาม lookup หา function จากตาราง Date.formatFunctions ก่อน
ถ้าไม่เจอ ก็จะไปเรียก Date.createFormat เพื่อสร้าง function ให้ก่อน

ลองตามไปดู function createFormat บ้าง
code ที่เป็นพระเอกของเราก็คือ บรรทัดสุดท้าย
    createFormat : function(format) {
var code = [],
special = false,
ch = '';

for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
} else if (special) {
special = false;
code.push("'" + String.escape(ch) + "'");
} else {
code.push(Date.getFormatCode(ch))
}
}
Date.formatFunctions[format] = new Function("return " + code.join('+'));
},

จะเห็นว่ามัน loop ไปตามแต่ละ character ที่อยู่ใน format string ที่เราส่งให้
จากนั้นก็ไปเอาไป lookup ตาราง formatCodes ที่เราเห็นข้างบน ได้ค่ามาก็จัดการ แปะให้กลายเป็น anonymous function
แล้วก็เก็บ cache ไว้ใน Date.formatFunctions

ลอง dump Date.formatFunctions มาดู
Date.formatFunctions["d/n/Y"]
function anonymous() { return String.leftPad(this.getDate(), 2, '0')+'/'+(this.getMonth() + 1)+'/'+this.getFullYear();
}


พอเห็น code แบบนี้แล้ว การเพิ่มวันที่ไทยเข้าไปเองก็ไม่ยากแล้ว

Related link from Roti

Sunday, August 08, 2010

export canvas to image

เมื่อวานกลุ่ม Creative Hacker ได้มีโอกาสไปนั่งคุยกันเรื่อง HTML5. ประเด็นหนึ่งที่ผมสนใจติดตามต่อ ก็คือเรื่องที่เราสามารถ export canvas ของเราออกมาเป็น Image ได้เลย

สิ่งที่สงสัยก็คือ, image ที่มัน export ออกมามันอยู่ในรูปแบบอะไร. กลับมาบ้านก็เลยใช้ chrome ช่วยเฉลย

$('#canvas')[0].toDataURL('image/png')

"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABkCAYAA..."

Update 2010-08-08 10:03

ใน firefox ถ้าต้องการให้ save ลงเป็น file เลยให้ทำดังนี้

var data = $('#canvas')[0].toDataURL('image/png');
data.replace('image/png', 'image/octet-stream');
document.location.href=data;

Related link from Roti

Monday, July 12, 2010

"new" operator in javascript

ช่วงนี้ได้มีโอกาสสอน javascript ก็เลยได้มีโอกาสทบทวนหลักการพื้นฐานของมัน
ปกติผมมักจะใช้อย่างเดียว ไม่เคยอ่าน spec หรือศึกษารากฐานของมัน (คิดว่าหลายๆคนก็คงเป็นเช่นนี้เหมือนกัน)

วันนี้จะพูดถึงประเด็นหนึ่งที่น่าสนใจก็คือ เรื่อง new operator

ลองดู code นี้ ที่แสดงการสร้าง object ใหม่ๆด้วย new operator
function Person(name) {
this.name = name;
}

p1 = new Person('pok');

โปรแกรมเมอร์ที่มาจากสาย OOP ทั้งหลายเห็น code นี้แล้วมักจะรู้สึกทะแม่งๆ ตรงที่ว่า
เหตุใด function ธรรมดา ถึงเอาไปสร้าง object ได้
(ใน javascript, function ที่ใช้นำไปสร้าง object เราเรียกมันว่า constructor)

สาเหตุที่เราสงสัยแบบนี้ ก็เพราะว่า paradigm ของเราผูกอยู่กับ โลก OOP แบบ class กับ instance
แต่ใน javascript นั้นโลกของมันไม่มี class, ทุกอย่างในโลกของ javascript คือ Object (มี object อยู่ type เดียวเท่านั้น)
สิ่งที่ต่างกันก็คือ constructor ที่ใช้สร้าง Object นั้นๆ
เช่น date object ก็เกิดจาก constructor ที่ชื่อ Date

เพื่อให้เข้าใจถึง concept constructor เราลองดูขั้นตอนภายในของ new operator บ้างว่า จริงๆแล้วมันทำงานอย่างไร
เริ่มด้วยการ inspect เจ้า p1 ที่สร้างจาก code ข้างบน



ทีนี้เราจะลองสร้าง p2 ที่มี structure เหมือนกับ p1 แต่ไม่ใช้ new operator ในการสร้าง
เริ่มด้วย new Object ธรรมดาขึ้นมาก่อน (อย่างที่บอก ใน javascirpt ทุกอย่างคือ Object ธรรมดาๆ)
p2 = new Object()

ลอง inspect ดู จะเห็นว่า มันคือ object เปล่าๆ ซึ่งมี __proto__ ชี้ไปยัง root object



สิ่งที่เราต้องทำก็คือ เปลี่ยน __proto__ reference ให้ชี้ไปยัง prototype ของ Person
(function ทุก function ใน javascript จะมี property ที่ชื่อ prototype เกิดขึ้นโดยอัตโนมัติ)
p2.__proto__ = Person.prototype;


สุดท้ายก็ทำการเรียกใช้ function Person โดยเปลี่ยน scope ของ this ให้กลายเป็น p2 object ของเรา
Person.apply(p2, ['pok']);


ลอง inspect p2 ดู จะเห็นว่าหน้าตามันเหมือน p1 ที่สร้างจาก new Person(...) แล้ว



Note: เราอาจเรียก function ที่เราใช้กับ new operator ว่า Template function ก็ได้
เพราะหน้าที่มันก็คือการ assign property ให้กับ object เปล่าๆที่ถูกสร้างขึ้นมาโดย new operator

Related link from Roti

Thursday, April 29, 2010

Good Designer

มีคนตั้งคำถามใน reddit (ดูแล้วก็เหมาะที่เราจะใช้ถามตัวเองด้วย)


เขาเป็นใคร และทำไมถึงถามคำถามนี้


I'm a developer, not a designer - I know my way around HTML and CSS. I also have a passing knowledge of Gimp and Inkscape. The technical stuff I don't have a problem with.

However, when I try to design a site myself the result is rather boring and minimalist. It's valid, accessible, SEO-friendly and so on, but it lacks originality and doesn't really stand out.


มีคำตอบมากมายหลายแบบ (แน่นอน คำถามมันปลายเปิดขนาดนั้น)

บางคนก็ว่า

A lot of design is obsessing over the tiniest details that have a larger impact on the overall "feel" of the design than you might have anticipated.


อันนี้ก็น่าสนใจ

I think if I could sum up effective web design today in a single word, it would be “subtlety”.
Most boring designs have no subtlety, because there isn’t really any detail there.
However, most really bad designs also have no subtlety, because everything is overdone.


(อะไรคือ subtlety?, ถ้าสงสัยแล้ว อย่าปล่อยให้มันลอยผ่านไปโดยไม่ค้นหา)



แล้ว Artistic หล่ะ

Artistic ability is a prerequisite to becoming a great graphic designer. That said, artistic ability is a skill.


skill คือ ทักษะ, ทักษะเกิดจากอะไร

it's practise, practise, practise like painting, because every project is different, every client is different and you can't afford to ever stop learning. In a roundabout way, for example, being a good photographer can be more important in design, than being good at photoshop.

It's about your ideas, ideas you can only get by having a massive arsenal of previous experience (personal is good, professional is better, you just need to have finished a lot of work). If you don't have this, you don't know what is possible and what you are capable of.


Related link from Roti

Thursday, April 22, 2010

Evolution of programming languages

อ่านบทความ Functional Logic Programming ใน Communications of the ACM แล้วชอบที่เขาเกริ่นลำดับพัฒนาการของ programming language

mnemonic instructions กับ symbolic labels ใน Assembly เกิดขึ้นเพื่อที่จะซ่อน macine code กับ address
arrays and expression ใน Fortran เกิดขึ้นเพื่อซ่อน registers
structured programming เกิดขึ้นเพื่อซ่อน goto กับ jump labels
encapsulate ใน OOP เกิดขึ้นเพื่อซ่อน representation of data

declarative language(เช่น functional programming) เกิดขึ้นเพื่อซ่อน ลำดับการทำงาน, assignment และ control statements

Related link from Roti

Wednesday, April 21, 2010

update เส้นทางขี่จักรยาน

เห็นเก่ง post เส้นทางขี่จักรยานแล้วฉุกใจคิด
เดิมคิดว่า ระยะทางจากบ้านไป opendream น่าจะประมาณ 5 กิโล
วันนี้ก็เลยลอง plot แผนที่ดู



สรุปว่าจากบ้านไป opendream = 10 KM
มิน่าวันนี้ทำไมรู้สึกเพลียๆ (อากาศมันร้อนด้วย)

ปล. เส้นสีฟ้าคือ ไปกลับ บ้าน-บริษัท Ingres
เส้นสีแดงคือ เส้นกลับถ้าใช้เสือหมอบ (ถ้าใช้เสื้อภูเขา กลับเส้นฟ้า)
เส้นสีเขียว คือ ไปกลับ บ้าน-opendream
ทั้งฟ้าและแดง ระยะทางเท่าๆกัน = 5 KM

Related link from Roti

Tuesday, March 23, 2010

ชอบแบบไหน


สองรูปข้างบนแสดงการต่อวงจรแบบเดียวกัน
คำถามคือ "แวบแรกที่เห็น คุณชอบแบบไหน?"


สมมติฐานของผมก็คือ จากคำตอบนั้นน่าจะคาดเดาได้ด้วยว่า คุณน่าจะชอบ programming language ตัวไหน.

Related link from Roti

Tuesday, March 16, 2010

วาดรูป-ฝึกสังเกต

ช่วงนี้ลูกๆปิดเทอม เลยได้ฤกษ์พากันไปเที่ยว แน่นอนว่าหน้าร้อนอย่างนี้ จะมีที่ไหนดีไปกว่าทะเลอีก ระหว่างทางต้องผ่านพิพิธภัณฑ์สัตว์น้ำหว้ากอด้วย แน่นอนว่าไม่แวะไม่ได้

ระหว่างเดินดูสัตว์น้ำทั้งหลาย ผมก็พยายามฝึกให้ลูกมองเห็นสิ่งเล็กๆน้อยๆต่างๆ โดยใช้วิธีตั้งคำถาม หรือให้เทียบเคียงกับตัวเอง เช่น พอเจอเจ้าปลากลับหัว ก็ชวนกันเดินด้วยมือแทนเท้า

ไปเที่ยวคราวนี้ลูกเริ่มโตขึ้น(5 ขวบ) ก็เลยเพิ่มกลยุทธ์การเรียนรู้ด้วยการวาดรูป
วิธีการก็คือ พ่อเป็นคนวาด ส่วนลูกทำหน้าที่บอกรายละเอียดว่าถูกต้องไหม นับจำนวนว่าครบไม่ครบ

อันนี้คือ เต่าทะเล กับ ปลาดาว ที่วาดกัน ลูกต้องนับขาปลาดาว กับนับจำนวนลวดลายของเต่า

From 7-14 bann grood,huahin

Related link from Roti

Thursday, January 28, 2010

code <- พิกัด -> diagram

วันก่อนนั่งแกะ code ของ Webmachine เห็น code ส่วนนี้น่าสนใจดี

อันนี้เป็น diagram แสดง flow decision ของ webmachine


ถ้าดูที่ขอบของ diagram เขาจะแสดงเลขพิกัดด้วย


ดู code ที่เขา implement จริง จะเห็นว่าเขาตั้งชื่อ function อ้างอิงเลขพิกัดและ version ของเอกสารด้วย
%% "Forbidden?"
decision(v3b7) ->
decision_test(resource_call(forbidden), true, 403, v3b6);
%% "Okay Content-* Headers?"
decision(v3b6) ->
decision_test(resource_call(valid_content_headers), true, v3b5, 501);
%% "Known Content-Type?"
decision(v3b5) ->
decision_test(resource_call(known_content_type), true, v3b4, 415);
%% "Req Entity Too Large?"
decision(v3b4) ->
decision_test(resource_call(valid_entity_length), true, v3b3, 413);
%% "OPTIONS?"
decision(v3b3) ->
case method() of
'OPTIONS' ->
Hdrs = resource_call(options),
respond(200, Hdrs);
_ ->
d(v3c3)
end;

Related link from Roti

Helloworld with Mochiweb

ใน erlang ถ้าคิดว่าจะใช้อะไรเป็น web server ดี ก็ต้องคิดถึง Yaws ก่อนเพื่อน
แต่ในระยะหลังเริ่มได้ยิน mochiweb และเห็นมี project ใหม่ๆที่นิยมใช้มากขึ้นเรื่อยๆ ก็เลยได้ฤกษ์ทำการทดสอบแบบง่ายๆ โดยจะ implement simple helloworld

เริ่มด้วยการ checkout source code ของ mochiweb ก่อน

svn checkout http://mochiweb.googlecode.com/svn/trunk/ mochiweb
cd mochiweb

จากนั้นก็สั่ง make เพื่อ compile code (แน่นอนว่าต้องติดตั้ง erlang รอไว้ก่อนแล้ว)

เริ่มต้นสร้างโปรเจค โดยสั่ง

escript /PATH_TO/mochiweb/scripts/new_mochiweb.erl helloworld

จะได้ directory หน้าตาประมาณนี้

./helloworld/
./helloworld/support/
run_tests.escript
include.mk
start.sh
start-dev.sh
./helloworld/src/
skel_web.erl
skel_sup.erl
skel_deps.erl
skel_app.erl
skel.hrl
skel.erl
skel.app
Makefile
./helloworld/priv/
./helloworld/priv/www/
index.html
Makefile
./helloworld/include/
./helloworld/doc/
./helloworld/deps/

file ที่เป็นหัวใจหลักก็คือ helloword_web.erl ทำหน้าที่เป็น process ที่ handle incoming request
ให้แก้ไข function loop ดังนี้
loop(Req, DocRoot) ->
"/" ++ Path = Req:get(path),
case Req:get(method) of
Method when Method =:= 'GET'; Method =:= 'HEAD' ->
case Path of
"helloworld" ->
Req:ok({"text/html", "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head><title >Welcome to mochiweb</title></head>
<body>
<h1>Helloworld</h1>
</body>
</html>"
});
_ ->
Req:serve_file(Path, DocRoot)
end;
'POST' ->
case Path of
_ ->
Req:not_found()
end;
_ ->
Req:respond({501, [], []})
end.


สั่ง run server โดยใช้คำสั่ง start-dev.sh
แล้วเรียก http://localhost:8000/helloworld

ถ้าเห็น crash report แบบนี้ อย่าตกใจ

=CRASH REPORT==== 28-Jan-2010::09:31:23 ===
crasher:
initial call: mochiweb_socket_server:acceptor_loop/1
pid: <0.57.0>
registered_name: []
exception error: bad argument
in function erlang:universaltime_to_localtime/1
called as erlang:universaltime_to_localtime({{1969,12,31},
{23,59,59}})

มันเป็น bug ใช้วิธี export TZ=GMT-7 แก้ปัญหาไปพลางๆก่อน

ทดลองแก้ไข code แล้วสั่ง refresh browser จะเห็นว่ามันไม่ reload ให้
ไม่ต้องเศร้าใจ ให้เปิด command prompt สั่ง make , ใน development mode, mochiweb จะตรวจดูว่า binary code เปลี่ยนหรือไม่ ถ้าเปลี่ยนมันจะ reload ให้เอง

ลองเปลี่ยน code ให้ รับ url /helloworld/ANYNAME แล้วแสดง message Hello ANYNAME ดูบ้าง
                case Path of
"helloworld/" ++ ID ->
Msg = io_lib:format("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
<html>
<head><title >Welcome to mochiweb</title></head>
<body>
Hello ~s
</body>
</html>"
, [ID]),
Req:ok({"text/html", Msg});

Related link from Roti

Tuesday, January 26, 2010

Git กับ Remote Branch

สรุปไว้กันลืม

สมมติว่าเรา clone git มาจาก Remote
โดย default เราจะได้ local branch ที่ชื่อ "master" ที่ track กับ remote branch ที่ชื่อ "master"

$ git branch
* master

แต่ถ้าเราสั่งให้ show branch ทั้งหมด

$ git branch -a
* master
origin/HEAD
origin/activity-calendar
origin/master
origin/pilot
origin/pilot_20100128
origin/user-admin

จะเห็นว่า git ทำการ fetch branch ทั้งหมดมาให้เรา เพียงแต่มัน map ขึ้นมาเป็น local branch ให้เราเฉพาะแค่ master

ถ้าเราสั่ง git fetch สิ่งที่เกิดก็คือ git จะทำการดึงข้อมูลการเปลี่ยนแปลงทุกอย่างของ server มาที่เครื่องเรา แต่จะยังไม่ update Working copy ให้เรา

ส่วน git pull ก็เทียบได้กับการสั่ง git fetch แล้วตามด้วย git merge โดยจะ merge จะเฉพาะ current HEAD ของเรา (ถ้า HEAD คือ master ก็จะ merge เฉพาะ master)

ที่นี้ถ้าเราต้องการ track remote branch บนเครื่องเรา สิ่งที่ต้องทำก็คือ

$ git checkout -b pilot origin/pilot
Branch pilot set up to track remote branch refs/remotes/origin/pilot.
Switched to a new branch "pilot"


ปัญหาที่เกิดบ่อยๆก็คือ เราสั่ง track remote branch โดยตั้งชื่อ local branch ไม่ตรงกับ remote branch เช่น

$ git checkout -b myb1 origin/b1
Branch myb1 set up to track remote branch refs/remotes/origin/b1.
Switched to a new branch "myb1"

เวลาเราแก้ไข file ใน branch myb1 เสร็จแล้วอยาก push ไปที่ server

$ vi MYFILE

$ git add MYFILE
$ git commit -m 'Add MYFILE'

$ git push
Everything up-to-date

จะเห็นว่ามันไม่ยอม push commit ใหม่ของเราไปที่ remote repository
ที่เป็นเช่นนี้เพราะถ้าเราสั่ง git push โดยไม่มี parameter สิ่งที่ git ทำก็คือ มันจะ push เฉพาะชื่อ branch ที่ตรงกันของทั้งสองฝั่ง

การแก้ไขก็คือ เปลี่ยนตัวแปร configuration push.defaut ให้เป็น tracking เพื่อที่จะให้มัน push เฉพาะ branch ที่มี tracking กับ remote branch โดยไม่สนใจเรื่องชื่อตรงกัน

$ git push
Everything up-to-date
pphetra@[~/t2]
$ git config push.default tracking
pphetra@[~/t2]
$ git push
Counting objects: 5, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 248 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To git@github.com:pphetra/test.git
7d96d34..0dd66fa myb1 -> b1


ที่นี้ถ้าเราต้องการสร้าง branch ใหม่บน remote หล่ะ
เริ่มจากการสร้าง branch บน server

$ git push origin origin:refs/heads/b4
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:pphetra/test.git
* [new branch] origin/HEAD -> b4
$ gitx

$ git branch -a
* b1
b2
master
myb1
remotes/origin/HEAD -> origin/master
remotes/origin/b1
remotes/origin/b2
remotes/origin/b4
remotes/origin/master

จากนั้นก็สั่ง checkout ตามปกติได้เลย

$ git checkout -b b4 origin/b4
Branch b4 set up to track remote branch b4 from origin.
Switched to a new branch 'b4'

Related link from Roti

Thursday, January 21, 2010

เล่น Lego

Lego เป็นของเล่นที่ดี แต่ราคาแพง สมัยเด็กๆจำได้ว่าอยากได้มาก ต้องรอจนมัธยมแล้ว แม่ถึงซื้อให้เล่น 1 ชุด โดยมีข้อแม้ว่าซื้อให้ ผมกับน้องสองคน ไม่ใช่ของใครคนใดคนหนึ่ง

พอโตขึ้นมามีลูกของตัวเอง ช่วงน้องปัณณ์อายุ 4 ขวบ อาเก่งกับอาหนึ่งซื้อ Lego ให้เป็นของขวัญ, ปรากฎว่าน้องปัณณ์ชอบของเล่นชิ้นนั้นมาก นั่นต่อ Lego ตามจินตนาการไปเรื่อย, หน้าที่พ่อก็เลยต้องคอยหา Input เข้าไปเรื่อยๆ เพื่อให้เกิดความท้าทายและไม่น่าเบื่อ (ตามกฎของ Flow)



ในกรณีของ Lego นี้, input มีอยู่สองสามแบบก็คือ
1. ซื้อชุดใหม่ให้เขา ให้เขาได้ตื่นเต้นกับแบบใหม่ๆ อันนี้ทำบ่อยๆไม่ดีแน่ เพราะราคามันสูงมากและถ้าได้มาง่ายไป มันจะไม่มีค่าเท่าไร
2. หา MOC (My Own Creation) ที่คนอื่นสร้างไว้มาให้เขาดู เขาจะได้เกิดแรงบันดาลใจ และเรียนรู้ว่ามันมีความเป็นไปได้ในการต่อเยอะแยะไปหมด
3. หาเนื้อหาใหม่ๆ ที่ทำให้เขาใช้ Lego เป็นเครื่องมือในการหมกมุ่นกับเนื้อหานั้นๆ เช่นช่วงวันเด็กผมพาไปปัณณ์ไปดูงาน Lego ที่เขาเอา Lego มาต่อเป็นเมืองจำลอง กลับบ้านมาก็เลยมีการทำฉากจำลองต่างๆกันบ้าง หรือแนะนำเกมส์ Spore ให้เขาเล่น ซึ่งพอเขาเล่น Lego เขาก็จะพยายามต่อเป็นสัตว์ประหลาดในเกมส์

อันนี้เป็นผลงาน MOC ของลูกชาย ที่ต่อเมื่อคืนนี้





ส่วนของพ่อได้ลำนี้ออกมา



หลังจากที่ลูกหมกมุ่นกับ Lego ได้เกือบ 1 ปี ตอนนี้ต่อมเก็บกดของพ่อเด็กเริ่มแตกแล้ว อันนี้เป็นสิ่งที่น่ากลัวมาก เพราะเราเป็นคนถือกระเป๋าเงิน, ลูกติด แต่มันไม่มีเงิน มันก็เลยซื้อไม่ได้ แต่ถ้าพ่อติด และพ่อมีเงิน อันนี้สิน่าหวาดเสียว

Related link from Roti

Tuesday, January 19, 2010

MultiMethod with Visitor Pattern

ต่อจากเมื่อวาน Link
อาจารย์โป้งได้อธิบายให้ฟังด้วยว่า วิธีหนึ่งที่เขานิยมใช้ implement multiple dispatch ใน Language ที่ support แต่ single dispatch ก็คือ Visitor Pattern (อาจารย์โป้งไม่ได้ใช้วิธีนี้)

ผมก็เลยเอาตัวอย่างเมื่อวานมาลองเขียนดู
เริ่มแรกก็คือ ให้เจ้า concrete animal ของเรามี accept method ที่รับ Visitor interface ก่อน
public interface Animal {
public void accept(IVisitor visitor);
}

public class Cat implements Animal {

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}

}

public class Dog implements Animal {

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}

}

จากนั้นก็ สร้าง Visitor Interface ที่มี method visit ครบทุก concrete animal ของเรา
public interface IVisitor {
public void visit(Cat cat);
public void visit(Dog dog);
}

ทดลอง feed อาหารโดยใช้ pattern ใหม่นี้
Cat cat = new Cat();
Dog dog = new Dog();

IVisitor bone = new IVisitor() {

@Override
public void visit(Cat cat) {
System.out.println("I don't like this. Meaw Meaw!!!");

}

@Override
public void visit(Dog dog) {
System.out.println("I 'm full. Hong Hong!!!");

}

};

IVisitor fish = new IVisitor() {

@Override
public void visit(Cat cat) {
System.out.println("I like this. Meaw Meaw!!!");

}

@Override
public void visit(Dog dog) {
System.out.println("Yes, I can eat this. Hong Hong!!!");

}

};

cat.accept(bone);
dog.accept(bone);

cat.accept(fish);
dog.accept(fish);

Related link from Roti