Tuesday, September 22, 2009

เมื่อวานเศร้ามาก อุตส่าห์นั่งปั่น code เพื่อเตรียม release แต่กลับพบว่า มันเปิดบน IE ไม่ได้ ช่วงหลังๆประมาทเพราะมีแต่ minor changed ทั้งนั้น เลยไม่ได้ทดสอบบน IE
แต่ไม่เป็นไร git มีเครื่องมือที่เรียกว่า 'bisect' สำหรับหาว่า commit ไหนเป็นตัวปัญหา

การใช้งานเริ่มด้วยคำสั่ง

$ git bisect start

จากนั้นก็ mark ว่า revision ที่กำลังทำงานอยู่มันไม่ดี

$ git bisect bad

บอกมันด้วยว่า revision ไหนที่มันยังดีอยู่

$ git bisect good

git ก็จะจัดการ checkout revision ที่อยู่ตรงกลางระหว่าง bad กับ good ให้
เราก็แค่บอกมันว่า revision ปัจจุบันที่ใช้งานอยู่มัน bad หรือ มัน good
กรณีของผม ผมทดสอบแล้วยัง error อยู่ก็เลยสั่ง

$ git bisect bad
Bisecting: 10 revisions left to test after this
[2c6383f3664f50b2121ba5d455d595a60c3aa85c] prototype for flex chart component.

ทำไปเรื่อยๆ จนกว่าจะจับ commit ที่มีปัญหาได้ ซึ่งมันจะฟ้องว่า

$ git bisect good
4bc966d79eb3cd7ec10672863b6c97b26f401ea7 is first bad commit
commit 4bc966d79eb3cd7ec10672863b6c97b26f401ea7
Author: pphetra
Date: Thu Aug 6 05:58:20 2009 +0000

หลังจากได้ revision ที่เป็นปัญหา ก็เป็นหน้าที่เราแล้วที่ต้องไปไล่ดู diff file ว่าอะไรที่ทำให้มันพัง

ก่อนจะออกไปก็ให้ clear สถานะ git bisect ด้วยคำสั่ง

git bisect reset


Note: กรณีที่มี Makefile ให้ run จาก command line, เราสามารถใช้คำสั่ง git bisect run ได้เลย

Related link from Roti

Monday, July 20, 2009

ทำ DSL ด้วย parser combinator

ช่วงนี้ทำระบบ billing ด้วย Grails อยู่ มันมีโจทย์ว่า user ต้องสามารถกำหนด policy การคิดเงินค่าโทรศัทพ์ได้เอง โดยรูปแบบของการคิดเงินถ้าเขียนออกมาเป็น text ก็ประมาณนี้
ถ้าเวลาที่โทรอยู่ในช่วงหนึ่งนาทีแรก ให้คิดเงินเต็มหนึ่งนาที ส่วนเวลาที่เกินให้ปัดที่หน่วยละ 6 วินาที โดยคิดหน่วยละ 10 % ของราคาต่อนาที

version แรกสุดที่ผมทำ หน้าตาออกมาประมาณนี้
[minimum: 60, nextCharge: 6, rateFunc: { x -> x * rate * 0.1}]

ไม่ต้องบอกก็รู้ตัวว่า ไม่มี user ที่ไหน config ได้แน่ มี closure หน้าตาประหลาดโผล่มาแบบนี้ (จริงๆแล้วมันคือ code ของ groovy ที่พร้อมจะ run นั่นเอง)

version ที่สอง ผมก็เลยปรับให้มันกระเดียดไปทางภาษาคนมากขึ้น หน้าตาออกมาแบบนี้
minimum 60 sec , nextcharge 6 sec : 10 %
หรือจะใช้หน่วย minute ด้วยก็ได้
minimum 1 min, nextcharge 6 sec : 10 %

