Monday, May 28, 2007

Tapestry 5 Ioc

เมื่อรู้ว่า T5 เปลี่ยนวิธี config ไปแล้ว
ผมในฐานะที่มี learning style เป็นแบบที่ต้องทดลองทำด้วยตัวเองจึงจะเข้าใจ,
ก็เลยลองทดสอบ config tapestry5 IoC เปรียบเทียบกับ spring

เริ่มด้วยการสมมติสถานการณ์ก่อน
สมมติว่าเรามี service ที่ใช้ในการ upload content เข้า repository
public interface UploadService {

public void upload(InputStream input, String type);

}

การทำงานภายในของ service นี้ก็คือ
1. ทำการ parse content, ซึ่งวิธี parse ก็ขึ้นอยู่กับ type ของ content
2. delegate การจัดเก็บ content ให้กับ RepositoryService เป็นคนทำ
3. ทำ index ของ content นั้น โดย delegate ให้กับ IndexService เป็นคนจัดการ
public class UploadServiceImpl implements UploadService {

private IndexService indexService;
private RepositoryService repositoryService;
private Map<String, Parser> parserMap = new HashMap<String, Parser>();

public void upload(InputStream input, String type) {
Parser parser = parserMap.get(type);
Document document = parser.parse(input);
Long key = repositoryService.persistent(document);
indexService.index(document, key);
}

public void setIndexService(IndexService indexService) {
this.indexService = indexService;
}

public void setRepositoryService(RepositoryService repositoryService) {
this.repositoryService = repositoryService;
}

public void setParserMap(Map<String, Parser> parserMap) {
this.parserMap = parserMap;
}
}

ถ้าร้อย service ด้วย spring เราจะต้องเขียนดังนี้
<beans>
<bean id="indexService" class="test.service.impl.IndexServiceImpl"/>

<bean id="repositoryService" class="test.service.impl.RepositoryServiceImpl"/>

<bean id="uploadService" class="test.service.impl.UploadServiceImpl">
<property name="indexService" ref="indexService"/>
<property name="repositoryService" ref="repositoryService"/>
<property name="parserMap">
<map>
<entry>
<key><value>pdf</value></key>
<bean class="test.service.impl.PdfParser"/>
</entry>
<entry>
<key><value>word</value></key>
<bean class="test.service.impl.MsWordParser"/>
</entry>
</map>
</property>
</bean>
</beans>


พอมาเป็น Tapestry5 IoC
สิ่งที่เปลี่ยนไปอย่างมาก ก็คือ "ไม่มี xml อีกแล้ว"
เมื่อไม่มี xml, เราก็ต้อง define module descriptor เป็น java แทน
public class MyAppModule {

/**
* bind ใช้กับกรณีพวก simple bean, ไม่ต้องมีอะไรให้ set มากมาย
*/

public static void bind(ServiceBinder binder) {
binder.bind(RepositoryService.class, RepositoryServiceImpl.class);
binder.bind(IndexService.class, IndexServiceImpl.class);
}

/**
* กรณีพวก bean ที่ซับซ้อนมากขึ้น ก็ให้ตั้งชื่อ method ว่า build นำหน้า
* แล้วก็กำหนด parameter ตาม dependency ที่ต้องการ
* โดยลำดับจะเป็นอะไรก่อนหลังก็ได้
*/

public static UploadService buildUploadService(
IndexService indexer,
RepositoryService repositoryService,
Map<String, Parser> configuration) {

UploadServiceImpl impl = new UploadServiceImpl();
impl.setIndexService(indexer);
impl.setRepositoryService(repositoryService);
impl.setParserMap(configuration);
return impl;
}

/**
* ตรงนี้แหล่ะที่ tapestry5 IoC ยังเหนือกว่า Spring
* เราสามารถกำหนด contribute ที่ทำหน้าที่ provide configuration ให้กับ builder ข้างบน
* โดย contribute method สามารถ plugin เข้ามาจาก module อื่นๆได้
* ทำให้มันมีลักษณะเป็น modular มากกว่า spring
*/

public static void contributeUploadService(MappedConfiguration<String, Parser> configuration) {
configuration.add("pdf", new PdfParser());
configuration.add("word", new MsWordParser());
}
}

เวลา run ก็
        public void run() {
RegistryBuilder builder = new RegistryBuilder();
builder.add(MyAppModule.class, ParserModule.class);

Registry registry = builder.build();

UploadService service = registry.getService(UploadService.class);
service.upload(null, "pdf");
}

ขยายความเพิ่มเติมเรื่อง contribution ของ Tapestry5 อีกนิดหนึ่ง
contribution ใน T5 (จริงๆมันมีตั้งแต่ version 4 แล้ว)
มันมีลักษณะเป็น aggregate นั่นคือ มันจะ scan หา contribution ทั้งหมดที่มี
ไม่ว่าจะอยู่ใน jar file หรือใน classpath
จากนั้น จึง aggregate และส่งต่อให้ builder method
,feature ที่ไกล้เคียงสุดของ spring สำหรับกรณีนี้ ก็คือ collection merging (spring version 2)

Note: ทั้ง spring และ tapestry5 IoC ต่างมี feature พวก auto binding ทั้งคุ่
แต่ผมไม่นิยมใช้ เนื่องจากการประกาศว่าต้องใช้อะไรบ้าง มันชัดเจนกว่าในตอนที่ต้องไล่ code เพื่อแก้ไขโปรแกรม

Related link from Roti

Thursday, May 24, 2007

tapestry 5

ช่วงนี้ลูกหลับกลางวันเป็นเวลา เลยได้มีโอกาสลองเล่น tapestry 5
หลังจาก download tutorial มาทดสอบดูแล้ว
ก็พบ issue ที่น่าสนใจดังนี้
1. เดิม tapestry หรือ framework อื่นๆมักจะใช้ servlet
เป็นจุดตั้งต้น framework ของตัวเอง
แต่ tapestry 5 เปลี่ยนไปใช้ filter เป็นจุดเริ่มต้น
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry.TapestryFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

น่าสนใจมาก ว่าการเปลี่ยนแบบนี้จะนำมาซึ่งข้อดีและข้อเสียอะไรบ้าง

2. การเปลี่ยน page เดิมจะใช้วิธี forward เป็นหลัก
แต่ใน tapestry5 จะใช้ redirect เป็นหลักแทน
ซึ่งข้อดี ก็คือการ handle กรณี user กด refresh จะทำได้ถูกต้องขึ้น

3. การเริ่มต้นสร้างโปรเจคใหม่ทำได้ง่ายมาก (อิทธิพลจาก rails)
ด้วยการ integrate กับ maven, ทำให้เรา create new project,
running project(start server) ได้ภายใน 2 command line.

4. loop การแก้ไข + ทดสอบ + แก้ไข + ทดสอบ ...
โดยไม่ต้อง restart server ทำได้เรียบร้อยดี
ยกเว้น case ที่เราเพิ่ม page ใหม่ๆ เข้าไปหลังจาก start server ไปแล้ว
ที่ต้องมีการ restart server ใหม่

5. ระยะเวลาการ start server เร็วกว่าเดิมแบบไม่เห็นฝุ่น
เดิมประมาณ 3-7 วินาที ของใหม่ หลัก 300 millisec
ที่เป็นเช่นนี้เพราะมีการ rewrite Ioc container ใหม่

6. เปลี่ยน Ioc(Inversion of Control) ไปใช้ java code + annotaion
แทน xml file, อันนี้แหล่ะตัวงงเลย เพราะผมใช้ xml ใน spring มาจนเคยตัวแล้ว

Related link from Roti

Wednesday, May 23, 2007

semaphore

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

ลองเปิด dictionary ดู ก็พบว่า
semaphore มีรากศัทพ์มาจากสองคำคือ
sema -> sign
phoros -> carrying
รวมกันแล้ว ก็คือ system ที่ใช้ส่งข้อมูลระหว่างกัน โดยอาศัยการมองเห็นเป็นสำคัญ

ใน wikipedia เขาเล่าว่า ฝรั่งเศษ เป็นคนเริ่มใช้ก่อน
โดยเริ่มนำมาใช้อย่างจริงจังในช่วงปี 1790-1795
ซึ่งเป็นช่วงที่เกิดการปฎิวัติ (French Revolution)
โดยเขาจะสร้างสถานี (staion) ใล่กันไป
แต่ละสถานีอยู่ห่างในระยะที่มองเห็นกันได้
เมื่อมี information ที่จะส่ง สถานีที่ส่งก็จะปรับ sign (แล้วแต่ว่าเลือกออกแบบ sign ไว้อย่างไร)
ของตัวเอง, สถานีถัดไปเมื่อมองเห็นก็จะปรับของตัวเองตาม
copy กันเป็นทอดๆไป จนกว่าจะถึงปลายทาง
(จะเห็นว่ามันเหมือนวิธีส่งโทรเลขเลย
ดังนั้นจึงมีชื่อเรียกอีกอย่างว่า optical telegraph)

ลองดูตัวอย่าง sign บ้าง
sign แบบแรกที่เราคุ้นเคย ก็คือสัญญาณธง


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


Note: รูปเพิ่มเติมใน flickr

ดูแล้ว semaphore ที่ทาง computer นำมาใช้
จะมาจากแนวพวก semaphore ที่ใช้ในวงการรถไฟ
เนื่องจากเป็นเรื่องของการจัดสรรทรัพยากรที่มีอยู่จำกัด
(ทรัพยากรที่จำกัดของรถไฟ ก็คือราง)
ซึ่งเป็นปัญหาพวก concurrency แบบเดียวกัน

Related link from Roti

Monday, May 21, 2007

unit_of_work

ใน rails เวลาเราเขียน action ส่วนที่เกี่ยวกับการ save, update
หน้าตาของ code ตรงนั้นส่วนใหญ่จะยึดตามแบบนี้
  def create
@role = Role.new(params[:role])
if @role.save
flash[:notice] = 'Role was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

ซึ่งดูแล้วก็รู้เรื่องดี, แต่สำหรับผมเวลาเขียน code ตรงส่วนนี้ทีไรมันรู้สึกขัดๆ

Obie Fernandez ก็คงรู้สึกแบบเดียวกัน
แต่เขาหาวิธีแก้ไขได้เนียนดี
โดยเขาเปลี่ยนวิธี save เป็นแบบนี้แทน
  def create
case unit_of_work do
@role = Role.new(params[:role])
end

when Success
redirect_to :action => 'list'

when ValidationError
render :action => 'new'
end
end

เป็นการเอา block + case statement มา combine กันได้สวยดี
โดยตัว unit_of_work เป็น method ที่รับ block เข้าไป

Related link from Roti

Wednesday, May 16, 2007

งัวเงีย

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

