Friday, May 02, 2008

this ใน javascript กับ dojo.hitch

ใน javascript, bug ที่พบกันบ่อยๆ ก็คือ scope ของ this ที่มักไม่ได้เป็นไปตามที่ programmer คิด
ยกตัวอย่าง
<a id='linkNode' href="#">link</a>
<script>
foo = {

greeting: 'hi',

debug: function() {
alert(this.greeting);
}
}

document.getElementById('linkNode').onclick = foo.debug;
</script>

ถ้าลอง run code ข้างบนนี้ดู ก็จะพบว่า ถ้าเรา click link นั้นเมื่อไร
message ที่แสดงใน alert มันจะแสดงคำว่า "undefined" แทนที่จะเป็น 'hi'
ที่เป็นเช่นนี้ก็เพราะว่า this object ใน function debug ณ ขณะที่เกิด event
function มันจะเปลี่ยน scope ทำให้ this กลายเป็น DOM element ของ anchor แทนที่จะเป็น object foo

แล้ว dojo.hitch หล่ะคืออะไร
function นี้ในแง่ของ functional language ถือว่ามันเป็น higher order function
เนื่องจากมันเป็น function ที่ return function

หน้าที่หลักของ dojo.hitch ก็คือการ bind scope ของ function เข้ากับ object ที่เราต้องการ
อย่างตัวอย่างข้างบน ถ้าเรานำ dojo.hitch มาช่วย ก็จะเขียนเป็นแบบนี้แทน
document.getElementById('linkNode').onclick = dojo.hitch(foo, "debug");

ถ้าลอง run ดู ก็จะได้ message 'hi' ขึ้นมา

จะเห็นว่า hitch ช่วยเราเปลี่ยน scope ของ this จากเดิมที่เป็น DOM element (ที่ trig event นั้น) ให้กลายเป็น foo object แทน โดยใช้ technique การ binding
ลองมาดู code ของ function hitch กัน

dojo.hitch = function(scope, method){
if(arguments.length > 2){
return dojo._hitchArgs.apply(dojo, arguments); // Function
}
if(!method){
method = scope;
scope = null;
}
if(dojo.isString(method)){
scope = scope || dojo.global;
if(!scope[method]){ throw(['dojo.hitch: scope["', method, '"] is null (scope="', scope, '")'].join('')); }
return function(){ return scope[method].apply(scope, arguments || []); };
}
return !scope ? method : function(){ return method.apply(scope, arguments || []); };
}

เริ่มต้นด้วย การข้าม case กรณี arguments​ > 2 ไปก่อนเลย
อันนั้นเป็นเงื่อนไขพิเศษ กรณีที่ต้องการให้มี fixed(prefix) arguments.

ส่วนถัดมาก็คือส่วน ตรวจสอบว่า มีการ pass parameter มาตัวเดียวหรือเปล่า
ถ้ามาตัวเดียว ก็จะถือว่า parameter นั้นเป็น method

สุดท้ายก็ตรวจสอบอีกว่า method เป็น string
หรือเป็น string ก็จะ return closure ที่ห่อคำสั่ง scope[method].apply
ถ้าไม่ใช่ script ก็จะ return closure ที่ใช้คำสั่ง method.apply แทน

อืมม์พอเริ่มเข้าใจ ก็เริ่มเห็นความงาม

ถ้าใครสนใจอยากอ่านให้เข้าใจ(หรืองง)เข้าไปอีก อ่านเพิ่มเติมที่นี่
Jammastergoat - dojo.hitch

Related link from Roti

Tuesday, April 29, 2008

What is programming.

ลอกมาจาก Programming 's Stone บทที่ 2

Ada is sitting in a room. In the evening the room becomes dark. Ada turns on the light.

That is the fundamental act of programming. There is a problem domain (the room), which is dynamic (gets dark). There is order to the dynamic problem domain (it will be dark until morning), permitting analysis. There is a system that can operate within the problem domain (the light), and it has semantics (the switch state).

There is a desire (that the room shall remain bright), and there is an insight (that the operation of the switch will fulfill the desire).
Dynamic problem domains, systems and semantics are covered in detail elsewhere.

Related link from Roti

Monday, April 28, 2008

Override service.

เมื่อวันก่อน ผมพึ่งพบปัญหา java process กิน cpu ไป 99% ตลอดเวลา
ในขั้นแรกก็จัดการ "kill -3" เสีย เพื่อที่มันจะได้ให้มัน dump stack trace ออกมาให้เราดู
"ajp-8009-2" daemon prio=1 tid=0x08c76ef0 nid=0x3d52 runnable [0x6aeb6000..0x6aeb8040]
at java.lang.ClassLoader.loadClass(ClassLoader.java:299)
- waiting to lock <0x7e734d70> (a sun.misc.Launcher$AppClassLoader)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
- locked <0x7e734d70> (a sun.misc.Launcher$AppClassLoader)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1267)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1198)
at java.beans.Introspector.instantiate(Introspector.java:1453)
at java.beans.Introspector.findExplicitBeanInfo(Introspector.java:410)
- locked <0x6ebe1848> (a java.lang.Class)
at java.beans.Introspector.<init>(Introspector.java:359)
at java.beans.Introspector.getBeanInfo(Introspector.java:222)
at java.beans.Introspector.<init>(Introspector.java:368)
at java.beans.Introspector.getBeanInfo(Introspector.java:222)
...
at org.apache.tapestry.util.exception.ExceptionAnalyzer.buildDescription(ExceptionAnalyzer.java:129)
at org.apache.tapestry.util.exception.ExceptionAnalyzer.analyze(ExceptionAnalyzer.java:86)
at org.apache.tapestry.util.exception.ExceptionAnalyzer.reportException(ExceptionAnalyzer.java:378)
at org.apache.tapestry.error.RequestExceptionReporterImpl.reportRequestException(RequestExceptionReporterImpl.java:59)

จากการไล่ stack trace ก็พบว่า มันมีปัญหาในส่วนของ introspector
ซึ่งเกิดจากหน้าจอรายงาน Error ของ Tapestry, พยายามที่จะ dump ข้อมูล nested Exception ออกมาให้เรา
โดยมันพยายามจะใช้กลไก Introspector ในการอธิบายปัญหาที่เกิดขึ้น, แต่ไม่รู้ด้วยเหตุใด เจ้า introspector มันดันหา class บางตัวไม่พบ
มันก็เลย lock, waiting to lock classloader อยู่

สาเหตุของปัญหาจริงๆ ยังจับไม่ได้คาหนังคาเขา แต่คาดว่าน่าจะเกิดจากการ stop webapp บางตัวลงไป กลางอากาศ ณ ขณะที่ user กำลังใช้งานอยู่
เมื่อยังจับต้นตอไม่ได้ แต่ก็ไม่อยากให้เกิดเหตุการณ์ cpu 99% อีก
ผมก็เลยต้องหาวิธีการแก้ปัญหาชั่วคราวก่อน

สิ่งที่เราต้องการก็คือ เราต้องการ replace function ในส่วนของการพยายามอธิบายรายละเอียด exception ที่เกิดขึ้น
(ให้มันลดการอธิบายรายละเอียดลง)
ในการแก้ปัญหานี้ ถ้าเจ้า framework ไม่ได้ออกแบบมาดีพอ เรามักจะต้องใช้วิธี download source code ของ framework มาแก้ไขแล้วก็็ build custom version เอาเอง
(หรือไม่ก็ใช้ AOP ซึ่งก็ยุ่งไปอีกแบบ)

โชคดีที่ tapestry ไม่ได้อยู่ในกลุ่มนี้
Howard ออกแบบ tapestry มาอย่างยอดเยี่ยม
มีความเป็น modular สูง สามารถถอด,เพิ่มเปลี่ยน function ได้ง่าย

ในกรณีนี้ สิ่งที่เราต้องก็คือไล่หาว่า service ตัวไหน รับผิดชอบงานนั้นอยู่
เมื่อเจอแล้ว ก็ทำตามคู่มือ ว่าด้วยการ Overriding a Service
(production ผมยังใช้ tapestry 4 อยู่)

Related link from Roti

Tuesday, April 22, 2008

Distributed Issue Tracker

หลังจาก Distributed Version Control เริ่มแพร่หลายกันมากขึ้น
สิ่งที่ติดตามมา ก็คือ Distributed Issue Tracker

Issue Tracker 2 ตัว ที่ลองเล่นก็คือ

ทั้งสองตัวนี้มีความเหมือนกัน 2 ประการคือ
  • implement ด้วย ruby
  • ใช้ควบคู่กับ git ได้ (ผ่านทาง ruby/git)


เริ่มที่เจ้า ticgit ก่อน
วิธีการใช้งาน ก็ตรงไปตรงมา
เริ่มจาก command line ของมัน ใช้ชื่อว่า 'ti'
การสั่ง ti ครั้งแรก มันจะแอบไปสร้าง branch ใน git repository ที่ชื่อ ticgit

$ git branch
* master

