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

Monday, January 18, 2010

MultiMethod

เมื่อวันเสาร์ไป compkucamp มา เจออาจารย์โป้งเข้าก็เลยถามว่า ช่วงนี้ทำอะไรอยู่, อาจารย์โป้งก็ตอบว่ากำลัง implement multimethod บน c++ อยู่ ว่าแล้วก็ควักโน๊ตบุ๊คออกมาแสดง อาจารย์โป้งใช้ software ได้ผสมปนเปมาก เริ่มจากเปิด microsoft visual c++ ขึ้นมา จากนั้นก็ switch ไปใช้ terminal บน mac เพื่อใช้ django generate project files จากนั้นก็ show file content ให้ดูโดยใช้ textmate

หลังจากกลับมาบ้าน และส่งลูกเข้านอนหมดแล้ว เพื่อแก้ข้อสงสัยที่ฟังมา ก็เลยต้องเข้า wikipedia ไปหาอ่านเรื่อง Multimethod หรือ Multiple Dispatch บ้าง

ในการทำความเข้าใจกับเรื่องนี้ เราก็ควรจะเริ่มจาก basic สุดก่อนก็คือ Single Dispatch ซึ่งใช้ใน Java, C++, Smalltalk, Objective-C

ลองดู code นี้
public abstract class Animal {

public abstract void feed(Food food);

}

public abstract class Food {

}

public class Fish extends Food {

}

public class Bone extends Food {

}

public class Dog extends Animal {

@Override
public void feed(Food food) {
System.out.println("I 'm full. Hong Hong!!!");
}

}

public class Cat extends Animal {

@Override
public void feed(Food food) {
System.out.println("I 'm full. Meaw Meaw!!!");
}

public void feed(Bone food) {
System.out.println("I don't like this. Meaw Meaw!!!");
}

}



ถ้าลอง run แบบนี้ดู
public class Runme {

public static void main(String[] args) {
Animal a = new Dog();
Animal b = new Cat();

Bone bone = new Bone();
a.feed(bone);
b.feed(bone);

}

}


กรณีที่เป็น single dispatch ผลลัพท์ที่ได้ก็คือ
I 'm full. Hong Hong!!!
I 'm full. Meaw Meaw!!!

จะเห็นว่า single dispatch จะตัดสินใจเลือก method โดยดูแค่ว่าจะเลือกให้ class ไหนรับผิดชอบในการ handle การ call, โดยไม่ได้สนใจ type ของ arguments
ส่วน multiple dispatch มันจะเลือก method โดยดู type ของ arguments ด้วย
ถ้าทดลองนำ code ข้างบน ไป run ใน groovy ซึ่งเป็น multiple dispatch ผลลัพท์ทีได้ก็คือ
I 'm full. Hong Hong!!!
I don't like this. Meaw Meaw!!!

Related link from Roti

Thursday, January 14, 2010

เรียนสีน้ำ

เมื่อวานที่ Opendream มีการจัดกิจกรรมเรียนวาดภาพสีน้ำครั้งแรก โดยมีน้องแพ็ครับหน้าที่เป็นผู้สอน, course แรกเริ่มด้วยการแจกรูปลายเส้นขนมเค็ก พร้อมกับให้ดูภาพจริงบน computer จากนั้นก็สั่งสั้นๆว่า "ระบาย"

ผู้เรียนทั้งหลายก็ก้มหน้าก้มตาระบาย (บางคนก็ประทัวงบ้างว่า ยังไม่สอนเลย จะระบายได้อย่างไร)



ผมชอบวิธีเรียนแบบนี้นะ ทักษะการระบายสีคือการปฎิบัติ อยากรู้ก็ต้องระบาย จะมามัวเรียนว่าจับพู่กันอย่างไร, หรือเรียนรู้ basic introduction ของสีน้ำ มันก็ไช่ที่