ขั้นแรกสุดในการชงนม ก็คือต้องหาขวดนมให้ได้ก่อน
เนื่องจากอุปสรรคเรื่องความมืด + สายตายังไม่ปรับ focus + สมองที่ยังงัวเงียอยู่
algorithm ที่ดีที่สุดในการหาขวดและจุกนมในความมืด ก็คือ brutal force
หลังจากคุ้ยและคลำกองขวดนมอยู่นานมาก (นานในความรู้สึก เพราะมันอยากกลับนอนเต็มที่แล้ว) ก็ได้ขวดนมขนาดที่เหมาะสำหรับเด็กอ่อนออกมา
ชั้นที่สอง ก็คือ ตวงน้ำให้ได้ 2 Oz. อันนี้ยากขึ้นอีก
เพราะว่า ต้องกัดฟันบังคับให้สมองส่วนที่เกี่ยวกับ "มิติ" มันยอมทำงาน
เมื่อได้ขวดนมพร้อมน้ำแล้ว ก็จัดการตักนมผงขึ้นมาหนึ่งช้อน
(ไม่ยากแล้ว เพราะ function "มิติ" มันทำงานแล้ว)
ผสมเข้าด้วยกันแล้วก็เขย่าๆ
ส่งต่อให้ภรรยา จากนั้นก็กระโดดกลับขึ้นเตียง
(หลังจากที่เขียนเรื่องนี้แล้ว คำถามที่ตามมาอีก ก็คือ
เอ๊ะ แล้วทำไม แฟนเราถึงไม่ชงนมเองหว่า)

ก่อนหลับไป ก็นึกว่า
เมื่อรู้แล้วว่าต้องชงนม
แล้วทำไมก่อนนอนตูไม่หยิบขวดนม + จุกนม
ใส่น้ำ,วางเตรียมไว้ให้เรียบร้อยก่อน
จะได้ไม่ต้องมาทุกข์ทรมานอย่างนี้วะ

ปล. เรื่องนี้สามารถเปลี่ยนบริบท จากชงนม
ไปเป็น software development ได้โดยไม่เสียความหมาย

Related link from Roti

Monday, May 14, 2007

น้องใหม่มาแล้ว

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

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

Update; เมื่อคืนลูกคนเล็กมันรับน้องพ่ออีกแล้ว
ต้องอุ้มเดินหกทุ่มถึงตีหนึ่ง
แล้วก็ตีห้าถึงแปดโมงเช้า
ส่วนตีสอง เป็นเวรของลูกคนโต ตื่นขึ้นมาอิจฉาน้อง
ต้องนอนปลอบกันเกือบครึ่งชั่วโมง

Related link from Roti

Tuesday, May 08, 2007

สกีน้ำ กับ J2EE

เห็นเจ้า rerngrit เขาไป เล่นสกีน้ำ แล้ว
ประเด็นที่ผมประทับใจสำหรับกระบวนการเล่นสกีน้ำ ก็คือวิธีการหัดเล่น
ผมชอบความรู้สึกตอนหัด ที่ค่อยๆมีแบบฝึก จากง่ายไปหายาก
ในแต่ละ step จะมีโจทย์ให้เราแก้แค่ไม่กี่เรื่อง
ทำให้เราสามารถค่อยๆ step ขึ้นไป,
รู้สึกถึง achievement
ซึ่งกลายมาเป็นตัวล่อหลอกที่ทำให้เราอยากเล่นแบบที่ยากขึ้นไปอีก

สมัยผมหัด ขั้นตอนการหัดโดยคร่าวๆ จะเป็นแบบนี้
  • นั่งเล่น
    เป็นการเริ่มต้นเรียนวิธีที่เชือกดึงตัวเรา, เรียนรู้ว่าเส้นทางที่วิ่งเวลาเข้าโค้ง
    ควรจะรักษาเส้นทางอย่างไรให้เชือกตึงตลอดเวลา ไม่เช่นนั้นจะเกิดอาการกระชากอย่างแรงได้
  • สกีสองขา
    การหัดจะเริ่มจากการ start โดยการนั่งออกไป (เนื่องจากเรานั่งเป็นแล้ว ขั้นนี้เลยไม่ยากแล้ว)
    จากนั้นระหว่างทางก็ให้พยายามยืนขึ้นให้ได้
    ความยากก็คือ ตอนยืนขึ้น กับการเข้าโค้งในลักษณะยืนครั้งแรก
  • สกีสองขาแต่ถอดทิ้งระหว่างทาง 1 อัน
    ลดความยากของการออก start ด้วยขาเดียว โดยการออก start สองขา
    จากนั้นพอยืนได้ ก็ให้ค่อยๆทอดทิ้งขาที่ไม่ถนัดทิ้งไป แล้วก็นำเท้าไปวางไว้หลังเท้าที่ใส่สกีไว้
    ความยาก จะอยู่ตรงอาการเก้ๆกัง ของการถอดกับการการวางเท้า
    เพราะพอถอดเสร็จแล้ว ระหว่างที่ขาที่ลอยยังไม่ได้วางลงไป, balance มันจะเสียได้ง่าย
  • สกีขาเดียว
    ยากตอนออก start, เพราะพวกนั่งกับพวกสองขา เราสามารถออก start โดยการนั่งรอบนทางวิ่งบนบกได้
    แต่พวกขาเดียว ต้องนั่งบนระเบียงไม้ จากนั้น เมื่อเชือกเริ่มดึงก็ต้องทิ้งตัวลงจากระเบียงไม้ในจังหวะที่เหมาะสม
  • wakeboard อันนี้ผมไปไม่ได้เล่น wakeboard ของแท้นะ ของมันแพง สมัยก่อนไม่มีของส่วนกลางให้เล่น
    แต่เคยเล่น อันที่แบนๆ เล็กๆ , ความยากก็คือ มันลื่นชิบเป๋งเลย
    หัดใหม่, เจอทางที่มีพริ้วคลื่นๆเล็กๆ ก็ตกน้ำป๋อมแป๋มแล้ว


นึกเปรียบเทียบกับสายอาชีพเราแล้ว
process การหัดสกีน้ำ จากขั้น novice -> intermediate
มันมีแต่ความสนุก, ความท้าทาย กับ เอาอีกๆ
ส่วน process การหัด J2EE จาก novice -> intermediate
มันมีแต่ความมึนงง, สับสน, อุปสรรคที่ทำให้ท้อถอย, กับคำพูดในหัว "เอ๊ะเรามาถูกสายอาชีพหรือเปล่า"

Related link from Roti

Friday, May 04, 2007

Rock Climbing

เห็น roofimon comment มาชวนปีนเขา แถมยังมี climbing gym ส่วนตัวที่บริษัทฯด้วย
ก็เลยนึกครึ้มใจ เขียนโฆษณาชักชวนให้เหล่าโปรแกรมเมอร์ทั้งหลายมาปีนหน้าผากันดีกว่า

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

จำบรรยากาศการปีนครั้งแรกได้แม่นเลย
ความยากระดับ 5 (ง่ายสุด)
เหงื่อนี้ไหลออกมาเป็นปี๊ปเลย (ผมกลัวความสูง)
รำพึ่ง, ไกด์ + อาจารย์ ซึ่งเป็นสาวสวยผมยาวหุ่นดี ในชุดสายเดี่ยวและกางเกงขาสั้นจู๋
ก็คอยให้กำลังใจและบอกบทอยู่ข้างล่าง
left foot in a right hole, ...
(ใครที่ไปกระบี่ แล้วไปนั่งดูเขาหัดปีนกัน จะรู้จักรูปประโยคแบบนี้ดี)
กว่าจะปีนขึ้นไปถึง ก็พาลจะเป็นลม ด้วยอาการขาดน้ำ
ต่อเมื่อปีนเป็นแล้ว ถึงรู้ว่า ถ้ารู้หลัก + ไม่เกร็งและกลัวจนเกินไป
การปีนขึ้นเส้นนั้นมันจะง่ายพอๆกับการเดินเล่น

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

ตอนนั้นแม้จะไม่ค่อยมีที่ปีน
แต่ก็เริ่มสะสมอุปกรณ์ปีนกันบ้างแล้ว
ที่ภูมิใจสุดก็คือเชือก เส้นละ 500 บาท
ที่แฟนของอาจารย์+ไกด์สาว ช่วยหักคอซื้อจากฝรั่งที่ไร่เลย์มาให้
(เพิ่มเติมสำหรับผู้ไม่สัดทัดเรื่องอุปกรณ์ปีนเขา, เชือกสำหรับปีน
มันจะมีอายุการใช้งาน ซึ่งนอกจะเกี่ยวกับเวลาแล้ว ยังเกี่ยวกับ
จำนวนครั้งที่เกิดการตกกระชากของคนปีนด้วย, ซึ่งเชือกสมัยนั้น
มันกำหนด hard fall ไว้แค่ 1 ครั้ง
การใช้เชือกเส้นที่ไม่รู้ประวัติว่าตกมากี่ครั้งแล้ว
ก็ดูจะเป็นการฆ่าตัวตาย ในสายตาของฝรั่งได้)

สมัยนั้นที่ปีนไม่ค่อยมีมากนัก
อยากปีนก็ต้องไปกระบี่
ก็เลยกลายเป็นกิจวัตรว่า
ทุกๆหยุดยาว เราจะลงไปปีนที่กระบี่กัน

มีอยู่ครั้งหนึ่ง ขณะที่กำลังฝึกซ้อม bouldering (ไม่ใช้เชือก และไม่ได้ปีนขึ้นสูง)

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

Related link from Roti

Friday, April 27, 2007

Flex Ecosystem

หลังจากที่ได้ข่าว Opensource Flex
ก็เลยต้องไปขวนขวายหามาว่า อะไรที่มัน opensource บ้าง
อันนี้คือภาพ sketch ที่ได้จากการฟัง podcast Architectore of flash
สีเขียว ก็คือ opensource (เฉพาะของ Adobe)

Related link from Roti

Thursday, April 26, 2007

ZX81 - 25 ปี

เผลอหน่อยเดียว คอมพิวเตอร์เครื่องแรกของผม ZX81
ก็มีอายุเกิน 25 ปีไปแล้ว
สำหรับคนที่เกิดไม่ทัน
spec ของเครื่องก็คือ
  • CPU Z80A 3.25MHz.
  • RAM 1K
  • LANGUAGE BASIC
  • UHF output for display on TV set.

จำตอนแกะกล่องได้แม่นเลย
ตื่นเต้นมาก ตอนต่อสายกับ Tv.
คาดหวังว่าจะเจอคอมพิวเตอร์พูดได้แบบ 2001 Space Odyssey
กลับกลายเป็นเจอ สี่เหลี่ยมดำๆ กระพริบได้บนหน้าจอแทน
แถมพอกดปุ่ม keyboard
กดเท่าไร ก็ได้แต่คำว่า Syntax Error กลับมา
สุดท้ายก็ต้องปิดเครื่อง แล้วก็สมัครไปเรียนเขียนโปรแกรม
จำได้ว่า เขาสอนให้ฟรีที่ siam center.