$ ti
I, [2008-04-22T12:21:53.049764 #7521] INFO -- : creating ticgit repo branch
Please specify at least one action to execute.
list state show new checkout comment tag

$ git branch
* master
ticgit

การสร้าง ticket ทำได้โดยการสั่ง
$ ti new -t 'my ticket'
I, [2008-04-22T12:24:38.588505 #7551] INFO -- : saving 1208841878_my-ticket_57

Title : my ticket
TicId : d00a299473633b5bd617375d1441617e56fea90d

Assigned : anon
Opened : Tue Apr 22 12:24:38 +0700 2008 (0 days)
State : OPEN

สังเกตุเลข Ticket Id ที่ใช้ SHA 40 ตัวอักษร สอดคล้องกับ Id ที่ git ใช้

ถ้าต้องการแสดง ticket ทั้งหมดที่มี ก็ใช้คำสั่ง list
$ ti list

# TicId Title State Date Assgn Tags
--------------------------------------------------------------------------------
1 d00a29 my ticket open 04/22 anon


การทำงานกับ ticket ก็ให้ใช้คำสั่ง checkout
$ ti checkout 1

$ ti list

# TicId Title State Date Assgn Tags
--------------------------------------------------------------------------------
* 1 d00a29 my ticket open 04/22 anon

สังเกตุว่าหลังจาก checkout แล้ว จะมี marker '*' ที่หน้าบรรทัด

แน่นอน เมื่อเป็น Issue Tracker, ดังนั้นในแต่ละ ticket เราสามารถใส่ comment ได้
$ ti comment -m 'my comment'

$ ti show

Title : my ticket
TicId : d00a299473633b5bd617375d1441617e56fea90d

Assigned : anon
Opened : Tue Apr 22 12:24:38 +0700 2008 (0 days)
State : OPEN

Comments (1):
* Added 04/22 12:32 by anon
my comment

feature ที่ขาดไม่ได้ ก็คือการ tag
ซึ่งช่วยให้เราสามารถค้น issue ได้ง่ายขึ้น
$ ti tag 1 ui

$ ti list -t ui

# TicId Title State Date Assgn Tags
--------------------------------------------------------------------------------
* 1 d00a29 my ticket open 04/22 anon ui

ลองดูวิธีการเก็บข้อมูลของมันบ้าง
เนื่องจากเรารู้ว่ามันแอบเก็บข้อมูลไว้ใน branch "ticget" เราก็เลยสามารถใช้คำสั่ง checkout เพื่อดู file ที่มันเก็บได้เลย
$ ls
hello

$ git checkout ticgit
Switched to branch "ticgit"

$ ls
1208841878_my-ticket_57

$ ls 1208841878_my-ticket_57/
ASSIGNED_anon COMMENT_1208842341_anon STATE_open TAG_main TAG_ui TICKET_ID

$ cat 1208841878_my-ticket_57/COMMENT_1208842341_anon
my comment

ดูจากชื่อ file แล้ว จะเห็นว่ามันเก็บ meaning ไว้ในชื่อ file เลย

ที่นี้ลองไปดูเจ้า ditz บ้าง
การใช้งาน ditz เริ่มต้นด้วย การ init database ก่อน
$ ditz init
Name (enter for "pok"): projectName
Issues can be tracked across the project as a whole, or the project can be
split into components, and issues tracked separately for each component.
Track issues separately for different components? (y/n): y

Current components:
None!

(A)dd component, (r)emove component, or (d)one: a
Component name: ui

Current components:
1) ui

(A)dd component, (r)emove component, or (d)one: a
Component name: domain

Current components:
1) ui
2) domain

(A)dd component, (r)emove component, or (d)one: d
Ok, bugs directory created successfully.

จะเห็นว่า ditz มี data model มากกว่า ticgit
Issue แต่ละอันที่สร้้างขึ้นมา จะต้องระบุว่ามันเป็นของ component ไหนด้วย

นอกจากมุมมองในส่วน component, ditz ยังเพิ่มมุมมองในส่วนของ release ด้วย
$ ditz add-release
Name: 0.1
Comments (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> remark for release 0.1
> Added release 0.1.


ลองเพิ่ม issue ลงใน ditz
$ ditz add
Title: feature x
Description (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> long remark for feature x
> Is this a (b)ugfix or a (f)eature? f
Choose a component:
1) projectName
2) ui
3) domain
Component (1--3): 3
Assign to a release now? (y/n): y
Assigning to release 0.1.
Issue creator (enter for "Polawat Phetra "):
Comments (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> Added issue domain-1.
You may have to inform your SCM that the following files have been added:
bugs/issue-911f48717bbca30266d6ed1cdea7819b57158afc.yaml


Issue แต่ละ Issue สามารถเลือก assign ให้กับ release ได้

$ ditz todo
Version 0.1 (unreleased):
_ domain-1: feature x

Unassigned:
_ ui-1: hi

$ ditz assign ui-1
Issue ui-1 currently not assigned to any release.
Choose a release:
1) 0.1 (unreleased)
Release (1--1): 1
Comments (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):
> Assigned ui-1 to 0.1.

การที่มี release ทำให้เราสามารถ track ความคืบหน้าได้ (ทำนองเดียวกับ trac)

$ ditz status
0.1 0/ 0 (100%) bugs, 0/ 2 ( 0%) features __


ส่วนรูปแบบการเก็บ เจ้า ditz เลือกเก็บโดยใช้ format yaml
ซึ่งสะดวกมากสำหรับการแก้ไขด้วยมือ
$ ls
bugs hello

$ ls bugs
issue-911f48717bbca30266d6ed1cdea7819b57158afc.yaml project.yaml
issue-fd2e35242ef0bd1db18fc8c0415ffedcf2dbf0cd.yaml

$ cat bugs/issue-911f48717bbca30266d6ed1cdea7819b57158afc.yaml
--- !ditz.rubyforge.org,2008-03-06/issue
title: feature x
desc: long remark for feature x
type: :feature
component: domain
release: "0.1"
reporter: Polawat Phetra
status: :unstarted
disposition:
creation_time: 2008-04-22 06:42:19.702124 Z
references: []

id: 911f48717bbca30266d6ed1cdea7819b57158afc
log_events:
- - 2008-04-22 06:42:21.838025 Z
- Polawat Phetra
- created
- ""

$ cat bugs/project.yaml
--- !ditz.rubyforge.org,2008-03-06/project
name: projectName
version: "0.2"
components:
- !ditz.rubyforge.org,2008-03-06/component
name: projectName
- !ditz.rubyforge.org,2008-03-06/component
name: ui
- !ditz.rubyforge.org,2008-03-06/component
name: domain
releases:
- !ditz.rubyforge.org,2008-03-06/release
name: "0.1"
status: :unreleased
release_time:
log_events:
- - 2008-04-22 06:40:33.376540 Z
- Polawat Phetra
- created
- remark for release 0.1

Related link from Roti

Monday, April 14, 2008

new migration

ตอนนี้ใน edge rails ได้มีการปรับปรุง migration แล้ว
จากของเดิมที่ migration จะใช้ เลข running version 1,2,3,...
ไปเป็น utc timestamp แทน เพื่อแก้ปัญหากรณี branch 2 branch ต่างคนต่างสร้าง migration ทำให้เลข version ชนกัน
สำหรับผมถือเป็นการปรับปรุงที่สำคัญมาก
เนื่องจากมันเป็นปัญหาที่กวนใจผมมานานมากแล้ว

ลองดูตัวอย่าง file ที่ script/generate migration สร้างให้

pphetra@mypann:~/projects/ruby/mg$ ls db/migrate/
20080414002052_create_users.rb 20080414002059_create_posts.rb


นอกจากนี้ยังได้เปลี่ยนแปลง table ที่ทำหน้าที่เก็บข้อมูล version
จาก schema_info ไปเป็น schema_migrations
โดยจะเปลี่ยนวิธีการเก็บ จากเดิมที่เก็บเฉพาะเลข current version
ไปเป็น เก็บทุก version ที่มีการ apply แล้ว

sqlite> select * from schema_migrations
...> ;
20080414002052
20080414002059


สุดท้ายก็คือมีการเพิ่ม rake task เข้ามาใหม่คือ
rake db:migrate:up กับ
rake db:migrate:down
โดย algorithm ที่ใช้ จะไม่เหมือนเดิม
จากเดิมเป็นการไล่เลข running ขึ้นลงเพียงอย่างเดียว
ไปเป็นการไล่ตรวจสอบด้วยว่า มีการ apply migration version นั้นๆแล้วหรือยัง
(เขาใช้ศัพท์เรียก migration พวกที่ยังไม่ apply ว่า interleaved migration)

ยกตัวอย่างการสั่ง up
ถ้าไล่แบบเดิม มันจะไล่ apply ตั้งแต่ current version + 1 ไปจน ล่าสุด (หรือถึง VERSION ที่ระบุ)
แต่ถ้าเป็นของใหม่ มันจะตรวจว่า มี migration อันไหนที่ยังไม่ apply บ้างแล้วจัดการ apply ให้เรา
กรณี down ก็เช่นกัน
ถ้ามันตรวจพบว่ามี migration ไหน ยังไม่ apply มันก็จะ skip migration นั้นๆให้

Links

Related link from Roti

Saturday, April 12, 2008

My History

เห็นใน feed มาพักใหญ่แล้ว
มาเล่นกันบ้างดีกว่า

$ history|awk '{a[$2]++} END{for(i in a){printf "%5d\t%s\n",a[i],i}}'|sort -rn|head
130 git
102 cd
74 ls
42 gitk
22 vi
19 svn
15 sudo
11 exit
11 df
8 echo

อ้าววันๆใครทำอะไรกันบ้าง มา show ให้ดูกันดีกว่า

Related link from Roti

Friday, April 11, 2008

Passing xml stream into BIRT

BIRT สามารถกำหนด datasource ได้หลายแบบ
ในบางสถานะการณ์ผมก็จะเลือกใช้ xml
ในคู่มือส่วนใหญ่ก็จะระบุว่า ถ้าเลือก xml ก็ต้องกำหนด location ของ xml
ซึ่งอาจเป็น url หรือ local file ก็ได้

ปัญหาก็คือ ผมอยากจะ pass xml stream ของผมเข้าไปที่ Engine ตรงๆ
ไม่อยากจะทำเป็น temporary file (ขี้เกียจจัดการ)
แล้วก็ไม่อยากทำเป็น url ด้วย (มีประเด็นเรื่อง security เพิ่มขึ้น)

พยายามมองหาวิธีในคู่มือ ก็หาไม่เจอ
สุดท้ายก็เลยต้องเปิด Krugle เพื่อค้นหาจาก source แทน
หลังจาก zoom ตาม hirarchy ของ class ไปเรื่อยๆ
(ไล่ข้าม project เลย, ปรากฎว่ามันอยู่ใน project Data Tools Platform)
ก็ไปเจอเจ้า Connection ซึ่งมี code แบบนี้
    public void open( Properties connProperties ) 
throws org.eclipse.datatools.connectivity.oda.OdaException
{
...

if ( appContext != null
&& appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) != null
&& appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) instanceof InputStream )
is = XMLDataInputStreamCreator.getCreator( (InputStream) appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) );
else if ( file != null )
is = XMLDataInputStreamCreator.getCreator( file );
else
throw new OdaException( Messages.getString( "Connection.PropertiesMissing" ) );