เมื่อความต้องการเป็นแบบนี้ ก็ต้องหาวิธี implement, วิธีที่คุ้นเคยมากสุดก็คือใช้ Antlr เขียน Parser แต่บังเอิญเป็นคนขี้เบื่อ ก็เลยมองหาวิธีอื่นแทน ช่วงนี้ Scala กำลังมาแรง ประกอบกับเคยเห็นว่ามันทำ Parser combinator ได้ด้วย ก็เลยหวยออกที่ Scala + Parser Combinator

เริ่มแรกสุด parser ของเรา extends จาก StandardTokenParsers (มากับ Core ของ Scala อยู่แล้ว ไม่ใช่ external library)

class RateParser extends StandardTokenParsers {

}

กำหนด delimiters และ reserved word

lexical.delimiters ++= List(":", "%", ",")
lexical.reserved += ("minimum", "nextcharge", "min", "minute", "sec", "second", "m", "s")

จากนั้นก็ define grammar
ถ้าดู code จะเห็นว่าเรา define parser ย่อยๆเต็มไปหมด อันนี้แหล่ะคือความหมายของ combinator นั่นคือเราเขียน parser ใหญ่ๆ ด้วยการการเอา parser เล็กๆย่อยๆมาประกอบกันนั่นเอง
  def rule: Parser[RateStrategy] = minimum ~ nextcharge ~ percent ^^
{ case (min ~ next) ~ percent => new RateStrategy(min, next, percent)}

def minimum: Parser[Int] = "minimum" ~> unit <~ ","

def nextcharge: Parser[Int] = "nextcharge" ~> unit <~ ":"

def percent: Parser[Int] = numericLit <~ "%" ^^ (_.toInt)

def unit: Parser[Int] = minuteUnit | secondUnit

def minuteUnit: Parser[Int] = numericLit <~ ("min" | "minute" | "m") ^^ (_.toInt * 60)

def secondUnit: Parser[Int] = numericLit <~ ("sec" | "second" | "s") ^^ (_.toInt)

จะเห็นว่ามี operator หน้าตาแปลกๆ เต็มไปหมด ไม่ต้องตกใจ มาลองดูแบบง่ายสุดก่อน เริ่มที่การ parse หน่วยเวลากันก่อน
จากโจทย์ของเขา จะเห็นว่าเราจะทำการ parse พวก "1 min", "60 sec"
กรณี minute จะเห็นว่า code หน้าตาแบบนี้
  def minuteUnit: Parser[Int] = numericLit <~ ("min" | "minute" | "m") ^^ (_.toInt * 60)

def minuteUnit: Parser[Int]
ก็คือการ define method ที่ return parser ที่ return Integer (Higher order function)
numericLit <~ ("min" | "minute" | "m")
ก็คือ ระบุว่า ประโยคจะเริ่มต้นด้วย integer จากนั้นจะตามด้วย "min" หรือ "minute" หรือ "m"
เครื่องหมาย "<~" เป็น operator ที่ extend มาจาก operator "~"
operator "~" มีความหมายว่า ถ้า parse argument ทางซ้ายสำเร็จ ก็ให้ทำ ทางขวาต่อ (chain)
แต่ถ้าไม่สำเร็จ ก็ abort
ส่วน "<~" เป็นการเพิ่มความหมายว่า argument ท่ีอยู่ทางซ้ายถือเป็นตัวที่เราสนใจ ให้ ignore argument ที่อยู่ด้านขวาไปได้เลย
Note: แน่นอน เมื่อมี "<~" ก็เลยมี "~>" ด้วย
^^ (_.toInt * 60)
เราเรียก transforms operator นั่นคือ ในกรณีนี้แทนที่จะ return String ตัวเลขนาทีไป เราจะเปลี่ยนให้มันเป็น integer ก่อน