จะว่าไป ชีวิตผมเปลี่ยน ก็เพราะไอ้เจ้าเครื่องนี้แหล่ะ

Related link from Roti

Sunday, April 22, 2007

น่าอ่าน

บิณฑบาตเรือและเรื่องที่เกี่ยวข้อง
วงศาวิทยาสังเขปว่าด้วยสถาปัตยกรรมภูมินิทัศน์ถิ่น

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

"..แผ่นดินจะเป็นริ้วปลา ท้องฟ้าจะเป็นใยแมงมุม มีทางแต่จะไม่มีคนเดิน
บ้านเมืองจะเป็นกล่องไม้ขีด พ่อแม่ลูกจะพลัดพรากกัน....."

ยายเขียวยังย้ำกับยายว่า " ….อายุฉันจะไม่ได้เห็นหรอก แต่แม่เหรยจะได้เห็น"

ใครอยากรู้ว่า แผ่นดินเป็นริ้วปลา, ท้องฟ้าเป็นใยแมงมุม, ทางที่ไม่มีคนเดิน
ที่เป็นจริงแล้วในปัจจุบันเป็นอย่างไร ตามไปอ่านได้เลย

Related link from Roti

Friday, April 20, 2007

Vuze กับเขาด้วย

ใน blognone มี Review Vuze Beta
เห็นแล้วก็ตื่นเต้นไปกับเขาด้วย
แต่ที่ตื่นเต้นไม่ใช่ความสวย หรือเรื่อง load bit.
แต่เป็นเรื่องการ integrate browser เข้ากับ SWT Application

จริงๆแล้ว SWT มี browser widget มานานมากแล้ว
แต่มันก็มี feature ที่จำกัดอยู่ เช่น ไม่สามารถสื่อสารระหว่าง javascript กลับมายัง Java Application
ที่ห่ออยู่ข้างนอกได้

ในหน้าจอของ Vuze ที่ mk capture มา
ผมแอบเห็นว่า มันน่าจะส่ง message คุยกันระหว่าง browser กับ Java ได้
ก็เลยไปไล่ source code ของ Vuze ดู ว่าเขา implement มันอย่างไร

จากการไล่ source code ดู ก็พบว่า
เขาพยายาม implement Message Architecture ที่คุยได้ทั้งสองทาง
คือจาก java ไป Browser และจาก Browser ไป Java

โดยจากฝั่ง Java ไป Browser เขาใช้กลไกปกติ ก็คือ
method execute ใน org.eclipse.swt.browser.Browser

ส่วนขากลับ นี่สิ เส้นผมบังภูเขา
(ผมเคยอยากได้ feature นี้เมื่อ 4 ปีก่อน แต่สุดท้ายก็หาทางทำไม่ได้)
เขาใช้วิธีส่ง message ผ่านทาง status bar ของ browser
อันนี้เป็น javascript ของเขา
function sendRealMessage ( message ) {
sequence++;
window.status = 'AZMSG' + ';' + sequence + ';' + message;
window.status = '';
}

ส่วนในฝั่ง java ก็แค่ add StatusTextListener เข้ากับ browser widget
แล้วก็เช็คดูว่า มี message ที่มี prefix 'AZMSG' หรือเปล่า
ถ้าใช่ก็ feed เข้า Message channel ต่อไป

Related link from Roti

Wednesday, April 18, 2007

Highlight Current Symbol

เวลาใช้ emacs ไล่ source code ของคนอื่น หรือของตัวเองก็เถอะ
สิ่งที่ผมขัดใจ ก็คือ feature ที่จะช่วย highlight symbol ที่เรากำลังสนใจ
มันก็มีคล้ายๆกัน แต่ยังไม่ตรงกับที่อยากได้นัก
คืออยากได้แบบ แค่วาง cursor ลงไป มันก็ highlight ให้เลย

ถึงวันนี้ก็สมหวังแล้ว เพราะมีคน release library ที่ทำแบบนี้ออกมาแล้ว
ดูได้ที่ light-symbol 0.1

Related link from Roti

Thursday, April 12, 2007

หน้าจอทำงาน

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

เมื่อก่อน สมัยจอยังเล็กๆธรรมดาอยู่
เวลา design ก็ต้องพึ่งกระดาษแผ่นใหญ่ๆ วางให้เต็มโต๊ะ แล้วก็ sketch ขีดๆเขียนๆไป

มาปัจจุบันจอมันใหญ่มากแล้ว
ก็เลยกัดฟันลงทุนซื้อจอใหญ่ (นอนก่ายหน้าผาก คิดอยู่นานมาก)
แต่ผลของมันก็คุ้มค่านะ
ที่เห็นผมเรียนรู้เรื่อง rails, erlang, haskell, ... จนไปพูดใน blognone techday ได้
ปัจจัยหนึ่งที่ช่วยสนับสนุน ก็คือ เจ้าจอตัวนี้นั่นแหล่ะ

ว่าแล้วก็ขอ show หน้าจอทำงาน (เมื่อก่อนจะรู้สึกอาย ว่าจอมันไฮโซไปหน่อย)
ปกติ ผมจะเปิด desktop ไว้ 4 workspace
อันนี้ capture งานที่ทำวันนี้

workspace แรก เปิด eclipse



workspace สอง เป็นพวก design



workspace สาม เป็นพวก document



workspace สี่ เป็น พวก console



Note: จะสังเกตุว่าไม่มีพวก IM ใช้ เพราะแก่แล้ว (ไม่ถนัดโต้ตอบแบบ online)

Related link from Roti

Wednesday, April 11, 2007

MandelBrot Set ด้วย Haskell

จากโจทย์ใน codenone ที่ Implement ด้วย Ruby ไปแล้ว
คราวนี้มาลอง implement ด้วย haskell บ้าง

เริ่มด้วย algorithm การคำนวณค่า
ถ้าเป็น ruby เขียนแบบนี้
def is_mandelbrot(x,y) 
x0 = x
y0 = y
x2 = x*x
y2 = y*y

iter = 0
maxiter = 30

while ( x2 + y2 < 4 && iter < maxiter )
y = 2*x*y + y0
x = x2 - y2 + x0
x2 = x*x
y2 = y*y
iter += 1
end

if iter == maxiter || iter == 0
0.0
else
iter.to_f * 100 / maxiter
end

end


ใน imperative language จะมีพวก while loop ให้ใช้
แต่ใน haskell ไม่มี loop แบบนี้
ดังนั้นเราต้องแปลงให้เป็น tail-recursive แทน

max_iter = 30
is_mandel x y = is_mandel' x y max_iter x y
is_mandel' x y cnt x0 y0
| cnt == 0 = 0.0
| exceed = (max_iter - cnt) / max_iter
| otherwise = is_mandel' (x2-y2+x0) (2*x*y+y0) (cnt-1) x0 y0
where x2 = x * x
y2 = y * y
exceed = x2 * y2 > 4


ขั้นถัดไปก็คือคำนวณว่าจะ render ภาพอย่างไร
โดยในโจทย์ เขาจะให้เรากำหนด coordinate ของมุมบนซ้าย กับมุมล่างขวา
จากนั้นก็ให้เราคำนวณรูป โดยกำหนดว่า resolution ของการ plot ต้องมีอย่างน้อย 200 pixel,
ถ้าเขียนด้วย python จะเขียนดังนี้ (code นี้คุณสุกรีเป็นคนเขียน)
def mandelbrot(ul,lr,callback,step=(200,200),maxiter=255,limit=2):
xs,ys = step
x0,y0 = ul.real,ul.imag
x1,y1 = lr.real,lr.imag
xd,yd = (x1-x0)/xs,(y1-y0)/ys
i = 0
y = y0
for i in range(ys):
x = x0
for j in range(xs):
color = mandelbrot_point(x,y,maxiter,limit)
callback(i,j,x,y,color)
y += yd

จะเห็นว่ามี for loop ซ้อนกัน 2 ชั้น
haskell ก็ไม่มี for loop ให้ใช้เหมือนเดิม
ดังนั้น เราจะเปลียนแนวคิดไปเป็น List แทน
เริ่มโดยหาจุดที่เป็นไปได้บน แกน X ก่อน
โดยต้องการ list หน้าตาแบบนี้
[(-2,0),(-1.98,1),(-1.96,2),....]
ตัวแรกใน tuple คือ ค่าที่จะนำไปคำนวณสมการ
ส่วนตัวที่สองใน tuple ก็คือค่าที่ใช้ plot จุดบนแกน x
loopX startX endX = take w $ iterate (\(x,x') -> (x + stepX, x' + 1)) (startX,0)
where stepX = (endX - startX) / width
w = floor width

take คือการดึงค่าออกจาก list ตามจำนวนที่ระบุ เช่น
take 3 [1,2,3,4,5] ก็จะได้ [1,2,3]
ส่วน iterate มันอธิบายยาก ลองดูตัวอย่าง
iterate (\x -> x + 1) 1 ได้ผลลัพท์คือ [1,2,3,4,... จน infinity]
iterate (\x -> x + 2) 1 ได้ผลลัพท์คือ [1,3,5,7,...]
ดังนั้น code ข้างบน ก็อ่านได้ว่า
"ดึง element ออกมาจาก list ตามจำนวน pixel ที่เราจะ plot"
ซึ่งก็คือ 200 element

จากนั้นก็หาจุดที่เป็นไปได้บน แกน Y
loopY startY endY = take h $ iterate (\(y,y') -> (y - stepY, y' + 1)) (startY,0)
where stepY = (startY - endY) / height
h = floor height


นำ list ของแกน X และ แกน Y
มาทำ cartesial product โดยใช้ List Comprehension
โดย sx คือ x ที่มุมบนซ้าย
ex คือ x ที่มุมล่างขวา
sy คือ y ที่มุมบนซ้าย
ey คือ y ที่มุมล่างขวา
mandelset sx sy ex ey = [(x',y',is_mandel x y) | (x,x') <- (loopX sx ex), (y,y') <- (loopY sy ey)]

ถึงขั้นนี้ เราก็ได้ผลลัพท์ออกมาเป็น list ของ tuple แบบนี้
[(x coordinate,y coordinate, ค่าที่ได้จากการคำนวณว่าอยู่ใน set ของ mandelbrot )]
ซึ่งสามารถนำไป plot ได้แล้ว

ขั้นตอนที่ยากอีกขั้นของ Haskell ก็คือ จะเลือกใช้ Graphic library อันไหนดี
เพราะมันมีให้เลือกเยอะ,
แต่ที่เหมือนกันหมดทุกเจ้าก็คือ
ถ้าอยากรู้ว่าทำงานอย่างไร ก็ให้อ่าน source code ของ example เอา

ผมตกลงเลือกใช้ Graphics.UI.GLUT
(หลังจากเลือกไปตัวหนึ่งแล้ว แต่มัน run ช้ามาก,
กับอีกตัวหนึ่งที่ install ไม่ผ่าน)

