Friday, December 30, 2005

จาก Wall Street Programmer
Advice for Newbie Wall Stree Programmers

Here’s a typical entry-level candidate with a good understanding of Computer Science, a medium grasp of C++, and absolutely no foundation in financial products:

After 2 years of working at Goldman, Morgan, Lehman, or Merrill, the candidate reaches a pretty good level of understanding of financial products He also gains a substantial amount of interoffice social skills (a result of office politics), a thorough respect for a slower than snail release process, and an anticipation of the requirement to have 2 weeks worth of meetings in order to approve an environment variable change in a configuration file. His debugging skills probably improve, but his programming skills are now completely in the toilet.

After 2 years of working at a small company, the candidate reaches a painfully thorough understanding of a small area of the financial world (whatever the company was focusing its business model on). He gains the same level of interoffice social skills (politics cannot be avoided…anywhere). He also gains excellent debugging skills by analyzing raw Solaris core dumps on a daily basis, and gains environment foundation skills by constantly tweaking Rendezvous channels, Sybase settings, Solaris process heap sizes, XML configuration files, and logging frameworks – in a live production environment (because DEV sucks everywhere…always). He gains an understanding of ‘trader-speak’ – something which will serve him greatly no matter where he goes, learns to build and deploy code to production within minutes of noticing a problem, and picks up about 4 new programming languages including C (because most API layers are in C), some shell language (because you just have to), Perl (because shell sucks), and a REALLY good understanding of SQL (using sqsh, or isql from command line).

Related link from Roti

Thursday, December 29, 2005

ActiveRecord & Metaprogramming

ถ้าเราดู source code ของ ActiveRecord ใน Rails
จะเห็นว่า magic ส่วนหนึ่งของ ActiveRecord ก็มาจาก
Metaprogramming นี่เอง
ผลลัพท์ที่ได้ ต้องใช้คำว่า งามมาก
แต่ source code นี่สุดๆเลย

ลองดูว่าเขาเขียนกันอย่างไร
เริ่มด้วย Base Class ก่อน
Model ทุกตัวของเราต้อง extend Base Class นี้
module ActiveRecord
class Base
#... lot of instance method
def find(*args)
# ...
end
#...
end
end

ใน base class นี้จะมี instance method อยู่เต็มเลย

เวลาเรานิยาม model ของเรา ใน rails
class Person << ActiveRecord::Base
has_one :address
end

ผลของ has_one ที่ใส่ลงไป
ทำให้เราสามารถ access attribute
ที่ชื่อ address ได้
p = Person.new
p.address = Address.new
p.address.line1 = 'xxxxxx'


คำถาม ก็คือ has_one มันเขียนอย่างไร
ใน ActiveRecord จะมี Module ที่เรียกว่า Association
(ซึ่งถูก include เข้าไปใน Base class)
module ActiveRecord
module Associations
def self.append_features(base)
super
base.extend(ClassMethods)
end


module ClassMethods
def has_one(name)
define_method("#{name}=") { |value|
instance_variable_set("@#{name}", value)
}

define_method(name) {
instance_variable_get("@#{name}")
}
end
end
end
end


Magic เริ่มต้นที่ append_feature
method นี้จะถูกเรียกใช้เมื่อมีการสั่ง include ActiveRecord::Associations
พร้อมทั้งส่ง parameter มาเป็น class ที่ include method นั้น
เช่น
class Pok
include ActiveRecord::Associations
end

parameter base ที่ append_features ได้รับ
ก็คือ Pok class นั่นเอง
จะเห็นว่า เมื่อได้รับ parameter base มา มันก็แค่สั่ง extend
ด้วย ClassMethods ต่ออีกที

magic อยู่ในส่วนนี้แหล่ะ
จะเห็นว่ามีการ define has_one ใน ClassMethods
(อันนี้ลดรูปมาให้ดูง่ายๆ code จริงๆซับซ้อนกว่านี้เยอะ)
      def has_one(name) 
define_method("#{name}=") { |value|
instance_variable_set("@#{name}", value)
}

define_method(name) {
instance_variable_get("@#{name}")
}
end

จะเห็นว่ามีการ define method has_one ไว้
ภายในก็จะมีการเรียกใช้ define_method
เพื่อกำหนด method ที่ชื่อ address กับ address= (assigment)
การ access instance variable ก็ไม่สามารถทำได้ตรงๆ
ต้องใช้ผ่าน instance_variable_get/set

จะเห็นว่ายุ่งยากเหลือใจทีเดียว (code ของจริงเป็นสปาเก็ตตี้กว่านี้)
อ่านเพิ่มเติมได้ที่นี่

Related link from Roti

Wednesday, December 28, 2005

ทดสอบแนวคิดการจัดการ large object ด้วยการเก็บชั่วคราวบน database

วันนี้มีโจทย์เรื่อง outofmemory
กล่าวคือ มีโปรแกรมที่จำเป็นต้อง access ข้อมูลจาก text file ขนาดใหญ่
โดยทำการ parse ข้อมูลเข้ามาเป็น object model ใน memory ก่อน
จากนั้นจึงจะ parse, aggregate เป็น ข้อมูลสรุปต่อไป
ซึ่งเมื่อข้อมูลมีปริมาณสูงถึงจุดหนึ่ง ก็จะเกิด outofmemory exception

ประเด็นของการทดลองก็คือ ต้องการใช้วิธีการ parse object แบบเดิม ​
แต่ต้องการให้ memory footprint น้อยที่สุด
(ไม่สามารถเปลี่ยนให้เป็นลักษณะการ parse แบบ sequential ครั้งเดียวได้
เพราะ business logic มันบังคับอยู่)

ทางเลือกที่ทดลอง ก็คือ ถ้าเราสร้าง Wrapper ขึ้นมาห่อ object ไว้
โดย object จริงๆจะมีการ save ลง database
และ Link เข้ากับ proxy ด้วย SoftReference

public class ItemWrapper {
private SoftReference ref;
private int key;

protected ItemWrapper(int key, Item item) {
ref = new SoftReference(item);
}

// getter here..
public XX getYY() {
return getItem().getYY();
}
...

private Item getItem() {
Item tmp = (Item) ref.get();
if (tmp == null) {
tmp = reload();
}
return tmp;
}

private Item reload() {
tmp = PersistentManager.getInstance().load(key);
ref = new SoftReference(tmp);
return tmp;
}


}

ถ้ามีการ access เมื่อไร ก็ check ว่า SoftReference ถูก
garbage ไปหรือยัง ถ้าถูก garbage ไปแล้ว ก็ให้ fetch ข้อมูลจาก
database ขึ้นมาใหม่

ข้อมูลที่ทดสอบคือ data ขนาด 100000 รายการ
ทดลอง implement ด้วย database 3 ตัวคือ JDBM, BerkeleyDB, MySQL

ขั้นที่ 1 ทดลองแบบ object ทั้งหมดเก็บใน memory ก่อน
ทดลองสร้างรายการ 100000 รายการ แล้วปรับค่า -Xmx จนได้ค่าเล็กสุด
ที่ไม่เกิด outofmemory
ได้ค่า footprint อยู่ที่ประมาณ 5 MB
เวลาที่ใช้ในการสร้าง items => 200 ms.


ขั้นที่ 2 ทดสอบ JDBM
การเก็บลง database ใช้วิธีนี้

RecordManager _manager = RecordManagerFactory.createRecordManager("/tmp/pok.db");
HTree hashtable = HTree.createInstance(_manager);

...

public void save(InternalItem item) {
hashtable.put(new Integer(item.getKey()), item);
count++;
if (count > 100) {
_manager.commit();
count = 0;
}
}

ผลการทดลองพบว่าในส่วนของ JDBM มี memory footprint เพิ่มขึ้นมา
2 MB นั่นคือคราวนี้ต้องใช้ heap memory เพิ่มขึ้นเป็น 7 MB จึงจะ run ได้
เวลาที่ใช้ในการสร้าง items => 20 วินาที
(เป็นเวลาที่ tune เรื่อง group commit แล้ว)
database file => 42 MB

ขั้นที่ 3 ทดสอบ BerkleyDB
การเก็บลง DB ใช้วิธีนี้


EnvironmentConfig ecfg = new EnvironmentConfig();
ecfg.setAllowCreate(true);

DatabaseConfig cfg = new DatabaseConfig();
cfg.setAllowCreate(true);

Environment env = new Environment(new File("/tmp/pokdb"), ecfg);

Database db = env.openDatabase(null, "test.db", cfg);
Database classDb = env.openDatabase(null, "class.db", cfg);
StoredClassCatalog cat = new StoredClassCatalog(classDb);
EntryBinding binding = new SerialBinding(cat, InternalItem.class);