...
}


ไล่เปิดดู Constants.APPCONTEXT_INPUTSTREAM สักหน่อยว่าค่าเป็นอะไร
แล้ว fix ลงโปรแกรมเราไปเลย
(ไม่ต้องการเพิ่ม dependency ด้วยการอ้างถึง class Constants,
แต่แน่นอนอนาคตถ้า upgrade birt ก็อาจ run ไม่ผ่านเอาดื้อๆได้)

Related link from Roti

Friday, April 04, 2008

พาลูกไปโรงเรียน

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

สองวันแรก โรงเรียนกำหนดไว้ว่า ผู้ปกครองต้องมาอยู่เป็นเพื่อนลูกด้วย
ผมก็เลยได้โอกาสระลึกถึงความหลังสมัยเรียนอนุบาล
เข้าไปเรียนเป็นเพื่อนลูกสองวัน

วันแรกนี่ชุลมุนมาก
เด็กๆ 3 ห้อง ห้องละ 30 คน พร้อมผู้ปกครอง(อย่างน้อย) 90 คน และบวกด้วยครูห้องละ 5 คน
ก็เลยเป็นบรรยากาศที่น่าสนใจ
เด็กที่กล้า ผู้ปกครองก็ไม่ต้องดูแลมากนัก
ส่วนเด็กที่ขี้อาย (ลูกผมจัดอยู่ในกลุ่มนี้) ก็จะเกาะพ่อหรือแม่เป็นตังเม
วันแรกคุณครูเน้นที่การจัดระเบียบ และ routine ที่เด็กๆต้องทำ
เช่น การกินน้ำการเก็บแก้วน้ำ, ลำดับการล้างมือ, การเช็คชื่อ, การทานข้าว

วันที่สอง ผู้ปกครองเริ่มคุ้นกัน
ก็เลยเป็นวันผู้ปกครองเมาท์ (ไม่ค่อยดูลูกแล้ว)
วันนี้ตามตาราง คุณครูกำหนดให้เป็นวันศิลปะ
ผมก็เลยระริกระรี้หน่อย (อยากเล่นเสียเอง)

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

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

ยกตัวอย่างหัวข้อสนทนาวันนี้

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

ช่วงนี้ระยะทางขี่จักรยานก็เลยเพิ่มขึ้น
จากวันละ 10 km (ไป-กลับที่ทำงาน)
ก็เพิ่มขึ้นอีก 12 km (ไป-กลับ โรงเรียนลูก)
รวมเป็น 22 km

Related link from Roti

Thursday, March 27, 2008

Object database ของ Git

ในการเก็บ repository, git มองทุกอย่างเป็น Object
โดยแต่ละ Object จะมี Identity Unique Hex Id ที่เป็นตัวอักษร 40 ตัว
ค่า id นี้เกิดจากการทำ Hashing(SHA1) จากเนื้อหาและ type ของ object นั้น
ข้อดีของการมี id ที่เป็น hash ของ content ก็คือ
จะไม่มีการ merge ซ้ำซ้อน(ที่เกิดจาก patch เดิม)เกิดขึ้นแน่นอน

Object ที่เก็บใน Git database แบ่งออกเป็น 4 แบบคือ
  • Blob object อันนี้ตรงตัว เอาไว้เก็บ content
  • Tree object อันนี้เอาไว้เก็บโครงสร้าง Tree directory, ภายใน object จะมีทั้งข้อมูลพวก ชื่อ file, permissions, รวมถึง link ที่ชี้ไปยัง Blob object ที่เป็นเนื้อหาของ file
  • Tag object อันนี้เป็น pointer ที่ชี้ไปยัง object อื่นๆ
  • Commit object, อันนี้จะเก็บ pointer ที่ชี้ไปยัง Tree object ที่ represent state ณ จังหวะเวลาที่สั่ง commit, ข้อมูลที่เก็บก็มีพวก เวลาที่ commit, ใครเป็นคน commit,
    แต่ข้อมูลที่สำคัญที่สุดก็คือ parent pointer ที่ชี้ไปยัง commit object ที่เกิดก่อนมัน
    โดยปกติ Commit object มักจะมี parent เพียงอันเดียว, แต่ก็มีบางสถานะการณ์ที่สามารถมี parent ได้มากกว่า 1 (เช่นการ merge)


ลองมาแอบดูโครงสร้างมันกัน
เริ่มจาก commit object ก่อน

ลองเรียกดู ประวัติการ commit แค่ 1 ครั้งย้อนหลัง

$ git log -1
commit 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
Merge: 4617ea2... 8c3f76a...
Author: pphetra <pphetra@pphetra.(none)>
Date: Wed Mar 26 16:10:52 2008 +0700

Merge branch 'master' of http://pann/git/og

ลองเอาเลข Id ยาวๆ นั้นมาสอบถาม type ดู

#flag -t คือ ขอดู type ของ object id ที่ระบุ
$ git cat-file -t 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
commit

# -p คือ pretty-print
# จะเห็นว่ามี parent มากกว่า 1 ตัว เนื่องจาก commit นี้เกิดจากการ merge
$ git cat-file -p 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
tree 9a85a027d0f5e29ba6b9874ef10a3a3c3101f17c
parent 4617ea2deeb7b84b84c0b3f4cd173046e5604b95
parent 8c3f76abb50c4ff27b8da6c4530640650d80d179
author pphetra <pphetra@pphetra.(none)> 1206522652 +0700
committer pphetra <pphetra@pphetra.(none)> 1206522652 +0700

Merge branch 'master' of http://pann/git/og


ทดลอง list ดู tree object ที่อยู่ใน commit object ที่ว่า

$ git ls-tree 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071
100644 blob 9088635d5273240813d7fab8daf26c389a8a51d9 .classpath
100644 blob 787a8786a3ec7b2fde43abbb4d81c3ea409b25fb .gitignore
100644 blob f08ecbab3a1d3c8277620d35928e01913be1d1f4 .project
100644 blob 46734c60ef2bb210adc2f86307bc00fb53df7309 APACHE2_HEADER
100644 blob 60c078179819ea624495166795ce6224771a7ba6 KEYS
100644 blob a139573f2386fabc6b246f04b2d1686b00ddd71d LICENSE
100644 blob 2677396f5edba3a549c447115eeae1a5eeea8a14 NOTICE
100644 blob fa5d691f0975f8afc054b16b77ba71079d819c25 OPTIONAL_LIBRARIES
100644 blob 7e7d6d5977e3fd752f4dd82a593efa836618aa63 README
100755 blob 87591cad0666cdb051721fffd04f9c01aae94e5b ant
100644 blob 96c49bfa7b369a5330df284f3dcb1a38c82fbac9 ant.bat
040000 tree 6ed701fa39af921e80b34481efb5b87634ab424e applications
100644 blob 78122b51b6bc277c2ac67c0347ff83fee54f7b7d build.xml
040000 tree 3cd7b0cd4dfeee73df183be101987170b7bd102a framework
040000 tree f14e65a88ad8552e18ebca8a85659f24bb5b5ee8 hot-deploy
100755 blob 7b0c125d5880833e11c656f552717c5e0b6f77a0 ij.ofbiz
100644 blob 1c7326b63b39e8b8b03289c52cceed30d3b17e3a rc.ofbiz
040000 tree f3bc94c18fa6861e111a4306c235e92fa9b6f737 runtime
040000 tree a2dc45456637d729def157b82d2983ebc2db23a4 specialpurpose
100644 blob 9085cf0ef6b6acbd77c39432f7e79f4863f661fa startofbiz.bat
100755 blob b5457726ccfdae38b87da761e9eba237075dc78f startofbiz.sh
100755 blob 9c6dbbcf1449a1fcb4e9d23de29e78180f0a4cd9 stopofbiz.sh

จะเห็นว่ามีทั้ง file permission และ type ที่เป็น file และ directory (tree)

ลองเปิด directory .git/object แล้ว
เอา ID 2c3c66169b4f9acd3a9dc358a9db8cde5d92c071 ที่ represent commit object
ไปเลียบๆมองๆดู ก็จะเห็นแบบนี้



internal structure เก็บกันอย่างนี้นี่เอง

ไอ้พวกเลขเลข Hex ยาวๆ 40 ตัวอักษรนี่มันไม่ค่อยสะดวกในการใช้งาน
ก็เลยมี concept ที่เรียกว่า reference ขึ้นมา
โดยเราสามารถใช้ naming ในการ represent object ที่เราต้องการได้

ประเภทของ reference ก็จะประกอบด้วย
  • heads
  • tags

ความแตกต่างหลักๆของ heads กับ tags ก็คือ
tags แสดงจุดๆหนึ่งที่อยู่ใน history และไม่สามารถแก้ไขค่าได้
ส่วน heads จะแสดง ยอด(tip) ของ history ของเรา
ซึ่งจะเปลี่ยนแปลงทุกครั้งที่มีการ commit ลงไป

ถ้าลองไปเปิด directory .git/refs/heads ดู ก็จะเห็น
file ที่มีชื่อ ตาม branch ที่เราตั้งไว้
(ยกเว้น master ที่เป็นชื่อ default ที่ git assign ให้เรา
ตอนที่เรา clone หรือ init repository ขึ้นมา)


เนื้อหาที่อยู่ใน file พวกนี้ ก็คือ เลข Object id นั่นเอง

$ cd .git/refs/heads
$ cat master
2c3c66169b4f9acd3a9dc358a9db8cde5d92c071

Related link from Roti

Friday, March 21, 2008

ไม่ไว้วางใจ ปรเมศวร์

ในกรณีของกระปุก ซึ่งกลายเป็นข่าวร้อนในตอนนี้
มีเรื่องหนึ่งที่ผมรู้สึกติดใจ ก็คือ
เนื้อหาที่อยู่ใน (อดีต) xxx.kapook.com
ผมรู้สึกว่า เนื้อหาในนั้นมันไม่ได้แค่ สุ่มๆตัดถ้อยคำมาจาก web โป๊ ทั่วๆไปมาแปะ
แต่มันเป็นการ จงใจ และ ตั้งใจ แต่งขึ้น

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

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

