Tuesday, March 21, 2006

Rails 's Date Widget Plugin

ปกติใน rails, ถ้าเราต้องการ entry ข้อมูลที่เป็น Date
rails จะมี helper ที่ชื่อ date_select

date_select("period", "start_date")

ซึ่งได้หน้าตาของ UI ออกมาอย่างนี้



ดูแล้วไม่ถูกใจ user ชาวไทยอย่างแน่นอน
ผมก็เลยหาทางเปลี่ยนไปใช้ jsCalendar แทน
หน้าตาที่ได้ ก็จะเปลี่ยนไปเป็นแบบนี้



โดย jsCalendar มีวิธีการใช้ง่ายๆดังนี้
<input type="text" id="data" name="period[dp_start_date]"/>
<button id="trigger">...</button>
<script type="text/javascript">
Calendar.setup(
{
inputField : "data",
ifFormat : "%d/%m/%y",
button : "trigger"
}
);
</script>


ที่นี้ ถ้าอยากนำมาใช้ใน rails
จะทำอย่างไรให้ดูเป็น rails-style
(เขียนน้อยๆ, default เยอะๆ)

ประเด็นของการทำ widget เอง คงแยกเป็น 2 เรื่อง คือ
  • การ render
  • การ parse request parameter


กรณีของการ render เราจะทำ helper method ขึ้นมาใหม่ตัวหนึ่ง
ให้ชื่อว่า date_picker
มีลักษณะการใช้งานดังนี้
<%= date_picker "period", "start_date" %>

(จะเห็นว่ามีวิธีการใช้ที่เหมือนกับ date_select ของ Rails เลย)

ส่วนการ parse parameter นั้น
จะออกแบบให้ transparent กับ code เดิม
โดยต้องการให้ใช้กับ code ที่ gen จาก scaffold ได้เลย
(code ที่อยู่ใน controller class)

ของเดิม เวลา date_select submit ข้อมูลกลับมา
ข้อมูลที่กลับมาจะอยู่ในรูปแบบนี้

param-name param-value
===========================
start_date(1i) 2006
start_date(2i) 03 (เดือน)
start_date(3i) 20 (วัน)

เราก็จะเลียนแบบวิธีการส่ง parameter แบบเดิมนี้ (rails เรียกว่า multiparameter_attributes)
โดยแทรก filter เข้าไป
เพื่อทำการดักแปลงข้อมูลที่ submit มาจาก form

การใช้งาน filter ออกแบบให้ใช้คำสั่งแบบนี้้
class ApplicationController < ActionController::Base
date_picker_filter
end


เมื่อตกลงใจเรื่อง design ได้แล้ว ก็มาถึงคำถามว่า
จะ implement เป็น plugin ได้อย่างไร

เริ่มด้วยการตั้งชื่อให้ plugin เราก่อน
โดยใช้ชื่อว่า datepicker

directory structure ของ plugin เป็นแบบนี้

+project-name
+app
+...
+vendor
+plugins
+datepicker
+lib
datepicker.rb
init.rb

file init.rb เป็น file ที่จะถูก rails เรียกใช้
มีเนื้อหาดังนี้

require 'datepicker'
ActionController::Base.send :include, DatePicker

การทำงานก็คือ สั่งให้ Class ActionController::Base include
Module Datepicker ของเรา (Mixin)

ใน module Datepicker
จะเริ่มด้วย method self.included
method นี้เป็น callback method
ซึ่งจะถูกเรียกใช้เมื่อมีการเรียก include module ของเรา

logic ที่ Datepicker ทำ ก็คือ
  • สั่ง extend controller ด้วย module ClassMethods
    add class method date_picker_filter ให้เรียกใช้จาก controller ได้
  • เพิ่ม helper date_picker ที่เรียกใช้จากใน view หรือใน controller ได้

module DatePicker

def self.included(controller)
controller.extend(ClassMethods)
controller.helper_method(:date_picker)
end

module ClassMethods
def date_picker_filter(options = {})
before_filter do |c|
ParamUpdator.new(options).update(c.params)
end
end
end

...

def date_picker(object, method, options = {})
...
end


ใน method date_picker_filter
จะมีการ add filter โดยใช้คำสั่ง before_filter)
และใช้ class ParamUpdator ในการ scan และแปลงวันที่ที่อยู่ใน params object

class ParamUpdator
def initialize(options={})
@prefix = options[:prefix] || 'dp_'
@be = options[:ad] || false # buddhist era
@delim = options[:delim] || '/'
@prefix_regexp = Regexp.new("^#{@prefix}")
@delim_regexp = Regexp.new(@delim);
end

def update(hash)
hash.each do |key, value|
if value.class.to_s =~ /^Hash/
update(value)
else
if @prefix_regexp =~ key
update_param(hash, key, value)
end
end
end
end

def update_param(hash, key, value)
darys = parseDate(value)
realName = key[(@prefix.length)..(key.length)]
hash["#{realName}(1i)"] = darys[0]
hash["#{realName}(2i)"] = darys[1]
hash["#{realName}(3i)"] = darys[2]
hash.delete(key)
end

def parseDate(value)
darys = value.split(@delim_regexp);
if darys[2].length <= 2
darys[2] = (darys[2].to_i + (@be ? 1957 : 2000)).to_s
end
darys.reverse
end

end #end ParamUpdator

ประเด็นที่น่าสนใจในส่วนของการแปลง parameter ก็คือ
object params ไม่ได้เป็น instance ของ Hash
แต่เป็น instance ของ HashWithIndifferentAccess

ส่วน date_picker ที่ใช้ render ก็มีหน้าตาแบบนี้

def date_picker(object, method, options = {})
prefix = options[:prefix] || "dp_"
format = options[:format] || "%d/%m/%y"
size = options[:size] || "10"
inputClazz = options[:input_class] || "date_input"
triggerClazz = options[:trigger_class] || "date_trigger"
be = options[:be] || false

maxsize = options[:maxsize] || format.length
id = "#{object}_#{method}"
name = "#{object}[#{prefix}#{method}]"
obj = self.instance_variable_get "@#{object}"
value = obj == nil ? "" : (date_to_string(format, obj.send(method), be))

<<EOS
<input type="text" size="#{size}" maxsize="#{maxsize}"
id="#{id}" name="#{name}"
class="#{inputClazz}" value="#{value}"/>
<button id="trigger_#{id}" class="#{triggerClazz}">...</button>
<script>
Calendar.setup(
{
inputField : "#{id}",
ifFormat : "#{format}",
button : "trigger_#{id}"
}
);
</script>
EOS

end

ประเด็นที่น่าสนใจ ก็คือการ get value จาก Model instance
ที่ใช้ method instance_variable_get
กับ assumption ที่ว่า model instance จะอยู่ในรูป instance variable

เหลือที่ยังไม่ได้ทำ ก็คือ ส่วนของการ validate
ที่จะ implement ด้วย javascript ที่ฝั่ง browser เลย

Related link from Roti

Friday, March 17, 2006

Hibernate(3.2) Transformer

ได้ข่าวมาจาก blog.hibernate.org
Hibernate 3.2 มี Helper ดีๆที่ช่วย convert object[]
เป็น pojo หรือ java.util.Map แล้ว

เดิมเวลาที่เรา select partial column ใน hibernate
Object ที่ return กลับมาก็คือ Object[]
List list = s.createQuery(
"select e.student.name as studentName, " +
" e.course.desription as courseDescription " +
"from Enrollment as e")
.list();
Object[] row1 = (Object[]) list.get(0) // return => object[]


แต่ถ้ามี Transformer ช่วยเราสามารถเขียนแบบนี้ได้เลย
List list = s.createQuery(
"select e.student.name as studentName," +
" e.course.description as courseDescription" +
"from Enrolment as e")
.setResultTransformer( Transformers.aliasToBean(StudentDTO.class))
.list();

StudentDTO dto = (StudentDTO) list.get(0);


นอกจากใช้ได้กับ createQuery แล้ว ยังใช้ได้กับ createCriteria
และ createSQLQuery อีกด้วย

สุดท้าย ก็คือสามารถแปลงเป็น Map object แทนที่จะเป็น bean
.setResultTransformer( Transformers.ALIAS_TO_MAP )

Related link from Roti

Thursday, March 16, 2006

การ debug ruby ด้วย emacs

ปกติใน ruby เราสามารถสั่ง debug ด้วยคำสั่ง
ruby -r debug filename.rb

ซึ่ง ruby จะเข้าสู่ mode debug

ข้อเสียของการ debug แบบนี้ ก็อาจจะเป็นเรื่องมุมมองที่จำกัด (การดู source code)
กับความสะดวกในการ set break point

วันนี้ผมก็เลยลองเอา emacs มาช่วย debug
ก่อนอื่น เราต้องมี file ที่ชื่อ rubydb3x.el หรือ rubydb2x ก่อน
เจ้าตัวที่ชื่อ 3x ใช้กับ emacs ที่ version >= 19.3x ขึ้นไป