...

public void save(InternalItem item) {
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry entry = new DatabaseEntry();

IntegerBinding.intToEntry(item.getKey(), key);
binding.objectToEntry(item, entry);
db.put(null, key, entry);
}

BerkleyDB ใช้ memory เยอะสุด
ต้อง config heap ถึง 32 MB จึงจะ run ได้
เวลาที่ใช้ในการสร้าง items => 12 วินาที
database file => 22 MB

ขั้นที่ 3 ทดลองกับ MySQL
การ save ใช้ sql ผ่าน jdbc และทำเป็น preparedStatment
โดยใช้ table type เป็น MyISAM

public void save(InternalItem item) {
saveStmt.setInt(1, item.getKey());
saveStmt.setString(2, item.getDesc());
saveStmt.setDouble(3, item.getAmount());
saveStmt.executeUpdate();
}

ผลการทดสอบ ต้อง config heap 7M
เวลาที่ใช้ในการสร้าง items => 27 วินาที
database file => 17 MB

สรุปผลเบื้องต้น

DBM-Xmxcreate timefile size
- (mem. only)5M200 ms.-
JDBM7M20 sec.42 MB
BerkleyDB32M12 sec.22 MB
MySQL7M27 sec.17 MB


ปัญหา
BerkleyDB ใช้ memory สูงมาก
เลยลองจับ profile ดู
พบว่ามี byte[] ค้างอยู่สูงมากๆ (91% ของทั้งหมด)
ไม่แน่ใจว่าเกิดจากการใช้ที่ไม่ถูกต้องหรือเปล่า

ส่วน JDBM นั้น ลองจับ profile แล้ว
พบว่า memory ของ SoftReference Object นั้น ใหญ่โตไม่เบาทีเดียว
คิดเป็นสัดส่วน memory ดังนี้
  • SoftReference 47 %
  • ItemWrapper 23 %
  • byte[] 12 % (เกิดจาก HTree)
  • Item 4.5 % (ตัวข้อมูลจริงๆ)

แต่ byte[] ใน JDBM ไม่ leak พอจบโปรแกรม ก็คืนหมด

ดูแล้ว SoftReference มันใหญ่เหลือเกิน
อย่างนี้มันจะช่วยประหยัด memory ได้ยังไงฟะ
หรือเราต้อง implement ReferenceQueue เพื่อ clear
SoftReference ??

Related link from Roti

Monday, December 26, 2005

Sudoku Solver in Mozart

คราวก่อนเคยเขียนเรื่อง Constraint Programming ไปแล้ว
ครั้งนั้นใช้ java

วันนี้จะลองใช้ Mozart Oz แก้ปัญหา sudoku บ้าง
เนื่องจากยังเขียน Oz ไม่เป็นเลย
ก็เลยไปลอก source code ของคนอื่นมาศึกษา
ต้นฉบับ


Root = {Map {List.make 9}
fun {$ Row}
Row = {List.make 9}
Row ::: 1#9
{FD.distinct Row}
Row
end
}

ประโยคข้างบนนี้ จะทำการสร้าง 2 Dimension Array ขนาด 9x9
ผลลัพท์ที่ได้เก็บในตัวแปร Root
fun ก็คือการ declare procedure
ส่วน fun {$ Row} => ตัว $ หมายถึง anonymous procedure

ถ้าลองเทียบเคียงกับ ruby ดู

root = Array.new(9).map {|row|
row = Array.new(9)
}


ส่วนที่ ruby ไม่มีก็คือ
  • Row ::: 1#9
    หมายความว่า แต่ละ column สามารถเป็นได้ตั้งแต่เลข 1 ถึง 9 (domain)
  • FD.distinct
    อันนี้เป็นประโยคที่กำหนด Constraint ใน Oz
    ความหมายก็คือ ตัวเลขในแถวเดียวกันห้ามซ้ำกัน



%% All numbers in each column are distinct
for X in 1;X=<9;X+1 do
{FD.distinct
{FoldR Root fun {$ Row Prev} {Nth Row X}|Prev end nil}
}
end

FoldR คืออะไร
ลองเทียบกับ Ruby ดู

x = [[1,2,3],[11,12,13]].inject([]) {|prev, row|
prev << row[0]
prev
}
x # => [1, 11]

FoldR น่าจะเทียบได้กับ Ruby Enumerable Inject
Nth ก็คือการ access array ที่ตำแหน่ง index ที่ระบุ
ส่วน | นั้น ก็คงคล้าย << ใน ruby
เพียงแต่ทิศทางจะกลับกัน << เป็นการ insert ต่อท้าย
ส่วน | เป็นการ append ข้างหน้า
Note: ถ้าเป็น lisp แล้ว | ก็คือ procedure cons นั่นเอง
(cons 1 (list 2 3 4)) => (1 2 3 4)

ถัดไปก็คือ constraint ของ 3x3 square

%% All numbers in each 3x3 square are distinct
for X in 0;X=<2;X+1 do
for Y in 0;Y=<2;Y+1 do
{FD.distinct
{FoldR
{List.filterInd Root fun {$ I Row} I>=1+X*3 andthen I=<3+X*3 end}
fun {$ Row Prev}
{Append
{List.filterInd Row fun {$ I Col} I>=1+Y*3 andthen I=<3+Y*3 end}
Prev
}
end
nil
}
}
end
end

ใน ruby ไม่มี method ที่เทียบเคียงกับ filterInd
แต่ถ้าจะ implement ด้วย ruby ก็จะมีหน้าตาแบบนี้

class Array
def filterInd
tmp = []
self.each_with_index {|elm, i|
tmp << elm if yield i
}
tmp
end
end

| กับ append ต่างกันคือ

{Show {Append [1 2 3] [5 6]}} => [1 2 3 5 6]
{Show [1 2 3] | [5 6]} => [[1 2 3] 5 6]

เทียบกับ Ruby

[1,2,3] << [5,6] # => [1,2,3,[5,6]]
[1,2,3] + [5,6] # => [1,2,3,5,6]


สุดท้าย

%% Process specs
{List.forAllInd Spec
proc {$ Y R}
Row={Nth Root Y}
in
{List.forAllInd R
proc {$ X I}
if {Not {IsFree I}} then
{Nth Row X} :: I
end
end
}
end
}
%% Search...
{FD.distribute ff {Flatten Root}}

{Nth Row X} :: I ก็คือการระบุค่าลงไปใน
variable ตัวนั้น ว่าให้มีค่าตามโจทย์ที่ให้มา (default คือ อะไรก็ได้ที่อยู่ระหว่าง 1-9)
ส่วน FD.distribute ff ก็คือระบุให้ใช้
First Faile distribute strategy

จากการทดลอง run ได้ search graph หน้าตาแบบนี้



เวลาที่ใช้ search = 10ms (เร็วกว่าตอนทดลองใน java 8 เท่า)

Related link from Roti

Saturday, December 24, 2005

Canon in D

"Canon in D" คือชื่อเพลงครับ
เป็นเพลงโปรดของผม
ชนิดที่ฟังทีไร น้ำตามันจะซืมออกมานิดๆ
อารมณ์ประมาณ อิ่มเอิบ

วันนี้ไปเห็นใน Del.icio.us
Canon in D Guitar
หนุ่มญี่ปุ่นเล่นได้ถึงใจมาก

ก่อนที่จะ click เข้าไปฟัง
คนที่ไม่เคยฟังเพลงนี้ ลองฟัง version ปกติก่อนครับ
Canon in D (piano)
จะได้อรรถรสมากขึ้น


Note: เพลงนี้เป็นเพลงของ Johann Pachelbel

Related link from Roti

Friday, December 23, 2005

VW Factory, Photo tour

A photo tour of the Transparent Factory in Dresden

ทำไมมันเนี๊ยบอย่างนี้หนอ

Related link from Roti

Yahoo Web Services + JSON + Cross domain javascript

สิ่งที่เราจะทดลองทำกัน ก็คือ ทดลองใช้ javascript
ที่ load จาก domain หนึ่ง access ไปยังอีก domain หนึ่ง
โดยมี JSON เป็น format ในการรับส่งข้อมูล

service ที่จะทดลองเรียกใช้ก็คือ Yahoo! Search Web Services
search service ของ yahoo ปกติจะ return เป็น xml format
แต่เราสามารถระบุ option เพื่อให้ใช้ return เป็น JSON format ได้
(อ่านเอกสาร Using JSON with Yahoo!)