Related link from Roti

Thursday, March 20, 2008

การใช้ Git กับโครงการ Orangegears

ช่วงนี้ผมกำลังเริ่ม contribute ให้กับ project Orangegears ซึ่งเป็นระบบ ERP ที่ fork มาจาก Ofbiz
โดยมีเหล่าน้องๆฝึกงาน (สองคน) มาช่วยกันรุมยำ source code

ประเด็นปัญหาแรกที่ต้อง solve ก็คือจะจัดการกับ version control อย่างไร
โดยมีประเด็นที่ต้องคำนึงถึงดังนี้
  • code ของ orangegears อยู่บน sourceforge.net และมีขนาดใหญ่
  • เนื่องจากยังเป็นมือใหม่ กับ ERP framework ตัวนี้, รูปแบบการแก้ไข orangegears จะออกไปในทางลองผิดลองถูก ซึ่งหมายความว่าจะมี throw away code จำนวนมาก
  • ต่อเนื่องจากข้อข้างบน การลองผิดลองถูกจะทำให้เกิดการ branch จำนวนมาก
  • code ที่น้องๆช่วยกันเขียน อาจจะมีปัญหาเรื่อง quality ของ code เช่นมี noise ปนอยู่จำนวนหนึ่ง (น้องใหม่ส่วนใหญ่ มักจะเขียน code แบบ แปะๆ อันไหนใช้ไม่ได้ก็ comment เอาแทนที่จะลบทิ้งไปเลย)
  • เนื่องจากเป็นระบบที่ไม่ชำนาญ ก็จะมีประเด็นเรื่องแก้ไขบ่อย, commit ถี่และบ่อย ซึ่งการ commit บ่อยๆ(แบบไม่ควรเกิด) ก็ถือเป็น noise ใน repository log อย่างหนึ่ง

เมื่อคิดสะระตะแล้ว solution ที่ผมเลือกใช้ก็คือ Git เนื่องจากมันตอบโจทย์เหล่านี้ได้ดี
  • distributed development ทำให้ผมเป็นอิสระจาก sourceforge.net, ไม่กิน bandwidth และไม่เสียเวลารอ
  • large file sets, ถึงจะไม่เท่ากับ linux kernel แต่ด้วยจำนวน 8000 กว่า file ก็ถือว่าไม่เบาเหมือนกัน
  • trial branches, git มีจุดเด่นด้านนี้เป็นพิเศษ
  • ผมสามารถยุบ patch หรือกรอง patch ได้ ทำให้ผมสามารถตัด patch ที่ไม่สำคัญทิ้งได้ หรือรวมทั้งยุบหลายๆ commit เป็น commit เดียวได้ (ลด noise)

แนวทางที่วางแผนไว้ก็คือ



  • มี working copy เพียงอันเดียวที่ไว้คอย sync กับ sourceforge subversion repository
  • developer ที่เหลือจะ clone ออกจาก master code ชุดนี้
  • การแก้ไข code ใดๆ จะต้องทำผ่าน branch ทั้งหมด
  • เมื่อน้องแก้ไข branch ใดๆจนพอใจแล้ว ก็จะแจ้งมาที่ผม ให้ทำการ pull มาที่ working copy หลัก เพื่อที่จะทดสอบ
  • เมื่อผลการทดสอบเป็นที่น่าพอใจ ก็จะทำการ merge เข้า master และ commit ขึ้น subversion

สถานะปัจจุบัน ตอนนี้ก็ทดลองทำอยู่ 2 branch และกำลังสนุกกับการลองผิดลองถูกกับ workflow และขั้นตอนการ merge อยู่

เท่าที่สัมผัส Git มาระยะหนึ่ง ในตอนแรกเนื่องเราเห็นภาพเพียงบางส่วน ก็จะรู้สึกว่ามัน fragile มาก
การเรียกใช้คำสั่งแต่ละที ดูเหมือนว่า โอกาสจะเกิด error มีสูงมาก
(สมัยทดลองที่ codefest นั้นสุดๆ เกิด error ในอัตรา 80 %)
หลังจากเริ่มใช้มันมากขึ้น มากขึ้น ก็เริ่มสัมผัสถึงความงามบางอย่างของมันแล้ว
ถึงตอนนี้ก็เลยรู้สึกติดและสนุกกับการใช้งาน Git เสียแล้ว

Related link from Roti

Wednesday, March 19, 2008

Recursive streams (Haskell)

หลายคนคงเคยเห็นตัวอย่าง function ที่ทำหน้าที่หาค่า Fibonacci แบบ recursive ธรรมดามาแล้ว

fib 0 = 1
fib 1 = 1
fib n = fib(n-1) + fib(n-2)


ใน haskell มันมีเทคนิคตัวหนึ่งที่เรียกว่า recursvie stream
ซึ่งเราสามารถนำมา apply หาค่า Fibonacci ได้

fibs = 1:1:zipWith (+) fibs (tail fibs)

หลักการก็คือ ถ้าเราสังเกตุค่า Fib ดู

1 1 2 3 5 8 13 21 ... ค่า fib sequence
1 2 3 5 8 13 21 34 ... tail fib (function tail จะทำการตัดตัวหน้าสุดทิ้ง)

ทั้งสองบรรทัดข้างบนบวกกันได้
2 3 5 8 13 21 34 55
ซึ่งก็คือ tail of tail of fib sequence

เมื่อนำหลักการข้างบนมาเขียน function fibs
fibs ก็เลยเท่ากับ 1 ตาม ด้วย 1 และตามด้วย ค่า fib บวกด้วย tail ของ fib

แต่ Diagram ที่ช่วยให้ผมเห็นภาพได้ดียิ่งขึ้น ก็คือ diagram นี้



เป็น diagram ที่อยู่ในหนังสือ The Haskell School of Expression
ของ Paul Hudak

Related link from Roti

Tuesday, March 18, 2008

Logger ใน ActiveRecord

จากโจทย์ที่ apirak ถามที่ codenone, ผมจะลองเล่าให้ฟังว่า ถ้าผมไม่ลักไก่ค้นด้วย google แล้ว
ผมจะมีวิธีไล่หามันใน rails code ได้อย่างไร

เรามาลองไล่ code ของ ActiveRecord กัน
เริ่มจากบรรทัดแรก

ActiveRecord::Base.establish_connection(
:adapter => 'mysql',
:host => 'localhost',
:username => 'root',
:password => '',
:database => 'test'
)

จะเห็นว่ามันเรียก class method จาก class ActiveRecord::Base
แต่ถ้าเราไปลองเปิด file "base.rb" ไล่หา method นี้ดู ก็จะหาไม่เจอ
ที่เป็นเช่นนี้ ก็เพราะเจ้า ruby มัน open class ได้ คนที่เขียน rails ก็เลยอาศัยคุณสมบัตินี้ มา implement
code ในลักษณะ Partition class ออกเป็นส่วนๆ ตามความรับผิดชอบ
ดังนั้น เจ้าเนื้อหาใน ActiveRecord::Base ก็เลยกระจายอยู่ในหลายๆ file

ลอง grep หาดู

$ grep -Rl establish_connection *
abstract/connection_specification.rb

ลองเปิด connection_specification.rb มาดู ก็จะพบ method หน้าตาแบบนี้
    def self.establish_connection(spec = nil)
case spec
when nil
raise AdapterNotSpecified unless defined? RAILS_ENV
establish_connection(RAILS_ENV)
when ConnectionSpecification
clear_active_connection_name
@active_connection_name = name
@@defined_connections[name] = spec
when Symbol, String
if configuration = configurations[spec.to_s]
else
spec = spec.symbolize_keys
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
adapter_method = "#{spec[:adapter]}_connection"
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
remove_connection
establish_connection(ConnectionSpecification.new(spec, adapter_method))
end
end

เห็น switch เยอะๆแล้วตาลายนิดหน่อย case ของเราจะตกลงตรงช่อง else
  # เริ่มด้วยการแปลง key ของ Hash table เราให้เป็น symbol ก่อน
spec = spec.symbolize_keys
# parameter ที่บังคับใส่ก็คือ :adapter
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
# หา adapter_method ที่รับผิดชอบ connection ของ database นี้
# กรณี mysql ก็จะเป็น mysql_connection
adapter_method = "#{spec[:adapter]}_connection"
# check ว่ามี class method นี้อยู่จริงหรือไม่
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
remove_connection
# ตรงนี้แหล่ะที่ทำผมแปลกใจ มัน recursive call ตัวเองอีกครั้งด้วย parameter ที่ห่อไว้ใน ConnectionSpecification object
establish_connection(ConnectionSpecification.new(spec, adapter_method))

แกะแล้วมี surprise เล็กน้อย เนื่องจากมัน recursive call ตัวเองด้วย parameter ที่ถูกแปลงไปแล้ว
(เจ้า ruby มันเป็น dynamic type ดังนั้น code ในลักษณะ Polymorphism ก็เลยต้องเขียนออกมาอย่างนี้)

ในการ recursive ครั้งที่สอง มันก็จะมาตก code ส่วนนี้

clear_active_connection_name
@active_connection_name = name
# spec ก็คือ ConnectionSpecification object
@@defined_connections[name] = spec

จะเห็นว่าหลังจากจบคำสั่ง establish_connection, เจ้า activerecord มันไม่ได้รีบร้อนจะเปิด connection ต่อ database ให้เรา
แต่อย่างไร, มันจะรอให้เราต้องการ access ข้อมูลจริงๆก่อน จึงจะเริ่มต้นเปิด connection ให้เรา

ตัว key ที่สำคัญใน code ข้างบน ก็คือ adapter_method ที่ชื่อ mysql_connection
ลอง grep หาดู ก็จะพบว่ามัน define ไว้ใน file "mysql_adapter.rb"
ลองเปิดดู
module ActiveRecord
class Base

...
...