ความยุ่งยากอันถัดไปก็คือ ต้องให้มันเรียกใช้จาก Groovy ได้ (โปรเจคหลักเป็น Groovy)
โชคดีที่ Class ที่ Scala compile ออกมามันหน้าตาดีมาก ไม่มีการแปลงชื่อหรือเปลี่ยนรูปมาก
ทำให้เราสามารถเรียกใช้ได้ตรงๆ
groovy:000> f = RateStrategyParser.parse("minimum 1 min , nextcharge 6 sec : 10%")    
===> RateStrategy@34a02677
groovy:000> f
===> RateStrategy@34a02677
groovy:000> f.calc(24.00, 72)
===> 28.80

Related link from Roti

Friday, June 26, 2009

Dreyfus model

เมื่อวานได้มีโอกาสนั่งดู presentation หัวข้อ "Developing Expertise: Herding Racehorses, Racing Sheep" ของ Dave Thomas. ฟังแล้วประทับใจมากทั้งขำหัวเราะจนท้องแข็ง และปิ๊งกับเนื้อหาที่มีการใช้ metaphor เรื่องเด็กสองขวบทำให้ผม(ซึ่งมีลูกเล็กๆ 2 ขวบ) เห็นภาพชัดเจน

Theme ใน presentation ของ Dave(ที่ไม่ใช่ program dave ในหนังสือ ruby on rails ที่มีคนแปลมั่วๆไว้) กล่าวอ้างถึง Dreyfus model of skill acquisition ซึ่ง Stuart และ Hubert Dreyfus เสนอไว้เมื่อปี 1980 ว่า ในการเรียนรู้ทักษะใดๆก็ตาม มันมี 5 level ที่เราต้องผ่าน
  • Novice
  • Advance Beginner
  • Competent
  • Proficient
  • Expert
ที่น่าสนใจก็คือ Dave claim ว่าใน domain ใดๆก็ตาม พวกที่มีมากสุดก็คือ level 2 Advance Beginner, คำถามที่น่าสนใจก็คือ ทำไม? ทำไมคนส่วนใหญ่ถึงไปติดค้างอยู่ที่ระดับนั้น. บางคนก็เสนอขึ้นมาว่า เพราะเขาคิดไปเองน่ะสิว่า ระดับที่เขาอยู่นั้นเป็นระดับ 4. อันนี้ตรงกับกฎข้อที่ 1 ของ Dunning-Kruger effect ที่ว่า
Incompetent individuals tend to overestimate their own level of skill.
บางคนก็เสนอว่า ในการที่จะเลื่อนจากระดับ 2 ไประดับ 3 ได้นั้น เขาต้องการ mentor แต่จำนวนคนในระดับ 3,4,5 ไม่พอที่จะเป็น mentor ให้กับทุกคนในระดับ 2 (การเป็น mentor นั้นต้องอาศัยส่วนผสมหลายอย่างที่ลงตัว ไม่ใช่อยากจะเป็นก็เป็นได้)

Dave เขาถามผู้ฟังว่าอะไรคือความแตกต่างระหว่าง level 2 กับ level 3. ความแตกต่างที่เห็นชัดสุดก็คือ "Dependent" คนที่อยู่ระดับที่ 3 สามารถตัดสินใจได้ด้วยตัวเอง ต่างกับระดับที่ 2 ที่ต้องการให้มีคนบอกว่า ต้องทำอะไรบ้าง. สิ่งที่ตามมากับ Dependent ก็คือ "Risk". ฟังถึงตรงนี้แล้วตรงใจมาก คนส่วนใหญ่ไม่พร้อมจะเสี่ยง ทุกคนอยากอยู่ใน safety zone หรือ comfort zone

quote ที่ผมชอบสุดก็คือ ประโยคนี้
don't never ever let the expert choose your next architecture because they will choose the components that they are curious to see if it work.
ปล. 1 ในหนังสือ Pragmatic Thinking and Learning: Refactor Your Wetware มีบทที่ว่าด้วย Dreyfus Model อยู่บนหนึ่ง มีให้อ่าน free อยู่ครึ่งบทด้วย ใครสนใจไปตามอ่านได้ครับ Link (ผมอ่านแล้ว แล้วก็ลืมหมดแล้ว จนมาฟัง presentation นี้ก็เลยปิ๊งขึ้นมา)