ขั้นแรกก่อนที่จะใช้ search service
เราก็ต้องสมัครขอใช้ service ก่อน
หลังจากสมัครแล้ว เราจะได้ appid มาตัวหนึ่ง
ซึ่งจะใช้เป็น parameter ในการเรียกใช้ service

หน้าจอที่ทดลองทำจะเป็นแบบนี้



เริ่มด้วย html body ของเราหน้าตาแบบนี้
<body>
<form action="#">
<input type="text" name="qry" id="qry"/>
<input type="submit" value="search" onclick="search();return false;"/>
</form>
<div id="list">
</div>
</body>

โดยเมื่อ submit ก็จะเรียกใช้ function search
function search() {
var id='yahoo';
var url='http://api.search.yahoo.com/WebSearchService/V1/webSearch?output=js
on&callback=updateList&appid=YOUR_APPID&query=';

oScript = document.getElementById(id);
var head = document.getElementsByTagName("head").item(0);
if (oScript) {
head.removeChild(oScript);
}
var qry = document.getElementById('qry');
var reqURL = url + encodeURIComponent(qry.value);
oScript = document.createElement("script");
oScript.type = 'text/javascript';
oScript.setAttribute("src", reqURL);
oScript.setAttribute("id", id);
head.appendChild(oScript);
}

สิ่งที่พิเศษใน function search ก็คือ วิธีการ call
เนื่องจากเราไม่สามารถใช้ xmlhttprequest connect ไปยัง domain อื่นๆ
นอกเหนือจาก domain ที่ javascript นั้น load มา

trick หนึ่งท่ีนิยมใช้ ก็คือ การใช้ javascript
dynamic สร้าง <script> dom object ขึ้นมา
และ add มันเข้ากับ <head> element
โดย web service ที่ให้บริการแบบนี้ได้ จะต้องผ่านข้อมูลกลับมา
ในรูปแบบของ javascript
ซึ่งก็เข้าทางกับ JSON notaion พอดี

