เมื่อวานฝนตกหนัก โชคดีที่หนีออกจาก Opendream ตั้งแต่บ่ายแก่ๆ ก็เลยไม่ต้องผจญกรรม(รถติด)มากนัก
ไปถึงบ้าน ฝนกำลังกระหน่ำ ก็เลยชวนลูกชายเบอร์สองออกไปเล่นน้ำฝนกัน (คนแรกไม่ชวน เพราะว่าพ่อพาขี่จักรยานตากฝนไปโรงเรียนบ่อยแล้ว)
เล่นได้พักเดียว ฟ้าผ่าเปรี้ยง เจ้าลูกชายวิ่งแนบเข้าบ้าน
Thursday, January 07, 2010
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
เทคนิคแรกที่เขาใช้ก็คือ Parameterized Module ซึ่งช่วยให้ Module มีพฤติกรรมในลักษณะ OOP ได้
ลองดู code ตอนที่เรา new instance domain ของเรา
จะเห็นว่า module ของเรามี function "save", "getter"(ตรงที่ get id จาก fakeauthor) เพิ่มขึ้นมาให้เองโดยที่เราไม่ต้องเขียน คำถามก็คือ เขาใช้เทคนิคอะไรในการ generate code ส่วนนี้
เริ่มแรกสุด code ในส่วน module นี้จะไม่ load ขึ้นมาผ่านกลไกปกติ แต่จะทำผ่านกลไกของตัวเอง โดยเริ่มต้น มันจะทำการ parse erlang file โดยใช้ function epp:parse_file ผลที่ได้เราเรียกว่า Form
ทดลองใช้ epp:parse_file กับ ตัวอย่างโปรแกรมที่น้องป้อเขียน
จะได้ Form ที่มีหน้าตาประมาณนี้
จะเห็นว่ามีลักษณะเป็น Abstract Syntax Tree + Meta Data
พอได้ form มา เจ้า ChicagoBoss ก็จะทำการแทรก,แปลง code ให้เป็นไปตามต้องการ โดยมันจะใช้ function ใน library erl_syntax ช่วยในการ generate Form
ตัวอย่าง code ในส่วนที่สร้าง getter
พอ transform code เสร็จ ก็จะทำการ compile เป็น binary ด้วย function compile:forms
compile เสร็จก็จัดการ load เข้า runtime โดยใช้ function code:load_binary
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
Labels:
erlang
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" ซึ่งมีนิยามง่ายๆแค่นี้
มันคือ pattern ฉนั้นอย่าพึ่งไปพยายามจินตนาการว่า มันทำอะไรได้บ้าง
หันกลับมาดู context ของ List บ้าง เรารู้ว่า type ของ List คือ
เริ่มจาก implementation ที่ง่ายที่สุดก่อน ก็คือ return, return มี type เป็น
ตัวถัดมาก็คือ function
เปลี่ยน
หน้าตาออกมาดังนี้
ได้นิยามของ List ในมุมมองของ Monad ออกมาอย่างงงๆ
แล้วมันมีประโยชน์อะไรหล่ะ ที่ทำ List type ให้เป็น instance ของ Monad type
ลองมาหัดใช้ List Monad ทำโจทย์
ถ้าใช้ List Monad ก็จะเขียนแบบนี้
ซึ่งมี form ที่ไกล้เคียงกับ solution ที่ใช้ List comprehension
กลับมาที่ solution ที่ McBride เขียนตอบผม
ถึงตอนนี้พอจะรู้เรื่องกับคำอธิบายของเขาแล้ว
เริ่มด้วยคำอธิบาย 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
Labels:
haskell
Sunday, January 03, 2010
บ้านน้ำแข็ง
ช่วงปีใหม่ หนีร้อนไปอยู่บ้านย่า พอกลับมาถึงบ้านตัวเองพบว่า ตู้เย็นที่ defrost ไว้ น้ำในช่องน้ำแข็งไหลลงมาอยู่ในถาดแล้วกลายเป็นน้ำแข็งแผ่นใหญ่ไปแล้ว เห็นแล้วก็เลยได้ idea ชักชวนลูกชายให้มาทำบ้านน้ำแข็งของชาวเอสกิโมกัน
เริ่มด้วยการสอนให้ลูกชายใช้ฆ้อนกับสิ่วตอกน้ำแข็งเป็นก้อนพอเหมาะ จากนั้นก็เรียงกันเป็นทรงกลม เชื่อมประสานก้อนน้ำแข็งด้วยเกลือ เสร็จแล้วก็ถ่ายรูปเพื่อให้(พ่อและ)ลูกได้ชื่นชมผลงาน
เริ่มด้วยการสอนให้ลูกชายใช้ฆ้อนกับสิ่วตอกน้ำแข็งเป็นก้อนพอเหมาะ จากนั้นก็เรียงกันเป็นทรงกลม เชื่อมประสานก้อนน้ำแข็งด้วยเกลือ เสร็จแล้วก็ถ่ายรูปเพื่อให้(พ่อและ)ลูกได้ชื่นชมผลงาน
Related link from Roti
Labels:
fun
Subscribe to:
Posts (Atom)