def self.mysql_connection(config) # :nodoc:
config = config.symbolize_keys
host = config[:host]
port = config[:port]
socket = config[:socket]
username = config[:username] ? config[:username].to_s : 'root'
password = config[:password].to_s

if config.has_key?(:database)
database = config[:database]
else
raise ArgumentError, "No database specified. Missing argument: database."
end

require_mysql
mysql = Mysql.init
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]

ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
end

...
end
end

จะเห็นว่ามี keyword ตัวหนึ่งที่ชื่อ logger
แล้ว เจ้า logger หล่ะมาจากไหน
เนื่องจากเจ้า method mysql_connection มันอยู่ใน scope ของ class ActiveRecord::Base
ก็ลองไปเปิด file "base.rb" ดู และหาคำว่า logger ก็จะพบบรรทัดนี้
  class Base    
# Accepts a logger conforming to the interface of Log4r or
# the default Ruby 1.8+ Logger class, which is then passed
# on to any new database connections made and which can be
# retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false

Bingo!!
ถ้าเราต้องการ Logger ก็เพียงแต่ supply มันให้ เจ้า ActiveRecord::Base แบบนี้
require 'logger'
ActiveRecord::Base.logger = Logger.new(STDOUT)

Related link from Roti

Monday, March 17, 2008

ทัวร์ Dinosaur

คุณลูกชายปิดเทอมแล้ว เลยได้ฤกษ์พากันไปเที่ยวกันทั้งครอบครัว
แน่นอนว่า พาลูกเที่ยว ก็ต้องให้คุณลูกเป็นคนเลือกที่หมาย
ผลก็คือ เราจะไปดูไดโนเสาร์ที่กาฟสินธุ์กัน (มีเสียงคุณแม่บ่นเล็กน้อยว่า อยากไปทะเล)



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

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

เมื่อได้ทราบข้อมูลเชิงลึกในข้อ 2 บวกกับรู้ว่า ทางวังยังไม่ได้ตอบรับเรื่องการเสด็จแต่อย่างไร
ผมก็เลยตัดสินใจใช้ "เส้นสาย" (ตัวอย่างที่ไม่ดี ขอนำมาสารภาพผิดในที่สาธารณะ)
ขอให้ลูกชายเข้าไปดูเฉพาะห้องโถง ซึ่งเป็นห้องที่มีโครงกระดูก dinosaur ตั้งอยู่เต็มไปหมด
ซึ่งก็ happy ending ดี เพราะเจ้าลูกชายสามารถระบุชื่อของ dinosaur หลักๆได้ทั้งหมด
ทำให้คนที่พาเข้าชม เขารู้สึกคุ้มค่าที่เสียเวลามา support เจ้าตัวเล็ก


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

เสร็จจากดูรอยเท้าไดโนเสาร์ ก็มุ่งหน้าต่อไปยังขอนแก่น
เพื่อจะแวะเที่ยวชมไดโนเสาร์ที่ภูเวียง


สรุป trip นี้ 5 วัน นอน โคราช,กาฟสินธุ์,ขอนแก่น,ชัยภูมิ
เหนื่อยสุดๆ พาลูกเล็ก 2 คนเที่ยว

Related link from Roti

Friday, March 07, 2008

snail-shell

เมื่อวานนั่งเขียนโจทย์เกี่ยวกับ shell-snail ใน codenone ด้วย Haskell
แต่ใน codenone มันทำ syntax highlight ไม่ได้
ก็เลยมาขอฉายซ้ำใน blog อีกที

เริ่มจากโจทย์ก่อน
ให้เขียนโปรแกรมที่รับค่า N และนำมา plot graph ให้ได้ตามนี้

กรณี N=3
***
*
***

กรณี N=4
****
*
* *
****

กรณี N=5
*****
*
*** *
* *
*****

กรณี N=6
******
*
**** *
* * *
* *
******


คิดจะเขียนโปรแกรมด้วย Haskell ก็ต้องคิดแบบ recursive เป็นหลัก
จะเห็นว่าที่ dimension N ใดๆ จะมี sub picture ที่ (N-2) ซ้อนอยู่ในนั้นด้วย
แต่จะซ้อนอยู่ในลักษณะกลับหัวกลับหาง (คือ flip ทาง horizontal ที่หนึ่งก่อน แล้วค่อย flip ทาง vertical อีกที)

ลองดูรูป N=6 จะเห็นว่าเกิดจาก

N=4

****
*
* *
****

กลับหัวกลับหางเป็น

****
* *
*
****

แล้วนำไปซ้อนกับรูป พื้นฐาน ที่ N=6

******
*
*
*
*
******


ดังนั้น นิยามของรูปของเราก็คือ

รูป N = (รูป (N-2) กลับหัวกลับหาง) ซ้อนกับ รูปพื้นฐาน N

เขียนเป็น function ได้ว่า

pic n = (flip_all (pic n-2)) `over` base n


เนื่องจากโปรแกรมเราเป็น recursive ดังนั้นมันก็ต้องมี รูปที่เป็นจุดเริ่มต้นให้มันด้วย
ซึ่งในกรณีนี้ก็คือ N=1 และ N=2
pic 1 = ["*"]
pic 2 = ["**"," *"]
pic n = (flip_all (pic n-2)) `over` base n

นิยามของ base n ก็ง่ายๆนั่นคือ
เอา '****' มาประกบหัวและหางเข้ากับ ' *'
โดยมีความกว้างและความสูงเท่ากับค่า N
-- base 3 = ["***",
-- " *",
-- "***"]
--
-- base 4 = ["****",
-- " *",
-- " *",
-- "****"]
--
-- for n = 4
-- hbar = "****"
-- rbar = " *"
--
base n = [hbar] ++ (replicate (n-2) rbar) ++ [hbar]
where hbar = replicate n '*'
rbar = (replicate (n-1) ' ') ++ ['*']

function ถัดไปก็คือ flip_all
ซึ่งเกิดจาก flip_vertical compose กับ flip_horizontal
--
--render $ flip_horizontal $ pic 4
--
-- "****"
-- "* "
-- "* *"
-- "****"
flip_vertical = reverse
flip_horizontal = map reverse
flip_all = flip_vertical . flip_horizontal

function ที่ดูจะยุ่งยากสุด ก็คือ function ที่ใช้วางรูปซ้อนกัน
เพื่อความง่ายในการซ้อน เราเลยเพิ่ม helper function ที่ใช้ในการปรับขนาดรูปให้เท่ากันเสียก่อน
ก่อนที่จะนำมาซ้อนกัน
-- patch (base 3) 5 = ["     ",
-- " ",
-- "*** ",
-- " * ",
-- "*** "]
--
patch xs n = replicate diff blankline ++
[ x ++ (replicate diff ' ') | x <- xs]
where blankline = replicate n ' '
diff = n - (length xs)


-- ["****", [" ", ["****",
-- " *", over "*** ", => "****",
-- " *", "* ", "* *",
-- "****"] "*** "] "****"]
--
over xs ys = zipWith over' xs ys
where over' linex liney = zipWith over'' linex liney
over'' x y | x == ' ' = y
| otherwise = x

ปรับนิยาม pic ของเราให้ใช้ patch function ช่วยปรับขนาดก่อนวางทาบกัน
pic 1 = ["*"]
pic 2 = ["**"," *"]
pic n = (patch (flip_all $ pic (n-2)) n) `over` base n


ส่วนการ render นั้นมันมีเรื่อง IO มาเกี่ยวข้องด้วย
ก็ต้องใช้ function พวก mapM (M มาจาก Monad) เข้ามาช่วย
render n = do 
print $ "N = " ++ (show n)
mapM print (pic n)


ลองสั่ง run ดู
*Main> mapM render [1..8]
"N = 1"
"*"
"N = 2"
"**"
" *"
"N = 3"
"***"
" *"
"***"
"N = 4"
"****"
" *"
"* *"
"****"
"N = 5"
"*****"
" *"
"*** *"
"* *"
"*****"
"N = 6"
"******"
" *"
"**** *"
"* * *"
"* *"
"******"
"N = 7"
"*******"
" *"
"***** *"
"* * *"
"* *** *"
"* *"
"*******"
"N = 8"
"********"
" *"
"****** *"
"* * *"
"* * * *"
"* **** *"
"* *"
"********"

Related link from Roti

Wednesday, March 05, 2008

Yak shaving

"Yak shaving" is a programmer's slang term for the distance between a task's start and completion and the tangential tasks between you and the solution. If you ever wanted to mail a letter, but couldn't find a stamp, and had to drive your car to get the stamp, but also needed to refill the tank with gas, which then let you get to the post office where you could buy a stamp to mail your letter—then you've done some yak shaving.


อันนี้เป็นอีกนิยามหนึ่ง

Any seemingly pointless activity which is actually necessary to solve a problem which solves a problem which, several levels of recursion later, solves the real problem you're working on.


สำหรับผม บางครั้ง Yak shaving ีมันก็สนุก จนอดใจที่จะไม่ลงไปแตะไม่ได้

ช่วงนี้ใช้ python เขียน script สำหรับ deploy java applicaion บน cluster อยู่
มันมี Yak shaving น้อยดี

Related link from Roti

Wednesday, February 27, 2008

แอบดู GWT code

เมื่อวานผมเขียน code GWT ในส่วนที่เกี่ยวกับ java.util.Date
แล้วเกิดสงสัยว่ามัน implement อย่างไร ก็เลยลอง download source code ของ GWT มาดู
เปิดดูแล้วก็ร้อง 'ฮ่อ'

ลองดูบางส่วนที่ผมตัดมาให้ดู

/**
* Represents a date and time.
*/

