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

Friday, December 28, 2007

Mail.app encoding

ช่วงนี้ที่บริษัทใช้ environment ผสมกัน ระหว่าง mac, windows, linux
ก็เลยเจอปัญหาพวก encoding กันบ่อยๆ

ตัว source code ไม่มีปัญหาแล้ว เพราะหลังๆผมบังคับให้ไปใช้ utf-8 กันหมดแล้ว

ตัวที่ทำให้ปวดหัวมากหน่อยก็คือ Mail
เนื่องจากผมใช้ Mail client เป็น Mail.app ของ apple
ทำให้เวลา forward, reply กันไปมา แล้วเกิดปัญหา encoding เพี้ยนขึ้น

ปัญหาหลักๆ ตัวหนึ่งเกิดที่ Mail.app
โดยปกติ mail ที่ส่งมา มันจะมี encoding แปะมาทั้งส่วน body และส่วน subject

Subject: =?windows-874?B?UkU6ILe0ys26wNLJ0uS3wg==?=
Content-Type: text/plain;
charset="windows-874"

แต่เวลาผม reply ต่อออกไป, เจ้า Mail.app ดัน set encoding เป็นแบบนี้แทน

Content-Type: text/plain;
charset=CP874;
delsp=yes;
format=flowed
Subject: =?UTF-8?B?RndkOiDguYDguKHguKXguYzguInguJrguLHguJrguJfguLXguYg=?=
=?UTF-8?B?4Liq4Lit4LiH?=

ตัว encoding ของ subject กับ body ที่ไม่ตรงกัน ถือเป็นจุดตั้งต้นของความเพี้ยน
(ยังไม่ตามต่อไป ว่า mail client ตัวไหนไปทำให้เพี้ยน แต่คาดว่าน่าจะเป็น outlook express)

ตอนนี้ผมก็เลยแก้ไข โดยการประพฤติตนเป็นพนักงานที่ดี โดยการ set default charset
ของผมให้เหมือนประชากรส่วนใหญ่ไปก่อน (ค่านี้ไม่มีหน้าจอ UI ให้ set ต้อง command line อย่างเดียว)

$ defaults write com.apple.mail NSPreferredMailCharset "CP874"

Related link from Roti

Thursday, December 27, 2007

กลับด้าน

ปกติเวลาสร้างตึกเราจะสร้างจากล่างขึ้นบน
ส่วนเวลารื้อถอนก็ทำกลับกัน นั่นคือค่อยๆทุบจากข้างบนลงมา (ยกเว้นกรณีใช้ระเบิด)

คำถามก็คือ แล้วตึกที่เขาทุบจากล่างขึ้นบนหล่ะมีไหม
พบคำตอบได้ที่นี่
http://www.dailymail.co.uk/pages/live/articles/news/news.html?in_article_id=472602&in_page_id=1770

Related link from Roti

Tuesday, December 25, 2007

vm เอ๋ย vm

ช่วงนี้ได้ server ใหม่มาใช้ เป็นพวก duo-core ด้วย ก็เลยได้ฤกษ์ทดลองเอา Xen มาใช้เสียที
(เดิมใช้แต่ vmware-server ซึ่งก็ถือว่า work มากแล้ว)

ลงแล้ว ก็เจอปัญหาอยู่หลายอย่างเหมือนกันเช่น
  • จะ enable vt ได้ ต้อง set config ใน bios ด้วย (default เป็น off)
    ทำให้หลงคิดว่าเป็นปัญหาที่ Xen อยู่พักใหญ่
  • กรณีที่เป็น Intel, แผ่นติดตั้งใหม่ๆ พวก ubuntu, opensuse ไม่สามารถติดตั้งบน xen ได้เลย
    เพราะมันใช้ isolinux ที่แสดง Logo เป็นรูป graphic
    ซึ่งจะทำให้ xen มัน hang และนิ่งไป
    ทางแก้ไข ก็คือต้องแตก แผ่นออกมา แล้วเข้าไปแก้ option ของ isolinux ไม่ให้แสดง Logo แบบ graphic
    อ่านเพิ่มเติมใน https://bugs.launchpad.net/ubuntu/+bug/83642
  • Windows 98 ติดตั้งบน Xen ไม่ได้
    ตายตั้งแต่ขั้น boot แผ่น setup เลย, โดยมีปัญหาตอน load driver ของ cdrom
  • ใช้ Xen คู่กับ vmware ไม่ได้