ปัญหาของโปรแกรมในส่วนของ UI ก็คือ State
เนื่องจาก Haskell มันเป็น pure functional
การจัดการกับ state ก็เลยต้องใช้ Monad เข้ามาช่วย
ลองดูการ define state
โดยผมจะเก็บค่า มุมบนซ้ายกับมุมล่างขวา
ซึ่งค่านี้จะเปลี่ยนไปเรื่อยๆ เมื่อ user กด zoom

data State = State { sx, sy, ex, ey :: IORef Float }

makeState :: IO State
makeState = do
sx <- newIORef (-2.0)
sy <- newIORef 2.0
ex <- newIORef 2.0
ey <- newIORef (-2.0)
return $ State { sx = sx, sy = sy, ex = ex, ey = ey }


เวลาจะ get ค่า ก็ทำแบบนี้
sx <- get (sx state)
ส่วนเวลาจะ set ค่า ก็ต้องทำแบบนี้
writeIORef (sx state) val

code ที่เหลือเป็น code ที่เกี่ยวกับ open-gl แล้ว
มีหน้าตาประมาณนี้ (ไม่ได้แสดงส่วนที่เกี่ยวกับการ zoom)
display :: State -> DisplayCallback
display state = do
clear [ ColorBuffer ]
sx <- get (sx state)
sy <- get (sy state)
ex <- get (ex state)
ey <- get (ey state)
renderPrimitive Points $ do
mapM_ plot $ mandelset sx sy ex ey
swapBuffers

reshape :: ReshapeCallback
reshape size@(Size w h) = do
viewport $= (Position 0 0, size)
matrixMode $= Projection
loadIdentity
ortho2D 0 (fromIntegral w) 0 (fromIntegral h)
scale 1 (-1) (1::GLfloat)
translate (Vector3 0 (-400) (0 :: GLfloat))


keyboardMouse :: State -> KeyboardMouseCallback
keyboardMouse state (MouseButton b) Down _ (Position x y) = case b of
LeftButton -> zoom state x y
_ -> return ()
keyboardMouse _ _ _ _ _ = return ()

main :: IO ()
main = do
(progName, _args) <- getArgsAndInitialize
initialDisplayMode $= [ DoubleBuffered, RGBMode ]
initialWindowSize $= Size (floor width) (floor height)
createWindow progName
myInit
state <- makeState
displayCallback $= display state
reshapeCallback $= Just reshape
keyboardMouseCallback $= Just (keyboardMouse state)
mainLoop

Related link from Roti

Tuesday, April 10, 2007

สะพานลอยหน้า Major

7 เดือนก่อน :
ผม: วันก่อนผมแบบจักรยานข้ามสะพานลอย Major,
เห็นพวก california fitness ~50 คน
กำลังปั่นจักรยานกันใหญ่เลย, ได้ feeling มากเลย
(ประมาณว่า เราขี่จักรยานจริงๆ พวกนั้นทำอะไรกันอยู่, โลกอยู่ข้างนอกนี่)
revolution: (เจ้าของร้านจักรยาน และเจ้าของ host server ที่ support rails) ยิ้ม...

5 เดือนก่อน :
roofimon: พี่ ผมเห็นพี่บนสะพานลอยคนข้ามหน้า Major ด้วย
ผม: เออ ผมกำลังแบบจักรยานกลับบ้านน่ะ

เมื่อวาน :
ขณะกำลังแบกจักรยานข้ามสะพานลอยหน้า Major
RerngIt: พี่ป๊อก พี่ป๊อก
ผม: เฮ้ยมาได้ยังไง
RerngIt: ผมย้ายมาอยู่แถวนี้แล้วพี่

เมื่อเช้า :
ไม่ห่างจากสะพานลอยหน้า Major
ตำรวจ 2 คนยืนเฝ้าตู้โทรศัพท์ที่โดนระเบิด
ทหารอีกคนกำลังกึ่งเดินกึ่งวิ่ง พร้อมกับพูด ว. ไปด้วย
เศษกระจกแตกเกลื่อนพื้น
ผมประคองจักรยานผ่านหน้าตู้ด้วยความระมัดระวัง ด้วยความกลัวยางแตก
จากนั้นก็ลงเดินแล้วแบกจักรยานข้ามสะพานลอย

Related link from Roti

Monday, April 09, 2007

Thrift

Note:
ไปเจอจาก bookmark ของ bact' บน del.icio.us
น่าสนใจดี ตรงตามที่อยากได้
เลยชิงเขียนถึงก่อน

Thrift มีแนวคิดคล้ายๆ COBRA นั่นคือ เข้ามาช่วยเราในเรื่อง Distributed Application
แต่ไม่ over spec เหมือน COBRA
ตัวมันจะเล็กๆ เบาๆ กว่า

ที่ผมชอบก็คือ มันเปิดโอกาสให้ผม เชื่อม ruby, python, java, php
เข้าหากันในลักษณะ remote procedure call ได้

วิธีการใช้ ก็คือ เราต้องเขียน definition ของ interface, parameter ที่เราต้องการใช้ ด้วย syntax ของ Thrift เองก่อน
(แบบเดียวกับที่เราต้องเขียน IDL file ใน COBRA)
จากนั้นก็สามารถจะ generate stub สำหรับฝั่ง server หรือฝั่ง client
บนภาษาที่เราต้องการได้
โดยขณะนี้ Thrift support การ generate java,cpp,php,python,ruby

ลองดูตัวอย่าง
เริ่มด้วยการเขียน definition file
#!/usr/local/bin/thrift -cpp -java -py -php -rb -r

struct Address {
1: string province
2: string zipCode
}

struct Person {
1: i32 id,
2: string name,
3: Address address
}


service PersonService {
Person getById(1:i32 id),
void create(1:Person person)
}


ลอง implement server ด้วย java
public class MyServer implements PersonService.Iface {

static Hashtable<Integer, Person> map = new Hashtable<Integer, Person>();

public void create(Person person) throws TException {
map.put(person.id, person);
}

public Person getById(int id) throws TException {
return map.get(id);
}

public static void main(String[] args) throws TTransportException {
PersonService.Processor proc = new PersonService.Processor(new MyServer());
TServerSocket tss = new TServerSocket(9090);
TThreadPoolServer server = new TThreadPoolServer(proc, tss);
server.serve();
}
}


ลอง implement client ด้วย ruby
#! /usr/bin/env ruby

require 'thrift/transport/tsocket'
require 'thrift/protocol/tbinaryprotocol'

require 'PersonService'

transport = TBufferedTransport.new(TSocket.new('localhost',9090))
protocol = TBinaryProtocol.new(transport)
client = PersonService::Client.new(protocol)

transport.open()

p = Person.new
a = Address.new
a.zipCode = '10900'
a.province = 'Bangkok'
p.address = a
p.name = 'pok'
p.id = 1

client.create(p)

np = client.getById(1)
puts np.name
puts np.address.zipCode


สุดท้ายลอง implement client ด้วย python
#!/usr/bin/env python

import sys
sys.path.append('..')

from person import PersonService
from person.ttypes import *

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol


transport = TSocket.TSocket('localhost',9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)

client = PersonService.Client(protocol)

transport.open()

p = client.getById(1)
print p.name, p.address.zipCode

Related link from Roti

Thursday, April 05, 2007

ruby ช้า

ใน codenone คุณสุกรีตั้งโจทย์ Mandelbrot Set ไว้
แน่นอนเมื่อตั้งตัวเป็นผู้สนับสนุน ruby แล้ว
ก็ต้องทดลอง implement ด้วย ruby ดู (haskell เข้าคิวอยู่)

เริ่มแรกสุด ลอง implement algorithm โดยใช้ Datatype ที่มากับ ruby
require 'complex'
def is_mandel(z)
max = 30
cnt = 0
c = z
while (z.abs < 2 && cnt < max)
z = z ** 2 + c
cnt += 1
end
cnt == max ? 0 : cnt
end

สั้นดี ได้ใจความ
ทดลอง run ด้วย loop นี้
def bm()
step = 0.005
x = 0.0
while x < 1.0
y = 0.0
while y < 1.0
is_mandel(Complex.new(x,y))
y += step
end
x += step
end
end

ผลออกมาเกินห้ามใจ
นี่ขนาดคำนวณ แค่ 40000 จุด (200x200 pixel)

user system total real
complex 23.180000 0.260000 23.440000 ( 26.981269)


ปรับไปใช้ algorithm ใน wikipedia
def is_mandel2(x,y)
x0 = x
y0 = y
x2 = x*x
y2 = y*y

cnt = 0
max = 30

while (x2 + y2 < 4 && cnt < max)
y = 2*x*y + y0
x = x2 - y2 + x0

x2 = x*x
y2 = y*y

cnt += 1
end
cnt == max ? 0 : cnt
end


ผลลัพท์ ดีขึ้นหน่อย ดีกว่าเกือบ 4 เท่า
แต่ก็ยังช้าอยู่ดี

user system total real
wikipedia 5.440000 0.080000 5.520000 ( 6.913239)


สุดท้ายก็เปลี่ยนไปใช้ rubyinline
เพื่อจะเห็นแสงสว่างที่ปลายอุโมงค์บ้าง
inline do |builder|
builder.c "
int is_mandel3(double x, double y) {
double x0 = x;
double y0 = y;
double x2 = x * x;
double y2 = y * y;
int cnt = 0;
int max = 30;
while (x2 + y2 < 4 && cnt < max) {
y = 2*x*y + y0;
x = x2 - y2 + x0;
x2 = x*x;
y2 = y*y;
cnt++;
}
return cnt == max ? 0 : cnt;
}"

end

ผลลัพท์ที่ได้ ก็อย่างที่ทุกคนคงคาดไว้

user system total real
inline 0.220000 0.000000 0.220000 ( 0.257189)


Put the right man on the right job.
ภาษาก็เหมือนกัน

Related link from Roti

อำนาจ

รัฐมนตรี ICT คนนี้ใช้ได้เหมือนกันนะ
ตรงที่ทำให้คน IT หลายคน ไดัสัมผัสกับ "อำนาจนิยม"

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

พูดอย่างนี้ไม่ได้แปลว่าต้อง ปลง กับ สถานะการณ์ตัวเอง (พวก IT)
เพราะเมื่อเจอการใช้อำนาจ ก็ควรจะต้องรวมตัวต่อต้านอำนาจ

Related link from Roti

Wednesday, April 04, 2007

Autism -> Engineering

Autism ใน longdo แปลว่า ความผิดปกติทางการสื่อสารและอารมณ์

มีลูกแล้ว ความสนใจในเรื่องของ Gene เรื่องของ เด็ก ก็เพิ่มสูงขึ้น
วันนี้อ่านเจอ สมมติฐานของ Baron-Conhen เรื่อง Autism-Engineer Hypothesis.
สมมติฐานนี้มีประมาณว่า
พ่อที่เป็นเป็น engineering ถ้าเลือกแต่งงานกับผู้หญิงในแบบ assortative mating
(assortative -> แต่งงานกับคนที่มีคุณสมบัติเหมือนตัวเอง)
มีโอกาสที่ลูกจะได้ gene ที่มีคุณสมบัติเด่นใน systemisers เบิ้ลจากพ่อและแม่
จนก้าวไปสู่บริเวณที่เรียกว่า Autism