public class Date implements Cloneable, Comparable<Date>, Serializable {

public Date() {
init();
}

private native void init() /*-{
this.jsdate = new Date();
}-*/
;


public native int getDate() /*-{
return this.jsdate.getDate();
}-*/
;

public native int getDay() /*-{
return this.jsdate.getDay();
}-*/
;

public native int getHours() /*-{
return this.jsdate.getHours();
}-*/
;

public native String toGMTString() /*-{
var d = this.jsdate;
var padTwo = @java.util.Date::padTwo(I);
var month =
@java.util.Date::monthToString(I)(this.jsdate.getUTCMonth());

return d.getUTCDate() + " " +
month + " " +
d.getUTCFullYear() + " " +
padTwo(d.getUTCHours()) + ":" +
padTwo(d.getUTCMinutes()) + ":" +
padTwo(d.getUTCSeconds()) +
" GMT";
}-*/
;

private static String padTwo(int number) {
if (number < 10) {
return "0" + number;
} else {
return String.valueOf(number);
}
}
}

งามดีเหมือนกัน ส่วนที่ cross กันระหว่าง javascript กับ java (ก่อนที่จะแปลงเป็น javascript)

ที่น่าสนใจก็คือ ตอนที่ฝั่ง javascript call มาที่ฝั่ง java
จาก code ข้างบนลองดูวิธีการ call method padTwo

// เริ่มด้วยการอ้างถึง method ที่ต้องการ
// จะเห็นว่ามันใช้ signature ของ method ในการอ้างถึง method ที่ต้องการ
// สิ่งที่ return กลับมาก็น่าจะเป็น javascript function

var padTwo = @java.util.Date::padTwo(I);
...
padTwo(d.getUTCHours()) + ...
...

Related link from Roti

Wednesday, February 20, 2008

GWT กับ Spring

ผม search หาวิธี map GWT เข้ากับ SpringFramework มาพักใหญ่แล้ว
ส่วนใหญ่ที่เห็นก็จะเป็นการเขียน DispatcherServlet ของตัวเองขึ้นมา
เช่น GWT-SL
วิธีการของ GWT-SL จะเป็นดังนี้
  • config servlet ใน web.xml
      <servlet>
    <servlet-name>rpc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
    <servlet-name>rpc</servlet-name>
    <url-pattern>*.rpc</url-pattern>
    </servlet-mapping>

  • config spring context โดยใช้ GWTHandler เข้ามาเป็นตัว wrapper
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans>
    <bean id="urlMapping" class="org.gwtwidgets.server.spring.GWTHandler">
    <property name="mappings">
    <map>
    <entry key="/wcfb110_jobService.rpc" value-ref="jobService"/>
    </map>
    </property>
    </bean>

    <bean id="jobService" class="wcf.web.fin.gwt.wcfb110.server.JobServiceImpl"/>
    </beans>
    </pre>


ดูก็ง่ายดีแล้ว แต่มาวันนี้เจอตัวอย่าง source code ของ Interface21(เจ้าพ่อ spring) ซึ่ง config spring กับ GWT
(โปรดระวัง เห็นนามสกุลเป็น .zip แต่จริงๆแล้วมันเป็นพวก .tgz)
เห็นแล้วต้องร้อง Wow, ทำไมมัน simple อย่างนี้

เขาใช้วิธีนี้
เริ่มด้วย web.xml ที่ทำเหมือนกันกับข้างบน
แต่ ตัว spring context เขาเปลี่ยนเป็นแบบนี้
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
>

<context:component-scan base-package="gwt.server" />

</beans>

แล้วตัว GWT Servlet ของเราก็ใส่ annotation เข้าไปแบบนี้
@Controller
public class NoteServiceImpl extends RemoteServiceServlet implements NoteService, ServletConfigAware {

public void setServletConfig(ServletConfig servletConfig) {
try {
init(servletConfig);
} catch (ServletException e) {
throw new IllegalStateException("Cannot initialise Controller.", e);
}
}

@RequestMapping("/gwt.Example/NoteService.rpc")
public void doRpc(HttpServletRequest request, HttpServletResponse response)
throws Exception {
doPost(request, response);
}

public Note loadNote(String name) {
return new Note(name, "This Note named " + name + " was constructed on the server");
}
}

ง่ายขึ้นเยอะ ลด dependency ของพวก wrapper ไปได้
แต่ข่าวร้ายสำหรับผมก็คือ annotation พวกนี้เป็น feature ของ spring 2.5.x
โชคร้ายที่โปรเจคผมยังใช้ spring 2.0.x อยู่เลย

Related link from Roti

Thursday, February 14, 2008

Git กับการ revert

ใน svn เวลาเราทำอะไรผิดผลาด แล้วเราอยากย้อนกลับเนื้อหา file
มันก็มี case อยู่แค่ 2 case คือ
  1. file ยังไม่ถูก commit
    อันนี้ง่ายเลย แค่ใช้ svn revert ก็จบ
  2. file ถูก commit เข้าไปแล้ว
    อันนี้ก็ง่าย ก็แค่ revert โดยระบุ revision ที่เราต้องการ

    svn update -r REVISION_ID



ส่วนใน git นั้น มันไม่ง่ายเหมือน svn เพราะว่า มันมี state มากกว่า svn
โดย git มันจะมี state ของ file ได้ดังนี้
  • สถานะปกติ ยังไม่ถูกแก้ไข
  • ถูกแก้ไขแล้ว แต่ยังไม่ได้ add เข้า index (index ของ git เปรียบเสมือน buffer area ที่รอการ commit)
  • add เข้า index แล้ว แต่ยังไม่ได้ commit


จะเห็นว่ามี state ที่ content ค้างอยู่ใน index แต่ยังไม่ถูก commit เพิ่มเข้ามา(เมื่อเทียบกับ svn)
ดังนั้นถ้าเราจะ revert มันก็ต้องคิดว่าจะ revert ขั้นไหน เช่น

  1. file นั้นยังไม่ได้ถูก add เข้า index กรณีนี้ใช้คำสั่ง
    git checkout -- FILENAME

  2. file นั้นถูก add เข้า index แล้ว แต่ต้องการเอาออกมาจาก index
    git reset

    (state จะกลับไปเหมือนในข้อ 1)
  3. file นั้นถูก commit ไปแล้ว แต่ต้องการย้อนกลับออกมาสู่สถานะที่ file นั้นอยู่ใน index
    git reset --soft COMMIT_ID

  4. file นั้นถูก commit ไปแล้ว แต่ต้องการย้อนออกมาสู่สถานะก่อนการ add เข้า index
    ให้ทำคำสั่งในขั้น 3 แล้วต่อด้วยคำสั่งในขั้น 2
  5. file นั้นถูก commit ไปแล้ว และต้องการย้อนกลับมาสู่ version ก่อนหน้านั้น โดยเก็บ track การ revert ไว้ใน history ด้วย
    git revert COMMIT_ID

  6. file นั้นถูก commit ไปแล้ว แต่ต้องการย้อนไปสู่ version(ใดๆ) ก่อนหน้านั้น โดยไม่ทิ้งร่อยรอย
    git reset --hard COMMIT_ID



Note: ผมใช้คำว่า file ซึ่งไม่ถูกต้อง จริงๆต้องใช้คำว่า content (อ่านได้ใน "Git อีกทีน่า")
แต่เพื่อให้สื่อสารได้ง่ายขึ้น ขออนุญาติใช้คำว่า file

Related link from Roti

Wednesday, February 13, 2008

Open class

Ruby

>> class Integer
>> def fact
>> (2..self).inject(1) {|acc,v| acc * v}
>> end
>> end
=> nil
>> 7.fact
=> 5040


Grovvy

groovy:000> Integer.metaClass.fact = {   
groovy:001> delegate == 1 ? 1 : delegate * (delegate-1).fact()
groovy:002> }
===> groovysh_evaluate$_run_closure1@89ff97
groovy:000> 7.fact()
===> 5040


Scala

scala> class IntExt(v:Int) {             
| def fact() = {
| (2 to v).foldLeft(1) (_ * _)
| }
| }
defined class IntExt

scala> implicit def FactExt(v:Int) = new IntExt(v)
FactExt: (Int)IntExt

scala> 7 fact
res33: Int = 5040


เจ้า scala นี่แปลกตรงมันใช้หลักการ Implicit conversions
อย่างประโยค
7 fact จริงๆคือ
7.fact()
แต่ method fact มันไม่มีใน class Int
ดังนั้น compiler จะดูว่ามี Implicit conversions rule อะไรอยู่บ้าง
ในกรณีนี้ ก็คือจะแอบแปลงให้เป็นแบบนี้
(new IntExt(7)).fact()

Related link from Roti

Friday, February 08, 2008

Here documents

พึ่งรู้ว่า Here documents ของ Ruby ทำอะไรแปลกๆแบบนี้ได้ด้วย
puts 'Produce', '-------', <<PRODUCE, 'Dairy', '-----', <<DAIRY, '* Organic'
1. Strawberries*
2. Blueberries

PRODUCE

1. Yogurt
2. Milk*
3. Cottage Cheese

DAIRY

ใน rails (postgresql adapter) ก็มีใช้เหมือนกัน Charlie เขาชี้ให้ดู

Related link from Roti

Thursday, February 07, 2008

haskell -> python -> ruby -> ...

วันนี้เจอโปรแกรม haskell เขียนโดย sigfpe


def q(a,b,c):print b+chr(10)+'q('+repr(b)+','+repr(c)+','+repr(a)+')'
q("def q(a,b,c):print b+chr(10)+'q('+repr(b)+','+repr(c)+','+repr(a)+')'","def e(x) return 34.chr+x+34.chr end;def q(a,b,c) print b+10.chr+'main=q '+e(b)+' '+e(c)+' '+e(a)+' '+10.chr end","q a b c=putStrLn $ b ++ [toEnum 10,'q','('] ++ show b ++ [','] ++ show c ++ [','] ++ show a ++ [')']")

ทายสิว่ามันทำอะไร
ลอง copy ไปใส่ file ชื่อ x.hs ดู
แล้วลอง run แบบนี้

$ runhaskell x.hs > x.py
$ python x.py > x.rb
$ ruby x.rb > y.hs

$ runhaskell y.hs > y.py
$ python y.py > y.rb
$ ruby y.rb > z.hs

... (ทำไปเรื่อยๆ จนกว่าจะเบื่อ)

Related link from Roti

Friday, February 01, 2008

วันโชคร้ายของจักรยาน

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


View Larger Map