Related link from Roti

Friday, December 21, 2007

protected with_scope

feature หนึ่งใน rails ที่มีคนใช้บ่อยก็คือ with_scope
ใน rails 2.0, with_scope ได้เปลี่ยน visibility จากเดิมที่เป็น
public method ไปเป็น protected method แล้ว
นั่นหมายความว่า เราจะใช้ with_scope ได้เฉพาะใน model เท่านั้น (ระวังประโยคนี้ด้วย ใน ruby เราสามารถหลายๆอย่างที่ไม่น่าเป็นไปได้ได้)

ที่เป็นดังนี้ ก็เพราะว่ามีคนเอา with_scope ไปใช้ที่ระดับ controller กันเยอะ
แล้วก็เจอผล ที่ไม่ตรงตามที่คาดหวัง
เช่น เอาไปใช้ใน filter แบบนี้
แล้วไปติดปัญหาตอน save ที่มันไม่ยอม save ข้อมูล default จาก with_scope ให้
class ApplicationController  < AC::Base 
def scope_story_by_account
Story.with_scope(:find =>
{:conditions => {:account_id => current_account.id},
:create => {:account_id => current_account.id}) {
yield }
end
around_filter :scope_by_account
end

class StoryController < ApplicationController
def create
@story = Story.new(params[:story])
# do some stuff to @story here...
@story.save #...@story.account_id won't be set
end
end


แน่นอน การเปลี่ยนแปลงแบบนี้ ก็ต้องมีคนบ่น
บางคนก็บ่นว่า
I think it's a poor choice to limit something that people find useful just because it doesn't fit your idea of useful. Why not let the coder make these
decisions?


คำตอบที่ได้น่าสนใจ (David Heinemeier คนเขียน rails เป็นคนตอบเองเลย)
You must be new here :). Rails is opinionated software. We make things that we consider good style easy to do and bad style hard. Or rather, we make good style beautiful and bad style ugly. So even though with_scope is going protected, you can still use it in filters if you really, really want to. Just use send(:with_scope) -- that'll side-step the access control. Yes, that'll be ugly and it's intended to be.


อ่านตัวอย่างการใช้ที่เหมาะสมจากที่นี่ “WITH_SCOPE WITH SCOPE”

Related link from Roti

Wednesday, December 19, 2007

Caching in AssetTagHelper

ใน rails 2.0 มี feature อยู่ตัวหนึ่งที่ช่วย combine javascript หรือ CSS หลายๆ file ให้รวมกันเป็น file เดียว
ซึ่งจะช่วยเพิ่มความเร็วของการ load web ของเราได้
อย่างเช่น สมมติว่าเรามีการ include javascript ดังนี้
javascript_include_tag "prototype", "cart"

ปกติมันจะ render เป็นแบบนี้
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="/javascripts/cart.js"></script>

แต่ถ้า เราเปิด feature cache ด้วยการใส่ option แบบนี้
javascript_include_tag "prototype", "cart", :cache => "shop"

มันก็จะ render แบบนี้แทน
<script type="text/javascript" src="/javascripts/shop.js"></script>

โดยเจ้า shop.js เกิดจากการ concat prototype.js เข้ากับ cart.js
และ gzip ก่อนส่งให้ browser

เงื่อนไขสำคัญที่จะทำให้ cache ทำงาน ก็คือ ใน environment file ต้องมีการกำหนด
config.action_controller.perform_caching             = true

ซึ่งใน default environment ค่านี้จะถูกกำหนดเป็น true ใน production mode
และเป็น false ใน development mode

อ่านรายละเอียดใน

Related link from Roti

Tuesday, December 18, 2007

ศิษย์ - ครู

อ่านเรื่องนี้แล้วรู้สึกระทึกใจ

http://gotoknow.org/blog/phoenix-mirror/154417

อ่านแล้วนึกถึงเจ้าลูกชาย ว่าจะได้พบเจอครูแบบไหน

Related link from Roti

SimpleDB