ปกติใน emacs มันจะมีโปรแกรมสำหรับใช้ในการ debug อยู่แล้ว
เรียกว่า gud , rubydb ก็ extend จาก gud นี่แหล่ะ

ก่อนจะใช้ก็ต้องมีการ load ขึ้นมาก่อน โดยใช้คำสั่ง
M-x load-library rubydb3x.el
M-x rubydb

หลังจากสั่งมันจะ prompt ขึ้นมาถาม

Run rubydb (like this):

กรณีเครื่องผม ผมมี ruby หลายตัว ก็เลยใส่ path เต็มดังนี้
/usr/local/bin/ruby -r debug filename.rb

filename คือ ruby file ที่ต้องการ debug

เมื่อ start ขึ้นมาแล้ว emacs จะเปิด buffer ใหม่ที่ชื่อ gdb-filename ขึ้นมา
พร้อมกับเปิด buffer แสดง ruby file ที่เราระบุ
และ process จะไปหยุดรออยู่ที่บรรทัดที่ 1 ของ ruby file ที่ระบุ

note: แต่ถ้า shell environment variable ของเรามีการ set ค่า
RUBYOPT=rubygems ไว้
มันก็จะไปหยุดในโปรแกรม rubygems.rb แทน

จากนั้นเราก็สามารถสั่ง debug command ได้จาก buffer ที่แสดง source code
หรือจาก buffer ของ gdb ก็ได้
keyboard binding ที่มาพร้อม rubydb3x ก็มี
  • "\C-b" "Set breakpoint at current line."
  • "\C-s" "Step one source line with display."
  • "\C-n" "Step one line (skip functions)."
  • "\C-r" "Continue with display."
  • "\C-f" "Finish executing current function."
  • "<" "Up N stack frames (numeric arg)."
  • ">" "Down N stack frames (numeric arg)."
  • "\C-p" "Evaluate ruby expression at point."

note: คำสั่งข้างบน ต้องมี prefix key นำด้วย
อย่างกรณีเครื่องผม ต้องกด C-x,C-a นำหน้าก่อน

ที่นี้ก็มาถึงการ customize rubydb เพิ่ม binding หรือคำสั่งที่ต้องการ
อย่างคำสั่งที่ผมใช้บ่อยๆ ตอนใล่ทำความเข้าใจ source code ก็คือ
คึำสั่ง v[ar] l[ocal] ที่ใช้แสดง local variables
เราสามารถเพิ่ม binding เข้าไปได้โดยเขียน elisp แบบนี้

(gud-def gud-view-local "v l" "\C-l" "show local variable")

คำสั่ง gud-def เป็น macro ที่ช่วยในการ define debug command
gud-view-local คือชื่อ command ของเรา (ตั้งชื่อขึ้นมาเอง)
คำสั่งนี้ map เข้ากับ คำสั่ง v l (command ของ ruby)
และ binding เข้ากับ key (C-x, C-a) C-l

มีคำสั่งอยู่หนึ่งคำสั่ง ที่ผมใช้แล้วไม่ work ก็คือ
gud-print ที่ใช้แสดง ruby expression ที่จุดที่ keyboard อยู่
ของผมสั่งแล้ว มันได้ expression กลับมาเป็น method ทั้งก้อนที่กำลังทำงานอยู่
ก็เลยต้อง customize ให้มันแสดงตัวแปรที่ cursor ชี้อยู่
โดย gdb เตรียม variable ตัวหนึ่งไว้ให้เราแล้ว
มีชื่อว่า gud-find-expr-function
โดยปกติมันจะชี้อยู่ที่ function gud-find-c-expr

;; Code for parsing expressions out of C or Fortran code. The single entry
;; point is gud-find-expr, which tries to return an lvalue expression from
;; around point.