ขาไปทำงาน ส่วนใหญ่จะขี่แบบเรื่อยๆเฉื่อยๆ เพราะพึ่งกินอิ่ม
ส่วนขากลับนี่ ทางมันตรง ก็เลยใส่เต็มที่ ลมออกหูทุกวัน (เหนื่อย)

ส่วนวันนี้ ค่อนข้างโชคร้ายหน่อย
ออกจากบ้านมาได้หน่อยเดียว ยางดันแตก
ก็เลยต้องเข็นกลับไปปะยางที่บ้าน
ดูบาดแผลแล้วเกิดจากเศษแก้วแตก

หลังจากปะเสร็จก็ออกมาอีกครั้ง
ปั่นมาถึงแถววังหิน
คราวนี้ โซ่ขาด
โชคดีได้ร้านซ่อมมอเตอร์ไซด์แถวนั้น จัดต่อโซ่ให้

ปล. เมื่อต้นอาทิตย์ ขี่จักรยานไปเจอเจ้าปั้นเหน่งกำลังข้ามถนน
สอบถามได้ความว่า codegent ตั้งอยู่แถวนี้ด้วย
เห็นชุดทำงานมันแล้ว ต้องอิจฉา บริษัทอะไรใส่เสื้อกล้ามโชว์รักแร้ไปทำงานได้ด้วย

Related link from Roti

Monday, January 28, 2008

Y combinator in BarcampBangkok

คำเตือน
post นี้อาจทำให้เกิด stack overflow ในสมองคนอ่านได้ (เหมือนที่เกิดกับคนเขียนไปแล้ว)

กลับจาก BarcampBangkok ด้้วยสมองอันหนักอื้ง
สิ่งหนึ่งที่ยังติดใจไม่หายก็คือ source code ของ Haskell หน้าตาแบบนี้
(จาก presentation เรื่อง Yoda in Haskell ของ Ben)
fix f = let x = f x in x

โชคดีที่เหลือบมองเห็น comment ที่ Ben เขาเขียนเหนือ function นี้ มันมีคำว่า Y combinator อยู่ด้วย

เรื่อง Y combinator เคยผ่านตามาหลายครั้งแล้ว แต่ไม่เคยทำความเข้าใจได้สักที
วันนี้เลยได้ฤกษ์การพยายามครั้งใหม่

จากการ search google ก็พบว่า code ข้างบนนั้น มันมีชื่อเรียกว่า
Fixed point combinator
หลักการมันประมาณนี้

เริ่มที่คำว่า Fixed point ก่อน
fix point ก็คือ ค่าที่ทำให้ผลลัพท์ของ function มีคุณสมบัติแบบนี้
f(x) = x

ในทางคณิตศาสตร์ สมมติเรามี function อยู่อันหนึ่งซึ่งมี definition ดังนี้
f(x) = x^2

ค่า fixed point คือ function นี้ก็คือ 0 และ 1
เพราะ 0^2 == 0 และ 1^2 = 1

ใน functional language ทุกอย่างเป็น function
ดังนั้น เมื่อมี function ก็แสดงว่า มันก็ต้องมี fixed point ได้เหมือนกัน
ลองนึกถึง higher-order function f ที่รับ parameter เป็น function
มันก็ควรจะมี fixed point function p ซึ่ง
f(p) = p


Y combinator ก็คือ function g ซึ่งรับ parameter เป็น function f
ผลลัพท์ที่ได้ ก็คือ function p ซึ่งเป็น fixed point ของ function f
p = g(f), f(p) = p

ลองอ่านคำอธิบาย contract ของ Y combinator เทียบกับ function ข้างบน
Give me myself and a higher order function f, and I’ll return to you a function that is a fixed point of f.


ถ้าลองแทน function ข้างบนไปมา ก็จะได้
f(g(f)) = g(f)

อ้าจ้องไปจ้องมา แล้วมันก็คือ let x = f x in x นั่นเอง

ประเด็นหลักๆของไอ้ Fix ก็คงจะอยู่ที่เรื่อง Lambda expression
เนื่องจากเราไม่สามารถเขียน recursive function ในรูป lambda expression ได้
ใครอยากรู้ลองอ่านไอ้นี่เอาแล้วกัน
http://en.wikipedia.org/wiki/Lambda_abstraction#Recursion

ลองดูตัวอย่าง่ factorial บ้าง
ปกติเราเขียน factorial อย่างนี้

fact n = if n == 0 then 1 else n * fact (n-1)


ลองปรับ form ให้เป็น lambda expression โดยสร้าง function genfact ขึ้นมา

genfact f n = if n == 0 then 1 else n * f(n-1)

ลอง apply fix เข้าไป

fix genfact 3 = fact (fix genfact) 3
= 3 * ((fix genfact) 2)
= 3 * (genfact (fix genfact) 2)
= 3 * 2 * ((fix genfact) 1)
= 3 * 2 * 1 * ((fix genfact) 0)
= 3 * 2 * 1 * 1
= 6


พอแล้ว stack ผม overflow แล้ว

Related link from Roti

Tuesday, January 22, 2008

Mixin - Thin & Think interface

เห็น Bow เขียนเรื่อง Mixin กับ python ไป
ก็เลยคิดว่าจะลองขยาย Idea ของเรื่องนี้ดู

ในหนังสือ Programming in Scala ที่ผมกำลังอ่านอยู่
เขาพูดถึง Mixin ในประเด็น
Thin versus Thick interfaces

Thick interface คือ interface ที่อุดมสมบูรณ์ เพรียบพร้อมไปด้วย method, พร้อมจะให้ developer เด็ด method ที่ต้องการไปเชยชม
ส่วน Thin interface คือ interface ที่มี minimum method
อยากได้อะไรที่เกินกว่าที่จัดหาให้ ก็ออกแรงเขียน code เอาหน่อย
เมื่อเทียบ ruby กับ java แล้ว
java จะออกไปในแนว Thin interface
ส่วน ruby จะออกไปในแนว Think interface

ในหนังสือเขายกตัวอย่างง่ายๆคือ
interface Ordered {

}

ถ้าเป็น Thin interface แล้วหล่ะก็
method ที่ควรจะมี ก็น่าจะมีแค่นี้ก็พอแล้ว
interface Ordered {
/**
* return -1 if this
< that
* return 0 if this == that
* return 1 if this
> that
*/

public int compare(Ordered that);
}

ถ้า developer อยาก check เงื่อนไข <= ก็ให้ทำแบบนี้เอาเอง
if (this.compare(that) <= 0) {
...
}

แต่ถ้ามองแบบ Think interface ก็ควรจะเป็นแบบนี้
// สมมติว่า java define operator ได้
interface Ordered {
public boolean <= (Ordered that);
public boolean < (Ordered that);
public boolean > (Ordered that);
public boolean >= (Ordered that);
}


Mixin ก็เข้ามามีบทบาทตรงนี้แหล่ะ
กรณี scala เราเขียน Mixin โดยใช้ keywork ว่า Trait ได้ดังนี้
trait Ordered[a] {
// abstract method, class ที่ include mixin นี้ ต้อง implement method นี้
def compare(that: a): Int
def < (that: a): Boolean = (this compare that) < 0
def > (that: a): Boolean = (this compare that) > 0
def <= (that: a): Boolean = (this compare that) <= 0
def >= (that: a): Boolean = (this compare that) >= 0
}

จะเห็นว่า operator 4 ตัวหลังนี้ ถือว่าเป็นของแถม

ตัวอย่างของ Mixin ยอดนิยมของ Ruby ก็คือ Enumerable
class ที่ include Enumerable ไป จะต้อง implement method บังคับ 2 method
ก็คือ each กับ <=>
แต่หลังจาก include แล้ว ก็จะมี method ให้ใช้เพิ่มขึ้นมาอีก 22 method.
(น่าจะถือเป็นตัวอย่างที่ดีของ Think interface)

Related link from Roti

Thursday, January 17, 2008

หนังสือ The Rails Way

เห็นชื่อคนเขียนแล้วสนใจ, น้อง Roof ก็เลยแวะเอาหนังสือมาให้ผมยืมอ่านดู
เปิดอ่านบทแรกก็ประทับใจเลย เพราะมันว่าด้วย process bootstrap ของ Rails
นั่งอ่านได้อย่างเพลิดเพลิน เพราะคนเขียนรู้จริงและรู้ลึก

อย่างบทที่สองที่ว่าด้วย Controller ก็มีพูดถึงการ send_file จาก controller
คนเขียนก็พูดถึงว่า method send_file ถ้าเราไปใช้ใน mongrel
มันจะ load เนื้อหาทั้งหมดเข้ามาไว้ใน memory
ทำให้มีปัญหากับ file ขนาดใหญ่ๆได้
เขาแนะนำทางออก ด้วยการเปลี่ยนไปใช้ กลไก X-Sendfile ซึ่ง support บน Apache(module mod_xsendfile), Lighttpd
ด้วยการ ส่ง response ที่มี header เป็น

response.headers['X-Sendfile'] = path_to_file

เจ้า web server ก็จะจัดการส่ง file กลับไปให้เราเอง

หลังจากอ่านจบบท ผมก็เข้าไป check feed
ก็พบว่าใน Rails edge พึ่งมีการ submit changeset 8628
ซึ่งเป็นการปรับปรุงคำสั่ง send_file ให้มี option แบบนี้

send_file '/path/file.png', :x_sendfile => true, :type => 'image/png'

นี่ถ้าไม่อ่านมาก่อน ก็คงไม่ได้สนใจเจ้า feed นี้แน่ๆเลย

ตอนนี้วางแผนคร่าวๆไว้ว่า จะพยายามอ่านให้ได้วันละ 1 บท
จะได้รีบเอาไปคืนน้อง Roof

Related link from Roti

Tuesday, January 15, 2008

แปลงเลข Roman

หลายคนคงเคยเห็นโจทย์ประเภท

"จงเขียนโปรแกรมแปลงเลขที่รับเข้ามาให้เป็นเลข Roman"

วันนี้ผมผ่านไปเห็น haskell code ที่เขียนโดย geezusfreeek ใน reddit
แล้วก็มี Bill Mill ช่วยเขียนอธิบายการทำงาน