SimpleDB เป็นบริการใหม่ของ Amazon
ประเด็นที่ผมสนใจก็คือ มีคนว่ากันว่า มัน implement ด้วย erlang
แต่บางคนก็บอกว่ามันมีพื้นฐานมาจาก Amazon 's Dynamo ต่างหาก
ซึ่งตัว Dynamo นี้ implement ด้วย Java

บน Erlang มีคนทำ database ทำนองเดียวกับที่ SimpleDB ทำเหมือนกัน
ชื่อว่า CounchDB ซึ่งออกมาก่อน SimpleDB ได้พักใหญ่แล้ว
ประเด็นที่น่าสนใจสำหรับ CouchDB ก็คือ
มัน implement ด้วย erlang + spidermonkey (javascript engine)
แต่ couchDB ก็มีปัญหาซึ่งยังแก้ไม่ตก
ซึ่งในมุมของ erlang ก็ถือว่าเป็นปัญหาที่น่าสนใจมาก (สำหรับคนที่ต้องการทำ fault tolerant)

Related link from Roti

Tuesday, December 11, 2007

native dependency with maven

วันนี้มีโจทย์ที่ต้อง config maven ให้เลือกใช้ dependency แบบที่ต้องขึ้นอยู่กับ platform
ต้นตอของเรื่องก็คือเจ้า swt library ที่ต้องเลือก jar ให้ถูกกับเครื่องที่จะไป run
เช่นถ้า build บน linux ก็ควรจะใช้ swt-linux-gtk-native.jar
หรือถ้าบน windows ก็ต้องเป็น swt-win32-native.jar

เริ่มต้นด้วยการ setup repository ก่อน
หน้าตาของ repository ก็ประมาณนี้

+ swt
+ swt-linux-gtk-native
+ 3.3.0
- swt-linux-gtk-native-3.3.0.jar
+ swt-win32-native
+ 3.3.0
- swt-win32-native-3.3.0.jar


จากนั้นก็ให้กำหนด profiles ใน pom.xml
สังเกตุว่าใน profile จะมี activate block ที่ใช้ข้อมูล os เป็น condition ในการ activate
<profiles>
<profile>
<id>unix</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-linux-gtk-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
<profile>
<id>win32</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-win32-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
</profiles>


จากนั้นใน dependency list ของเรา ก็ให้อ้างถึง jar โดยใช้ property name แทน

<dependency>
<groupId>swt</groupId>
<artifactId>${swt.os-dep}</artifactId>
<version>${swt.version}</version>
</dependency>

Related link from Roti

Tuesday, December 04, 2007

dinosaur บุกโรงเรียนลูก

เนื่องในช่วงวันพ่อปีนี้ โรงเรียนอนุบาลของลูกผมก็ส่งคำเชิญมาว่า ให้ไปร่วมกิจกรรมในวันพ่อกัน
แต่มีข้อบังคับมาด้วยว่า คุณพ่อต้องเตรียมกิจกรรมมาร่วมกับเด็กๆด้วย
เนื่องจากช่วงนี้ลูกชายผมกำลังบ้า dinosaur, ผมก็เลยเตรียมอุปกรณ์ กะว่าจะไปชวนเด็กๆ ทำฉาก dinosaur กัน

ดูรูปแล้วกันว่าสนุกแค่ไหน

เริ่มจากการบรรยายสรุปก่อน


จากนั้นก็วาดรูป dinosaur, ใครอยากได้พันธุ์ไหน ระบุได้
แล้วก็ให้เด็กๆไประบายสี
ระหว่างนั้น ผมก็เตรียมทำฉาก


สุดท้ายก็มาจัดฉากกัน
dinosaur เยอะไปหน่อย คับฉาก


Note:
เด็ก 14 คน, วาด dinosaur ไป 14 ตัว
ยิ่งวาดยิ่งมัน นึกในใจว่ามือกำลังเริ่มเข้าที่เลย น่าจะมีเด็กเยอะกว่านี้อีกหน่อย

Related link from Roti

Wednesday, November 28, 2007

git-stash

เมื่อวาน sirn comment ถึงเจ้า Mercurial Queues extension ของ Mercurial
ตัว mq ไม่เคยใช้ แต่อ่านดูถึง feature ที่สามารถทำ stack ของ patch ได้ก็รู้สึกเคลิ้ม