ดูตาราง


ไม่ต้องตกใจ สมมติฐานก็คือสมมติฐาน
(โชคดีที่เราไม่ได้แต่งงานกับสายอาชีพ Engineer)

Link
Two new theories of autism: hyper-systemising and assortative mating
(หน้า 4)

Related link from Roti

Tuesday, April 03, 2007

SVN Branching and Merging

ใช้ version control มานานแล้ว
แต่ยังไม่เคยได้ทำ branch & merge operation สักที
หลักๆก็ใช้อยู่แค่ trunk เส้นเดียว
ไม่เคยสร้าง branches ให้ปวดหัว
(ไม่สร้าง branches ก็ปวดหัวไปอีกแบบ)

ปกติในการใช้ version control,
เราสามารถแบ่งลักษณะการใช้ออกเป็น 3 กลุ่ม ตามลักษณะการใช้ branches ได้ดังนี้

พวกที่ 1 ไม่ใช้ branches เลย มีแต่ trunk อย่างเดียว
(บริษัทผม จัดอยู่ในกลุ่มนี้)

พวกที่ 2 พวกนี้สุดโต่งไปอีกด้านหนึ่งก็คือ ใช้แต่ branches เป็นหลัก
trunk เก็บเฉพาะ code ที่ verify ว่า stable แล้วเท่านั้น
ในพวกนี้ เวลาใครจะทำอะไรกับ code ไม่ว่าจะ bug ตัวจิ๊บจ๊อย ก็ต้องสร้าง branch ออกไป
เมื่อเสร็จแล้วจึงจะ merge เข้า trunk

พวกที่ 3 เป็นพวกผสมระหว่าง 1 และ 2 นั่นคือ
การเปลี่ยนแปลงเล็กๆน้อยๆ ก็ใช้ trunk ไป
แต่ถ้าเปลี่ยนเยอะหน่อยก็ใช้ branches

ปัจจุบัน ปัญหาที่ผมเจอก็คือ
เนื่องจาก methodology ที่ผมใช้พัฒนา software ในปัจจุบัน
ใช้เทคนิคแบบ Agile ในการพัฒนา (ทำไปคิดไป)
ซึ่งพอถึงจุดหนึ่ง มันจะเกิด refactor ขนานใหญ่ขึ้น
ก็เลยหลีกเลี่ยงไม่ได้ที่จะต้องทำ branches เพื่อแยกไป refactor ต่างหาก
(แยกไปลองผิดลองถูกต่างหาก จะได้ไม่กระทบคนอื่นเขา)

ใน svn, เวลาเราจะทำ branches
เราสามารถทำได้ง่ายๆ ดังนี้
  • สร้าง directory ที่จะใช้เก็บ branches ของเรา
    มาตรฐานแบบไม่เป็นทางการของ svn กำหนดว่า directory branches ให้เราอยู่แล้ว
    ดังนี้เราก็เลือกสร้าง folder ใต้ directory นี้
    ความยากในขั้นนี้ก็คือการตั้งชื่อ folder

    สมมติเราต้องการสร้าง folder mybranche-1 ก็ให้ใช้คำสั่งนี้

    svn mkdir http://hostname/svn/yourproject/branches/mybranche-1

  • ใช้คำสั่ง copy เพื่อ copy จาก trunk ไปยัง folder ที่เราสร้าง

    svn copy http://hostname/svn/yourproject/trunk \
    http://hostname/svn/yourproject/branches/mybranche-1


แค่นี้ ก็ได้ branche เส้นใหม่ ให้เราได้ลองยำเล่นสมใจแล้ว

ความยากขั้นถัดไป ก็คือการ merge
โดยการ merge จะเกิดได้ 2 กรณี
  • กรณีแรก เราทำ code เสร็จไว, ต้องการ commit change กลับเข้าไปยัง trunk
    การ merge ก็จะเกิดใน ทิศทางจาก branche มายัง trunk
  • กรณีที่สอง เราแก้ไข branch นานมาก จน trunk เปลี่ยนไปเยอะแล้ว,
    ถ้าเกิดเหตุการณ์แบบนี้ เราก็ควรจะ merge การเปลี่ยนแปลงจาก trunk ไปยัง branche เป็นระยะๆ
    เพื่อลดความแตกต่างระหว่าง code ลง


ข้อด้อยของ svn ก็คือ มันไม่มี feature ที่ช่วยจำว่า เรา merge อะไรไแล้วบ้าง
ดังนั้นจึงมีโอกาสที่เราจะ merge ซ้ำซ้อนลงไปได้
best practice ก็คือ ในการ commit การ merge แต่ละครั้ง
ให้เขียน log message ให้ชัดเจนว่า เป็นการ merge จาก revision ไหนถึงไหน
เพื่อจะได้ใช้เป็นข้อมูลเตือนความจำ

ลองดูตัวอย่างการใช้คำสั่ง merge จาก branch ไปยัง trunk
สมมติเรามีเส้นทางของ version เราดังนี้

#trunk# --- revision 149 ------- .... ------- >
\
\ branches
\
+ revision 150 ----- .... ----- revision 159 >


ขั้นตอนการ merge กลับ, เราจะใช้คำสั่ง svn merge เข้ามาช่วย
โดยเราจะสั่งคำสั่งดังนี้

ย้าย current directory ไปยัง working copy ของ trunk
แล้วทำการ update code ให้เป็น code ล่าสุดก่อน

$ cd $WORKING_COPY_OF_MAIN_TRUNK
$ svn update


trick ของการใช้ merge ก็คือ ประเด็นว่าเราจะต้องหาเลข revision เริ่มต้นให้ได้ก่อน (ในที่นี้คือ 150)
เพราะส่วนใหญ่มักจะลืมไม่ได้จดไว้
คำสั่งที่ช่วยหาเลขนี้ก็คือ

svn log -stop-on-copy

โดยมันจะ report log ไปจนถึง จุดที่เกิดการทำ branches (ใน svn การทำ branches คือการ copy)

เมื่อได้เลข revision มาแล้ว ก็สั่ง merge ส่วนเปลี่ยนแปลงที่เกิดขึ้นระหว่าง revision 150 ถึง 159 ใน branches
กับ working copy ของ trunk
โดยเริ่มแรกให้ใส่ parameter --dry-run เพื่อตรวจสอบความถูกต้องก่อน

$ svn merge --dry-run -r 150:159 http://domain/svn/branches/your_branches_name


ซึ่งหลังจากเกิดการ merge แล้ว,
เราก็ต้องตรวจ working copy ของเรา
ว่ามีอะไรผิดผลาดไหม เช่นพวก conflict
พอมั่นใจว่าถูกหมด ก็สั่ง commit ตามปกติ

ส่วนการ merge จาก trunk ไปยัง branch ก็ทำแบบเดียวกัน
เพียงแต่กลับทิศทางกัน

Related link from Roti

Monday, April 02, 2007

Expertise


Research on the acquisition of expertise has shown that
(a) it takes a long time,
(b) it takes intense, focused effort
(c) it can not be taught by direct tuition.

One of the most widely replicated findings in all domains studied is that it
takes at least 10 years to reach expert level.


ที่มา
The Acquisition of Expertise in Software Engineering Education

Related link from Roti

Friday, March 30, 2007

อคติ, ความกลัว

เมื่อวานได้คุยกับเพื่อนที่เป็นอาจารย์อยู่ที่ราชภัฐภูเก็ต
เพื่อนเล่าให้ฟังถึงปัญหา 3 จังหวัด แล้วก็พาดพิงไปถึง
การปฎิบัติตัวของอาจารย์บางคน ต่อนักศึกษาหญิงมุสลิม

มีอาจารย์บางคน ห้ามไม่ให้นักศึกษาหญิงมุสลิมใช้ผ้าคลุมหัว
ในชั่วโมงเรียนของตัวเอง ซึ่งถ้าไม่ยอมทำตาม ก็จะใช้เงื่อนไขต่างๆบีบบังคับ

ฟังแล้วหดหู่ใจมาก,
อยากรู้นะ ว่าความเกลียดชังพวกนี้มีรากฐานมาจากอะไร
ความกลัว?

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

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

Related link from Roti

Thursday, March 22, 2007

แด่แม่พลอย(สี่แผ่นดิน)

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

บทสัมภาษณ์ "คึกฤทธิ์คิดลึก ทศกัณฐ์วรรณกรรม" ใน ถนนหนังสือ 3, 1 กรกฏาคม 2528

Related link from Roti

Tuesday, March 20, 2007

ruby กับ gcd

คุณ sugree พูดถึง gcd ใน howforge
และไปตั้งโจทย์ไว้ใน codenone.com

ผมก็เลยลองไปแคะดูว่า method หรือ function gcd ใน ruby นั้นมีอยู่หรือยัง
ลองอ่านดูใน document ของ ruby
ก็พบว่า class Integer มี method ที่ทำ gcd อยู่ 2 version ก็คือ
gcd กับ gcd2
ในเอกสาร ก็ไม่ได้อธิบายว่าทำไมถึงมี 2 version
แต่เดาได้เลยว่า น่าจะเกี่ยวกับ algorithm ในการ implement

เพื่อแก้ความสงสัย ผมก็เลยทดสอบจัด benchmark เปรียบเทียบดู
require 'benchmark'
r = (1..10000)
m = 70
Benchmark.bm do |x|
x.report("gcd") { r.each {|n| n.gcd m}}
x.report("gcd2") { r.each {|n| n.gcd2 m}}
end

ผลที่ได้ ก็คือ gcd2 นั้นทำงานเร็วกว่า
      user     system      total        real
gcd 0.300000 0.000000 0.300000 ( 0.303847)
gcd2 0.130000 0.000000 0.130000 ( 0.126843)


ถ้าลองแกะดู source code ของ ruby 1.8.4 จะเห็นว่า gcd2
ใน algorithm แบบ Euclidean algorithm
มีหน้าตาแบบนี้
def gcd2(int)
a = self.abs
b = int.abs

a, b = b, a if a < b

while b != 0
void, a = a.divmod(b)
a, b = b, a
end
return a
end


ที่สนุกกว่านั้นก็คือ ใน library 'mathn' มันมีการ override
method gcd2 ด้วย algorithm "prime factorizations"
ผลที่ได้คือ ช้าสุดๆ
ลองดู benchmark ของ gcd2 ที่ถูก override แล้ว
require 'mathn'
Benchmark.bm do |x|
x.report("gcd2-mathn") { x.each {|n| n.gcd2 m}}
end

      user     system      total        real
gcd2-mathn 4.450000 0.030000 4.480000 ( 4.545717)