เห็นแล้วงามจับใจ

ถัดไปอีกวันหนึ่ง David pollak ก็เขียนด้วย scala บ้าง

Related link from Roti

Monday, January 14, 2008

Server in the house

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

วันนี้เห็นนิทานเล่มนี้ใน net แล้วอดหัวเราะไม่ได้



ลองตามไปอ่านดู Link
ในหน้า 3 จะเห็นว่าเป็นหนังสือของ Microsoft, Windows Home Server

Related link from Roti

Friday, January 11, 2008

Qi4j

Qi4j ออกเสียงว่า "chee for jay"
ความพยายามใหม่ของ Rickard Öberg (co-founder of JBoss)
ที่ตั้งชื่อว่า "Composite Oriented Programming"
โดยมีฐานต่อยอดจาก Java
Qi4j is a framework for domain centric application development, including evolved concepts from AOP, DI and DDD.


แนวคิดหลักๆคือ
1. fine-grained parts, แยกส่วน model ออกเป็นหลาย part เช่น behavior part กับ state part
แล้วเวลาใช้ ค่อยนำมาประกอบกันในลักษณะของ Composite
การแยกส่วนแบบนี้เชื่อว่า จะนำมาสู่การ reuse ที่ดียิ่งขึ้น
2. Context-base behavior, composite เดียวกัน แต่ถ้าอยู่ใน context ที่ต่างกัน
ก็จะมี behavior ที่ต่างกันได้

ลองดูเฉพาะข้อ 1 กันก่อน
ถ้าใครคุ้นกับ ruby, พออ่านข้อ 1 แล้วอาจจะนึกถึง Mixin
ลองเปรียบเทียบกับตัวอย่างใน tutorial ที่ว่าด้วย HelloWorld ของ Qi4j ดู

ปกติ HelloWorld model ถ้าเขียนแบบปกติ ก็เขียนได้ดังนี้
public class HelloWorld {
String name;

public String say() {
return "hello " + getName();
}

public void setName(String name) {
this.name = name;
}

public void getName() {
return name;
}
}

ถ้าเราแยก HelloWorld ออกเป็นส่วนๆ เราจะเห็นว่า มันประกอบด้วย
behavior part ซึ่งก็คือ method say
กับ state part ซึ่งก็คือ property name

เขียนแบบ Qi4j ก็จะต้องทำดังนี้
(ใน Qi4j เขามี concept ว่า "Classes ard dead, Long live interfaces.")

เริ่มด้วย declare model เราด้วย interface
public interface HelloWorld
extends HelloWorldBehaviour, HelloWorldState
{
}

@Mixins( HelloWorldStateMixin.class )
public interface HelloWorldState
{
Property<String> name();
}

@Mixins( HelloWorldBehaviourMixin.class )
public interface HelloWorldBehaviour
{
String say();
}

implement logic ของ behavior
public class HelloWorldBehaviourMixin
implements HelloWorldBehaviour
{
@ThisCompositeAs HelloWorldState state;

public String say()
{
return state.phrase() + " " + state.name();
}
}

implement logic ของ state
public class HelloWorldStateMixin
implements HelloWorldState
{
@PropertyField Property<String> name;

public Property<String> name()
{
return name;
}
}

สุดท้ายประกอบทั้งหมดเข้าหากัน
public interface HelloWorldComposite
extends HelloWorld, Composite
{
}


ลองเปรียบเทียบกับการเขียน Mixin ด้วย ruby ดูบ้าง
module HelloState
attr_accessor :name
end

module HelloBehavior
def say
puts "hello #{@name}"
end
end

class HelloWorld
include HelloState
include HelloBehavior
end


ตอนนี้ tutorial ในส่วน context base behavior ยังไม่ออกมา
ก็ต้องรอดูกันต่อไป ว่าจะออกมาหน้าตาเป็นอย่างไร

Related link from Roti

Thursday, January 10, 2008

ปวดหัวกับ PXE

ช่วงนี้ผมกำลัง config พวก Network boot ด้วย PXE
แล้วก็เจอปัญหาที่ว่า เครื่องรุ่นใหม่ๆ สามารถ boot ได้ตามปกติ
แต่เครื่องรุ่นเก่ากลับเจอ error ฟ้องว่า
PXE-E53 No boot filename received

ท้าวความก่อนว่า เจา PXE protocol นี่มันจะอาศัย DHCP protocol เป็นพาหนะ
เนื่องด้วย DHCP protocol เขาออกแบบไว้ดี, มันออกแบบให้มี slot options เผื่อไว้
เจ้า PXE protocol ก็เลยอาศัย options นี่แหล่ะเป็นช่องทางไว้สื่อสาร

สำนึกแรกผม เมื่อเห็นคำว่า "No boot filename received"
ก็ต้องหมายความว่า DHCP server ไม่ได้ส่ง option นี้กลับมา
แต่ถ้ามันไม่ได้ส่งกลับมาแล้ว ทำไม เครื่องรุ่นใหม่ๆ boot ได้หล่ะ
หรือว่า config DHCP server ไม่ถูกหว่า ทำให้มันส่งกลับมาไม่ถูก
ก็เลยหมกมุ่นแต่กับการแก้ configuration ของ DHCP
ซึ่งไม่ว่าจะแก้อย่างไร ก็ไม่มีความคืบหน้าใดๆให้เห็น

หลังจากพายเรือวนในอ่างมาหลายวัน
สุดท้ายก็ต้องใช้อาวุธลับ นั่นก็คือ wireshark + tcpdump เข้ามาช่วย debug
(โดยมีพี่สมภพเป็นคนช่วยแกะ packet)
ได้ความว่า

ในเครื่องใหม่ๆ protocol ของ PXE (เฉพาะครึ่งแรก ตอนที่ solve หา bootfile name กับ tftp server)
จะเป็นดังนี้


  • client broadcast จาก port 68 ไปยัง port 67 อันนี้เรียกว่า DHCP Discover
  • server broadcast กลับมา พร้อมค่า configuration ต่างๆ อันนี้เรียกว่า DHCP Offer
  • client broadcast ไปอีก โดยส่งค่า configuration ที่ได้จากขั้น 2 ไปด้วย ขั้นนี้เรียก DHCP Request
  • server broadcast ยืนยันรับทราบกลับมา ขั้นนี้เรียกว่า DHCP ACK

หลังจาก 4 step นี้แล้ว เจ้า PXE client ก็จะเข้าสู่ขบวนการ download file จาก tftp server

แต่ในเครื่องเก่าๆ protocol ของ PXE มันเป็นแบบนี้
เริ่มด้วย 4 อันแรกนี่เหมือนปกติ ทุกอย่าง แต่มันเพิ่มมาอีก 1 อันคือ


  • client ส่ง DHCP Request ไปที่ server ซำ้อีกครั้ง
    แต่สิ่งที่แตกต่างจากขั้นแรก ก็คือ
    • การส่งไม่ได้ใช้กลไก broadcast เหมือนครั้งแรก (เนื่องจากมันได้เบอร์ IP ของตัวเองมาแล้ว)
    • port ที่ request ไป กลายเป็น port 4011



เมื่อ check ที่ server ก็พบว่า ที่ port 4011 นั้น ยังไม่มี process start อยู่เลย
ความคิดแรกสุด ก็คือ เราทำ port forwarding จาก port 4011 ส่งไป 67 ดีไหม
หรือ จะติดตั้ง PXE daemon ซึ่งปกติเป็นคนรับผิดชอบเจ้า port นี้อยู่

สุดท้ายเลือกทางง่าย ก็คือลง PXE daemon ไปก่อน (เพราะทำ forward ไม่เป็น)

Related link from Roti

Friday, January 04, 2008

Singleton ?

ใครใช้ Java ก็คงรู้สึกว่า Singleton เป็นส่วนหนึ่งของชีวิต (โดยเฉพาะ Lazy-loaded)
เมื่อก่อน (นานมาแล้ว)
เขานิยมใช้ pattern แบบนี้
class MyClass {
private static MyClass instance;

private MyClass() {}

public static MyClass getInstance() {
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}

ซึ่งก็ใช้ได้ ยกเว้นว่า ถ้าอยู่ในสภาพแวดล้อมแบบ concurrency แล้ว ก็มีโอกาสที่ MyClass จะถูกสร้างซ้ำซ้อนได้มากกว่า 1 ครั้ง
สมัยนั้นมีคนถกเถียงเรื่องนี้อยู่วุ่นวาย แต่ผมขี้เกียจอ่านและติดตาม
(ใครสนใจว่าถกกันอย่างไร ลองอ่านตัวอย่างอันนี้ดู The double-checked locking is broken)
ถึงจะรู้ว่ามีปัญหาเรื่อง concurrency แต่ผมก็ใช้ pattern นี้มาตลอด (โดยแอบเข้าข้างตัวเองว่า เอาน่า อย่างมากก็เปลือง resource นิดๆหน่อยๆ)
จนมาภายหลังที่มี springframework ออกมา ผมก็เลยยกหน้าที่นี้ให้ spring ไป

จนมาเมื่อวาน อ่านหนังสือเล่มหนึ่ง พบว่าเขามีวิธีการเขียนแบบใหม่ที่ดีกว่ากว่าแล้ว
เรียกว่า Initialization on demand holder idiom
public class Something
{
private Something() {}

private static class LazyHolder
{
private static final Something something = new Something();
}

public static Something getInstance()
{
return LazyHolder.something;
}
}


เป็นอันว่าเรื่องนี้ผมตกข่าวไป เพราะใน history ของ wiki เห็นเขาเขียนเรื่องนี้มาตั้งแต่ July 2006 แล้ว

Related link from Roti

Thursday, January 03, 2008

CouchDB เตรียมเข้า Apache

สืบเนื่องมาจากเรื่องเก่า ที่เคยพูดถึง CouchDB
ตอนนี้มีข่าวใหม่ว่า IBM เข้าไปเป็น sponsor
และจะนำ CouchDB เข้าไปอยู่ใน Apache Software Foundation อีกด้วย

Related link from Roti