วันนี้อ่านเจอคำสั่ง git-stash ที่สามารถ pending สิ่งที่ทำอยู่ แล้วย้อนกลับไปที่ clean state ก่อนหน้าได้
อันนี้แหล่ะ use-case ที่เจอบ่อย

สมมติกำลังทำ feature A อยู่ ระหว่างที่กำลังเมามันกับการ refactor
ก็ดันเกิดมี bug ร้ายแรงที่ต้องรีบแก้
ถ้าเป็น svn ง่ายสุด ก็อาจจะ checkout อีก copy
แล้วตามไปแก้ที่นู่น
แต่ถ้าเป็น git หล่ะ
# while doing something
git stash
# fix bug
git commit -a -m "xxxxx"
git stash apply
# กลับมาเมามันต่อ

Related link from Roti

Tuesday, November 27, 2007

Git อีกทีน่า

วันนี้ลองมาดูประเด็นที่ทำให้คนที่เคยใช้ cvs หรือ svn เกิดอาการเหวอเมื่อใช้ git ได้

ขั้นตอนปกติในการใช้ git
  1. สร้าง working copy ด้วยคำสั่ง git clone url
  2. แก้ไข file ที่ต้องการ
  3. สั่ง git add yourfilename เพื่อ mark ว่าต้องการ commit file นี้ในอนาคต
  4. สั่ง git commit
ดูเผินๆแล้ว จะเห็นว่าไม่น่ามีอะไร
สำหรับ user ที่ใช้ svn มาก่อน ก็จะบอกว่า
ขั้นที่ 3 มันเป็นขั้นที่เกินมาเนี่ย ใน svn ไม่เห็นต้องสั่งอะไรแบบนั้นเลย

ถ้าไม่รู้ว่าจริงๆแล้ว git ทำอะไรในชั้นที่ 3 มันก็อาจจะทำให้เกิด error ขึ้นได้
ลองจำลองเหตุการณ์ใหม่
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git commit
ผลก็คือ เฉพาะการแก้ไขในขั้นที่ 2 เท่านั้น ที่ถูก commit เข้าไป
การแก้ไขในขั้นที่ 4 ไม่รวมอยู่ในนั้นด้วย
ขั้นตอนที่ถูกก็คือ
  1. user clone project
  2. user แก้ไข file x
  3. user สั่ง git add x เพื่อบอก git ว่าต้องการที่จะ commit file นี้
  4. user กลับไป edit file x อีกรอบ
  5. user สั่ง git add x อีกรอบ
  6. user สั่ง git commit
จะเห็นว่า git มีความแตกต่างกับพวก svn หรือ cvs อยู่
ตัว git เองมองสิ่งที่ track คือ content ไม่ใช่ file ทั้ง file
การที่เราสั่ง git add เป็นการสั่ง add content ที่เปลี่ยนแปลง ณ ขณะนั้น
การเปลี่ยนแปลงที่ตามหลังมา ไม่นับว่าอยู่ในกลุ่มที่จะ commit

keyword "Content" ยังนำความแตกต่างมาสู่วิธีการ commit change ด้วย
เราสามารถจัดการ content ที่ต้องการให้้ git commit ได้ในระดับ fine-grained
สมมติว่ามีการเปลี่ยนแปลงใน file ที่เราแก้ไข 5 จุด
เราสามารถเลือกได้ว่า การ commit ครั้งนี้ เราต้องการที่จะ commit เฉพาะจุดที่ 2 กับ 3 เท่านั้นนะ

ในการที่จะจัดการกับ content ที่จะ commit
เราสามารถทำได้โดยการ switch เข้าสู่ mode interactive ด้วยคำสั่ง
git add -i
ลองดูตัวอย่างการใช้งาน
สมมติว่าผมมี file x อยู่ ใน file นี้ผมมีการแก้ไข 2 จุดก็คือ
มีการ insert บรรทัดแรกสุด กับ บรรทัดท้ายสุดลงไป

เริ่มด้วยคำสั่ง git add -i
git ก็จะแสดงให้เห็นว่า file x มีการเปลี่ยนแปลงเพิ่มบรรทัดอยู่ 2 จุดนะ

pphetra@pann:~/temp/s$ git add -i
staged unstaged path
1: unchanged +2/-0 x

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help