ผมเคยหัดเขียนสีน้ำ เมื่อตอนยังหนุ่มๆเหมือนกัน สมัยนั้นพบว่า อุปสรรคสำคัญของคนที่อยากจะเรียนสีน้ำ ก็คือ "ความกลัว" กลัวว่าจะวาดหรือระบายออกมาไม่สวย. ก็เลยไม่กล้าลงมือทำ น่าสนใจตรงที่ว่า ความกลัว นั้นมันเกิดมาได้อย่างไร. เป็นผลจากการศึกษาหรือ culture หรือเป็น Generalize ของมนุษย์

เพื่อเป็นกำลังใจให้น้องๆที่ชอบคาดหวังกับตัวเอง ก็เลยไปค้นรูปสีน้ำรูปแรก(18 ปีแล้ว) มาให้ดู


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



ประเด็นที่สำคัญของการระบายสีน้ำสำหรับผม ก็คือ ความสุขอยู่ที่ระหว่างการระบาย ถ้าไป focus ผิดที่ก็จะไปวางความสุขไว้ที่ผลลัพท์ อยากเห็นผลลัพท์ที่สวยงาม จริงๆแล้ว ผลลัพท์ที่สวยงามเป็นแค่ผลพลอยได้

Quote ที่ตรงที่สุดสำหรับเรื่องนี้ คือ

Experience is what you get when you didn't get what you wanted.

เวลาลงสี จะเห็นได้ชัดเลยว่า มันออกมาไม่เหมือนที่เราอยากได้

Related link from Roti

Thursday, January 07, 2010

เล่นน้ำฝน

เมื่อวานฝนตกหนัก โชคดีที่หนีออกจาก Opendream ตั้งแต่บ่ายแก่ๆ ก็เลยไม่ต้องผจญกรรม(รถติด)มากนัก

ไปถึงบ้าน ฝนกำลังกระหน่ำ ก็เลยชวนลูกชายเบอร์สองออกไปเล่นน้ำฝนกัน (คนแรกไม่ชวน เพราะว่าพ่อพาขี่จักรยานตากฝนไปโรงเรียนบ่อยแล้ว)



เล่นได้พักเดียว ฟ้าผ่าเปรี้ยง เจ้าลูกชายวิ่งแนบเข้าบ้าน

Related link from Roti

Tuesday, January 05, 2010

Dynamically generate code in Erlang

ปัจจุบัน application framework ทั้งหลาย พยายามจะทำให้ชีวิตเราง่ายขึ้น ด้วยการลด noise ที่เราไม่จำเป็นต้องเห็น, generate code ที่จำเป็นต้องใช้ให้เรา

Chicago Boss Framework ก็อยู่ในกระแสนี้เช่นกัน ตัว Relation Mapping layer ของมันก็พยายามจะลดรูปให้เหลือน้อยที่สุด, คำถามสำหรับผมก็คือ ใน Erlang นี่เขาใช้เทคนิคอะไรมาช่วย generate code หรือทำ magic บ้าง

ลองดูตัวอย่างการใช้งานก่อน เริ่มด้วยการ define Domain model
-module(blog_post, [Id, Title, Text, AuthorId]).
-compile(export_all).
-belongs_to(author).

-module(author, [Id, Name]).
-compile(export_all).
-has_many(blog_posts).

เทคนิคแรกที่เขาใช้ก็คือ Parameterized Module ซึ่งช่วยให้ Module มีพฤติกรรมในลักษณะ OOP ได้
ลองดู code ตอนที่เรา new instance domain ของเรา
FakeAuthor = (author:new(id, "YOUR NAME")):save(),
BlogPost = blog_post:new(id,
"BLOG TITLE",
"BLOG CONTENT",
FakeAuthor:id()),
SavedBlogPost = BlogPost:save(),

จะเห็นว่า module ของเรามี function "save", "getter"(ตรงที่ get id จาก fakeauthor) เพิ่มขึ้นมาให้เองโดยที่เราไม่ต้องเขียน คำถามก็คือ เขาใช้เทคนิคอะไรในการ generate code ส่วนนี้

เริ่มแรกสุด code ในส่วน module นี้จะไม่ load ขึ้นมาผ่านกลไกปกติ แต่จะทำผ่านกลไกของตัวเอง โดยเริ่มต้น มันจะทำการ parse erlang file โดยใช้ function epp:parse_file ผลที่ได้เราเรียกว่า Form