ปล 2. ขอแสดงความเสียใจกับ Roti และ Opengis ที่ Opendream อนุญาติให้ผมเลือก components ตามใจชอบ (ตอนนี้พยายามยัด erlang ลงไปใน architecture อยู่)


Related link from Roti

Thursday, June 25, 2009

ไก่กับไข่ใน python OOP

วันนี้นั่งทำความเข้าใจกับ OOP ใน python
เนื่องจากไปเห็นว่า BaseModel ของ django มัน extend type
ก็เลยสงสัยว่าอะไรคือ object อะไรคือ type


# In Python, the __class__ attribute points to the type of an object

In [1]: object.__class__
Out[1]: <type 'type'>
# object มี type เป็น type

In [2]: type.__class__
Out[2]: <type 'type'>
# recursive structure นิ

#__bases__ attribute points to a tuple containing supertypes of an object
In [3]: object.__bases__
Out[3]: ()

In [4]: type.__bases__
Out[4]: (<type 'object'>,)
# เฮ้ยทำไม base ของ type เป็น object หล่ะ
# เจอปัญหาไก่กับไข่แล้ว

In [5]: isinstance(object, object)
Out[5]: True

In [6]: isinstance(type, object)
Out[6]: True


สรุปได้ว่า
  • <type 'object'> เป็น instance ของ <type 'type'>
  • <type 'object'> เป็น subtype ของ no object.
  • <type 'type'> เป็น instance ของตัวเอง.
  • <type 'type'> เป็น subtype ของ <type 'object'>.
<type 'type'> ก็คือ Metaclass ที่ดันเป็น subtype ของ instance ของตัวเอง

Related link from Roti

Friday, May 15, 2009

ย้าย commit ใน git

ปัญหาก็คือ branch ของผมมีหน้าตาเป็นแบบนี้

A deploy
/
D---E---F---G master

แต่ผมอยากย้าย commit G ไปอยู่ใน branch deploy
ให้มีหน้าตาแบบนี้

G--A deploy
/
D---E---F master

หลังจากนั่งทดลองอยู่นาน ก็พบว่า คำตอบนั้นง่ายนิดเดียว

git checkout master
git reset --hard HEAD^

Related link from Roti

Tuesday, April 21, 2009

pre-amp

หลวมตัวซื้อ mac มาใช้ ก็พบว่ามีหลายอย่างที่มันไม่ยอมทำให้เหมือนคนอื่น
เช่น mic input ของมันก็รับ input ที่ระดับ line-level แทนที่จะเป็น microphone-level
ซึ่งความแตกต่างของมันก็คือ ระดับของสัญญาณ
line-level มี voltage peek-to-peek ที่ระดับ millivolt
ส่วน mic-level มี voltage peek-to-peek ที่ระดับ volt

ครั้งจะไปหาซื้อ mic ของ apple มาใช้ ก็ดูเป็นการลงทุนที่ไม่คุ้มค่ายิ่งนัก
ต่อวงจรขยายเองน่าจะคุ้มกว่า
หลังจากเปิด google ไปสักพัก ก็พบวงจรนี้

เมื่อได้วงจรมา ก็ดองไว้อีกอาทิตย์กว่าๆ ก็มีอัศวินม้าขาวมาช่วยต่อวงจรให้
(อัศวินม้าขาวก็คือ "กอบ"-เพื่อนร่วมงานผม ผู้เชี่ยวชาญทั้งด้าน eclipse RCP + SWT และด้าน hardware)

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



Related link from Roti

Monday, April 06, 2009

สรุปหนังสือจากงานหนังสือ 52

พอแก่แล้วก็เริ่มสนใจประวัติศาสตร์ ชุดนี้น่าสนใจตรงขอบเขตที่พยายามจะมองภาพรวมของภูมิภาค
  • เอเซียตะวันออกเฉียงใต้ในยุคการค้า ค.ศ. 1450-1680 เล่ม 1 ดินแดนใต้ลม (แอนโทนี รีด)
  • เอเซียตะวันออกเฉียงใต้ในยุคการค้า ค.ศ. 1450-1680 เล่ม 2 การขยายตัวและวิกฤติการณ์