ลองสั่ง help ดู ก็จะได้คำอธิบายย่อๆดังนี้

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 8
status - show paths with changes
update - add working tree state to the staged set of changes
revert - revert staged set of changes back to the HEAD version
patch - pick hunks and update selectively
diff - view diff between HEAD and index
add untracked - add contents of untracked files to the staged set of changes

ลองเลือก menu patch
มันก็จะแสดง file ขึ้นมา ให้เราก็ระบุหมายเลข file ที่ต้องการ

*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 5
staged unstaged path
1: unchanged +2/-0 x
Patch update> 1
diff --git a/x b/x
index abe1496..61916db 100644
--- a/x
+++ b/x
@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? ?
y - stage this hunk
n - do not stage this hunk
a - stage this and all the remaining hunks
d - do not stage this hunk nor any of the remaining hunks
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks

กรณีนี้เราต้องการ ที่จะ commit เฉพาะ row บนสุดเท่านั้น
เราก็สั่ง split
จากนั้น git จะแสดงการเปลี่ยนแปลงให้เราตัดสินใจทีละอัน

@@ -1,3 +1,5 @@
+hi bunny
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/s/?]? s
Split into 2 hunks.
@@ -1,3 +1,4 @@
+hi bunny
hello
hi pok
hi pann
Stage this hunk [y/n/a/d/j/J/?]? y
@@ -1,3 +2,4 @@
hello
hi pok
hi pann
+hi pune
Stage this hunk [y/n/a/d/K/?]? d
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 7
Bye.
จะเห็นได้ว่า การจัดการ patch ของ git เร้าใจกว่า svn เยอะ
แต่ก็อาจจะนำความเวียนหัวมาสู่ผู้ที่คุ้นเคยกับโลกของ svn อย่างเดียวได้

Related link from Roti

Monday, November 26, 2007

Dojo DateWidget in Thai [Dojo 1.0]

จากปีก่อน ที่เขียนถึง Dojo DateWidget in Thai สำหรับ version 0.4
วันนี้ได้ฤกษ์ลอง version 1.0 บ้าง



ใน version 1.0, widget สำหรับ date ใช้ชื่อว่า DateTextBox
ซึ่งประกอบด้วย widget 2 ตัวประกอบกัน ก็คือ TextBox กับ _Calendar
โดยตัว _Calendar จะเป็น popup ที่ทำหน้าที่แสดงปฎิทิน
(เครื่องหมาย _ นำหน้า หมายความว่า dojo ตั้งใจให้ widget นี้เป็น internal widget)

สิ่งที่เราต้องการ ก็คือ เราต้องการให้ internal state ของ DateTextBox
ยังเก็บค่า Date เป็น Date object ปกติของ javascript อยู่
แต่ต้องการให้เฉพาะส่วนการแสดงผลเท่านั้น ที่แสดงผลเป็นปี พ.ศ.
นอกจากนั้นยังต้องการให้ calendar ที่ popup ขึ้นมาแสดงผลเป็นภาษาไทยด้วย

ใน dojo 1.0, ตัว widget ซับซ้อนขึ้น
มีการ reuse เยอะขึ้น อย่างเช่น DateTextBox นั้นมี inherited structure ดังนี้

DateTextBox -> TimeTextBox -> RangeBoundTextBox ->
MappedTextBox -> ValidationTextBox -> TextBox

