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