ทดลองใช้ epp:parse_file กับ ตัวอย่างโปรแกรมที่น้องป้อเขียน
-module(p).
-export([start/0, say_something/2]).
say_something(What, 0) ->
done;
say_something(What, Times) ->
io:format("~p~n", [What]),
say_something(What, Times - 1).
start() ->
spawn(tut14, say_something, [hello, 3]),
spawn(tut14, say_something, [goodbye, 3]).


จะได้ Form ที่มีหน้าตาประมาณนี้
{ok,[{attribute,1,file,{"p.erl",1}},
{attribute,1,module,p},
{attribute,2,export,[{start,0},{say_something,2}]},
{function,3,say_something,2,
[{clause,3,
[{var,3,'What'},{integer,3,0}],
[],
[{atom,4,done}]},
{clause,5,
[{var,5,'What'},{var,5,'Times'}],
[],
[{call,6,
{remote,6,{atom,6,io},{atom,6,format}},
[{string,6,"~p~n"},{cons,6,{var,...},{...}}]},
{call,7,
{atom,7,say_something},
[{var,7,'What'},{op,7,'-',...}]}]}]},
{function,8,start,0,
[{clause,8,[],[],
[{call,9,
{atom,9,spawn},
[{atom,9,tut14},
{atom,9,say_something},
{cons,9,{...},...}]},
{call,10,
{atom,10,spawn},
[{atom,10,tut14},
{atom,10,say_something},
{cons,10,...}]}]}]},
{eof,11}]}

จะเห็นว่ามีลักษณะเป็น Abstract Syntax Tree + Meta Data

พอได้ form มา เจ้า ChicagoBoss ก็จะทำการแทรก,แปลง code ให้เป็นไปตามต้องการ โดยมันจะใช้ function ใน library erl_syntax ช่วยในการ generate Form

ตัวอย่าง code ในส่วนที่สร้าง getter
parameter_getter_forms(Parameters) ->
lists:map(fun(P) ->
erl_syntax:add_precomments([erl_syntax:comment(
[lists:concat(["% @spec ", parameter_to_colname(P), "() -> ", P]),
lists:concat(["% @doc Returns the value of `", P, "'"])])],
erl_syntax:function(
erl_syntax:atom(parameter_to_colname(P)),
[erl_syntax:clause([], none, [erl_syntax:variable(P)])]))
end, Parameters).

parameter_setter_forms(ModuleName, Parameters) ->
lists:map(
fun(P) ->
erl_syntax:add_precomments([erl_syntax:comment(
[
lists:concat(["% @spec ", parameter_to_colname(P), "( ", P, "::",
case lists:suffix("Time", atom_to_list(P)) of
true -> "tuple()";
false -> "string()"
end, " ) -> ", inflector:camelize(atom_to_list(ModuleName))]),
lists:concat(["% @doc Set the value of `", P, "'."])])],
erl_syntax:function(
erl_syntax:atom(parameter_to_colname(P)),
[erl_syntax:clause([erl_syntax:variable("NewValue")], none,
[
erl_syntax:application(
erl_syntax:atom(ModuleName),
erl_syntax:atom(new),
lists:map(
fun
(Param) when Param =:= P ->
erl_syntax:variable("NewValue");
(Other) ->
erl_syntax:variable(Other)
end, Parameters))
])]))
end, Parameters).


พอ transform code เสร็จ ก็จะทำการ compile เป็น binary ด้วย function compile:forms

compile เสร็จก็จัดการ load เข้า runtime โดยใช้ function code:load_binary

Related link from Roti

Monday, January 04, 2010

P07 กับ List Moand

เห็นน้องป้อทำโจทย์ P07 ของ Ninety-Nine Lisp Problem ก็เลยกลับไปอ่านวิธีทำของตัวเอง แล้วก็พบว่าถึงแม้เวลาจะผ่านมา 4 ปีแล้ว ตัวเองก็ยังไม่เคยเข้าใจคำตอบของ Conor McBride เลย วันนี้ก็เลยได้ฤกษ์ทำความเข้าใจกับ List Monad ที่ Conor ใช้ตอบคำถามผม