(defvar gud-find-expr-function 'gud-find-c-expr)

เราสามารถเปลี่ยนไปชี้ custom function ของเราแทนได้
อย่างของผม ผมเปลี่ยนเป็นการ select inner most expr แทน

(defun gud-custom-find-expr()
(interactive)
(save-excursion
(let ((pos (gud-innermost-expr)))
(buffer-substring-no-properties (first pos) (cdr pos)))))

(setq gud-find-expr-function 'gud-custom-find-expr)

Related link from Roti

Wednesday, March 15, 2006

Native Leak?

App ที่ผมทำให้ลูกค้ารายหนึ่ง
เดิม run อยู่บน IBM P-series
ก็ใช้ได้ปกติดี ตายบ้างเป็นปกติ (ปีละ 2-3 ครั้ง)
เวลาล่ม ก็มีทั้ง user ทำล่ม
เช่น เรียก report ใหญ่ๆในเวลาที่ load online กำลัง peak
พอ report response ช้า ก็ submit เข้ามาใหม่
ส่วนกรณีที่เป็นที่ system ก็คือ
java มันเบื่อขึ้นมา มันก็ core dump ซะเฉยๆ

พอย้ายไปอยู่บน mainframe
คราวนี้ล่มรายอาทิตย์เลย
(โชคดีที่เขาจัดจ้าง team อื่นเข้ามาดูเรื่องนี้โดยเฉพาะ
ไม่ใช่หน้าที่ของบริษัทผม
ผมก็เลยไม่ต้องปวดหัวมากนัก)

ช่วงแรกๆ หลายๆคนที่เกี่ยวข้อง ก็ชี้นิ้วอย่างเดียว
เขียนโปรแกรมอย่างไง
ใช้ technique ถูกต้องหรือเปล่า
system ไม่มีปัญหา ล่มเพราะ app แหง๋ๆ
(แปลกที่ไม่มีใครย้อนไปดูเลยว่า app มัน run มาปีกว่าแล้ว)
ปากเปียกปากแฉะ อธิบายไป
พร้อมกับช่วยจำแนก อธิบายวิธีแก้ปัญหา

ปัญหาก็ค่อยๆแก้ไปเรื่อยๆ
ช่วงนี้เหลือ error หนักๆ ก็คือ
มันจะค่อยมี error out of memory ขึ้นมาอยู่เรื่อยๆ
(ไม่ถี่มาก แต่ก็ถือว่าเป็นปัญหา เพราะเป็นระบบ online
ที่ให้บริการ ณ counter ทั่วประเทศ)

วันนี้ได้ cc จดหมายจาก IBM

Javacore shows that both the Xms and Xmx have been set to 1280 m.
The heapdump shows that only 380 mb of heap is occupied.
Verbose GC shows that at least 80% heap is free when the oom occured.
This looks to be a case of native memory exhaustion.
...
Based on these facts we would reccomend the changing the Xmx and Xms
Heap Settigns as below to ensure that we have enough native memory
left with us for native allocation Reduce the Xmx value to 1024 mb
Reduce the Xms value to 256 mb


ตอนนี้ผู้ต้องสงสัยเบอร์ 1 คือ jdbc type 2
แต่โชคร้ายที่ jdbc type 4 มันก็มี known issue ของมันอยู่
ทำให้ไม่สามารถ switch ไปเฉยๆได้

Note: ข้อเตือนใจ
เขียน app run ใน type 2 ได้
อย่าคาดหวังว่าแค่เปลี่ยน driver type
แล้วมันจะ smooth run ต่อได้นะครับ

ลืมบอกไป
product ผู้สนับสนุน bug อย่างเป็นทางการ ก็คือ websphere 5 ครับ

Related link from Roti

วันนี้นั่งฝึกทำโจทย์ใน Ruby Quiz
มีโจทย์อยู่ข้อ ที่ต้องเขียน method ที่รับ parameter เป็น Number หรือ Range ก็ได้
โดยมีจำนวนไม่จำกัด
เช่น

build(1, 7, 11, 20..24, 32..78)
build(1..2)
build(8, 11..17)

ต้องการแปลงให้เป็น array ชุดเดียว ที่เก็บเลขที่เป็นไปได้ทั้งหมด
ผมก็นั่งเขียนทื่อๆเลย

def build(*args)
list = []
args.each {|arg|
case
when arg.class == Range:
arg.each {|n|
list << n
}
else
list << arg
end
}
list
end

ไปเห็น code ที่คนอื่นเขียนไว้
โจทย์เดียวกัน

def build(*args)
args = args.map {|arg| Array(arg) }.flatten
end


"เด็กเอ๋ยเด็กน้อย ความรู้เจ้ายังด้อยเร่งศึกษา.."

Related link from Roti

ติดตั้ง Ingres2006 บน Gentoo 2006.0

ca-openingres move ไปสังกัด Ingres Corporation อีกแล้ว
คราวนี้ใช้ชื่อเป็น ingres2006
ก็เลย load มาติดตั้งเสียหน่อย

file ที่ให้มาเป็นพวก rpm file
เครื่องผมไม่ได้ลงไว้
ก็เลยติดตั้ง package rpm โดยใช้คำสั่ง
emerge rpm

ในคู่มือของ Ingres กรณีที่ต้องการความสะดวกก็ให้เรียก program ingres_express_install.sh
แต่เครื่องผม มันฟ้อง error ของ rpm ว่าไม่สามารถลงได้ เพราะ /bin/sh ไม่มี
ก็เลยเข้าไปแก้ script ingres_express_install.sh
เพิ่ม --nodeps ลงไปในคำสั่ง rpm

หลังจากสั่ง install โปรแกรมจะฟ้อง error จำนวนหนึ่ง
ประมาณว่า
ingres: iisutm command not found
ingres: ii.... command not found
ให้จดไว้ มา run เองข้างนอก

ก่อน run ก็ให้ login เป็น user ingres (rpm มันสร้างให้แล้ว)
สั่ง setup
export II_SYSTEM=/opt/Ingres/IngresII
export PATH=$PATH:$II_SYSTEM/ingres/bin:$II_SYSTEM/ingres/utility
export LD_LIBRARY_PATH=$II_SYSTEM/ingres/lib


แล้วก็ใล่ run ii... ทั้งหลาย

Related link from Roti

Tuesday, March 14, 2006

เปลี่ยน Caps-lock key เป็น Control key

สำหรับพวกใช้นิ้วก้อยเยอะ
ใน linux console เราสามารถเปลี่ยน Caps-lock key ได้ดังนี้

dumpkeys > my_keymap

แก้ไข file ตรงตำแหน่ง

keycode 58 = Caps_Lock #=> เปลี่ยนเป็น Control

จากนั้นก็ load key โดยใช้คำสั่ง

loadkeys my_keymap

Related link from Roti

Sunday, March 12, 2006

ศึกษา Student (AI Program สมัยแรกๆ)

วันนี้ลองนั่งเขียนโปรแกรมที่ solve algebra equation ดู
พวกที่เป็นโจทย์แบบนี้ -> (x + 2)/ 10 = 8

ลอกวิธีการมาจาก code ในหนังสือ Paradigms of AI Programming
ของ Peter Norvig
โดยเนื้อหาที่เขาพูดถึง ก็คือวิธีการ implement program Student
ที่ Daniel Bobrow ทำ Research Project ไว้ในปี 1964
ตัวโปรแกรม Student ออกแบบมาให้ solve โจทย์แบบนี้


(student '(If the number of customers tom gets is twice the square of
20 % of the number of advertisements he run |.|
and then number of advertisements is 45 |.|
then what is the number of customers Tom gets ?))

;; ที่เขียนข้างบนนี้เป็นโปรแกรมจริงๆนะ run ได้ ไม่ใช่ description

ผมตัดมาให้ดูเฉพาะส่วน solve equation (เฉพาะที่มีตัวแปรเดียว)
หลักการ ก็คือ แปลงโจทย์ให้อยู่ในรูป tree structure ให้ได้ก่อน

=
/ \
(/) 8
/ \
+ 10
/ \
x 2

ตรงนี้ lisp ได้เปรียบในการ represent
เพราะพวก tree structure สามารถเขียนในรูป list ได้ตรงๆเลย
กรณีข้างบน ก็เขียนได้ดังนี้

'(= (/ (+ x 2) 10) 8)

ส่วน ruby ผมลองใช้ class ดู
ซึ่งเขียนแล้ว ก็รู้สึกเยิ่นเย้อนิดๆ (ขนาดมี attr_accessor ช่วย)

class Exp
include ExpHelper

attr_accessor :op, :lhs, :rhs
def initialize(lhs, op, rhs)
@lhs = lhs
@op = op
@rhs = rhs
end

...

end

ที่รู้สึกเยิ่นเย้อ ก็เพราะ lisp จะเขียนแค่นี้

(defstruct (exp (:type list)
(:constructor mkexp (lhs op rhs)))
op lhs rhs)


ขั้นถัดไป ก็คือก็ทำการย้ายข้างให้ ด้านซ้าย มีแต่ตัวแปรอย่างเดียว
กรณีโจทย์ข้างบน ก็จะถูกแปลงเป็น x = (8 * 10) - 2
ใน lisp ที่ norvig เขียน จะมีหน้าตาแบบนี้

(defun isolate (e x)
"Isolate the lone x in e on the left-hand side of e."
;; This assumes there is exactly one x in e
;; and that e is an equation.
(cond ((eq (exp-lhs e) x)
;; case I: X = A -> X = n
e)
((in-exp x (exp-rhs e))
;; case II: A = f(x) -> f(x) = A
(isolate (mkexp (exp-rhs e) '= (exp-lhs e)) x))
((in-exp x (exp-lhs (exp-lhs e)))
;; Case III: f(x) * A = B -> f(x) = B/A
(isolate (mkexp (exp-lhs (exp-lhs e)) '=
(mkexp (exp-rhs e)
(inverse-op (exp-op (exp-lhs e)))
(exp-rhs (exp-lhs e)))) x))
((commutative-p (exp-op (exp-lhs e)))
;; Case IV: A*f(x) = B -> f(x) = B/A
(isolate (mkexp (exp-rhs (exp-lhs e)) '=
(mkexp (exp-rhs e)
(inverse-op (exp-op (exp-lhs e)))
(exp-lhs (exp-lhs e)))) x))
(t ;; Case V: A/f(x) = B -> f(x) = A/B
(isolate (mkexp (exp-rhs (exp-lhs e)) '=
(mkexp (exp-lhs (exp-lhs e))
(exp-op (exp-lhs e))
(exp-rhs e))) x))))

ซึ่งผมชอบที่เขาเขียนตรงที่
  • เขียนแบบไม่มี side-effect (ไม่มี assign statement เลย)
  • Recursive สวยดี

อันนี้ตอนแปลงเป็น ruby ก็นึกอยู่ว่า
จะเขียนแบบ imperative ดีไหม จะได้เปรียบเทียบกันดู
แต่สุดท้าย ก็รู้สึกว่า โจทย์แบบนี้ไม่ควรเขียนแบบนั้น ก็เลยเขียนตาม lisp
โดยให้ method isolate เป็น method ของ class Exp

def isolate(var)
case
when @lhs == var
self
when var.in(@rhs)
Exp.new(@rhs, @op, @lhs).isolate(var)

when var.in(@lhs.lhs)
Exp.new(@lhs.lhs,
"=",
Exp.new(@rhs, self.inverse(@lhs.op), @lhs.rhs)).isolate(var)

when ["*", "+"].include?(@lhs.op)
Exp.new(@lhs.rhs,
"=",
Exp.new(@rhs, inverse(@lhs.op), @lhs.lhs)).isolate(var)

else
Exp.new(@lhs.rhs,
"=",
Exp.new(@lhs.lhs, @lhs.op, @rhs)).isolate(var)
end
end


ตรงเงื่อนไขที่ check ว่า ตัวแปรอยู่ผั่งไหน (จะได้สลับด้านได้ถูก)
ผมอาศัยว่า class ของ ruby เป็น class แบบเปิด (แก้ไขได้)
ก็เลยใช้วิธี modify Symbol Class (ผมใช้ Symbol represent variable)

class Symbol
def in(exp)
to_sym == exp ||
(exp.is_a?(Exp) &&
(to_sym.in(exp.lhs) || to_sym.in(exp.rhs)))
end
end


พอจัด tree เสร็จ
สุดท้ายก็ evaluate ด้านที่อยู่ด้านขวา
ใน ruby ก็ใช้คำสั่ง eval

def solve()
Exp.new(@lhs, '=', eval_equation(@rhs))
end

def eval_equation(exp)
return case
when exp.is_a?(Numeric): return exp
else
eval("#{eval_equation(exp.lhs)} #{exp.op} #{eval_equation(exp.rhs)}")
end
end


ตอน run ก็เขียนดังนี้ (ยังไม่ได้เขียน parser ก็เลยดูไม่ค่อยสวย)

e = Exp.new(Exp.new(Exp.new(:x, '+', 2), '/', 10), '=', 8)
puts "Equation -> #{e}"
puts "Solve -> #{e.isolate(:x).solve()}"

# output
# ------
# Equation -> x + 2 / 10 = 8
# Solve -> x = 78
#

Related link from Roti

upgrade glibc บน Gentoo

มีปัญหากับการลง SBCL(Steel Bank Common Lisp) ซึ่งต้องการใช้ NPTL
ก็เลย emerge glibc ใหม่
ซึ่งก็ไปเจอ message ว่าต้องใช้ USE="nptl nptlonly"
ก็เออออไปตามมัน
ซึ่งก็ไปตายขณะ build โดยฟ้องว่า gcc version เก่าไป
ด้วยความมักง่าย ไม่รู้จักอ่าน (จริงๆแล้วคือค้นใน google)
ก็เลยดุ่มๆไปเรื่อยๆ
upgrade gcc เสร็จ
ใช้ gcc-config switch ไปตัวใหม่ (3.4.5)
ทดสอบโปรแกรมดู พบว่า
โปรแกรม python เกิดปัญหากับ libstdc++.so.5 , run ไม่ได้
ก็เลยพยายาม switch gcc-config กลับ
ปรากฎว่าไม่สำเร็จ
ก็เลยมั่ว set /etc/env.d/xxgcc เอง
set ให้ ld path มันชี้ไปที่ libstdc++ ตัวเก่า
เพื่อให้มัน run python ให้ได้ก่อน (emerge ใช้ python)

จากนั้นก็เลยค้น google เจอ Gentoo Linux GCC Upgrade Guide
พยายามทำไปตามที่เขาว่า แต่ไปตายตรง revdep-rebuild
มันฟ้องว่า บาง ebuild มันหายไป

เมื่อไม่รู้ว่าจะทำอะไรต่อแล้ว
ก็เลยมั่วต่อ สั่ง emerge glibc ตัวใหม่จนสำเร็จ
ทดลอง run SBCL
สำเร็จแฮะ run ได้

แต่เมื่อทดลอง startx
ก็พบ error

x relocation error: X symbol --guard, version GLIBC.2.3.2 not defined in file libc.so.6
with link time reference]

ค้น google ดูพบ forum ที่อุทิศเพื่อเรื่องนี้ (5 หน้า)
x relocation error

หลังจากนั่่งอ่านอยู่พักใหญ่ พยายามทำความเข้าใจ
(ระหว่างนั้นก็สั่ง download Gentoo livecd-2006.0 ไว้ก่อนเลย)
สุดท้ายก็เลือกลงใหม่ดีกว่า
เพราะ คิดว่าถ้ามั่วไปเรื่อยๆ อาจจะเสียเวลามากกว่า ลงใหม่ทีเดียว

Related link from Roti

Friday, March 10, 2006

Virtual Desktop Manager for Mac Os X

หามานาน
สมหวังสักที
Desktop Manager

Related link from Roti

Pattern การ implement Object ใน Prototype.js (1.4.0)

style ปกติที่เราใช้สร้าง javascript object
ก็คือการใช้ function
ตัวอย่าง

function Person(name, age) {
this.name = name;
this.age = age;
this.dump = function() {
return this.name + ", " + this.age;
}
}

var p = new Person("pann", 1);

ซึ่งก็มีหลายคนออกมาชี้ว่า
วิธีนี้ ถ้าเรามี object จำนวนมาก
มันจะเปลือง memory นะ, เพราะ method dump
มันถูก declare ขึ้นมา 1 function ต่อ 1 object
ก็เลยมีการพลิกแพลงออกไปใช้ prototype
(เวลา javascript object ถูกเรียกใช้งาน มันจะมองหา attribute ภายใน
scope ของมันก่อน ถ้าไม่เจอก็จะค้นหาจาก object ที่ pointer prototype ชี้อยู่)

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype = {
dump: function() {
return this.name + ", " + this.age;
}
}


Prototype.js ก็เห็นดีเห็นงามกับแนวทางที่ 2 (อันนี้เป็นสาเหตุ ว่าทำไม library ถึงชื่อ prototype)
แต่ตบแต่ง style เสียใหม่
ได้หน้าตา การประกาศ object ออกมาแบบนี้

var Person = Class.create();

Person.prototype = {
initialize: function(name, age) {
this.name = name;
this.age = age;
},

dump: function() {
return this.name + ", " + this.age;
}
}

ซึ่งก็ดูดีขึ้นนะ (ถ้าเข้าใจว่ามันทำงานอย่างไร)
magic อยู่ที่ function Class

var Class = {
create: function() {
return function() {
this.initialize.apply(this, arguments);
}
}
}

เมื่อเราเรียก var Person = Class.create()
ตัวแปร Person จะชี้ไปยัง function ที่ return กลับออกมาจาก Class object
และเมื่อเราเรียก Person.new ก็จะเกิดการเรียกใช้ function ที่ชื่อ initialize
ซึ่งถูก declare ไว้ใน Person.prototype object

เมื่อมี object แล้วก็ต้องมีการ extend หรือ inheritance
ใครที่มาจากสาย java แล้วใช้ความคิดแบบ java เข้ามาจับ
รับรองว่าได้ปวดหัวกับการ debug แน่นอน
คำแนะนำที่ดี ก็คือ อย่า design แบบ deep hierachy tree เลย
พวก polymorphic หรือ overloading
ก็อย่าไปนึกถึง

แต่ถ้าอยากลองดู ลองอ่านอันนี้ดู Object Hierarchy and Inheritance in Javascript
ซึ่งดูเหมือนจะเป็น technique แบบเก่า
ถ้าอยากได้แบบใหม่ๆหน่อย ก็ลองอ่านนี่ดู
Javascript Inheritance
แต่ผมไม่ค่อยชอบ technique ที่เขาแนะนำเท่าไร
มันดูแปลกๆในส่วนของการ copyPrototype

ใน Prototype.js ก็มี method extend ให้ใช้เหมือนกัน
แต่ส่วนใหญ่จะใช้ในลักษณะของ Mixin แบบ ruby เสียมากกว่า
โดยการ design จะใช้วิธีเสริม function เข้าไปใน object ที่มีอยู่แล้ว
เช่น

var Enumerable = {
...

findAll: function(iterator) {
var results = [];
this.each(function(value, index) {
if (iterator(value, index))
results.push(value);
});
return results;
},

...
}

Object.extend(Array.prototype, Enumerable);

ทำให้เราสามารถทำแบบนี้ได้


[1,2,3].findAll(function(value) {
return value > 1;
})
// => [2,3]

Related link from Roti

ลานนาจัง กับ ทักษิน

แถาบ้านมีคนญี่ปุ่นอยู่คนหนึ่ง ชื่อ ยูโกะจัง
ทำงานเป็น air hostage อยู่การบินไทย
ยูโกะมีลูกสาวคนหนึ่ง ชื่อ ลานนาจัง
(ชื่อไทยซะด้วย)

ตอนนี้ ลานนาจัง อายุได้ 7 เดือนแล้ว
กำลังน่ารักเลย

วันก่อนภรรยาผมเล่าให้ฟังว่า
ยูโกะจังบ่นให้ฟังว่า

ลานนาจังเกิดวันเดียวกับทักษิน worry นะคะ..
โตขึ้นมาจะเป็นยังไง ...
money money หรือเปล่า, material girl...

Note: กรุณาอ่านออกเสียงแบบคนญี่ปุ่นพูดไทย

Related link from Roti

Form Object ใน Prototype.js (1.4.0)

หลังจากดู presentation A (Re)-Introduction to javaScript
ของคุณ Simon Willison จบแล้ว ก็เกิดความเข้าใจใหม่ๆขึ้นมาอีกมาก

เดิมผมจะมีปัญหากับการอ่าน javascript ที่เขียนแนว Object อยู่มาก
ที่เป็นแบบนี้ ก็เพราะ ผมเอาแนวคิดของ java (class-based) ไปตีความเจ้า javascript (Prototype-based)
ก็เลยเกิดอาการ มึนๆ งงๆ
แต่หลังจากเรียนรู้ ruby, lisp มาระยะหนึ่ง
ซึ่งช่วยขยายกรอบความคิดเกี่ยวกับเรื่อง programing language ขึ้นอีกเยอะ
ก็เลยทำให้หลังจากดู presentation ข้างบนจบ (slides ยาวมาก)
ก็เลยเกิดอาการ Satori เล็กๆขึ้นมา

ผลของ Satori ก็เลยทำให้เกิดความฮึกเหิม ย้อนกลับไปใล่ดู
source code ของ Prototype.js
โดยเลือกดูส่วนที่เกี่่ยวกับการจัดการ Form ก่อน

Object ของ Prototype ที่ใช้จัดการกับ form
ใช้ชื่อตรงตัวเลยว่า Form
ใน source code มีการประกาศไว้ดังนี้

var Form = {
serialize: function(form) {
...
},

getElements: function(form) {
...
},

...
}


method ประกอบด้วย
  • serialize
    return string ที่ใช้ในการ submit form ผ่าน ajax
    ตัวอย่างการใช้งาน
    สมมติ html ดังนี้

    <form id="myform" action="#">
    <input name="input1" type="text" value="xxx"/>
    <input type="checkbox" name="input3" checked="checked" />
    <input name="input2" type="submit"/>
    </form>

    แล้วใช้คำสั่ง Form.serialize

    Form.serialize('myform'); // => input1=xxx&input3=on&input2=

  • getElements
    รับ parameter เป็น form เหมือนกัน
    ผลลัพท์ที่ได้คือ Array ของ Element ที่อยู่ใน form (เฉพาะพวกที่ใช้ input เช่น input, textarea, ...)
  • getInputs
    ค้นหา Element ประเภท input โดย
    รับ parameter เป็น form, ประเภทของ input และ ชื่อของ element นัั้น
    เช่น

    Form.getInputs('myform', 'text', 'input1')

  • disable
    ไว้สั่ง diable form ที่ต้องการ
  • enable
    สั่ง enable form ที่ต้องการ
  • findFirstElement
    อันนี้ดูไม่ค่อยมีประโยชน์เลย รู้สึกว่าจะมีไว้ใช้ใน focusFirstElement
  • focusFirstElement
  • reset


เห็นได้ว่า method ทั้งหมด มี require parameter
เป็น form element หรือ form name ทั้งสิ้น (รับได้ทั้ง 2 แบบ)

Helper object ที่ Form เรียกใช้ มีอยู่ 2 ตัวคือ
  • Form.Element
    มีแค่ 2 method คือ
    • serialize(element)
      อันนี้ถูกเรียกจาก Form.serialize โดยมีเป้าหมายพื่อ encode ข้อมูลเพื่อ submit
    • getValue(element)
      อันนี้ไว้ใช้ get ค่าที่อยู่ใน Element ที่ต้องการ
      รับ parameter เป็น element id หรือ element object ก็ได้

  • Form.Element.Serializers
    อันนี้เป็น lower layer แล้ว ถูกเรียกใช้จาก Form.Element อีกที


ใน Prototype มี Helper Function ที่ชื่อ $F
ซึ่งจริงๆแล้ว ก็คือ short-cut ของ Form.Element.getValue นี่เอง

var $F = Form.Element.getValue;

Related link from Roti

Thursday, March 09, 2006

Synergy

ได้โปรแกรมดีๆมาช่วยงานอีกตัวแล้ว
Synergy เป็นโปรแกรมที่่ช่วยให้เรา share keyboard และ mouse
ระหว่างเครื่องคอมพิวเตอร์ โดยไม่ต้องใช้ hardware
รวมทั้งยัง support การ share ระหว่าง platform ด้วย (windows, Linux/Unix, Mac OS X)

การใช้งานก็ไม่ยาก
เครื่องคอมพิวเตอร์ที่เราจะใช้ keyboard และ mouse
จะ start ใน mode ของ server
ส่วนเครื่องที่เหลือ start ใน mode client

การ start ใน mode server ต้องมี config file ด้วย
หน้าที่ของ config file ก็คือจัดเรียง screen
ว่าเครื่องไหนอยู่ขวา, ซ้าย, บน, ล่าง เมื่อเทียบกับเครื่องอื่น
การจัดเรียงนี้ จะช่วยให้เราลาก mouse ข้ามเครื่องได้

ตัวอย่าง config ของผม

section: screens
g5.local: # my Apple G5
erp: # my Laptop Gentoo
end

section: links
g5.local:
left = erp

erp:
right = g5.local
end

section: options
keystroke(alt+f1) = switchInDirection(left)
keystroke(alt+f2) = switchInDirection(right)
end

Related link from Roti

Wednesday, March 08, 2006

Search Rails api with ri

เดิมเวลาผมจำไม่ได้ว่า method ที่ต้องการมี argument อะไรให้ pass บ้าง
ก็จะ start gem_server แล้วใช้ browser ค้นดู

document ของ rails ในกรณีนี้
ได้มาจากการใช้ program rdoc generate html files ขึ้นมา
ซึ่งปกติเวลาเรา install rails ด้วย gem
มันจะ automatic ทำให้อยู่แล้ว

วันนี้มีอีก solution หนึ่ง
ก็คือการใช้ ri ในการ search
โดยปกติ ri จะค้นหา document ที่อยู่ภายใต้ site-directory
(เครื่องผมอยู่ที่ /usr/share/ri)
กับภายใต้ ~/.rdoc

เราสามารถใช้คำสั่ง rdoc --ri
เพื่อสร้าง output file ที่ ri สามารถใช้ได้
โดย files ที่ได้จะอยู่ภายใต้ directory ~/.rdoc

ตัวอย่างเช่น ถ้าผมต้องการให้ ri สามารถ search activerecord ได้
ก็จะทำการสั่งดังนี้

cd /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib
rdoc --ri


สำเหตุที่ใช้ ri ก็เพราะว่าต้องการเอาไปใช้กับ emacs

Related link from Roti

pack200

ตกข่าวอีกตามเคย
พึ่งรู้ว่า java web start ของ jdk1.5 support gzip format
ที่ได้จากโปรแกรม pack200
(ช่วย compress jar file ให้มีขนาดเล็กลงได้)

อ่านที่นี่
Pack200 and Compression
ตัวอย่างที่ sun แสดงไว้ เขาใช้ servlet เป็นคนเลือก format ในการส่งกลับ
กรณีที่ใช้ apache อย่างเดียว ก็ให้ดูตัวอย่างการ config จาก link นี้
How to use Pack200 for your Java Web Start applications on Apache Web Server

ตอนที่เห็นครั้งแรก ก็นึกว่ามันเป็น compress format ใหม่ๆ
แต่จริงๆแล้วมันก็คือ gzip format ธรรมดานี่แหล่ะ
โดยขั้นตอนการทำงาน เท่าที่ดู (เดา)
ก็คือ
เริ่มจาก มันจะ unpack jar เราก่อน (jar ก็เป็น compress format แบบหนึ่ง)
จากนั้นก็ pack กลับภายใต้ชื่อ file-name.pack
ตรงนี้ไม่รู้ว่า format เป็นอะไร
แต่ถ้าลองเทียบกับ tar format แล้ว
มันจะมีขนาดเล็กว่านิดหน่อย
(ทดสอบโดยใช้คำสั่ง jar xf เพื่อ extract jar file ออกมา
จากนั้นก็ใช้คำสั่ง tar cf กับเนื้อหานั้นๆ)
สุดท้ายก็ compress pack format ด้วย gzip
(file ที่ได้ ก็เลยชื่อ file-name.pack.gz)

ลองทดลองกับ jar file ดังนี้
  • oscar.jar
    (OSGi framework)

    jar size 149469 bytes
    pack200 size 45426 bytes
    jar.gz size 138213 bytes

  • SwingSet2.jar
    เป็น demo ที่ bundle มากับ jdk

    jar size 1432007 bytes
    pack200 size 1148563 bytes
    jar.gz size 1390818 bytes

  • Stylepad.jar
    มากับ demo เหมือนกัน

    jar size 102221 bytes
    pack200 size 65864 bytes
    jar.gz size 96329 bytes

Related link from Roti

รูปเส้นทางขี่จักรยาน trip-น่าน




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

พี่ๆที่ไปด้วย เขาลงเรื่องไว้ที่นี่
http://thaimtb.com/cgi-bin/viewkatoo.pl?id=81687
http://www.thaimtb.com/cgi-bin/viewkatoo.pl?id=81340

Related link from Roti

Embeded Java



อาทิตย์ก่อนได้ลองเล่น Snap ซึ่งเป็น product ของ ImSys
ตัวอย่าง features
  • Sun certified J2ME-CLDC
  • Native Java execution
  • 10/100Base-T Etherne
  • 2 Mbytes flash memory
  • 8 Mbytes DRAM


feature ที่ระบุว่าสามารถทำ native java execution
ก็เพราะ cpu ที่ snap board ใช้ (CISC/WISC)
สามารถ program ระดับ microcode ได้
(แก้ไขระหว่าง runtime ได้อีกต่างหาก)
ก็เลยสามารถ implement ให้ interpret java byte code ได้

Note: คำว่า WISC ย่อมาจาก Writable Instruction Set Computing

การพัฒนา java ก็ใช้ developer tool อะไรก็ได้
ขอให้ compile ให้ได้ byte code แล้วกัน
พอได้ class file ก็ใช้ ftp load โปรแกรมลงไป (snap start ftp server ได้)

ตอนแรก กะว่าจะทดลองลง Oscar (OSGi Framework) ดู
ปรากฎว่ามีปัญหาเรื่องขนาด เพราะ OSGi ดูเหมือนจะเล็ก แต่จริงๆแล้วขนาดไม่เบาเลย
ก็เลยเลือกแต่ core กับ shell bundle ลงไป
สุดท้ายไปติดปัญหาว่า snap ไม่ support jar file ก็เลยล้มเลยความตั้งใจ

จากนั้นก็แอบดู model ของการ implement cgi program
ก็ง่ายๆ ตรงไปตรงมาดี หน้าตาประมาณนี้

public class HelloCgi implements HttpCgi {
public void execute(Hashtable variables, OutputStream os) throws .. {
....

}
}

ตอน start server ก็แค่

HttpServer hs = new HttpServer();
Thread t = new Thread(hs)
hs.addCgi(new HelloCgi());
t.start();


ยังเหลือที่อยากลองอีกอย่าง ก็คือ อยากลองดูว่า เราสามารถ implement Jini บน snap ได้หรือไม่?

Related link from Roti

Monday, March 06, 2006

How to be an expert

เห็นที่ bact' ลง link video แสดงการเล่น yoyo ชั้นเซียนแล้ว
ไปอ่านเจอ How to be an expert ก็เลยเอาทำ link ไว้

Related link from Roti

Wednesday, March 01, 2006

cl-dot

cl-dot คือ library ที่ช่วยในการ generate graphviz dot output

สมมติเรามี structure แบบนี้ใน lisp
(defstruct node name value childs)

แล้วก็มี tree ที่มีหน้าตาแบบนี้
(setq tree (make-node :name '1
:childs (list
(make-node :name '2)
(make-node :name '3
:childs (list
(make-node :name 4))))))


การใช้ cl-dot กับ structure แบบนี้
ทำได้โดยการ implement generic function
ที่ cl-dot เตรียมไว้

ส่วน generic function คืออะไร?
generic function คือ abstract operation
น่าจะเทียบได้กับ abstract method ใน java
เพียงแต่ method ของ java เป็นส่วนหนึ่งของ ojbect
ส่วน lisp method ไม่จำเป็นต้องอยู่ใน object

(note: generic method ทำอะไรได้พิสดารเชียวหล่ะ
เช่น Method combination, Multimethods
อ่านได้ใน Object Reorientation: Generic Functions
ของ Peter Seibel)

c-dot กำหนด generic function ไว้ 4 ตัว แต่เราจะลองแค่ 2 ตัวคือ
(defgeneric object-node (object)
(:documentation
"Return a NODE instance for this object, or NIL. In the latter case
the object will not be included in the graph, but it can still have an
indirect effect via other protocol functions (e.g. OBJECT-KNOWS-OF).
This function will only be called once for each object during the
generation of a graph."))

(defgeneric object-points-to (object)
(:documentation
"Return a list of objects to which the NODE of this object should be
connected. The edges will be directed from this object to the others.
To assign dot attributes to the generated edges, each object can optionally
be wrapped in a instance of ATTRIBUTED.")
(:method ((object t))
nil))


ในตัวอย่าง struture ข้างบน เราจะ implement function object-node
เนื่องจากเรามี object อยู่ชนิดเดียวใน tree จึง implement แค่ function เดียว
(defmethod cl-dot:object-node ((object node))
(make-instance 'cl-dot:node
:attributes `(:label ,(node-name object)
:shape :box)))


ในส่วนของ edge ก็ implement ผ่าน function object-points-to
(defmethod cl-dot:object-points-to ((object node))
(mapcar #'(lambda (child)
(make-instance 'cl-dot:attributed
:object child
:attributes '(:weight 3)))
(node-childs object)))

เวลา run ก็ใช้คำสั่งนี้
(cl-dot:print-graph (cl-dot:generate-graph tree))

ผลลัพท์ที่ได้
digraph {
1 [label="1",shape=box];
3 [label="3",shape=box];
4 [label="4",shape=box];
2 [label="2",shape=box];
1 -> 3 [weight=3];
1 -> 2 [weight=3];
3 -> 4 [weight=3];
}

Related link from Roti

vnc2swf

คุณ OHM ถามเรื่อง screencast ว่าเขาทำอย่างไร
โปรแกรมตัวหนึ่งที่ใช้กัน ก็คือ vnc2swf ครับ
มี 2 version คือ c version (vnc2swf) กับ python version (pyvnc2swf)

หลักการก็คือ vnc2swf ประพฤติตนเป็น vncclient ต่อไปยัง vncserver ที่เราระบุ
(ถ้าจะอัดหน้าจอของเครื่องเราเอง ก็ต้อง start vncserver บนเครื่องเรา)
โดยมันจะแปลงหน้าจอไปเป็น flash movie (swf)

การ start vnc2swf ก็ใช้คำสั่ง
vnc2swf -nowindow out.swf :1 > out.html
:1 คือ display number ของ vncserver

อ่าน tutorial ที่นี่ครับ

ปล. คุณ OHM อย่าลืมเขียนอธิบายเรื่องซ่อมมือถือให้ฟังด้วยนะครับ

Related link from Roti

debug แบบมักง่าย

ปกติถ้าเป็น java เวลาใล่ source code ของ library ต่างๆ
ก็มักจะใช้ debugger ใล่ดู

พอมาใช้ rails, ruby การ debug ก็ใช้วิธีมักง่าย
ก็คือเข้าไปแทรก print statement (puts) ลงใน source
ของ library ตรงๆเลย

วันนี้เรียกใช้งาน web app ตัวหนึ่ง ที่เขียนด้วย rails
เกิด error ประเภท fcgi error
นึกอยู่ตั้งนาน ว่าเกิดอะไรขึ้น
เมื่อก่อนมันยัง run ได้อยู่เลย

โชคดีที่นึกได้ว่า เมื่ออาทิตย์ก่อน เข้าไปแก้ library base.rb ของ ActiveRecord
ก็เลยเข้าไปดู
ปรากฎว่า ลืมเอา puts ที่ใส่ไว้ออก
ไอ้ puts ตัวนั้นก็เลยส่งข้อความออกมากวน fcgi

[Wed Mar 1 15:15:52 2006] [error] [client 127.0.0.1] FastCGI: comm with server
"/Library/WebServer/Documents/snn/dispatch.fcgi" aborted:
error parsing headers: malformed header 'add_conditions SELECT * FROM tags ,'

Related link from Roti

Decision Tree

อ่านเจอใน OnLamp เรื่อง Building Decision Trees in Python
ของ Christopher Roach
ของเดิมเขาเขียนด้วย python ก็เลยลอง implement ด้วย ruby และ clisp ดู

เริ่มด้วยการทำความเข้าใจ Decision Tree ก่อน
ตัว Decision Tree เป็น topic หนึ่งใน maching learning ของ AI
นอกจากนี้ยังเป็น technique ที่ใช้หา ความสัมพันธ์ของข้อมูล ใน Data mining. อีกด้วย

พูดหลักการเฉยๆ จะมองไม่เห็นภาพ ต้องดูตัวอย่างจริง
ยกตัวอย่างว่า เรามี data อยู่ชุดหนึ่ง (sample data)
แล้วเราต้องการหา decision tree จาก data นี้

decision tree ที่ได้ อาจจะมีหน้าตาได้หลายแบบ ขึ้นอยู่กับ
ลำดับของ attribute ที่เราเลือกออกมาทำ category
เช่น ถ้าเลือก house type, district, previous customer
หน้าตาของ tree ก็จะออกมาแบบนี้



แต่ถ้าเลือกลำดับ income ก่อน หน้าตา ก็อาจจะได้ออกมาแบบนี้



จะเห็นได้ว่าเราสามารถจัดกลุ่ม tree ได้หลายแบบมาก
คำถาม ก็คือ tree แบบไหนที่ให้ information ได้ดีที่สุด
คำตอบก็คือ tree ที่สั้นที่สุด (Occam's Razor)
ซึ่งก็คือ tree ที่หน้าตาแบบนี้



algorithm ที่ใช้ในการเลือกลำดับของ attribute ก็คือ
ID3 ที่ใช้การคำนวณ Entropy เข้ามาช่วย
โดยเลือก attribute ที่ทำให้ Entropy ของ tree ลดลงมากที่สุด (entropy สูง, ซับซ้อนสูง)
Tutorial ที่อธิบายวิธีการคำนวณ Entropy ได้ดี อยู่ที่นี่
DMS Tutorial - Decision trees

ผมลองใช้ข้อมูลที่อยู่ใน tutorial ข้างบน [link]
มาลอง implement และ run ด้วย ruby และ lisp ดู

ผลลัพท์ที่ได้ (ใช้เกณฑ์การตัดสินของตัวเอง, bias ล้วนๆ)
  • การลอก(แปลง) python มาเป็น ruby ทำได้ง่ายมาก
  • ตัว code ที่ได้, ทั้ง python และ ruby อ่านง่ายทั้งคู่ ส่วน lisp อ่านยากกว่า
    (ในส่วนของ lisp ถ้าจะให้อ่านง่าย ก็ต้องทำการลดรูป syntax
    โดยใช้ macro เข้ามาช่วย)


ลองดู code ของ ruby กับ lisp ดู (code ของ python ให้ดูที่ tutorial ของ OnLamp)
function entropy
def entropy(datas, attr)
include Math

freqs = Hash.new {|h, k| h[k] = 0.0}

datas.each do |data|
freqs[data[column_by_name(attr)]] += 1
end
summ = 0.0

freqs.values.each do |val|
summ += (-val/datas.length) *
((log (val/datas.length)) / (log 2))
end

summ
end


(defun group-by-attr (data attr)
(let ((hash (make-hash-table)))
(mapc #'(lambda (record)
(let* ((elm (funcall attr record))
(value (gethash elm hash 0)))
(setf (gethash elm hash) (+ value 1))))
data)
hash))

(defun entropy (data attr)
(let ((freqs (group-by-attr data attr))
(total 0))
(maphash #'(lambda (key value)
(let ((frac (/ value (length data))))
(setq total (+ total
(* (- frac)
(log frac 2))))))
freqs)
total))


function gain
def gain(datas, attr, target_attr)
freqs = Hash.new {|h, k| h[k] = 0.0}
subset_entropy = 0.0

datas.each do |data|
freqs[data[column_by_name(attr)]] += 1
end

freqs.keys.each do |key|
prob = freqs[key] / datas.length
filter_datas = datas.find_all {|data|
data[column_by_name(attr)] == key
}

subset_entropy += prob *
entropy(filter_datas, target_attr)
end

entropy(datas, target_attr) - subset_entropy
end


(defun gain (data attr target-attr)
(let* ((freqs (group-by-attr data attr))
(total 0))
(maphash #'(lambda (key value)
(let ((prob (/ value (length data)))
(subset (remove-if-not
#'(lambda (rec)
(eql key (funcall attr rec))) data)))
(setq total (+ total
(* prob
(entropy subset target-attr))))))
freqs)
(- (entropy data target-attr) total)))


ใน lisp ผมลองลดรูปด้วยการใช้ macro มาช่วย
(แต่ยัง design macro ไม่เก่ง ผลเลยไม่ดีเท่าไร)
function entropy ถูกลดรูปมาเป็น
(defmacro summ-loop (lst value &rest body)
`(let ((total 0))
(mapc #'(lambda (,value)
(let ((result ,@body))
(setf total (+ total result))))
,lst)
total))

(defun entropy2 (data attr)
(let* ((freqs (group-by-attr data attr))
(values (hash-values freqs)))
(summ-loop values val
(let ((frac (/ val (length data))))
(* (- frac) (log frac 2))))))


Note: การพยายามลดรูปด้วยการ define macro แบบนี้ ถ้าตั้งชื่อไม่สื่อ หรือไม่มีเอกสารพอ
ก็น่าจะนำไปสู่ code ที่อ่านยากขึ้นแทน

Related link from Roti

Tuesday, February 28, 2006

Maven2

maven2 ออกมานานแล้ว
แต่ผมยังไม่กล้าใช้สักที
เนื่องจากเคยมีประสบการณ์ที่ขมขื่นกับการ upgrade maven มาแล้ว

วันนี้เห็น feature Archetype ของ Maven2 ก็เลยได้ฤกษ์ทบทวนและทดลองใช้เสียที
ใน maven2 ตัว execute file ของ maven ใช้วิธีเปลี่ยนชื่อเป็น mvn
เพื่อหลีกเลี่ยง Name conflict กับ maven 1.0.x

ที่ชอบก็คือ project เก่าเราที่ใช้ maven 1.0.x
สามารถใช้ maven 2.x co-exists กับ maven 1.x พร้อมๆกันได้ด้วย

โดยใน maven 1.x ปกติจะใช้ file ชุดนี้
  • project.xml
  • maven.xml
  • project.properties
  • build.properties

maven 2.x เปลี่ยนไปใช้ file ชุดนี้แทน
  • pom.xml
  • settings.xml


เนื้อหาของ pom.xml ก็คือเนื้อหาเก่าที่เคยอยู่ใน project.xml
เพียงแต่มีการเปลี่ยนชื่อและเพิ่มเติม element ใหม่ๆเข้ามา

default source structure ของ Maven 2.x หน้าตาเป็นแบบนี้
  • src/main/java
  • src/main/resources
  • src/main/filters
    เก็บพวก properties file ที่ใช้ transform resources file
  • src/main/assembly
    (อันนี้เดาว่าเก็บพวก ejb descriptor ทั้งหลาย)
  • src/main/config
  • src/test/java
  • src/test/resources
  • src/test/filters
  • src/sites
  • LICENSE.txt
  • README.txt


output files ทั้งหลายจะถูกสร้างใน sub-directory target
เหมือนใน maven 1.x

ใน maven2 มีการเพิ่ม concept ของ Build Lifecycle เข้ามา
ซึ่งจะมาแทนที่การใช้ tag preGoal, postGoal เดิม ที่เราใช้ใน maven.xml
โดย phase หลักๆใน lifecycle จะประกอบด้วย
  • validate
    ตรวจว่า project เรามีข้อมูลเบื้องต้นครบถ้วนหรือไม่
  • generate-sources
  • process-sources
  • generate-resources
  • process-resources
  • compile
  • process-classes
    ทำพวก bytecode enhacement ที่จุดนี้
  • generate-test-sources
  • generate-test-resources
  • process-test-resouces
  • process-test-resources
  • test-compile
  • test
    unit testing
  • package
  • before-integration-test
  • integration-test
  • after-integration-test
    อันนี้เป็นการ deploy และ test บน test environment
  • verify
    test package ว่า valid หรือไม่
  • install
    install package ไปที่ local repository เพื่อให้ project อื่นๆ
    ที่ต้องการใช้ artifact ตัวนี้ สามารถอ้างใช้งานได้
  • deploy

การ integrate goal จาก plugin ที่ต้องการ
เข้าไปใน project ทำได้โดยการ add
element plugin เข้าไปใน pom.xml
ซึ่ง plugin แต่ละตัว จะรู้บทบาทว่า
ตัวเองควรจะเข้าไป cooperate ใน phase ไหน
(แต่เราสามารถ overwrite ระบุ phase ที่เราต้องการให้ plugin run ได้ด้วย)

ยกตัวอย่าง antlr plugin ที่ปกติจะ run ในช่วง generate-sources
(เพราะต้อง compile .g -> ให้เป็น .java)

<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antlr-plugin</artifactId>
<configuration>
<grammars>java.g</grammars>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>


วิธีการพัฒนา plugin ใน maven 2.x
ก็มีการเปลี่ยนแปลงไปเช่นกัน
เดิมต้องมี jelly script เสมอ
ก็เปลี่ยนไปใช้ java ล้วนๆ
โดยใช้ศัพท์ Mojo (Maven [plain] Old Java Object) สำหรับ plugin แบบนี้

Dependency Management ก็มีการปรับปรุงใหม่
มีการเพิ่ม Transitive Dependencies, Dependency Scope เข้ามา

Feature อื่นๆยังมีอีกเยอะแยะเลย
อย่างนี้ไม่ใช้ไม่ได้แล้ว

Related link from Roti

Sunday, February 26, 2006

GMF (Graphical Modeling Framework) Screencast
feature ใหม่ (หรืออาจจะเก่าแล้ว แต่พึ่งรู้) สามารถ generate diagrame editor สำหรับ ecore model ที่เราต้องการได้
(เดิม EMF สามารถ generate Model Editor ให้เราได้เหมือนกัน แต่เป็นแค่ Tree Editor หน้าตาธรรมดา)

Related link from Roti

Saturday, February 25, 2006

มายา

ดูแล้วรู้สึกว่า โลกปัจจุบัน เรามีแต่สิ่งลวงตาเต็มไปหมด
FluidEffect
เข้าไปที่ portfolio->before/after

Related link from Roti

Rails "with_scope"

ใน ActiveRecord มีคำสั่งหนึ่งที่ชื่อ with_scope
คำสั่งนี้ช่วยให้เราจัดการกับ business logic ประเภท

user A ที่สังกัดหน่วยงาน 1001 เมื่อ login เข้ามาแล้ว
สามารถ query ข้อมูลได้เฉพาะข้อมูลของหน่วยงาน 1001 เท่านั้น
และเมื่อสร้าง transaction,
transaction นั้นจะมีรหัสหน่วยงานที่รับผิดชอบเป็น 1001

หรือไม่ก็

User B มี authorize แบบ Regulator
ดังนั้นจึงสามารถ grant สิทธิให้กับเฉพาะ user
ที่สังกัดอยู่ภายใต้ Company ที่ user B ดูแลอยู่


ลองดู controller code ของตัวอย่างที่ 1
กรณีที่เขียนแบบไม่มี with_scope

def list
@txs = Tx.find(:all,
:conditions =>
["organize_id = ?", session[:user].organize_id])
end

def create
...
# tx ที่สร้างจะสังกัดหน่วยงาน ตามหน่วยงานของ user
tx.organize_id = session[:user].organize_id
...
tx.save
...
end


ถ้าใช้ with_scope เข้ามาช่วย

def list
Tx.with_scope(:condition => "organize_id = #{session[:user].organize_id}") do
@txs = tx.find(:all)
end
end

def create
Tx.with_scope(:create => {:organize_id = session[:user].organize_id}) do
...
Tx.save
end
end


ถ้าให้สวยขึ้นอีก ก็ให้แยกส่วนที่ซ้ำๆออกมา

protected
def org_scope
{
:find => {:condition => "organize_id = #{session[:user].organize_id}",
:create => {:organize_id = session[:user].organize_id}
}
end

public
def list
Tx.with_scope(org_scope) do
Tx.find(:all)
end
end

def create
Tx.with_scope(org_scope) do
...
end
end


Note: เริ่มพบว่า Rails api เริ่มไม่ consistency เยอะขึ้นเรื่อยๆ เช่น
กรณี find โดยตรงเราสามารถทำแบบนี้ได้

@txs = Tx.find(:all,
:conditions =>
["organize_id = ?", @org_id])

แต่ถ้าใช้ผ่าน with_scope จะ call แบบนี้ไม่ได้

Tx.with_scope(:find =>
{:conditions =>
["organize_id = ?", @org_id]}) do
Tx.find(:all)
end


สาเหตุก็คือ ใน method add_conditions ใน file "base.rb" ของ ActiveRecord
มีการใช้ sanitize_sql (method ที่รับผิดชอบการแปลงแบบที่กล่าวข้างบน) เฉพาะกับ
:condition ที่ส่งมากับคำสั่ง find เท่านั้น
ไม่ได้ใช้กับ :condition ใน with_scope ด้วย

940 def add_conditions!(sql, conditions)
941 puts "add_conditions #{sql}, #{conditions}"
942 segments = [scope(:find, :conditions)]
943 segments << sanitize_sql(conditions) unless conditions.nil?
944 segments << type_condition unless descends_from_active_record?
945 segments.compact!
946 sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
947 end

ทางแก้ ก็อาจจะ patch base.rb
เพิ่ม sanitize_sql เข้าไปครอบส่วน scope

940 def add_conditions!(sql, conditions)
941 puts "add_conditions #{sql}, #{conditions}"
942 segments = [sanitize_sql(scope(:find, :conditions))]
943 segments << sanitize_sql(conditions) unless conditions.nil?
944 segments << type_condition unless descends_from_active_record?
945 segments.compact!
946 sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
947 end


Note: เข้าไป check source code ของ Rails
มีคน patch ส่วนแก้ไขเข้าไปแล้ว อยู่ใน change set [3379]

Related link from Roti

Wednesday, February 22, 2006

Testing with Hansel

Hansel คือ code coverage ตัวหนึ่ง
จุดที่ผมชอบ ก็คือ วิธีที่มัน integrate กับ JUnit ซึ่งดูเรียบง่ายดี

ลองดูตัวอย่าง
ทดลองเขียน Collection ขึ้นมาอันหนึ่ง ที่เก็บ integer
โดย item ใน collection จะเรียงลำดับจากน้อยไปมากเสมอ
public class OrderList {

private ArrayList bag;

public OrderList() {
bag = new ArrayList();
}

public void add(int value) {
int size = bag.size();
boolean found = false;
for (int i = 0; i < size; i++) {
Integer elm = (Integer) bag.get(i);
if (value < elm.intValue()) {
if (i == size -1) {
bag.add(new Integer(value));
} else {
bag.add(i, new Integer(value));
}
found = true;
break;
}
}
if (! found) {
bag.add(new Integer(value));
}
}

public int first() {
return ((Integer) bag.get(0)).intValue();
}

public int last() {
return ((Integer) bag.get(bag.size() - 1)).intValue();
}
}


เขียน JUnit Testcase
public class TestOrderList extends TestCase {

/*
* Test method for 'util.OrderList.first()'
*/

public void testFirst() {
OrderList l = new OrderList();
l.add(6);
l.add(9);
l.add(1);
assertEquals(1, l.first());

}

/*
* Test method for 'util.OrderList.last()'
*/

public void testLast() {
OrderList l = new OrderList();
l.add(6);
l.add(9);
l.add(1);
assertEquals(9, l.last());

}

}


ทดลอง run ดู ก็จะพบแถบเขียวสวยงาม



ที่นี้ลองใส่ Hansel เข้าไป
โดยการเพิ่ม method นี้ลงไปใน test case
    public static Test suite() {
return new CoverageDecorator(TestOrderList.class,
new Class[] {OrderList.class});
}


ทดลอง run ใหม่



จะเห็น message error ว่า
 Coverage failure: Branch not completely covered. Condition 'i == size - 1' is not fulfilled.
at util.OrderList.add(OrderList.java:20)


ไปไล่โปรแกรมแล้ว จะพบที่ผิดตัวเบ้อเริ่มเลย
บรรทัดที่ 20 เป็นบรรทัดที่ตกค้างจากการที่เขียน code โดยไม่ได้วางแผนมาก่อน
สามารถตัดทิ้งออกไปได้ทั้งยวงเลย

จะเห็นว่า Hansel เข้ามาช่วยอุดจุดอ่อน
ของ Programer ที่ไม่ชอบวางแผน ชอบเขียนโปรแกรมไป-คิดไป-แก้ไป
(แก้ไปแก้มา จนงงเอง) ได้เป็นอย่างดี

หลักการทำงานของ Hansel ก็คือ มันจะทำการ modify class file ที่เราต้องการ test
โดยใช้ BCEL (Byte Code Engineering Library)
ทำการใส่ probe เข้าไปตามบรรทัดต่างๆ แล้ว check ดูว่ามีการเรียกใช้ probe เหล่านั้นหรือไม่
การใส่ probe ของ Hansel ก็ไม่ได้ใส่ดะไปทุก statement นะ
มีการเลือกใส่เฉพาะ statement ที่เป็น branch เท่านั้น

Related link from Roti

Tuesday, February 21, 2006

Sea of Lies

วันนี้ดูสารคดีเรื่องเครื่องบินของสายการบิน iran air ที่ถูกยิงตกโดยเรือรบสหรัฐ เมื่อวันที่ 3 กรกฏาคม 1988
ผู้โดยสารและลูกเรือเสียชีวิตไป 290 คน
สาเหตุเกิดจากความผิดผลาดของลูกเรือและกัปตัน
ในการจำแนกระหว่างเครื่องบิน F14 กับ airbus
"Identify, Friend or Foe?" query, he received a different response: military aircraft. Rogers' decision to fire was made while under the impression that the query was correct--in fact, Anderson had forgotten to reset the system after the first query, and the response he received was probably from a fighter plane on the runway back at Bandar Abbas.

รวมทั้งอาการ panic ของลูกเรือบางคน
Then something happened that psychologists call "scenario fulfillment" - you see what you expect. Petty Officers Anderson and Leach both began singing out that the aircraft, now definitively tagged on the big screen as an F-14, was descending and picking up speed. The tapes of the CIC's data later showed no such thing. Anderson's screen showed that the plane was travelling 380 knots at 12,000 feet and climbing. Yet Anderson was shouting out that the speed was 455 knots, the altitude 7,800 feet and descending.

โดยที่ระหว่างที่ยิง เรือรบสหรัฐกำลังอยู่ในน่านน้ำอิหร่านอีกด้วย

เมื่อเรือกลับถึงอเมริกา ก็ได้รับการต้อนรับเยี่ยงวีรบุรุษที่กลับจากสงคราม
ลูกเรือทั้งหมดได้รับ Combat-action ribbons
commander ผู้ควบคุมการต่อสู้ทางอากาศ ได้รับ navy 's Commendation Medal
(for heroic achievement, "ability to maintain his poise and confidence under fire",
or his ability to "quickly and precisely complete the firing procedure"")

แน่นอนกัปตันต้องถูกขึ้นศาลทหาร
ผลการสอบสวน ถือว่าปฎิบัติโดยชอบแล้ว (justifiable self-defense)

อ่านเหตุการณ์ทั้งหมด
Sea of Lies

ปล. สำหรับเติือนสติ ผู้ที่ชอบ Hero แบบในหนัง hollywood
และถูกสะกดจิตว่า ทหารอเมริกันมันเก่งแบบในหนัง

Related link from Roti

Selenium on Rails

ดู demo ของการ integrate
Selenium กับ Rails ได้ที่นี่
Show don't Tell
In the demo I create a new Rails project, install Selenium on Rails, create a test case using Selenium IDE, create another in RSelenese, and run all the test as a Rake task.


ช่วงนี้ผมเริ่มใช้ selenium กับ java project แล้ว
พอมาดูการใช้กับ Rails แล้ว
รู้สึกว่ามัน integrate เข้ากับ Rails ได้ดีมากเลย
(ไม่ว่าจะเป็นการเก็บ test script ไว้ใต้ directory test,
การใช้ script/generate เข้ามาสร้าง stub,
การใช้ rake test,
การใช้ ruby selenium script)

Related link from Roti

จบ trip จักรยาน

จบ trip จักรยานครั้งนี้ ด้วยความรู้สึกยากจะบรรยาย
ความรู้สึกเหมือนได้สัมผัสทั้งสวรรค์ และ นรก คละเคล้ากันไป
เส้นทางที่ขี่คราวนี้ อยู่ในจังหวัดน่านจังหวัดเดียว

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

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


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

Note:
  • ช่วงเวลาที่สุข มักจะน้อยกว่า ช่วงเวลาที่ทุกข์ เสมอ
  • พื้นที่ 75% ของจังหวัดน่าน เป็นภูเขา

Related link from Roti

Monday, February 20, 2006

Visualizing Regular Expression
อ่านวิธีการที่เขา implement
น่าสนใจทีเดียว

Note: เขาใช้ OpenLazlo implement ด้วย

Related link from Roti