เล่มนี้ของอ.เจตนา นาควัชระ  ผมสนใจหัวข้อ "สถาปัตยกรรมร่วมสมัยกับความคาดหวังของคนนอก"
  • เก่ากับใหม่ อะไรดี มนุษยศาสตร์ไทยในกระแสของความเปลี่ยนแปลง (เจตนา นาควัชระ)
สองเล่มนี้ พูดถึงประเด็นที่ไกล้เคียงกัน เล่มแรกผมสนใจการทำงานของจิตในเรื่องการคิด,  ส่วนเล่มสองเขาพูดประเด็นที่บอกว่า ระหว่าง trigger กับ action มันมี freedom อยู่, เล่มนี้ใช้แนวเขียนแบบ Story 
  • ทลายกับดักความคิด - Conceptual Blockbusting : A Guide to Better Ideas
  • พลิกคำถามเปลี่ยนชีวิต - Change Your Questions Change Your Life
เล่มนี้เป็นวรรณกรรมสำหรับเด็ก เป็นเรื่องของเด็กชาวเล (Note: ดูสิว่าจะอ่านแล้วรู้สึกทะแม่งเหมือนเรื่อง กะทิ หรือเปล่า, เรื่องกะทิกับผมมีปัญหากันตรงที่ ผมรู้สึกว่ามัน Fake) เล่มนี้กะว่าจะอ่านให้ลูกฟังก่อนนอน
  • บีตั๊ก ดาวดวงนั้นระหว่างน้ำกับฟ้า
ส่วนการ์ตูนเล่มนี้ คนเขียนเป็นคนมาเลเซีย ผมเคยเห็นลายเส้นของเขามานานแล้ว พอมีคนแปล ก็เลยตัดสินใจหยิบโดยไม่ลังเล
  • เด็กน้อยจากหมู่บ้าน The Kampung Boy ของ Lat.
ส่วนของสำนักพิมพ์มติชน คงไม่ต้องอธิบายมาก
  • เศรษฐศาสตร์แห่งชีวิต The Logic of Life
  • ประวัติศาสตร์โลกผ่านเกลือ Salt : A World History
  • ปฎิบัติการล่าไอคิว The Know-It-All
ของลูกชายได้มาสิบกว่าเล่ม เล่มที่ลูกชอบมากสุดคือเล่มนี้ เล่าตั้งแต่กำเนิดโลกยันมนุษย์ถ้ำ
  • โลกยุคก่อนประวัติศาสตร์ Prehistoric world 

Related link from Roti

Thursday, March 19, 2009

excluding transitive dependency in maven

ใครที่เคยใช้ maven คงจะรู้ดีกว่า มันช่วยลดอาการปวดหัวในการจัดการกับ jar file ได้ระดับหนึ่ง เพราะมันช่วยเราจัดการพวก transitive dependency ให้เราได้ (แต่ไปปวดหัวเรื่อง setup มันแทน)

แต่การที่มัน solve transitive dependency ให้ก็นำมาซึ่งปัญหาอันใหม่ นั่นก็คือ version conflict, สมมติว่า project เรามี dependency ถึง library Foo กับ library Bar. ทั้ง Foo และ Bar บังเอิญมี dependency ซึ่ง Library Z เหมือนๆกัน แต่ดันเป็นคนละ version. เมื่อเรา build project, เราก็จะมี library Z เข้ามาใน class path เราทั้งสอง version, ทำให้เกิด error พวก missing method หรือ missing class ได้
(Note: ที่ผมเจอ ผมเจอปัญหานี้ตอน mvn eclipse:eclipse, แต่จากการทดสอบด้วยคำสั่ง mvn dependency:resolve ปรากฎว่า มันได้ผลลัพท์ไม่เหมือนกัน เจ้า dependency:resolve ดูเหมือนจะจัดการได้ถูกต้อง)