ใน Yahoo Search Service เราสามารถระบุ parameter
output=json เพื่อให้ pass format กลับมาในรูป JSON
และระบุ callback=functionName
เพื่อให้ script ที่ pass กลับมาทำการเรียกใช้ javascript function ของเรา
ตัวอย่างของ ข้อมูลที่ส่งกลับมาจาก Yahoo Search Service
updateList({"ResultSet":
{"totalResultsAvailable":"17899065",
"totalResultsReturned":"10",
"firstResultPosition":"1",
"Result":[
{"Title":"Java.com",
"Summary":"official Java site from Sun. ....",
"Url":"http:\/\/www.java.com\/",
"ClickUrl":"http:\/\/www.java.com\/",
"ModificationDate":"1134806400",
"MimeType":"text\/html",
"Cache":{...}
},
....
}
}


callback function ของเรา หน้าตาแบบนี้
function updateList(results) {

var ul = document.createElement('ul');
for (var i=0, link; link = results.ResultSet.Result[i]; i++) {
var li = document.createElement('li')
var a = document.createElement('a')
a.setAttribute('href', link.ClickUrl)
a.appendChild(document.createTextNode(link.Title))

li.appendChild(a)
ul.appendChild(li)
}
var listBlock = document.getElementById('list');
if (listBlock.hasChildNodes()) {
listBlock.replaceChild(ul, listBlock.firstChild);
} else {
listBlock.appendChild(ul);
}

}

การทำงานก็คือ เมื่อได้ข้อมูลกลับมา ก็จัดการ render
ลงบน div block ที่ชื่อ list

Note:
  • ยังไม่ลองใน IE ,ลองแต่ใน firefox กับ safari

Related link from Roti

ทดลองใช้ Mylar

วันนี้ได้ฤกษ์ทดลองใช้ Mylar
Mylar คือ Eclipse Plugin ที่เข้ามาช่วยให้เราจัดการ
project หรือ workspace ที่มีขนาดใหญ่ๆได้ง่ายขึ้น
โดย Mylar จะช่วยกรอง information ให้เรา เพื่อลดเวลา
ในการ navigate หา Resources

ลองมาดูตัวอย่างทำงานจริง
เริ่มแรกสุดเวลาใช้ Mylar เราต้องเริ่มด้วยการสร้าง Mylar Task ขึ้นมาก่อน



ใน Mylar Task จะแบ่ง information ออกเป็น
  • Task Summary
    ตรงนี้สามารถ link ไปยัง Bugzila หรือ Issue Tracking ได้
  • Planning
    อันนี้ดีมากเลย ช่วย track เวลาทำงานจริงให้เราได้ด้วย
  • Documentation
    อันนี้เป็น free text ใส่ตามใจชอบ




เมื่อเริ่มต้นทำงานกับ Task หนึ่งๆ เราก็จะกดปุ่ม start (ปุ่มเทาๆด้านซ้ายของ task name)
MyLar จะปิด editor ที่เราเปิดค้างอยู่ทั้งหมด
กรณีที่เป็นการทำงานกับ Task นี้ครั้งแรก
มันจะรอให้เราเลือกเปิด file ที่ต้องการเอง
แต่ถ้าเป็น task ที่เคยทำมาแล้ว มันก็จะเปิด file
ที่เกี่ยวข้องให้อัตโนมัติ


ที่ชอบก็คือ Mylar จะซ่อน information ส่วนที่ไม่เกี่ยวกับ task ออกไป
เช่นในกรณี Package Explorer ก็จะแสดงให้เห็นแต่ resources ที่เรา edit
หรือถ้าเรา navigate ไปตาม tree ใน Explorer
ส่วนที่ไม่เกี่ยวข้องก็จะถูก render เป็นสีเทาจางๆ



กรณี Outline ก็เช่นกัน
method หรือ field ที่ไม่ได้ถูก edit ก็จะถูก render เป็นสีเทาด้วย



Feture อื่นๆที่น่าสนใจมาก ก็คือ
สามารถ integrate เข้ากับ Bugzilla ได้
ถ้าสนใจให้ลองดู flash demo ของ Mylar
ที่นี่ Link

สรุปผลการทดลองใช้
ต้องขอยกให้เป็น plugin ที่ดีมากเลย
ช่วยให้เรา focus ได้ดียิ่งขึ้น
จำได้ว่าเมื่อก่อนเวลาจะ edit project ใหญ่
จะเสียเวลาส่วนหนึ่งไปกับการ scan หาข้อมูลที่ต้องการ

ไว้อาทิตย์หน้าจะแวะเข้าไปบังคับให้น้องๆที่ทำงานใช้กัน

Related link from Roti

Thursday, December 22, 2005

JavaScriptGenerator (RJS Template)

JavaScriptGenerator เป็น Plugin ของ Rails
ที่ช่วยให้เราเขียน ajax ได้สะดวกขึ้น
ลองดูตัวอย่าง

สมมติว่าเราต้องการให้มีหน้าจอ Project Listing
ที่ user สามารถ add project ใหม่ๆได้



เมื่อ user กด add ก็จะแสดง form ขึ้นมา



เมื่อ user กด "add" ก็จะ validate
ว่า project name เป็นค่าว่างหรือไม่
ถ้า validate ผ่าน ก็จะ update "project listing" ให้



กรณีีที่ไม่ผ่าน
ก็จะ แสดง error message



ถ้า user กด cancel ก็จะซ่อน form
และ error message

มาดูว่า ถ้าเขียนโดยใช้ JavaScriptGenerator แล้ว
จะต้องทำอย่างไรบ้าง

ขั้นที่ 1 ไป download plugin มาติดตั้งให้เรียบร้อยก่อน
svn ของ RJS Template อยู่ที่
http://wiki.rubyonrails.org/rails/pages/Plugins

ขั้นที่ 2 implement controller
เริ่มทำหน้าจอ listing ก่อน
class RjsController < ApplicationController

def index
list
render :action => 'list'
end

def list
@projects = Project.find(:all)
end

end

โดย rhtml จะมีหน้าตาแบบนี้
<h4>Listing projects</h4>
<ul id="list">
<% for project in @projects %>
<li>
<%=h project.name %></td>
</li>
<% end %>
</ul>

<div id="post_error">
</div>

<div id="input" style="display:none">
<%= form_remote_tag (
:url => { :action => :new }) %>
<label for="name">Name :</label>
<%= text_field_tag ('name', '', :id=>'name-field') %>
<%= submit_tag "add", :id=>'project-add'%>
<%= end_form_tag %>
<%= link_to_remote ('Cancel',
:url => {:action => :cancel_new}) %>
</div>
<br />

<div id="add_link">
<%= link_to_remote ('New project',
:url => { :action => :display_new }) %>
</div>

จะสังเกตเห็นว่า เรามี ul
ที่มี id => "list"
และก็มี div block
id => "post_error" ไว้แสดง error message
id => "input" ส่วนของ ajax form
id => "add_link" ส่วน link ที่เป็นจุดเริ่มต้นการทำงาน

ขั้นที่ 3 เมื่อ user เลือก link "new project"
ก็จะเกิด ajax request "display_new" ไปที่ server
(จริงๆแล้ว ขั้นนี้ทำเป็น javascript ธรรมดาก็ได้
แต่อยากลอง feature ก็เลยทำเป็น ajax)
controller เราก็จะ define method ดังนี้
class RjsController < ApplicationController

...

def display_new
end

end

ส่วน view ที่ render กลับ
เราจะใช้ rjs template เข้ามา render แทน
โดยแทนที่จะใช้ file ที่นามสกุล rhtml
เราจะตั้งเป็นนามสกุล rjs แทน
page.show('input')
page.hide('add_link')
page.visual_effect :highlight, 'input', :duration => 3

จาก code ข้างบน rjs จะทำการ generate
javascript ตอบกลับไป
โดย page.show ก็คือสั่งให้ DOM Element ที่ต้องการ show ขึ้นมา
ส่วน visual effect ก็คือ ไปเรียกใช้ Mir.aculo.us script ให้ทำงาน

ขั้นที่ 4 ถ้า user submit form เข้ามา
class RjsController < ApplicationController

...

def new
if params[:name] == nil or params[:name] == ""
render :action => :new_error
else

## do some business logic here.

render :action => :new_success
end
end

end

โดย rjs template กรณี success จะเป็นดังนี้
page.insert_html :bottom, 'list',
content_tag("li", params[:name])
page.visual_effect :highlight, 'list', :duration => 3

กรณีที่มี error ก็จะเป็นดังนี้
page.replace_html 'post_error',
'Project Name must specific!!!'
page.visual_effect :highlight, 'post_error', :duration => 3


ดูๆแล้วก็ง่ายดีเหมือนกัน
ถ้าอยากรู้ว่าง่ายแค่ไหน ก็ต้องลองเขียน version ที่ไม่ใช้ rjs
เปรียบเทียบกันดู
ผมลองแล้ว แต่ไม่บอก :)

Related link from Roti

CLisp Package Installer?

วันนี้อยากลองเล่น Web Application Framework
ของ CLisp ดูบ้าง ตัวที่อยากลองเล่นมีี่ชื่อว่า KPAX
ก็เลยเข้าไปหาทาง download มาเล่นดู

ในคู่มือของ KPAX ก็อธิบายไว้ว่า คุณควรจะใช้ ASDF
("Another System Definition Facility") ในการติดตั้ง
ASDF ถ้าเปรียบเทียบให้เห็นภาพ ก็คือ software ประเภทเดียวกับ apt-get, fink, gems

เอาก็เอา ทดลอง install asdf
หมดเวลาไป ชั่วโมงกว่า
เพราะใส่ path ผิด ไม่มีเครื่องหมาย "/" ปิดท้าย

จากนั้นก็เริ่ม install
คำสั่ง install ก็ดูง่ายดี

(asdf-install:install "http://weitz.de/files/cl-ppcre.tar.gz")

(สั่ง run จากใน lisp เลย)

หลายจาก screen วูบวาบอยู่สักระยะหนึ่ง
ก็มี error ว่า

*** - Server responded 404 for GET http://www.cliki.net/s-http-server?download
The following restarts are available:
RETRY :R1 Retry installation


โฮ่ๆ dependency ของ CPAX มันอ้างถึง
s-http-server ที่ดันย้ายที่อยู่ของตัวเองไปแล้ว
จัดแจง search หาว่า มันย้ายไปไหน
ได้มาแล้ว ก็มานั่งคิดต่อว่า แล้วกูจะทำไงต่อดีวะ
เคยได้ยินมาว่า lisp เวลา error แล้ว เราสามารถ
เข้าไปเปลี่ยนแปลง definition หรือ code ส่วนที่ผิดได้
แล้วก็ให้มัน run ต่อได้เลย
(ไม่รู้จำผิดหรือเปล่า เคยได้ยินประมาณว่า
พวก software ในยานสำรวจอวกาศ
มันเกิด bug แล้วมาติดอยู่ที่หน้าจอแบบนี้เหมือนกัน
พวก programmer ก็เลยต้องเข้าไปแก้ แล้วก็ run ต่อจากจุดเดิมได้)

หลังจากนั่งจิ้มเป็น ลิงจิ้ม keyboard อยู่พักหนึ่ง
ก็ยอมแพ้ เปลี่ยนเป็นเลือก abort
แล้วก็สั่ง install s-http-server ก่อนแทน

(asdf-install:install "http://homepage.mac.com/svc/s-http-server/s-http-server.tar.gz")


ฮาๆ เหมือนเดิม s-http-server ก็มี dependency ที่ย้ายหนีไปแล้ว
อยู่เต็มไปหมดเลย
นั่งใล่ install dependency ไปเรื่อยๆ
สุดท้ายก็ค้นเจอ page นี้
page นี้มีตารางอธิบายว่า อะไร depend กับ อะไรบ้าง
คราวนี้ง่ายแล้ว ก็ใล่ install จากตัวย่อยสุดขึ้นมาทีละตัว

เฮ้อ! มิน่าไม่มีใครเขาใช้ clisp กัน
แค่ติดตั้ง ก็ต้องทดสอบความอดทนของผู้ใช้ด้วย

Related link from Roti

User User User

ตัดมาจาก "Creating Passionate Users"
เขาเขียนถึงเรื่อง "The Quantum Mechanics of Users"
ซึ่งเขาอ้างมาจากหนังสือ Blink ของ Malcolm Gladwell อีกที
But it's not just the subatomic world that gets weird when you look too closely--in some cases, asking a user to explain his choices changes his choices! In his book Blink, Malcolm Gladwell (author of The Tipping Point) gives an example where students were asked to rank order 44 different kinds of strawberry jams. When compared with the rankings of experts, the students did fairly well -- "even those of us who aren't jam experts known good jam when we taste it." But--and here's where it gets weird--when the students were asked in advance to provide not just the rankings but a written explanation of their choices, the student rankings lost virtually all correlation with that of the experts. As Gladwell puts it, "By making people think about jam, [the researchers] turned them into jam idiots."


อันบนนี้ไม่เลว น่าสนใจ
แต่เจอประโยคนี้ก่อน
The comments about listening to what the users are saying, what they're not saying, and how it's being said reminds me of the quote by Claude Debussy, "Music is the silence between the notes."

ไอ้ประโยคเรื่อง music นี้ผมเคยได้ยินมาก่อนนะ
แต่ไม่เคยคิดจะเอามาเกี่ยวโยงกับ user requirement ได้เลย

Related link from Roti

Wednesday, December 21, 2005

JGoodies, Swing Data Binding

สืบเนื่องจากเรื่อง JFace Data Binding
bact' ถามว่าไม่มี swing binding บ้างหรือ?

ขอแนะนำ JGoodies
ใน JGoodies จะมี subproject อยู่ดังนี้
  • animation - Time-based real-time animations in Java
  • binding - Data Binding Framework
  • forms - Forms layout system
  • looks - look & feel
  • validation - Validation Framework

ตัวที่เราสนใจก็คือ binding

ไม่พูดพล่ามทำเพลง ลองดูตัวอย่าง code ที่ทำการ binding
เริ่มที่ domain ก่อน
public class Person {

private String name;
private String address;
private String telNo;
private BigDecimal salary;

... // setter & getter

}

ให้ salary เป็น BigDecimal เพื่อที่จะได้ทดสอบ case ที่ต้อง convert ด้วย

Swing Form paint โดยใช้ Matisse ใน netbeans 5.0
โคตรใช้ง่ายเลย สวรรค์ดีๆนี่เอง



ลอง binding แบบง่ายๆดู
private javax.swing.JTextArea textAddress;
private javax.swing.JTextField textName;
private javax.swing.JTextField textSalary;
private javax.swing.JTextField textTelNo;
...
...
Bindings.bind(textName, new PropertyAdapter(person, "name"));
Bindings.bind(textAddress, new PropertyAdapter(person, "address"));
Bindings.bind(textTelNo, new PropertyAdapter(person, "telNo"));


ในส่วนของ salary ที่เป็น BigDecimal
ก็ต้องมีการเขียน Wrapper ขึ้นมา เพื่อใช้ convert
โดย JGoodies เตรียม class AbstractConverter ให้เราแล้ว
class SalaryConverter extends AbstractConverter {

public SalaryConverter(ValueModel subject) {
super(subject);
}

public Object convertFromSubject(Object object) {
BigDecimal value = (BigDecimal) object;
if (value == null) {
return "";
}
return value.toString();
}

public void setValue(Object object) {
if (object != null) {
BigDecimal tmp = new BigDecimal((String) object);
subject.setValue(tmp);
} else {
subject.setValue(new BigDecimal("0.00"));
}
}

}

Note: code นี้เป็นการทดลอง implement
ดังนั้นการทำงานจะไม่สมบูรณ์ ไม่สามารถรองรับได้ครบทุก case


เมื่อเตรียม converter ได้ ก็ทำการ binding ด้วยคำสั่งนี้
Bindings.bind(textSalary,
new SalaryConverter(
new PropertyAdapter(person, "salary")));

จะเห็นว่าสามารถนำ adpater มา chain ต่อกันได้

ลองทดสอบอีก โดยเพิ่มการ validate salary เข้าไป
คราวนี้เราจะใช้ class AbstractVetoableValueModel เข้ามาช่วย
Note: สมมติว่า JGoodies ไม่มี framework
ในส่วนของ validation เราก็เลยต้อง implement เอง

class ValidSalary extends AbstractVetoableValueModel {

public ValidSalary(ValueModel subject) {
super(subject);
}

public boolean proposedChange(Object oldValue, Object newValue) {
try {
BigDecimal tmp = new BigDecimal((String) newValue);
textSalary.setBackground(Color.white);

return true;
} catch (Exception e) {
textSalary.setBackground(Color.red);
}
return false;
}


}


code ในการ binding ของเรา ก็จะเปลี่ยนเป็น
Bindings.bind(textSalary,
new ValidSalary(
new SalaryConverter(
new PropertyAdapter(person, "salary"))));


จบดื้อๆ
ใครสนใจลองไปอ่านดูได้
ขอแนะนำ presentation นี้
แล้วจะเห็นว่า JGoodies มีอะไรหลายๆอย่างที่น่าสนใจทีเดียว
(รวมถึงเรื่อง architecture ของ framework
ที่ดูน่าสนใจทีเดียว)

Related link from Roti

Monday, December 19, 2005

Homebrew CPU

สุดยอด
http://www.homebrewcpu.com/

Magic-1 is a homebuilt minicomputer. It doesn't use an off-the-shelf microprocessor, but rather has a custom CPU made out of 74 Series TTL chips. Altogether there are more than 200 chips in Magic-1 connected together with thousands of individually wrapped wires. And, it works. Not only the hardware, but there's also a full ANSI C compiler for Magic-1 (retargeted LCC), and a rudimentary homebrew operating system.


ปล. ในหน้าแรกอย่าลืมอ่าน P.S. กับ P.P.S.
ตลกดี

Related link from Roti

Programming Language

อ่านเจอใน Jonas Boner
เรื่อง "Java Killed The Innovation of Computer Language"

Java killed the innovation of computer languages.


AspectJ is one of the few innovations in computer languages the last years.


in general nothing has really happened the last 10 years. We have seen many new scripting languages popping up lately (Ruby, Python, Jython, groovy etc.) and even though most of them are both fun and useful - non of them are really innovating, merely reusing and sometimes reshaping old ideas

Related link from Roti

JFace Data binding

ได้ยินข่าวแว่วๆมาสักพักแล้ว
อ่านเจอใน The visual Editor
ว่าตอนนี้ Eclipse3.2M4 มี api ของ JFace Data binding Framework แล้ว
โดยมี api ชุดนี้จะ finalize ที่ version3.2M5

ลองดูตัวอย่างว่า ถ้าเรา binding class person เข้ากับ textfield
แล้วจะต้องเขียนประมาณไหน

Composite top = new Composite(parent, SWT.NONE);
Text firstName = new Text(top, SWT.BORDER);
Person person = new Person();

IDataBindingContext dbc = DataBinding.createContext(top);
dbc.bind(firstName, new Property(person, "firstName"), null);

Related link from Roti

Sunday, December 18, 2005

Fitnesse

ว่าจะเขียนถึง Testing Framework ตัวนี้มานานแล้ว
วันนี้เห็นว่าเขาปรับปรุงหน้า web page ของเขาสวยงามดีแล้ว
ก็เลยได้ฤกษ์ทดลองเสียที

ถ้าสงสัยว่ามันต่างจากพวก xUnit อย่างไร
ก็ลองดู quote นี้

xUnit: Building the Code Right

Fitnesse: Building the Right Code

jUnit เป็น Tool ที่ programmer ใช้ช่วยให้
ตัวเอง(และคนอื่น) มั่นใจว่า code ทำงานได้ถูกต้อง
แต่คำว่าทำงานได้ถูกนั้น มันมีความหมายอื่นอีก
ก็คือ "ทำงานได้ถูกต้องตามที่ลูกค้าต้องการ"

FitNesse ก็คือ tool ที่เข้ามาช่วยทำ Acceptance test
โดยการใช้ wiki เข้ามาเป็นช่องทางในการสื่อสารระหว่าง
ลูกค้า กับ developer โดยลูกค้าจะเป็นคน
กำหนดข้อมูลตัวอย่างว่า อะไรที่ควรทำได้ และอะไรที่ไม่ควรทำ
ไว้ใน wiki page
จากนั้นก็สั่งให้ fitnesse run test (จากการ click ใน wiki นั้นเองเลย)
ซึ่งผลลัพท์ที่ได้จากการ run ก็จะนำมา render เปรียบเทียบกับ
ค่าที่ลูกค้าคาดหวังว่าจะได้ ซึ่งเป็นการ feedback
ถึง status ของโปรแกรม ว่าทำงานถูกต้องหรือไม่

ลองมาดูตัวอย่างการใช้งานแบบง่ายๆดู
  • เริ่มด้วย customer สร้าง page ที่กำหนด ค่า input และ output
    ที่คาดหวังว่าจะเป็นก่อน
    (สามารถสร้างใน excel แล้วค่อย import เข้ามาก็ได้)



  • tester จะ edit page เพื่อใส่ class ที่รับผิดชอบ testcase นี้
    รวมทั้งกำหนด classpath ด้วย



  • หน้าตา java code ที่อยู่เบื้องหลัง test case นี้
    import fit.ColumnFixture;

    public class Wage extends ColumnFixture {

    public String givenWage;

    private BigDecimal getGivenWage() {
    return new BigDecimal(givenWage).setScale(2);
    }

    public String roundWage() {
    BigDecimal tmp = getGivenWage();
    ...
    return tmp.toString();
    }

    }


  • เมื่อต้องการ จะ test ก็เพียงแต่กดปุ่ม "test" ที่อยู่ข้างซ้ายของจอ
    ลองดูตัวอย่างผลลัพท์ที่ได้



    สีแดงแสดง error, ส่วนสีเขียวแสดงว่าค่าถูกต้องตรงตามที่คาดหวังไว้


ตัวอย่างที่แสดง เป็นตัวอย่างง่ายๆ
ถ้าอยากเห็นตัวอย่างการใช้งานจริง
(ซึ่งต้องการอะไรที่ซับซ้อนกว่าตัวอย่างนี้)
ลองเข้าไปที่นี่
SuiteAcceptanceTests อันนี้เป็น test ที่ fitnesses test ตัวมันเอง

Update
fitnesse สามารถใช้กับ java, .net, ruby, Python
, Delphi, Smalltalk, Perl

Related link from Roti

Friday, December 16, 2005

การใช้งาน Rails Plugin command

วันนี้เรียนรู้วิธีการใช้งาน Rails Plugin ขึ้นมาอีกหนึ่งอย่าง
คำสั่งแรก ก็คือ

script/plugin discover

คำสั่งนี้ rails จะ scan หา plugins repository site
และ add plugin source ที่พบไว้ใน
file ~/.rails-plugin-sources ของเรา

ตอนแรกที่เห็นคำสั่งนี้ ก็นึกว่าเขาคงมี web service
ที่ให้บริการเรื่อง query เพื่อที่จะสอบถามได้ว่า
มี repositories อะไรที่ลงทะเบียนไว้บ้าง
แต่เปล่าเลย หลังจากเปิดดู source code
ก็พบว่า เขาใช้วิธีง่ายกว่านั้น
กล่าวคือ
ปกติ Rails จะมี page Plugins
ที่ลงรายการ plugin ที่มีคนทำแล้ว พร้อมคำอธิบายสั้นๆ
สิ่งที่คำสั่ง discover ทำ ก็คือ
load page ที่ว่ามา
แล้วก็ใช้ pattern matching หา link ที่มีคำว่า "plugin"

|- class Discover
||+ def initialize(base_command)
||
||+ def options
||
||+ def parse!(args)
||
2- def scrape(uri)
23 require 'open-uri'
23 puts "Scraping #{uri}" if $verbose
23 dupes = []
3- content = open(uri).each do |line|
4- if line =~ /<a[^>]*href=['"]([^'"]*)['"]/
45 uri = $1
5- if uri =~ /\/plugins\// and
uri !~ /\/browser\//
56 uri = extract_repository_uri(uri)
56 yield uri unless dupes.include?(uri) or
Repositories.instance.exist?(uri)
56 dupes << uri
56 end
45 end
34 end
23 end
||
||+ def extract_repository_uri(uri)
|| end


หลังจากได้ listing ของ plugin source มาแล้ว
คราวนี้เราก็สามารถใช้คำสั่ง

script/plugin install PLUGIN_NAME

เพื่อ install plugin ที่เราต้องการ

Note:
ผมชอบใช้วิธี svn export มากกว่าแฮะ

Related link from Roti

คนเดินถนน

เมื่อก่อนบ้านผมอยู่ในซอยที่เงียบสงบ
ต่อมามีโรงหนังดังมาตั้งอยู่ปากซอย
ซอยก็เลยกลายเป็นที่ระบายรถ

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

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

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

แต่ที่เหมือนกันหมด ก็คือแววตา
หรือสีหน้า ที่ไม่รับรู้การมีตัวตนของคนเดินถนน

Related link from Roti

Thursday, December 15, 2005

ผจญภัยกับ Emacs

Note:
post นี้
  • อาจจะช่วยสร้างกำลังใจให้กันคนที่หวาดหวั่นกับ Emacs
  • สนับสนุนสิ่งที่คุณ poonlap post ไว้ใน Bad Knowhow and Good Wrapper


ช่วงนี้ผมกำลังสนใจ Constraint Programming
ก็เลยไป load Mozart Programming System มาลองใช้ดู
Environment การพัฒนาของตัวนี้จะอยู่บน Emacs เป็นหลัก
ผมก็ไม่ถนัด Emacs เท่าไร
พึ่งหัดใช้มาได้ไม่เกิน 2 เดือน
จำคำสั่งอะไรก็ไม่ได้
การใช้ Emacs ก็เลยใช้ mouse เป็นหลัก (XEmacs)
(key-binding มันเยอะแยะไปหมด ใครจะจำได้วะ)

ตอนแรกที่ลองใช้ Mozart ก็ทดลองบน Ubuntu ก็พบว่าใช้ได้ดี
แต่พอทดลองลงบน Mac OS X บ้าง
กลับพบว่ามี error ขึ้นมาดังนี้

Buffer is read-only: #<buffer *Oz Compiler*>

ลอง search หาใน google ดู ก็เห็นมีคนถามเหมือนกัน
แต่ไม่มีคนตอบ

ก็เลยเปลี่ยนไปอ่านคู่มือ Emacs แทน
หาอะไรที่เกี่ยวกับ read-only
ก็พบว่ามีบท 27.7 Read-only Buffers
ก็เลยเข้าไปอ่าน
อ่านไปอ่านมา ก็เห็นว่ามีคำสั่ง toggle-read-only ให้ใช้
ก็เลยไปทดสอบสั่งใน buffer ที่ error ดู
M-x toggle-read-only
"เฮ้ย มหัศจรรย์ มันใช้ได้" !!
Error หายไปแล้ว
เหลือแต่ข้อสงสัยว่า ทำไมมันเปิด buffer มาแบบ read-only วะ

ด้วยความมันส์ในอารมณ์
ก็เข้าไปเปิด script file ของ mozart ที่ใช้ทำงานใน emacs
สำหรับคนที่ไม่เคยใช้ Emacs, script file พวกนี้
จะเขียนด้วยภาษา eLisp
โชคดีที่ผมหัดเรียน scheme มาได้พักใหญ่แล้ว
(ใช้ scheme เป็น ก็จะอ่าน code lisp ออก
เพราะ concept มันคล้ายๆกัน)

หลังจากตาลายอยู่พักใหญ่ ก็เริ่มอ่าน code รู้เรื่องขึ้น
จะว่าไปแล้ว code lisp ที่ดูเหมือนอ่านยาก
ถ้าจับหลักได้ มันก็อ่านง่ายดีเหมือนกันนะ
ในที่สุด ก็เจอ code ส่วนที่เป็นตัว open buffer

(let ((buffer (generate-new-buffer "*Oz Compiler*")))
(setq oz-compiler-buffers (cons buffer oz-compiler-buffers))
(save-excursion
(set-buffer buffer)
(compilation-mode)
(set (make-local-variable 'compilation-parse-errors-function)
'oz-compilation-parse-errors))

หลักการมั่วของผม ก็คือดูแต่โครงๆ
อย่างอันนี้ ก็ให้รู้ว่ามันกำลังสร้าง buffer ใหม่ด้วยคำสั่ง generate-new-buffer
และก็มีการ switch ไปที่ buffer นั้นด้วยคำสั่ง set-buffer
แล้วก็มีเรียกใช้คำสั่ง compiler-mode (ซึ่งก็ไม่รู้เหมือนกันว่ามันคืออะไร)
ผมก็เลยทดลองแทรกคำสั่ง ลงไปแบบนี้

(let ((buffer (generate-new-buffer "*Oz Compiler*")))
(setq oz-compiler-buffers (cons buffer oz-compiler-buffers))
(save-excursion
(set-buffer buffer)
(compilation-mode)
(toggle-read-only)
(set (make-local-variable 'compilation-parse-errors-function)
'oz-compilation-parse-errors))

ทดลอง restart emacs ใหม่
Error ยังอยู่เหมือนเดิม
เกือบจะถอดใจแล้ว
เผอิญเหลือบไปเห็นว่า file ที่ผมแก้อยู่ มันมีนามสกุลอยู่ 2 แบบ (ชื่อเดียวกัน)
อันหนึ่งเป็น el (อันที่ผมแก้อยู่) อีกอันเป็น elc
ก็เลยลองค้น google ดู จึงรู้ว่า elc ก็คือ script ที่ compile ไว้แล้ว

คราวนี้ก็ต้อง search หาอีกว่า จะ compile el script นี้ต้องสั่งอย่างไร
ในที่สุดก็เจอในคุ่มือ elisp บท 16.2 The Compilation Functions

หลังจาก compile เสร็จ
error ก็อัตรธานหายไป
ฮา ฮา, ของไชโย 3 ที

(ส่วนสาเหตุจริงๆ ที่ทำให้เกิด read-only นั้น
ไม่อยากหาต่อแล้ว แค่นี้ก็หมดไปเกือบ 2 ชั่วโมงแล้ว)

สรุป
อย่างที่ post ของคุณ poonlap บอกแหล่ะครับ ยิ่งเล่นเยอะ bad knowhow ก็เยอะขึ้น
การเล่นครั้งนี้ จริงๆก็ไม่ได้มีประโยชน์อะไรที่เป็นชิ้นเป็นอัน
แต่มันจะช่วยสะสม degree ของ bad knowhow ของผมขึ้นมาอีก

Related link from Roti

Wednesday, December 14, 2005

ทดลอง Rails Plugin

Rails version ปัจจุบัน (>=0.14) มีการเพิ่ม architecture
ที่เรียกว่า plugin เข้ามาใหม่
เพื่อให้ใช้เป็นมาตรฐานสำหรับการ customize rails
หรือการเพิ่ม extension ใหม่ๆลงไป

เดิมที่ยังไม่มีมาตรฐานนี้
developer ต่างคนก็ต่างใช้วิธีการของตัวเองในการ add Mixin ลงไป
เช่นบางคนก็ติดตั้ง library ใน gems
แล้วค่อยไปแก้ environment.rb ให้ require เข้ามา
หรือไม่ก็เขียน code ไว้ใน $RAILS_PROJECT/lib directory แล้ว require
ใน code ส่วนที่ต้องการใช้

ส่วนของใหม่ กำหนดให้เก็บ code นี้ไว้ใน
direcotry $RAILS_PROJECT/vendor/plugins
โดยเราต้องสร้าง directory $PLUGIN_NAME/lib ขึ้นมา
และภายในนั้น Rails จะเรียก require file ที่ชื่อ $PLUGIN_NAME.rb
ให้โดยอัตโนมัติ (อันนี้เดาเอานะ เพราะหา source code หรือเอกสารยืนยัน
ยังไม่เจอ แต่ได้ทดลองเปลี่ยนชื่อ หรือสร้าง file ชื่ออื่นๆไว้ มันก็ไม่ได้เรียกใช้แต่อย่างไร)

ส่วนการ install plugin ที่คนอื่นๆเขียนไว้
นิยมใช้ svn export ออกมาจาก repository
โดยเราต้อง cd ไปที่ $RAILS_PROJECT/vendor ก่อน

สำหรับ plugin ที่ผมทดลองใช้แล้วก็คือ Asset Timestamping
โดย install โดยใช้คำสั่ง

$ svn export http://svn.aviditybytes.com/rails/plugins/asset_timestamping

plugin นี้จะช่วยแปะ timestamp ลงไปใน path ของพวก javascript หรือ css
เช่น
เดิมใน view ที่สร้างโดย scaffold มันจะ require css โดยใช้คำสั่งนี้

<%= stylesheet_link_tag 'scaffold' %>

ซึ่งผลลัพท์ html ที่ได้จะได้ link หน้าตาแบบนี้

<link href="/stylesheets/scaffold.css" media="screen" rel="Stylesheet" type="text/css" />

เมื่อ install plugin แล้ว มันจะเปลี่ยนเป็นแบบนี้

<link href="/stylesheets/scaffold.css?1134445305" media="screen" rel="Stylesheet" type="text/css" />


Note: ที่ต้องทำเช่นนี้ ก็เพราะว่า browser มักจะทำ
cache ในส่วนของ javascript กับ css ไว้
เวลาที่เรามีการเปลี่ยนแปลง file พวกนี้
browser มันไม่ยอมรับรู้ตามไปด้วย
เดือดร้อนต้องเปลี่ยนชื่อ หรือเพิ่ม file ใหม่
หรือไม่ก็โทรไปบอกว่า ช่วยลบ cache ทิ้งด้วยครับ

ลองมาดู source code ของ Asset Timestamping กัน
จะได้รู้ว่า Mixin เขียนอย่างไร

12 module ActionView #:nodoc:
13 module Helpers #:nodoc:
14 module AssetTagHelper
15 private
16 def timestamped_compute_public_path(source, dir, ext)
17 public_path = compute_public_path_without_timestamp(source,dir
,ext)
18 if not source.include?('?')
19 file_paths = ["#{RAILS_ROOT}/public/#{dir}/#{source}",
20 "#{RAILS_ROOT}/public/#{dir}/#{source}.#{ext}"
]
21 for path in file_paths
22 if File.exists?(path)
23 return public_path << '?' + File.stat(path).mtime.to_i.t
o_s
24 end
25 end
26 end
27 public_path
28 end
29 alias :compute_public_path_without_timestamp :compute_public_pat
h
30 alias :compute_public_path :timestamped_compute_public_path
31 end
32 end
33 end

หลักการก็คือ มันทำการ mixin เข้าไปที่ module ActionView::Helpers::AssetTagHelper
ทำการตัดต่อเปลี่ยนชื่อ method ก่อน
โดยของเดิมให้เปลี่ยนไปใช้ชื่อใหม่ว่า compute_public_path_without_timestamp (บรรทัดที่ 29)
และให้เอาชื่อ method ที่พึ่งเพิ่มเข้าไปใหม่ ให้ไปแทนที่ของเดิม (บรรทัดที่ 30)

method timestamped_compute_public_path
เนื้อหาก็ไม่มีอะไรมาก
เริ่มแรก ก็ให้ gen url ขึ้นมาโดย method เดิม (บรรทัดที่ 17)
จากนั้นก็หา file และแปะ mtime ของ file นั้นตามหลังไป (บรรทัดที่ 23)

ดูแล้ว ก็รู้สึกสนุกกับ ruby
ที่มี class แบบเปิด
ใครใคร่แก้ แก้ (mixin)
ไม่เหมือน java ที่
ใครไคร่แก้ ก็ต้อง extends เอา (อันนี้สำหรับกรณีที่เขาออกแบบมาเผื่อ)
แต่ถ้าไม่ได้ออกแบบมาเผื่อ
ใครใครแก้ ก็ต้องcglib เอา

Related link from Roti

Tuesday, December 13, 2005

เรื่องทางสาธารณะสุขบ้าง

PracticallyKM
อันนี้อ่านเจอใน GotoKnow
เป็นเรื่องที่คุณหมอ phichet เล่าถึงประสบการณ์การดูงาน
ที่ออสเตรเลีย น่าสนใจทีเดียว

อ่านไปอ่านมาก็เจอที่คุณหมอพูดถึง
นพ.สุภัทร ฮาสุวรรณกิจ
จำชื่อได้ว่า เป็นเพื่อนร่วมกิจกรรม (สมัยเรียน)
ก็เลยตามไปดูใน google
ได้บทความที่เขาเขียนมาจำนวนหนึ่ง
เลยเอามาลง link ไว้



อ่านแล้วก็รู้สึกว่าเราชักจะหมกมุ่นกับ โลกของ technical มากเกินไปแล้ว

Related link from Roti

Monday, December 12, 2005

Javascript in ruby style

นึกแล้วว่า Prototype มันต้องมีอะไรดีๆซ่อนอยู่
ด้วยความที่ว่ามันไม่มีเอกสารให้ดู
เราก็เลยไม่รู้ว่ามันทำอะไรได้บ้าง

วันนี้อ่านเจอ article เรื่อง Prototype Meets Ruby: A Look at Enumerable, Array and Hash
อ่านแล้วต้องร้อง "ว้าว" ดังๆ

Related link from Roti

ทดลองใช้ OpenLaszlo

ผมเคยทดลอง evaluate OpenLaszlo ตั้งแต่สมัยที่มันยังไม่มี คำว่า open นำหน้า
จากการทดลองเล่น ยอมรับเลยว่าติดใจใน interface ที่ดู หรูหรา มาก
แต่ตอนนั้นมีปัญหาว่า compile-time มันสูงมาก
อีกอย่าง swf file ที่ได้ก็มีขนาดใหญ่เกินไป (1 MB นิดๆ)

วันก่อนเจอที่ mk เขียนถึง OpenLaszlo ก็เกิดอาการสนใจอยากลองเล่นอีกครั้ง

เริ่มแรกทดสอบขนาดก่อนเลย
เอาที่ helloworld ก่อนเลย เพราะมันเป็น app ที่เล็กที่สุดที่เป็นไปได้แล้ว

<canvas>
<text>Hello Laszlo!</text>
</canvas>

ขนาดที่ได้ ก็คือ 102,599 Byte
ลองมาดูว่า ตัวเลข 100 กว่า KB นี้มันแถมอะไรมาให้บ้าง
(openlaszlo มี feature ที่ช่วย describe ว่า
app ของเราหลังจาก compile แล้วจะมีอะไรแปะอยู่ข้างในบ้าง)
ใน hello app ของเรา จะแบ่งออกเป็น 2 ส่วนคือ
LFC (laszlo foundation class) กับ Instances
โดย LFC ของ flash version 7 มีขนาดก่อน zip 209 KB
ส่วน instance ของเรามีขนาด 168 Byte
หลังจาก zip แล้วจะได้ขนาด 69 KB
ซึ่งไม่เท่ากับ swf file ที่เราได้กลับมา
แสดงว่ามี overhead ในการแปลงเป็น swf อีก 30 KB
(ตรงนี้ไม่รู้เหมือนกันว่า ขนาด 30 KB นี้
เป็น fix size หรือ variable size
เมื่อโปรแกรมใหญ่ขึ้น)

สรุปขนาดของ App
เล็กสุด ก็น่าจะประมาณ 100 KB

ที่นี่ด้านเวลาบ้าง
เวลาใน version นี้คงไม่เป็นปัญหาแล้ว
เพราะเราสามารถ pre-compile เป็น format lzo ได้
(จริงๆแล้ว binary ข้างในคือ swf format)

ที่นี้ลองมาดู factor สุดท้ายบ้าง
ก็คือเรื่องของ runtime environment
(พัฒนาเสร็จแล้ว จะเอาไปใช้แล้ว)
กรณี static web app
อันนี้หมายถึง file laszlo ของเราไม่ได้ dynamic เปลี่ยนแปลงไปมา
(หมายถึงโปรแกรมนะ ไม่ใช่ data)
การเอาไปใช้ วิธีที่ง่ายที่สุด ก็คือ copy file lzo (ที่ได้จากการ pre-compile)
ไปไว้ใน web application ที่เราต้องการ
โดยแค่เปลี่ยนนามสกุลเป็น swf ก็ใช้ได้แล้ว

ส่วนกรณีที่ lzx (โปรแกรม) ของเราเปลี่ยนไปมาได้ (dynamic)
อันนี้ไม่ขอแนะนำ เพราะ compile time
สูงมาก (>15 sec)

Related link from Roti

Wednesday, December 07, 2005

Switch to Postgresql

อ่านเจอเรื่อง FeedLounge Now running on PostgreSQL
เขาพูดถึงประเด็นว่าทำไมเขาถึง switch จาก MySQL -> PostgreSQL
แรกเริ่มเดิมทีเขาใช้ MySQL 's MyISAM table แต่ต่อมาก็เปลี่ยนไปใช้
mysql 's InnoDB table แทน โดยเขาพบปัญหาจาก
table locking behavior in MyISAM
เนื่องจากลักษณะงานของเขามี write ครึ่งหนึ่งของ read
(read 4 ล้าน quries/day, write 2 ล้าน queries/day)
ผลก็คือทำให้ scalable ของเขาต่ำมาก (< 10 users)

ผลลัพท์จากการใช้ InnoDB เขาบอกว่า
พบปัญหา slow performance
จากการ load Data (ไม่ใช่เรื่อง query)
ก็เลยตัดสินใจเปลี่ยนอีกที
คร่าวนี้ย้ายไป PostgresSQL แทน

เหตุผลที่เป็นแรงจูงใจในการเปลี่ยนคราวนี้ ก็มี
  • Database Size
    เมื่อตอนเปลี่ยนจาก MyISAM -> InnoDB database
    ที่เคยมีขนาด 1 GB ก็กลายเป็น 10+GB
    ปัจจุบัน ณ ขณะที่เปลี่ยน ขนาดได้กลายเป็น 34 GB
    หลังจากย้ายลง Postgres แล้ว ขนาดเหลือเพียง 9 GB
  • Load time
    การ load data ลง database
    เดิม MySQL ใช้เวลา 1 วัน ในการ load
    เมื่อเปลี่ยนมาใช้ PostgreSQL ก็เหลือแค่ 4 ชั่วโมง


นอกจากนี้ยังพบว่า Postgres ใช้ memory
แค่ 1/3 ของ mysql

ที่น่าสนใจก็คือ comment ที่มีคนมา post
เช่น
  • InnoDB ใน MySQL 5 มี feature "compact" row format
    ที่ช่วยลดขนาด table ได้
  • Postgres ไม่สามารถ set ให้ใช้ share buffer
    ได้เต็มที่เหมือน MySQL แต่ Postgers สามารถใช้ประโยชน์จาก
    OS cache ได้, และมีค่า effective_cache_size ที่ใช้ set
    เพื่อให้ optimizer รับรู้
  • Feedlounge ใช้ GUID เป็น primarykey
    คนของ MySQL ก็เลยบอก
    GUIDs tend not to do well for index storage efficiency, since InnoDB stores the uncompressed primary key in all secondary index records.

    ผลก็คือเขาต้องใช้ IO เยอะขึ้นไปอีก (จากการ load index)
  • UUID is really poor choice for primary key for Innodb.
    The data is going to be clustered by it… and it is random which means you will insert in radom spots in giant BTREE (as it holds rows). Furthermore inserts in the middle will frequently result in page spits which causes IO and fragmentation.

Related link from Roti

Monday, December 05, 2005

Samorost



samorost เป็นชื่อ game ที่เขียนโดย
Jakub Dvorský
art มากๆ

Related link from Roti

Constraint Programming

เขียนเรื่อง ruby, rails เยอะแล้ว กลับมาที่ java บ้าง

วันก่อนผมอ่านเจอเรื่อง Solving Sudokus in Java
คนที่ยังไม่รู้จัก sudokus ให้ลองดูคำอธิบายได้ที่ wikipedia on sudokus
(อธิบายละเอียดยิบเลยครับ)
ที่สนใจก็คือ ผมยังไม่เคยได้ยินคำว่า Constraint Programming มาก่อน
ใน tutorial ข้างบนเขาใช้ library ที่ชื่อ Koalog Constraint Solver
ซึ่งเป็น commercial product
ผมก็เลยลอง search หา Library ที่เป็น opensource license ดู
เจอเจ้า Cream: Class Library for Constraint Programming in Java
ผลผลิตจาก Japan (ดีที่ web site เขาเป็นภาษาอังกฤษ)

ลองดูวิธีการใช้ Cream solve Sudokus กัน
(เอกสารของ cream มีไม่เยอะ แต่โชคดีที่เขามีตัวอย่างให้ดู
รวมทั้งมีตัวอย่าง sudokus ด้วย ก็เลยแกะมาเล่าให้ฟังได้)
โดยเราจะใช้โจทย์เดียวกับ tutorial ที่อ้างถึงข้างบน
เริ่มด้วยการ define problem array ก่อน
static int prob[][] = {
{6,0,0,0,5,8,4,9,0},
{0,2,4,0,0,0,0,0,0},
{0,0,0,0,2,0,0,3,6},
{0,0,0,0,0,0,9,0,7},
{7,0,0,3,0,0,8,0,0},
{1,5,0,0,8,9,0,0,0},
{0,3,1,0,0,0,0,0,0},
{0,0,0,0,3,0,5,0,0},
{0,0,8,0,9,5,0,0,0}
};


ก่อนที่จะ solve ก็ต้องมีการสร้างตัวแปรให้ cream รับรู้ก่อน
ในกรณีของเรา ก็คือ เราจะมีตัวแปรทั้งหมด 9 * 9 = 81 ตัว
โดยตัวแปรทั้งหมดจะต้องถูกสร้างภายใต้ context network
Network net = new Network();

IntVariable v[][] = new IntVariable[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (prob[i][j] == 0) {
v[i][j] = new IntVariable(net, 1, 9);
} else {
v[i][j] = new IntVariable(net, prob[i][j]);
}
}
}

จะเห็นว่าถ้าค่าใน prob array เป็น 0 เราจะ new variable
ด้วย constructor new IntVariable(net, 1, 9)
ความหมายก็คือ ค่าใน variable นี้ เป็นไปได้ตั้งแต่ 1 ถึง 9
ส่วนกรณีที่มีค่าอยู่แล้ว ก็จะใช้ constructor new IntVariable(net, x)
โดย x ก็คือค่าของ variable นั้นๆเลย

ขั้นถัดไปก็คือการ declare constraint ของปัญหา
เริ่มด้วย
  • แต่ละ row ห้ามมีเลขซ้ำกัน
    // แต่ละ row ห้ามมีเลขซ้ำกัน
    IntVariable tmps[] = new IntVariable[9];
    for (int i = 0; i < 9; i++) {
    for (int j = 0; j < 9; j++) {
    tmps[j] = v[i][j];
    }
    new NotEquals(net, tmps);
    }


  • แต่ละแถวห้ามมีเลขซ้ำกัน
    // แต่ละ column ห้ามซ้ำกัน
    for (int j = 0; j < 9; j++) {
    for (int i = 0; i < 9; i++) {
    tmps[i] = v[i][j];
    }
    new NotEquals(net, tmps);
    }


  • ในแต่ละ box 3 * 3 ห้ามมีตัวเลขซ้ำกัน
    // ในแต่ละ box 3x3 ห้ามมีตัวเลขซ้ำกัน
    for (int bi = 0; bi < 3; bi++) {
    for (int bj =0; bj < 3; bj++) {
    int cnt = 0;
    for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
    tmps[cnt++] = v[i+(bi*3)][j+(bj*3)];
    }
    }
    new NotEquals(net, tmps);
    }
    }


จะเห็นว่า constraint ข้างบนใช้ constructor NotEquals(net, array_of_variable)
สร้างขึ้นมา
ความหมายก็คือ ในตัวแปรที่ผ่านเข้าไปทั้งหมดนั้น ห้ามมีตัวใดตัวหนึ่งซ้ำกันเลย

พอเสร็จจากขั้นการ declare constraint ก็เป็นขั้นการ solve แล้ว
Solver solver = new DefaultSolver(net);
Solution sol = solver.findFirst();

for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(sol.getIntValue(v[i][j]));
if (j < 8) {
System.out.print(",");
}
}
System.out.println();
}

โดยในการ call เราสามารถเลือก call โดยกำหนด timeout ได้ด้วย
จากการจับเวลาเครื่องผม
กรณีใช้ findFirst จะใช้เวลาประมาณ 76-80 ms
ส่วนกรณี findBest จะใช้เวลาประมาณ 90-100 ms

คนที่สนใจ Constraint Programming ลองเข้าไปดูที่นี่ครับ

Related link from Roti