จะเห็นว่า การ re-open-class ได้แบบ ruby มันมาทั้งข้อดีและข้อเสีย
อย่างตัวอย่างข้างบน programmer คนแรกรู้ว่า gcd2 มันเร็ว
ก็เลยเขียน code โดยเรียกใช้ gcd2
แต่อยู่มาวันหนึ่งมีคนเกิดอยากได้ function ที่อยู่ใน 'mathn' เข้า
effect ที่เกิดจากการ require 'mathn'
ก็จะทำให้ gcd2 ที่เคยเร็วจี๋เปลี่ยนเป็นช้าสุดๆ โดยไม่มีใครรู้ตัว

จากการไล่ดู code ต่อ ก็พบว่า ruby 1.8.6 มีการเปลี่ยนแปลงใหม่
โดยตัด method gcd2 ออกไป และเปลี่ยนให้ gcd ไปใช้ Euclidean algorithm แทน

Note: ผมเขียนโปรแกรมมาก็นานแล้ว ยังไม่เคยใช้ gcd เลย

Related link from Roti

Friday, March 16, 2007

เกินมาจากไหน

เมื่อ 3 วันก่อน Database ของระบบที่ให้บริการอยู่เกิดล่มลง
จากการวิเคราะห์ log files ต่างๆ
มีข้อสันนิษฐานว่า จำนวน sessions ที่ client connect เข้ากับ database
มันเกิดค่าที่ตั้งไว้ (ไม่ใช่สาเหตุตรงๆที่ทำให้ล่ม แต่มันทำให้ resources ต่างๆมันไม่พอ)

จากการนับจำนวน user ที่เป็น client ต่อตรง ณ ขณะนั้น
มีจำนวน 262 จอ
นำจำนวน session มาลบออก ก็จะได้เป็นจำนวน connection
ที่ web application ต่อ = 485 - 262 = 218 connection

มาจากไหนวะ ตั้ง 218, connection leak เหรอ
จากการคำนวณแล้ว connection ทั้งหมดที่ web app พึงใช้น่าจะประมาณ 52 connection

ปัญหานี้เคยคิดไว้เหมือนกัน
เลยทำหน้าจอสอบถามใน web app ให้ admin สามารถดูได้ว่า
connection pool size ที่ใช้อยู่ปัจจุบันมีค่าเท่าไร
เปิดเข้าไปดูใน app
ปรากฎว่ามี active อยู่แค่ 5
แล้วมันจะกลายเป็น 218 ได้ไงวะเนี่ย

ผ่านไป 3 วัน
เข้าไปอ่านทบทวน file ~tomcat/conf/context.xml ที่ config Datasource ไว้
<!-- The contents of this file will be loaded for each web application -->
<Context>

<Resource auth="Container"
description="DB
Connection"
driverClass="ca.ingres.jdbc.IngresDriver"
maxPoolSize="21"
minPoolSize="5"
acquireIncrement="5"
name="jdbc/ssoDS"

...
</context>


อ่านบรรทัดแรกปุ๊บ สะดุ้งโหย๋ง
ชิบหาย web application เรามีตั้ง 10 กว่า app
มิน่าเล่า
ค้นหาเอกสาร tomcat ดู
ถ้าอยาก config datasource ให้ share กันระหว่าง context ได้
ต้อง config ไว้ใต้ elememt GlobalNamingResources ใน file server.xml

จากการสอบถาม เจ้าตัวผู้เป็นคน config
เขาบอกว่า เดิมก็พยายาม config ไว้ใน GlobalNamingResources เหมือนกัน
แต่ทำแล้วไม่ work สักที ก็เลยย้ายไปลองใส่ไว้ใน context.xml
ซึ่งใส่ปุ๊บก็ work เลย.

เข้าข่าย programming by coincident อีกแล้ว

Related link from Roti

Thursday, March 15, 2007

Terracotta #2

ต่อจากตอนที่แล้ว

Roots
คือ objects ที่เราต้องการให้ทุก node ที่เข้ามา join cluster
มองเห็นเป็น object เดียวกันหมด

สิ่งที่เกิดขึ้นเมื่อเรา define root object ก็คือ
ตอนที่ class loader กำลังเริ่ม load class
จะมีการ add Advice (AOP) แบบนี้เข้าไป
Object around() : get(@Root * *.*) {
String name = thisJoinPointStaticPart.getSignature().toLongString();
return ClusterManager.getOrCreateRoot(name);
}

ซึ่งพฤติกรรมของมันก็คือ เมื่อมีการพยายามที่จะ get ค่า จาก field ที่เป็น root
around advice ก็จะ intercept การ access นั้น
แล้ว delegate ต่อไปยัง ClusterManager แทน
ซึ่ง ClusterManager ก็จะทำการ connect ไปยัง Terracotta Server
เพื่อขอค่า instance ของ root object นั้นมา

ที่ฝั่ง server เมื่อได้รับ request ก็จะทำการตรวจสอบว่า
ใน repository ของตัวเองมี root object ตามชื่อนั้นหรือไม่
ถ้าไม่มี ก็จะทำการสร้างขึ้นมาใหม่ให้ แต่ถ้ามีก็จะทำการส่ง Object นั้นกลับมา

จะเห็นได้ว่า Terracotta Server จะเป็น Node หลักที่มี Data เก็บไว้ในเครื่องตัวเอง
ส่วน Virtual machine อื่นๆ ที่ join เข้าไป ก็จะมี replicate set ของ Data นั้นๆ

object ที่จะกำหนดให้เป็น Root จะเป็น Object ใดๆ ก็ได้
ไม่จำกัดว่าต้องเป็น Serializable
กลไกที่ Terracotta ใช้ replicate changed จะมีลักษณะเป็น fine grained
นั่นคือ replicate เฉพาะส่วนที่เปลี่ยนแปลง ไม่ได้ทำหมดทั้งก้อน

จากตัวอย่างเมื่อวาน ถ้าเราลองเปิด admin UI ของ Terracotta ดู
จะเห็นว่าเรามี root object ที่ชื่อ bid.Bidding.bid



Virtual Heap
เมื่อมีการ define Root Object ก็ต้องมีการ replicate object นั้น ไปให้ทุก Virtual machine ที่ต้องการใช้
วิธีการที่ Terracota ใช้ก็คือการ มีการกำหนดพื้นที่สำหรับเก็บข้อมูลบนแต่ละ Virtaul machine ขึ้นมา
ซึ่งเรียกว่า Virtual Heap
การเก็บข้อมูลใน Virtual Heap จะใช้หลักเดียวกับ cache ก็ืคือ
พื้นที่เก็บมีจำกัด ข้อมูลไหนที่ไม่ค่อยได้ใช้ จะต้องถูก flush ออก เพื่อเปิดทางให้กับข้อมูลที่ถูกใช้บ่อยๆแทน

เวลาที่ Node ของเรา เริ่ม join เข้า cluster
Terracota จะยังไม่ copy ข้อมูลจาก server มาทันที
แต่จะรอให้มีการ access object นั้นก่อน จึงจะ copy ข้อมูลมาใส่ไว้ใน Virtual Heap ให้
(ทำในลักษณะ fine grained นั่นคือ เฉพาะ field ที่เกิดการ access จริงๆ)

Related link from Roti

Wednesday, March 14, 2007

Terracotta #1

ได้ฤกษ์ทดลอง Terracotta แล้ว หลังจากที่ดองเรื่องไว้นานแสนนาน
สำหรับคนที่ไม่รู้จัก Terracotta
Terracotta คือ clustering Techonology อันหนึ่ง
ที่ใช้หลักการ AOP เข้าไปเปลี่ยนแปลง java byte code ของเรา
ทำให้ POJO Object ของเราประพฤติตนในลักษณะ cluster ได้

AOP library ที่ Terracotta เลือกใช้ก็คือ AspectWerkz
ส่วน byte code manipulation library เขาใช้ ASM

สิบปากว่าไม่เท่าตาเห็น
ทดสอบเขียน application ง่ายๆ ในเรื่องการประมูล

เริ่มด้วย class Bid แทน item ที่กำลังถูกประมูลอยู่
ภายในมี information แค่ว่า ใครประมูลล่าสุด ด้วยราคาเท่าไร
package bid;

import java.math.BigDecimal;

public class Bid {
private String name = "";
private BigDecimal currentPrice = BigDecimal.ZERO;

public BigDecimal getCurrentPrice() {
return currentPrice;
}
public void setCurrentPrice(BigDecimal currentPrice) {
this.currentPrice = currentPrice;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


จากนั้นก็ทดลองเขียน class Bidding ที่เป็น console application ที่ใช้ในการประมูล
โดยวิธีใช้งานมันจะหน้าตาประมาณนี้
Command (b-bid, n-new, s-show, q-quit) : n
id ? 1
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = , currentPrice = 0
Command (b-bid, n-new, s-show, q-quit) : b
id ? 1
name :pok
price :200
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = pok, currentPrice = 200
Command (b-bid, n-new, s-show, q-quit) : q

ตั้งโครง class Bidding ก่อน โดยในการประมูลครั้งหนึ่งๆ เรามี item ที่จะประมูลได้หลายอันพร้อมๆกัน
ดังนั้นเราจึงใช้ HashMap ในการเก็บ instance Bid ที่กำลังประมูลอยู่
public class Bidding { 

ConsoleReader console;
HashMap<String,Bid> bids = new HashMap<String,Bid>();

public static void main(String[] args) throws IOException {
new Bidding().run();
}
}

method run ก็คือ method ที่ใช้ loop เพื่อรับ input จากหน้าจอ console ของ user
    public void run() throws IOException {
console = new ConsoleReader();

while (true) {
String tmp = console.readLine("Command (b-bid, n-new, s-show, q-quit) : ");

switch (tmp.charAt(0)) {
case ('q'):
return;
case ('n'):
newBid();
break;
case ('s'):
show();
break;
case ('b'):
bid();
break;
}
}
}

method newBid คือ method ที่ใช้สร้าง bid item อันใหม่
    public void newBid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = new Bid();
bids.put(id, bid);
}

method show ทำหน้าที่ print bid item ทั้งหมด
    public void show() throws IOException {
for (String key : bids.keySet()) {
Bid bid = bids.get(key);
console.printString("id : " + key + ", -> ");
console.printString("name = " + bid.getName() + ", currentPrice = " + bid.getCurrentPrice());
console.printNewline();
}
}

method bid ก็คือการเสนอราคาประมูล
    public void bid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = bids.get(id);
String bidder = console.readLine("name :");
String price = console.readLine("price :");
BigDecimal p = new BigDecimal(price);
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}


จะเห็นว่าเราเขียนโดยไม่มีแนวคิดเรื่อง cluster หรือ share thread อยู่ในนี้เลย
ทีนี้ก็มาถึงคำถามว่า ถ้าเราต้องการจะให้มันเป็น cluster ขึ้นมาหล่ะ
เราจะทำอย่างไร
เริ่มด้วยลักษณะ cluster ที่เราต้องการก่อน
เราจะ start program bidding หลายๆตัว บน virtual machine คนละตัวกัน
แต่เราต้องการให้ bidding มองเห็น instance variable bids (HashMap) ของทุกตัวเป็นตัวเดียวกัน
จะได้แย่งกันประมูลได้