วิธีแก้ไขก็คือ การระบุ exclusion ลงใน pom.xml ของ maven
จากของเดิม
<dependency>
<groupId>my.pphetra</groupId>
<artifactId>foo</artifactId>
<version>1.0</version>
</dependency>

<dependency>
<groupId>my.pphetra</groupId>
<artifactId>bar</artifactId>
<version>1.0</version>
</dependency>

ไปเป็น
<dependency>
<groupId>my.pphetra</groupId>
<artifactId>foo</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>test</groupId>
<artifactId>Z</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>my.pphetra</groupId>
<artifactId>bar</artifactId>
<version>1.0</version>
</dependency>


ทางแก้ดูแล้วง่ายดาย แต่จริงๆแล้ว มันไปยุ่งตอนที่หาว่า library ตัวไหนที่เราควรจะไปใส่ excludes ให้มัน
วิธีการก็คือ ตอนที่สั่ง mvn command (กรณีของผมก็คือ eclipse:eclipse) ให้ใส่ -X ลงไปด้วย
จากนั้นก็นั่งไล่หาว่า เจ้าตัวไหนคือตัวปัญหา (คำเตือน วิธีนี้มีผลข้างเคียงก็คือ ทำให้เกิดอาการง่วงนอน)

Related link from Roti

Wednesday, February 04, 2009

การใช้งาน git กับ host ที่ไม่ support git

วันก่อนเจอคำถามถึงว่า ต้องการทำ version control กับ web application project
โดยทำกับ directory ที่ live อยู่บน host ที่ไม่ support git จะมีวิธีไหนทำได้บ้าง?

วิธีแรกก็คือใช้ sshfs ในการ mount remote file system แล้วก็สร้าง git repository บน live directory นั้นเลย

sshfs pune.ingres.co.th:/srv/www/htdocs/test /mnt -o workaround=rename
cd /mnt
git init
git add .
git commit -m 'init project'

Note: workaround=rename เป็นการหลีกเลี่ยง bug ของ git กรณีที่ทำงานกับ sshfs

วิธีที่สองก็คือ หลีกเลี่ยงการเก็บ ตัว repository (directory .git) ไว้บน server โดยการ สร้าง repository ไว้บน local ของเรา
จากนั้นก็ใช้ environment variable GIT_DIR กับ GIT_WORK_TREE ในการ config directory ที่ถูกต้อง

sshfs pune.ingres.co.th:/srv/www/htdocs/test /mnt
cd /mnt
export GIT_DIR=/mylocalgit
export GIT_WORK_TREE=/mnt
git init
git add .
git commit -m 'init project'


วิธีที่สามก็คือ แยก repository และ working tree ที่เราทำงาน ออกจาก live directory
แล้วใช้ technic reset --hard ในการ sync ข้อมูลแทน

#สร้าง git repository

mkdir myproject
cd myproject
git init
...
git commit -m 'my project'

#เมื่อต้องการ sync project กับ remote file บน host

sshfs pune.ingres.co.th:/srv/www/htdocs/test /mnt
cd /mnt
GIT_DIR=/myproject/.git git --work-tree=. reset --hard


วิธีที่ 1-3 workflow ไม่ค่อยเป็นทางการเท่าไร ถ้าต้องการ workflow ดูดีๆหน่อย ก็ต้องประมาณนี้

auto <<sshfs mount dir>>
+-------------+ update +==============+
! bare repo. !------->! working tree !
+-------------+ +--------------+
! ^
pull ! ! pull
v !
+--------------+
! Developer !
! working tree !
+--------------+

เริ่มด้วยการสร้าง bare repository สำหรับเก็บข้อมูลที่จะ publish ก่อน

GIT_DIR=mygit git init