จากการไล่การทำงานของ DateTextBox เราจะพบจุดตัดที่เราสามารถ override
ส่วนการแสดงผลได้ที่ method format และ parse
สามารถนำมา declare class ตัวใหม่ที่ชื่อ ThaiDateTextBox ได้ดังนี้
dojo.declare("ThaiDateTextBox", [dijit.form.DateTextBox], {

// กำหนดตัว calendar ตัวใหม่ที่แสดงผลภาษาไทย
_popupClass: "ThaiCalendar",

// value ที่รับเข้ามา มี type เป็น date object
// เมื่อได้มาแล้วก็โยนไปให้ dojo.date.locale.format จัดการ format ให้ก่อน
// แล้วค่อยนำมาตัด ปี เพื่อ + 543 หรือ 43 เข้าไปอีกที (ขึ้นอยู่กับ format)
// note: มี bug กรณี format yy ที่จะมี overflow
format: function(value, constraints) {
if(!value || value.toString() == this._invalid){ return null; }
var sidx = constraints.datePattern.indexOf('yy');
var fvalue = dojo.date.locale.format(value, constraints);
var ycnt = 2;
var adj = 43;
if (/yyyy/.exec(constraints.datePattern)) {
ycnt = 4;
adj = 543;
}
return fvalue.substr(0,sidx) + (parseInt(fvalue.substr(sidx, ycnt)) + adj);
},

// value ที่รับเข้ามา จะอยู่ในรูป datePattern ที่เรากำหนดตอน declare widget
// เมื่อได้มา ก็ทำการ - 543 เพื่อแปลงเป็น ค.ศ. ก่อนส่งให้ dojo.date.locale.parse
parse: function(value, constraints) {
var sidx = constraints.datePattern.indexOf('yy');
if (/yyyy/.exec(constraints.datePattern)) {
nyear = parseInt(value.substr(sidx, 4)) - 543;
} else {
nyear = String(parseInt(value.substr(sidx, 2)) + 2500 - 543).substr(2,2);
}
var newValue = value.substr(0, sidx) + nyear;
return dojo.date.locale.parse(newValue, constraints);
}
});


ส่วนตัว Calendar ที่ popup ขึ้นมาให้ user เลือกนั้น
ถ้าไล่ดู code ภายใน จะเห็นได้ว่า เขามีกลไก i18n ในการแสดงผลพวกชื่อเดือน ชื่อวัน
แต่เนื่องจากเจ้า dojo 1.0 มันไม่ได้ bundle locale ของไทยมาให้ด้วย
เราจึงต้องสร้างขึ้นมาเอง

ในการที่จะสร้าง locale th
เจ้า dojo ได้เตรียมกลไกไว้เรียบร้อยแล้ว
โดย script ที่จะใช้สร้าง จะอยู่ใน directory util/buildscript/cldr
ซึ่ง dojo ได้นำข้อมูล xml จาก Common Locale Data Repository
และเตรียม xlst file ซึ่งใช้สำหรับ generate เป็น javascript code ให้เราแล้ว
แค่เราเข้าไปแก้ไข build.xml เพิ่ม "th-TH" เข้าไปในรายการที่ต้องการให้ generate

<!-- Arbitrary defaults. locales and currencies properties can be altered or eliminated to build the entire set -->
<property name="locales" value="en-us,th-th"/>


ประเด็นปัญหาถัดไปก็คือตัวปี พ.ศ.
ถึงแม้เราจะเลือกใช้ locale th-TH แล้วก็ตาม
มันก็ยังมีปัญหาว่า ปี ยังได้เป็นปี ค.ศ. อยู่ดี
ตรงส่วนนี้ เราต้องแอบเข้าไป override method ใน Calendar widget
ในใน dojo 0.4, จุดตัดสำหรับเรื่องนี้ค่อนข้างง่าย เพราะคนเขียนเขาแบ่ง method ออกเป็น method ย่อยๆ เพื่อให้อ่านง่าย
แต่ใน dojo 1.0 คนเขียน เขาเขียนส่วน render ยาวเป็นพรืด ทำให้เราเลือก override ยากหน่อย

สำหรับกรณีนี้ เราเลือก override method "_setText"
ซึ่งเป็นทางเลือกที่ไม่ค่อยดีนัก เพราะเป็นการ override แบบที่แอบไปใช้กลไกภายในของมัน
เพราะมันมีผลว่า ถ้ามันมีการ upgrade internal structure เมื่อไร
class ของเราก็จะมีโอกาส broken ได้โดยง่าย
dojo.declare("ThaiCalendar", [dijit._Calendar], {
_setText: function(node, text) {
if (/YearLabelNode/.exec(node.getAttribute("dojoattachpoint"))) {
arguments[1] = parseInt(text) + 543;
}
return this.inherited('_setText', arguments);
}
});


เวลานำไปใช้ ก็ใช้แบบนี้
<script type="text/javascript" src="./js/dojo/dojo.js"
djConfig="isDebug: true, parseOnLoad: true, locale: 'th'"></script>

...

<span id="d1" dojoType="ThaiDateTextBox"
constraints="{datePattern:'dd/MM/yy'}"/>

Related link from Roti