เริ่มด้วยคำอธิบาย Monad ที่บอกว่า Monad คือ "an abstract datatype of actions" ซึ่งมีนิยามง่ายๆแค่นี้
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a


มันคือ pattern ฉนั้นอย่าพึ่งไปพยายามจินตนาการว่า มันทำอะไรได้บ้าง
หันกลับมาดู context ของ List บ้าง เรารู้ว่า type ของ List คือ [a] จะเห็นว่า type [a] มันสามารถมองเป็น m a ได้ (เพราะ type constructor มันมี free variable 1 ตัวเหมือนกัน) ฉนั้นเราลองมา implement ให้ List เป็น Monad กัน

เริ่มจาก implementation ที่ง่ายที่สุดก่อน ก็คือ return, return มี type เป็น a -> m a ฉนั้นกรณีของ List, คำสั่ง return 4 ก็ควรได้ค่า [4] ออกมา
instance Monad [] where
return x = [x]

ตัวถัดมาก็คือ function >>= นิยามของ type มันคือ m a -> (a -> m b) -> m b
เปลี่ยน m a ให้เป็น [a] จะได้ [a] -> (a -> [b]) -> [b] จะเห็นว่า definition มันไกล้เคียงกับ map function ที่มี type เป็น [a] -> (a -> b) -> [b] ดังนั้นเราสามารถ implement >>= โดยใช้ map และ concat
หน้าตาออกมาดังนี้
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)

ได้นิยามของ List ในมุมมองของ Monad ออกมาอย่างงงๆ
แล้วมันมีประโยชน์อะไรหล่ะ ที่ทำ List type ให้เป็น instance ของ Monad type

ลองมาหัดใช้ List Monad ทำโจทย์
ถ้าให้ list ของ [1..10] มาให้หาตัวเลข 2 ตัวที่ผลคูณมีค่า = 16

ถ้าใช้ List Monad ก็จะเขียนแบบนี้
guard True xs = xs
guard False xs = []

solve = do
x <- [1..10]
y <- [x..10]
guard (x * y == 16) (return (x,y))

ซึ่งมี form ที่ไกล้เคียงกับ solution ที่ใช้ List comprehension
[(x,y) | x <- [1..10], y <- [x..10], x * y == 16]

กลับมาที่ solution ที่ McBride เขียนตอบผม
flat1 :: Store a -> a
flat1 (E a) = return a
flat1 (S xs) = xs >>= flat1

ถึงตอนนี้พอจะรู้เรื่องกับคำอธิบายของเขาแล้ว
Your (flat xs) on a list of stores becomes my (xs >>= flat1), systematically lifting the operation on a single store to lists of them and concatenating the results. The return operation makes a singleton from an element. This way of working with lists by singleton and concatenation is exactly the monadic structure which goes with the list type, so you get it from the library by choosing to work with list types. In Haskell, when you choose a typed representation for data, you are not only choosing a way of containing the data but also a way to structure the computations you can express on that data

Related link from Roti

Sunday, January 03, 2010

บ้านน้ำแข็ง

ช่วงปีใหม่ หนีร้อนไปอยู่บ้านย่า พอกลับมาถึงบ้านตัวเองพบว่า ตู้เย็นที่ defrost ไว้ น้ำในช่องน้ำแข็งไหลลงมาอยู่ในถาดแล้วกลายเป็นน้ำแข็งแผ่นใหญ่ไปแล้ว เห็นแล้วก็เลยได้ idea ชักชวนลูกชายให้มาทำบ้านน้ำแข็งของชาวเอสกิโมกัน

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

Related link from Roti

Saturday, January 02, 2010

Parameterized Module

วันนี้นั่งดู code ของ erlang web framework ที่ชื่อ "Chicago Boss" แล้วก็สงสัยว่า syntax ของ model/domain ของมัน ซึ่งดูไม่คุ้นตาเลย

เริ่มจากตัว domain model code ก่อน, เจ้า chicago boss กำหนดให้เขียนแบบนี้
-module(blog_post, [Id, Title, Text, AuthorId]).
-compile(export_all).