สร้าง hook script ที่ชื่อ post-receive
#!/bin/sh 
WEBROOT_DIR=/srv/www/htdocs/test
GIT_WORK_TREE=${GIT_WORK_TREE-$WEBROOT_DIR}
if [ "$GIT_DIR" = "." ]; then
GIT_DIR=`pwd`
fi
while read oldrev newrev ref ; do
if [ "$ref" = "refs/heads/master" ]; then
echo "Updating $GIT_WORK_TREE"
echo "Using $ref, now at $newrev"
if [ ! -d "$GIT_WORK_TREE" ]; then
mkdir "$GIT_WORK_TREE"
fi
cd $GIT_WORK_TREE
git --work-tree=$GIT_WORK_TREE reset --hard $ref
fi
done

Note: ตัว script ลอกมาจาก presentation ของ Jon Loeliger

จากนั้นในฝั่ง working copy ของเรา ก็ระบุ remote branch ไว้ด้วย
เมื่อไรก็ตามที่ต้องการ publish content ก็ให้ push ไปที่ remote branch นั้น

mkdir myproject
cd myproject
git init
git add .
git commit -m 'bla bla'

# add bare repo to remote list
git remote add origin /path/to/bare/repo

# เมื่อเราสั่ง push
# directory ที่กำหนดไว้ใน WEBROOT_DIR ก็จะ update ให้โดยอัตโนมัติ

Related link from Roti

Thursday, January 22, 2009

cluster by

Tom MoerTel เขียน function มหัศจรรย์ไว้ใน post ClusterBy: a handy little function for the toolbox
function มีหน้าตาแบบนี้
import Control.Arrow ((&&&))
import qualified Data.Map as M

clusterBy :: Ord b => (a -> b) -> [a] -> [[a]]
clusterBy f = M.elems . M.map reverse . M.fromListWith (++)
. map (f &&& return)

แล้วก็ทำแบบนี้ได้
*Main> let antwords = words "the tan ant gets some fat"
*Main> clusterBy length antwords
[["the","tan","ant","fat"],["gets","some"]]
*Main> clusterBy head antwords
[["ant"],["fat"],["gets"],["some"],["the","tan"]]
*Main> clusterBy last antwords
[["the","some"],["tan"],["gets"],["ant","fat"]]

ตัว function กระทัดรัดมาก แต่อ่านแล้วไม่เข้าใจเลย ก็เลยต้องออกแรงนั่งแกะหน่อย
เริ่มแรกสุด มันจะทำแบบนี้ก่อน
map (f &&& return) ["the", "tan", "ant", "gets", "some", "fat"]

เจ้า &&& มีชื่อเรียกว่า fanout ลองดูการทำงานมัน (เพราะอธิบายยากมาก)
*Main> (head &&& last) "abc"
('a','c')
*Main> (head &&& id) "abc"
('a',"abc")

จะเห็นว่ามัน apply function แรกกับ function ที่ 2 เข้ากับ parameter ผลลัพท์ที่ได้ก็จับมาเข้าคู่เป็น tuple ไว้ (หรือจะเรียกว่า pair ก็ได้ เพราะมีแค่ 2 elements)
Note: ถ้าใครยังงงๆกับ fanout ลองดู diagram ที่ Daniel Lyons เขียนไว้ใน Haskell Arrows

ที่นี้มาลองดู return บ้าง การที่เราสั่ง return "abc" ผลลัพท์ของมันก็คือ Monad "abc"
(ถ้ายังไม่รู้จัก monad ก็ให้สมมติไปก่อนว่า monad ก็คือ container structure แบบหนึ่ง)
ดังนั้นผลลัพท์ของ

map (head &&& return) ["the", "tan", "ant", "gets", "some", "fat"]
ก็จะได้
[("t", Monad "the"), ("t", Monad "tan"), ...]