เริ่มแรกสุด terracotta มี architecture เป็นแบบ hub and spoke
ไม่ได้เป็น peer-to-peer ดังนั้นเราต้อง start terracotta server ขึ้นมาก่อน
โดยใช้คำสั่ง start-tc-server.sh

จากนั้นเราก็ต้องกำหนด configuration เพื่อให้ terracotta รู้ว่า
ควรจะตัดต่อ code เราตรงไหนบ้าง
โดยสิ่งแรกที่ต้องกำหนดก็คือ root object
root object คือ object ที่เราอยากให้ ทุกๆ virtual machine ที่ join เข้าวง cluster มองเห็นเป็นตัวเดียวกัน
ในกรณีของเรา root object ก็คือ

HashMap<String,Bid> bids = new HashMap<String,Bid>();

ซึ่งใน config ของ terracotta เราจะกำหนดโดยใช้ xml ดังนี้
<roots>
<root>
<field-name>bid.Bidding.bids</field-name>
</root>
</roots>


เมื่อกำหนด root object แล้ว
เราก็ต้องบอกให้ terracotta จัดการ modify access method ทุกๆอันของเรา
ที่มีการเรียกใช้ root object
โดยเราสามารถเลือก lock ว่าจะเป็น read lock, write lock, และ concurrent

เริ่มด้วย method show ก่อน
อันนี้เลือกได้ว่าจะเป็น read หรือ concurrent หรือไม่กำหนดเลยก็ได้
ถ้าเป็น read ถ้ามันเจอใครกำลัง write อยู่ มันจะ wait รอ
ส่วน concurrent เป็นแบบที่ไม่สนว่าใครกำลังทำอะไร จะเขียนหรืออ่านก็ได้
ลองกำหนดเป็นแบบ read จะได้ไม่เจอ case แปลกๆเช่น ชื่อเป็นชื่อของคนคนหนึ่งและราคาเป็นของอีกคนๆหนึ่ง
  <named-lock>
<lock-name>show</lock-name>
<method-expression>* bid.Bidding.show(..)</method-expression>
<lock-level>read</lock-level>
</named-lock>

สังเกตุว่ามันมี type เป็น named-lock
ซึ่งมีความหมายว่า method ที่เราร้อย AOP นี้ไม่ได้เขียนแบบคำนึงถึง multi-thread มาก่อน (ไม่ได้ใช้ keyword พวก synchronized)
โดยเราต้องกำหนด name ของ lock ให้มันด้วย

จากนั้นก็ว่าด้วย method newBid โดย
เรากำหนดให้เป็น write lock
  <named-lock>
<lock-name>newBid</lock-name>
<method-expression>* bid.Bidding.newBid(..)</method-expression>
<lock-level>write</lock-level>
</named-lock>


method สุดท้ายก็คือ bid
method นี้พิเศษหน่อยตรงมันจะมี process การอ่าน console ก่อน จากนั้นจึงค่อย เขียนลงไป
เราเลยลองใช้ synchronized ครอบลงไป เพื่อไม่ให้เจอ user ใส่ข้อมูลค้างไว้
แล้วลุกไปกินกาแฟ ทำให้ระบบเราหยุดเพราะเจอ write lock เปิดทิ้งไว้
แก้ code ดังนี้
        synchronized(bids) {
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}

จากนั้นก็กำหนด configuration ของ lock
แต่คราวนี้จะสังเกตว่าเราเปลี่ยนไปใช้ autolock type แทน
  <autolock>
<method-expression>* bid.Bidding.bid(..)</method-expression>
<lock-level>write</lock-level>
</autolock>


เสร็จ แค่นี้เราก็ทดลอง run มันจาก หลายๆ virtual machine เข้าไปประมูล
บน bids ชุดเดียวกันได้แล้ว
เวลา run ก็ใช้คำสั่งประมาณนี้
โดย tc-config.xml ก็คือ configuration file ของเรา
~/dev/terracotta-2.2/dso/bin/dso-java.sh -cp bin:jline-0.9.91.jar -Dtc.config=tc-config.xml bid.Bidding 


ง่ายจนน่าตกใจ

Related link from Roti

Tuesday, March 13, 2007

Propaganda

รอบกายเรานี้มีแต่ propaganda
ดังนั้นเราควรจะเข้าใจกฎเกณฑ์พื้นฐานของมันหน่อย

Theorists of propaganda have identified five basic rules:

1. The rule of simplification: reducing all data to a simple confrontation between “Good and Bad”, “Friend and Foe”.
ผมเคยถกประเด็น technique กับ it consultant ที่มีอาชีพทนายมาก่อน
เจอ technique นี้เข้าไป แพ้หลุดลุ่ยเลย
ไอ้เราก็มัวอักๆอักๆ เพราะสำหรับเราแล้ว
ทุกๆอย่างมันมีทั้งข้อดีและข้อเสีย

2. The rule of disfiguration: discrediting the opposition by crude smears and parodies.
ป้ายสีอีกฝ่ายเสีย

3. The rule of transfusion: manipulating the consensus values of the target audience for one’s own ends.
เกาะกระแส
หาประโยชน์จากเสียงส่วนใหญ่

4. The rule of unanimity: presenting one’s viewpoint as if it were the unanimous opinion of all right-thinking people: drawing the doubting individual into agreement by the appeal of star-performers, by social pressure, and by “psychological contagion”.
(unanimity แปลว่า การยอมรับอย่างเป็นเอกฉันท์)
โชคร้ายที่เราไม่มีกลไกที่จะรู้ได้ว่า เสียงส่วนใหญ่เป็นอย่างไร
นักการเมือง ก็มักอ้างว่าความคิดเห็นตัวเองตรงเสียงส่วนใหญ่
ส่วนใครที่ค้าน ก็มักจะถูกจัดการว่า คุณต้องยอมรับประโยชน์ส่วนรวม, หรือใช้ชั้นเชิงการแสดงชักจูง

5. The rule of orchestration: endlessly repeating the same messages in different variations and combinations.
ตัวอย่างที่เห็นชัด ก็คือ โครงการที่เกี่ยวกับสถาบันกษัตริย์ (ไม่ได้พูดประเด็นถูกผิดนะ)

The Five rules of propaganda

Related link from Roti

Monday, March 12, 2007

ว่ายน้ำในทะเล

อาทิตย์ที่ผ่านมา ผมพาแฟนและลูกชายไปเที่ยวหาดเจ้าหลาว, จันทบุรีมา
ไม่นึกว่าชายหาดเมืองจันฯ ก็สวยไม่แพ้ที่อื่นเหมือนกัน
แต่ที่ชอบก็คือน้ำทะเลช่วงนี้ ที่เรียบสนิท ปราศจากคลื่น

กิจกรรมหนึ่งที่ผมขาดไม่ได้เวลาไปเที่ยวทะเล
ก็คือการว่ายน้ำในทะเล ที่ภาษาอังกฤษเขาใช้คำว่า open water swimming
ประเด็นที่ผมหลงไหลในการว่ายน้ำทะเล มีอยู่ดังนี้
1. ผมชอบบรรยากาศ สีสรร ของใต้น้ำ สีกับแสงมันสวยดี (จริงๆแล้วมันมีแค่ฟ้ากับเขียว แต่มันมีหลายเฉดสี)
สัมผัสของน้ำก็ทำให้รู้สึกดี
2. ความหนาแน่นของน้ำทะเล ทำให้เรารู้สึกตัวเบา ว่ายเหนื่อยน้อยกว่าในน้ำจึดเยอะ
3. ความรู้สึกเปิด อิสระ, ไม่มีลู่ว่าย ไม่มีเส้นสีดำบนพื้นสระที่กำหนดแนวว่าย
ไม่มีผนังสระที่ต้องกลับตัว, ไม่มี boundery ที่บอกว่า เฮ้ยว่ายครบ 100 metre แล้ว
มีแต่ว่ายเท่าไรก็ไปไม่ถึงไหน
4. ภาวะสมาธิที่เกิดจากความเงียบ,การทำซ้ำ และการ focus ที่การเคลื่อนไหว



อุปสรรคของการว่ายน้ำในทะเล
เท่าที่เจอมาก็มี
1. ความกลัว อันนี้เป็นผลมาจากหนังที่เกี่ยวฉลามทั้งหลาย



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

ความกลัวฉลามนี้จะไม่เกิดถ้ามีเพื่อนว่ายไปด้วย
อย่างที่เคยว่ายที่เกาะโลซิน ปัตตานี ซึ่งน้ำลึก > 80 ฟุต
ครั้งนั้นมีเพื่อนว่ายไปด้วย ว่ายอยู่ชั่วโมงกว่า ไม่รู้สึกกลัวเลย

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

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

3. กระแสน้ำ
อันนี้มักจะเจอตามเกาะ, พวกชายฝั่งธรรมดายังไม่เคยเจอ
ที่แรงสุดที่เคยเจอก็ที่เกาะแสมสาร สัตหีบ
ผมลงจากหาดทรายบนเกาะไปว่ายน้ำเล่น
ว่ายออกไปไม่กี่เมตร ก็เจอกระแสน้ำแล้ว
พัดทีตัวปลิวเลย
เวลาจะกระแสน้ำแรงๆ ท่าว่ายที่เหมาะที่สุดก็คือท่ากบ

4. อึ
ไม่ได้สะกดผิด, อันนี้เกิดขึ้นเวลาที่เราว่ายจากฝั่งไปขึ้นเรือ
ซึ่งบันใดเรือมันมักจะอยู่ท้ายเรือข้างๆห้องน้ำ
อันนี้ต้องเล็งและกะจังหวะให้ดี
ไม่งั้นจะเกิดภาวะสยองของการว่ายฝ่าเหล่าของเสียสีเหลืองได้

Related link from Roti

Wednesday, March 07, 2007

Erlang Book

3-4 วันที่ผ่านมานี้, mailing list ของ ruby กับ erlang
มีการ post announce ว่า Pragmatic Bookshelf จะออกหนังสือเล่มใหม่
เรื่อง Programming Erlang. สร้างความตื่นเต้น (วัดจาก feedback) ให้กับ mailing list ทั้งสองได้ไม่น้อย