เวลาเอาไปใช้งาน ก็จะใช้แบบนี้
BlogPost = blog_post:new(id, "my title", "la la", SomeAuthorId)
BlogPost:save()

เริ่มจาก บรรทัดนี้
-module(blog_post, [Id, Title, Text, AuthorId]).

syntax การ declare แบบนี้ มีศัพท์เฉพาะ เรียกว่า Parameterize Module
parameter ที่ตามหลัง module name (ในกรณีนี้ก็คือ Id, Title, Text, …) จะกลายเป็นตัวแปรที่สามารถ access ได้จากทุก function ที่อยู่ใน module นั้น

ส่วนที่น่าสนใจถัดไปก็คือ
BlogPost = blog_post:new(id, "my title", "la la", SomeAuthorId)

function "new" คือ function ที่ erlang generate ให้เมื่อเรา define module ให้เป็น parameterized โดย parameters ที่เราผ่านค่าเข้าไปจะถูกนำไป initialize ค่าตัวแปรที่เราประกาศไว้ และค่าที่ return กลับมาจาก function new ก็คือ instance ของ module นั้นๆ

หลังจาก new module instance แล้ว เราก็สามารถเรียกใช้ function ใน module instance นั้นๆ โดยใช้ syntax แบบนี้
BlogPost:save()

จะเห็นว่าเราเรียก function จาก instance ของ Module ซึ่งภายใน save function ถ้ามีการ access ตัวแปร Title, ตัวแปร Title ก็จะเป็นตัวแปรที่อยู่ใน scope ของ instance BlogPost (มันคือ OOP นั่นเอง)

ถ้าใครอยากรู้ว่า Parameterized Module เขาออกแบบมาเพื่อช่วยแก้ปัญหาอะไร ขอแนะนำให้อ่าน paper นี้ เฉพาะส่วน introduction ก็พอ

Related link from Roti

Friday, January 01, 2010

Conversations with Architects

หลังจากมีลูกครบสามแล้ว นานๆทีถึงจะมีเวลาเข้าร้านหนังสือ เมื่อวานได้โอกาสดีแวบเข้าไปฉกหนังสือน่าอ่านมาหนึ่งเล่ม ชื่อว่า "คุยกับต้นกล้าสถาปนิก - Conversations with Architects" เป็นหนังสือรวมสัมภาษณ์เหล่าสถาปนิกรุ่นใหม่ (เนื่องจากคนสัมภาษณ์อายุราว 60 แล้ว, นิยามของรุ่นใหม่ก็เลยอายุ ~40 up ทุกคนเลย)

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

อ่านวิชาชีพคนอื่นแล้วก็ต้องหันมามองตัวเอง เมื่อเทียบกับวิชาชีพอื่นๆแล้ว วิชาชีพ IT ยังเป็นอะไรที่ใหม่มากอยู่ สิ่งที่ผมอยากเห็นก็คือการรวมตัวกันเป็นชุมชน (Community of Practice) เพื่อที่จะแลกเปลี่ยน เรียนรู้ร่วมกัน, โดยการเกาะกลุ่มนั้นเกิดจาก passion ในการเรียนรู้ มากกว่าการเล็งผลประโยชน์ทางธุรกิจ

เมื่อพูดถึง Comminuty of Practice แล้ว ก็เลยขอประชาสัมพันธ์ วันเสาร์ที่ 9 มกราคม 2553 นี้ @roofimon กับผม และเพื่อนๆ จะจับเข่าคุยกันเรื่อง Functional Programming โดยสถานที่ได้รับความเอื้อเฟ้ือจาก Opendream ใครที่สนใจไม่เป็นทางการ เนื้อหาที่ได้จะน่าสนใจยิ่งนั้น คุณสมบัติขั้นต่ำสำหรับผู้เข้าร่วมก็คือ ผู้เข้าร่วมต้องไม่อายที่จะถามคำถามที่ตัวเองสงสัย และไม่กลัวว่าคำถามนั้นจะเป็นคำถามที่แสดงความไม่รู้ของตัวเอง ( :) โลกนี้มีใครบ้างที่รู้อะไรจริงๆ)

Related link from Roti

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