การทำงานลำดับถัดมาก็คือ M.fromListWith (++) หรือถ้าเขียนเต็มๆก็คือ Data.Map.fromListWith (++)
เจ้า fromListWith เป็น function ที่ไว้ใช้สร้าง Dictionary(Map) structure
โดยมันจะรับ array ของ pair (ในทีนี้ก็คือ [("t", M "the"), ("t", M "tan"), ...]) แล้วสร้างเป็น Map structure โดยใช้ element แรกของ pair เป็น key ส่วน element ที่สองก็เป็น value ไป
แต่เจ้า fromListWith มีความพิเศษตรงที่ว่า กรณีที่มันพบว่า first element นั้นซ้ำกับที่เคยมีอยู่แล้ว
มันก็จะ apply function ที่เราส่งเข้าไป (ในที่นี้ก็คือ (++)) ให้กับ value ทั้งสอง
ลองดูตัวอย่าง
fromListWith (++) [("a", "ant"), ("b", "bat"), ("a", "axe")]
จะได้ผลลัพท์เป็น
Map ที่มี 2 entry
entry แรกมี key เป็น "a" และ value เป็น "axeant"
ส่วน entry ที่สอง มี key เป็น "b" และ value เป็น "bat"

กรณีของเรา element ที่สองมันมี type เป็น Monad
เจ้า function (++) เมื่อเจอกับ Monad มันจะมีพฤติกรรมเป็นแบบนี้แทน
*Main> [1] ++ [2]
[1,2]
*Main> (return 1) ++ (return 2)
[1,2]
*Main> (return "ant") ++ (return "axe")
["ant","axe"]

Note: ดูเหมือนว่าใน haskell่, Array กับ Monad มีความสัมพันธ์แน่นแฟ้น

คำสั่งลำดับถัดไปก็คือ M.map reverse
อันนี้ไม่มีอะไรแล้ว แค่ reverse ค่า value ทุกตัวใน Map

สุดท้ายก็คือ elems ก็คือ กรองเอาแต่ค่า value ใน Map

Related link from Roti

Monday, January 05, 2009

เวลาหายไปไหน

ยิ่งแก่ก็ยิ่งฟุ้งซ่านอยากทำนู่นอยากทำนี่เต็มไปหมด แต่เวลาก็มีนิดเดียว
ปีใหม่นี้ก็เลยหาเครื่องมือมาช่วยจัดการเวลา ได้ solution ลงตัวที่ klok
กับ chandler

เจ้า chandler นี่เอามาใช้จัดการ GTD ส่วน klok นี่เอามาช่วยคุม flow(ทำงานให้ต่อเนื่องไม่ switch ไปมา) กับ balance เวลาที่จัดสรรให้กับแต่ละ project

Related link from Roti

Sunday, January 04, 2009

ปากกาแท่งใหม่

ปีใหม่นี้ผมให้ของขวัญตัวเองเป็นปากกาหมึกซึมยี่ห้อ LAMY รุ่น safari ราคา 700 กว่าบาท, ซึ่งแท่งเก่าที่มีก็ใช้ยี่ห้อนี้อยู่แล้ว ใช้มาเกือบ 10 ปี หัวปากกามันหลวมใช้ไม่ค่อยดีเสียแล้ว

requirement ในการเลือกปากกาของผมก็คือ ผมใช้วาดรูปหรือ sketch เป็นหลัก เรื่องเขียนตัวหนังสือเป็นเรื่องเป็นราวนี่น้อยมาก ก็เลยเลือกขนาดของหัวเป็น medium (M)

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

เมื่อสัปดาห์ก่อนผมได้อ่านหนังสือ Pragmatic Thinking and Learning ซึ่งเขาก็ชักจูงให้เห็นความสำคัญของการจดบันทึก ก็เลยเกิดอาการกลับตัวกลับใจ รื้อฟื้นสมุดโน้ตขึ้นมาใช้อีกครั้ง ส่งผลให้มีสาเหตุที่ต้องซื้อปากกาแท่งใหม่ ด้วยความเชื่อที่ว่า การมีปากกาที่ดีจะทำให้เราสนุกกับการเขียน (เหตุผลเดียวกับการใช้ mac)

Related link from Roti