สิ่งที่น่าสนใจก็คือ
ในกรณีนี้หัวข้อหนังสือคือ erlang
ดังนั้นไม่แปลกที่ mailing-list ของ erlang ก็ควรจะคึกครื้นเป็นธรรมดา
แล้ว่ทำไม่ mailing-list ของ ruby ถึงคึกไปด้วย
เหตุผลก็น่าจะประมาณนี้
1. Pragmatic Bookshelf หนังสือส่วนใหญ่ focus พวก ruby, rails, agile
มีกลุ่มลูกค้าชัดเจน มี brand royalty ค่อนข้างสูง
แฟนๆ ruby ก็เลยชอบไปหมด ไม่ว่าจะออกหนังสืออะไรมา
2. คนชอบ Ruby เป็นพวก "hyper-enthusiasts"
มีอะไรใหม่ๆ มันส์ๆ ก็ชอบ
สังเกตจากใน mailing list ก็มีหลายคนเขียนตอบไปว่า
มี erlang แล้ว อย่าลืมออกหนังสือ haskell ด้วยหล่ะ
ซึ่ง Pragmatic ก็ตอบไปว่า "อยากออก แต่ขั้นแรก ต้องหาคนเขียนที่เหมาะสมให้ได้ก่อน"

Related link from Roti

Tuesday, March 06, 2007

module_function [Ruby]

ziddik ถามเรื่องพฤติกรรมแปลกๆของ module_function
สำหรับคนที่ไม่รู้จัก module_function ลองมาดูว่ามันคืออะไรก่อน

สมมติเรา define Library math ไว้แบบนี้
module Math
def sin(x)
...
end
end


ปัญหาก็คือ คนใช้ต้อง include Math ก่อน
จึงจะใช้ได้
include Math
puts sin(10)

ซึ่งดูไม่สะดวกนัก
เช่นอาจจะเกิด name conflict ได้ง่าย

module function เข้ามาช่วยในจุดนี้โดย
module Math
def sin(x)
...
end

module_function :sin
end

จะทำให้เราสามารถเรียกใช้แบบนี้ได้
Math.sin(10)
หรือ
include Math
sin(10)


ขั้นตอนภายในจะแปลกๆหน่อยตรง
1. มันจะ copy function sin เป็น instance method อันใหม่
มี code แยกออกไปจากกัน (ไม่ share กัน)
2. มันจะ mark method ให้กลายเป็น private

ส่วนคำถามว่าทำไม มันถึง mark ให้เป็น private
มีคนพยายามเดาใจ Matz เหมือนกัน
แต่ผมอ่านแล้วก็ยังไม่เข้าใจ
Re: module_functions are private?

ส่วนตัวอย่างในคำถามของ ziddik
ถ้าอยากให้ใช้ได้ ต้องเขียนเป็นแบบนี้
module HelloModule
Version = "1.0"

def hello(name)
puts("Hello, #{name}. ")
end

module_function :hello
end


class Hello
include HelloModule
def myHello(name)
hello(name)
end
end

หรือจะ ใส่ public :hello ใน class Hello ก็ได้
แต่ตามที่ใน mailing list บอก
การทำเช่นนี้อาจจะเปิดโอกาสให้
shooting yourself in the foot.

Note: ผม search code ใน rails ดู
พบว่ามีการใช้ module_function อยู่นิดหน่อย
ใน package actionMailer กับ actionWebService
(ใช้อยู่นิดเดียว)

Related link from Roti

Ruby 's Require ภาค 2

ปกติถ้าเราจะใช้ class อะไร
เราก็ต้อง load class นั้นขึ้นมาใน context ของเราก่อน
เช่นเรามี file p.rb
class Pok
def hi
"hello"
end
end

ลอง สั่ง irb
$ irb
>> Pok.new
NameError: uninitialized constant Pok
from (irb):1
>> require 'pok'
=> true
>> Pok.new
=> #<Pok:0x11158a8>

นี่คือกลไกปกติใน ruby
แต่ถ้าใครเคยใช้ rails แล้ว จะเห็นว่า
มันไม่ได้เป็นไปตาม pattern ข้างบนนี้
สมมติเรามี file $RAILS_ROOT/app/model/activity.rb
class Activity < ActiveRecord::Base
end

ลองสั่ง script/console ดู

$ script/console
Loading development environment.
>> Activity.new
=> #<Activity:0x245efcc @new_record=true, @attributes={"name"=>nil}>

คำถามก็คือ rails ไป load activity.rb มา โดยเราไม่ต้องสั่ง require สักแอะได้อย่างไร

คำตอบอยู่ใน file ที่ชื่อ
/..verylong../active_support/dependencies.rb
ภายในมี method อยู่ตัวหนึ่งที่ชื่อ const_missing

สำหรับคนที่ไม่คุ้นกับ method นี้
method นี้เป็น method ใน class Module
หน้าที่ method นี้ก็คือ ในกรณีที่มีการเรียกใช้ Constant ที่ไม่มีอยู่จริง
>> class Module
>> def const_missing(name)
>> puts 'hi'
>> end
>> end
=> nil
>> Bunn
hi
=> nil
>> P
hi
=> nil
>>


ใน dependency.rb ก็มีการ override method นี้ดังนี้
class Module #:nodoc:
# Rename the original handler so we can chain it to the new one
alias :rails_original_const_missing :const_missing

# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
def const_missing(class_id)
file_name = class_id.to_s.demodulize.underscore
file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
begin
require_dependency(file_path)
...
long long code for error case

Magic ก็คือ require_dependency ที่ rails เรียกใช้
,require_dependency คือ function ที่ rails สร้างขึ้นมา
เพื่อใช้แทน require โดยมีข้อแตกต่างคือ
ในกรณีของ development environment
module, class ที่ load โดย require_dependency จะถูก
flush ออกไปหลังจากที่จบ request แล้ว
ทำให้ developer สามารถแก้ไข source code ได้อย่างสบายใจ
ไม่ต้อง restart web server ใหม่ทุกครั้งหลังจากแก้ไข code

ส่วนประโยค file_name = class_id.to_s.demodulize.underscore
ก็คือการ solve หาชื่อ file ที่จะ require
โดยมีหลักประมาณว่า
"Activity" -> 'activity'
"OrderItem" -> 'order_item'

Related link from Roti

Friday, March 02, 2007

Ruby 'require'

คำสั่งง่ายๆคำสั่งหนึ่งที่หลายคน (รวมผมด้วย) มองข้ามไปก็คือ require
คำว่ามองข้าม หมายถึง ใช้พอเป็น, แต่่ไม่สนใจกลไกของมัน
ถ้ามีปัญหาก็มั่วอย่างเดียว จนกว่าจะใช้ได้

วันนี้ก็เลยนั่งสะสาง ความเข้าใจเกี่ยวกับเจ้า require

require คือกลไกการ include source code อย่างหนึ่งของ ruby
มีคำสั่งที่เหมือนกันอีกคำสั่งหนึ่งก็คือ load
สิ่งที่ต่างกันก็คือ ถ้าสั่ง require ซ้ำๆกัน
มันจะ load แค่ครั้งแรกครั้งเดียว

ลองสร้าง file p.rb ที่ current directory
a = 1
b = 2
@c = 3
def d
"hi"
end

จากนั้นก็เรียก irb แล้วสั่ง
$ irb
>> require 'p'
=> true
>> require 'p' # load ครั้งที่ 2 return false เพราะถือว่า load ไปแล้ว
=> false

เงื่อนไขอย่างหนึ่งของการ require ก็คือ
local variable ที่อยู่ใน p.rb จะไม่ตามมาด้วย
>> require 'p'
=> true
>> a
NameError: undefined local variable or method `a' for #
from (irb):3
>> b
NameError: undefined local variable or method `b' for #
from (irb):4
>> @c
=> 3
>> d
=> "hi"


คำถามถัดมาก็คือ แล้วเวลาสั่ง require 'p'
มันมองหา file ที่ชื่อ p.rb ที่ไหนบ้าง,
ruby มีตัวแปร array อยู่ตัวหนึ่งที่ชื่อ $LOAD_PATH
ซึ่งตัวแปรนี้มี short-name อีกชื่อหนึ่งคือ $:
(คนไม่ชอบ perl เห็นแล้วคงร้องยี้)
>> $LOAD_PATH
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby", "/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>> $:
=> ["/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/bin",
"/usr/local/lib/ruby/gems/1.8/gems/wirble-0.1.2/.",
"/usr/local/lib/ruby/site_ruby/1.8",
"/usr/local/lib/ruby/site_ruby/1.8/powerpc-darwin8.7.0",
"/usr/local/lib/ruby/site_ruby",
"/usr/local/lib/ruby/1.8",
"/usr/local/lib/ruby/1.8/powerpc-darwin8.7.0", "."]
>>

ดังนั้นถ้าเราอยาก add load path ส่วนตัวของเรา
เราก็สามารถ append path เข้าไปใน ตัวแปรนี้ได้เลย
>> $LOAD_PATH << '/tmp' 


ที่นี้ลองไปดูที่ rails บ้าง
rails เตรียม custom configuration ให้เรา set load_path
ไว้ใน file $RAILS_ROOT/config/environment.rb
Rails::Initializer.run do |config|
config.load_paths += %W( #{RAILS_ROOT}/extras )
end

การทำงานภายใน ก็คือมันไปเรียกใช้ตัวแปร $LOAD_PATH
แต่แทนที่จะ append ไปข้างหลัง $LOAD_PATH array
มันใช้วิธีไส่ไว้ข้างหน้าแทน เพราะกลไกการหามันจะเริ่มจาก
หน้าไปหลัง การใส่ไว้ข้างหน้า ทำให้สามารถ override file ข้างหลังได้
# Set the <tt>$LOAD_PATH</tt> based on the value of
# Configuration#load_paths. Duplicates are removed.
def set_load_path
configuration.load_paths.reverse.each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
$LOAD_PATH.uniq!
end

Related link from Roti

Thursday, March 01, 2007

Linux บน Mainframe

ปกติเห็นใครใช้ Linux ก็จะดีใจ
เพราะเห็นว่ามันออกไปในแนวทางพึ่งตนเองดี

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

สัญญา สปส. 2900 ล้านที่เซ็นไป เขาเริ่มทำงานแล้ว (ใครจะหาว่าสัญญามีกลิ่นตุๆ ก็ว่ากันไป)
โดยมีการยก mainframe เข้าไปติดตั้ง
เพื่อพร้อมที่จะทำการขึ้นระบบแรก
mainframe ที่ว่านี้จะมีแต่ linux ล้วนๆ
โดยทำเป็น virtual machine
ซึ่งคาดเดาว่าจะช่วยลด cost ที่ต้องมี server กระจัดกระจายได้
+ share กันไช้ cpu ทำให้รวมๆแล้วมีจำนวน cpu ที่ต้องใช้ลดลง

เมื่อมี hardware ก็ต้องมี software
ระบบแรกที่จะส่งมอบ phase แรก
ก็คือระบบ "ประชาสัมพันธ์ข่าวสารกระทรวง"
ว้าว ระบบนี้มีบุญจริงๆ ได้ run บน mainframe ด้วย

เอ้ามองโลกแง่ดีหน่อย
มีเวลา 5 ปี ที่จะหาระบบดีๆ ขึ้นไป run
เพื่อให้คุ้มกับค่างบ 2900 ล้าน

Related link from Roti