Wednesday, November 30, 2005

คำถามดีๆ

คราวก่อนที่ post เรื่อง Higher Order Procedure ไป
bact ได้ post ถามว่า
method(op) กับ : ใน oper[:+]

นี่คือทำให้เราพิมพ์ + กับ - ได้ใช่มั๊ยครับ ?
คือ + กับ - เป็น object ด้วย

เป็นคำถามที่ดีจริงๆ
ผมชักชอบ blog มากขึ้นแล้วหล่ะ
คำถามใน blog ทำให้ความรู้เราเพิ่มขึ้น
(เพราะมันช่วยชี้ให้เห็นว่า เราลืมมองอะไรไปบ้างหรือเปล่า)
อย่างคราวก่อนที่ mk ถามเรื่อง "?" ที่อยู่หลัง ruby method
นั่นก็เป็นคำถามที่ดีเหมือนกัน
เพราะสมัยนั้นที่ผมมอง ruby code ผมไม่เคยตั้งคำถามเรื่องนั้นเลย
คำถามนั้นทำให้เราเห็นจุดอ่อนของเรา

กลับมาที่คำถามของ bact
ตอนแรกผมก็มองเหมือน bact (อาศัยความเคยชินจาก scheme)
+ มันต้องเป็น object ประเภท procedure แน่เลย
แต่กลายเป็นไม่ใช่
ลองดู code ต่อไปนี้
อันนี้ จาก scheme48

pphetra@[~]: scheme48
Welcome to Scheme 48 1.1 (made by pphetra on Sun Nov 13 15:03:12 ICT 2005).
Copyright (c) 1993-2004 by Richard Kelsey and Jonathan Rees.
Please report bugs to scheme-48-bugs@martigny.ai.mit.edu.
Type ,? (comma question-mark) for help.
> +
#{Procedure 52 +}
>

อันนี้ชัด + ใน scheme คือ procedure

ต่อไปจาก ruby บ้าง

pphetra@[~]: irb
irb(main):001:0> :+.class
=> Symbol
irb(main):002:0> 1.method(:+).class
=> Method
irb(main):003:0> 1.method(:+).call(2)
=> 3
irb(main):004:0> 1.method("+").class
=> Method
irb(main):005:0> 1.method("+").call(2)
=> 3
irb(main):006:0>

อา :+ จริงๆแล้วคือ symbol
ใน ruby, Object ทุกตัวจะมี method ที่ชื่อ method
คำสั่ง 1.method(:+) return ค่า
ออกมาเป็น method + ของ object 1
ซึ่งเราสามารถใช้ method call เพื่อสั่ง run จรีิงๆ
ปกติ method method จะรับ symbol เป็น parameter
แต่ก็สามารถส่งค่าเป็น String ให้ก็ได้เหมือนกัน

คำถามถัดไป ก็คือ แล้ว symbol คืออะไร
อืมม์ ยังไม่ค่อยแจ่มเหมือนกัน
แต่ดูตัวอย่างนี้ก่อน

pphetra@[~]: irb
irb(main):001:0> f1 = :pok
=> :pok
irb(main):002:0> f2 = :pok
=> :pok
irb(main):003:0> f1.object_id
=> 3918094
irb(main):004:0> f2.object_id
=> 3918094

เห็นในคำอธิบายเขาบอกว่า symbol จะมี instance เดียว
เสมอ ไม่ว่าจะอ้างถึงกี่ครั้งก็ตาม
หรือ ไม่ว่าจะอ้างจากคนละ context ก็ตาม

pphetra@[~]: irb
irb(main):001:0> module A
irb(main):002:1> $f1 = :pok
irb(main):003:1> end
=> :pok
irb(main):004:0> module B
irb(main):005:1> module C
irb(main):006:2> $f2 = :pok
irb(main):007:2> end
irb(main):008:1> end
=> :pok
irb(main):009:0> $f1.object_id
=> 3920142
irb(main):010:0> $f2.object_id
=> 3920142

Related link from Roti

Tuesday, November 29, 2005

Run JettyLaucher in Ðebug Mode

ช่วงหลังๆนี่ผมเปลี่ยนมาใช้ JettyLaucher แทน Tomcat Plugin
เพราะดูมัน lightweight ดี
แต่มีที่ติดใจอยู่ก็คือ กรณีที่เราแก้ java file
เราจะต้องสั่ง restart web app ใหม่ทุกครั้ง

วันนี้อ่านเจอ Tip ง่ายๆ
ที่ทำให้เราไม่ต้อง restart web app ทุกครั้งที่แก้ java file
เส้นผมบังภูเขาแท้ๆ
ให้ start jetty ใน debug mode แทน
เพราะ eclipse ใน debug mode
มันจะทำ hot replace class file ให้เราโดยอัตโนมัติ

Related link from Roti

Monday, November 28, 2005

Python Lambda & Ruby Block

เห็น mk เขียนเล่าเรื่องเรียนรู้ Ruby แล้ว
มีประเด็นที่สะกิดใจอยู่นิดหนึ่งตรง
เรื่อง iterator ที่ mk ยังคาใจอยู่

ที่สะกิดใจ ก็คือ เกิดความสงสัยว่า python
ก็น่าจะมี concept คล้ายๆแบบนี้เหมือนกัน
ก็เลยลองเปิดหนังสือดู
ก็พบในบทว่าด้วยเรื่อง lambda & filter
ลองดูตัวอย่างนี้

เริ่มที่ ruby ก่อน
ary = [1, 2, 3, 4] 
result = ary.select { |x| x % 2 == 0 }
result # => [2, 4]


ถ้าเขียนแบบเดียวกันใน python
ary = [1, 2, 3, 4]
result = filter(lambda x: x%2 == 0, ary)
print result # => [2, 4]


โจทย์เดียวกัน
ลองเขียน ruby block อีกแบบดู
ary = [1, 2, 3, 4]
prc = lambda {|x| x%2 == 0}
result = ary.select(&prc)
result # => [2, 4]

Note: สังเกตุเครื่องหมาย & หน้า ตัวแปร prc
เป็นการบอกว่า pass ค่า block นะ ไม่ใช่ argument

น่าจะพอสรุปได้ว่า ruby block กับ
python lambda เป็นเรื่อง concept เดียวกัน
เพียงแต่ ruby ปรับปรุง syntax ให้สามารถ
pass block (lambda) ได้ง่ายกว่า

Note: แฟนผมเดินเข้ามาแอบอ่าน post นี้
ดูสักพัก แล้วก็เดินส่ายหัวจากไปพร้อมกับเสียงงืมงัม python ruby ruby python ...
(ช่วงนี้ผมเขียน post บ่อย แกกลัวว่าจะแอบ chat
กับสาวๆ)

Related link from Roti

ETL

ETL => extract, transform and load
CloverEtl

Related link from Roti

Higher Order Procedures

วันนี้เจอ ruby code ที่เขียนแบบนี้
oper = proc do |op|
proc do |x, y|
x.method(op).call(y)
end
end

add = oper[:+]
add[1,2] # => 3

sub = oper[:-]
sub[2,1] # => 1


นี่ถ้าเป็นเมื่อก่อน ตอนที่ยังไม่เรียน scheme
ก็คง งงงวย พักใหญ่
(การเขียน คง กับ งงงวย ติดกัน อาจทำให้ผู้อ่านเกิด
อาการเมา blog ได้)
แต่หลังจากเรียนรู้ scheme แล้วก็เลยรู้ว่า
มันคือ higher order procedure นี่เอง


• A procedure is said to be “first-order” if
none of its arguments is itself a procedure.
• A procedure is said to be “higher-order” if
one or more of its arguments is a procedure.


ถ้าเขียนใน scheme ก็จะเป็นแบบนี้
(define oper
(lambda (op)
(lambda (x y)
(op x y))))

(define add
(oper +))

(define sub
(oper -))

(add 1 2) ;; => 3
(sub 2 1) ;; => 1

Related link from Roti

Sunday, November 27, 2005

Enhance xmp (example printer)

อ่านเจอใน http://eigenclass.org/
Mauricio Fernandez. เขาเขียน utilities เล็กๆ
ที่ชื่อ Enhance xmp
เพื่อใช้ evaluate ตัวอย่าง code และแสดงค่า ณ ตำแหน่งต่างๆในโปรแกรม

ยกตัวอย่าง
สมมติเรามี snippet code ดังนี้
person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
person1
person2


ถ้าเราต้องการแสดงให้เห็นว่า variable person1 และ person2
มีค่าเป็นอะไร เราก็มักจะเขียน comment ลงไปแบบนี้
person1 # => "Jim"
person2 # => "Tim"


ปกติเวลาเราเขียน comment แบบนี้
ก็มักจะใช้วิธี manual, ก็คือเปิด editor ออกมาใส่ค่าลงไปเอง

utilities ที่ Mauricio เขียน จะช่วย fill-in ค่าพวกนี้ให้เราโดยอัติโนมัติ
โดยเราเพียงแต่เขียนแบบนี้
person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
person1 # =>
person2 # =>

เมื่อ run ผ่านโปรแกรม xmp
xmp ก็จะใส่ค่าลงไปให้เราเอง

ที่นี้ลองมาใล่ดูว่า source code เขาทำงานอย่างไร
technique ที่เขาใช้ก็คือ
เขาจะอ่าน source code ขึ้นมาก่อน
จากนั้นก็แทรก code ไปตัดต่อโปรแกรมบรรทัดที่ต้องการ
โดยสร้างตัวแปรขึ้นมารับผลลัพท์ที่ได้จาก evaluate บรรทัดนั้น
จากนั้นก็ให้พิมพ์ผลลัพท์ออกมาทาง stderr io
code = ARGF.read
idx = 0
newcode = code.gsub(/^(.*) # =>.*/) do |l|
expr = $1
(/^\s*#/ =~ l) ? l :
%!((#{VAR} = (#{expr}); $stderr.puts("#{MARKER}[#{idx+=1}] => " + #{VAR}.inspe
ct) || #{VAR}))!
end

จะเห็นว่าเขาอ่านโปรแกรมขึ้นมา แล้วใช้ gsub
ในการหาบรรทัดที่มี pattern ที่มี # =>
ถ้าเจอบรรทัดนั้น ก็จะทำการแทรก code เพิ่มเข้าไป
ลักษณะของ code ที่ได้ออกมาในตัวแปร newcode จะมีหน้าตาแบบนี้
person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
((_xmp_1133097178_956604 = (person1); $stderr.puts("!XMP1133097178_657069![1] =>
" + _xmp_1133097178_956604.inspect) || _xmp_1133097178_956604))
((_xmp_1133097178_956604 = (person2); $stderr.puts("!XMP1133097178_657069![2] =>
" + _xmp_1133097178_956604.inspect) || _xmp_1133097178_956604))

จะเห็นได้ว่ามีการสร้างตัวแปรมารับ
แล้วก็พิมพ์ตัวแปรนั้นออกมาในรูปของ xxx[idx]=>value

พอแก้ code ไดดังนี้ เขาก็ทำการเรียกใช้งาน code ดังนี้
stdin, stdout, stderr = Open3::popen3("ruby", "-w")
stdin.puts newcode
stdin.close
output = stderr.readlines

เขาใช้ standard library open3 เข้ามาช่วย
(พวกที่ใช้ win32 จะไม่มี library ตัวนี้ให้)
โดยการเรียกโปรแกรม ruby ในอีก subprocess หนึ่ง
โดย subprocess จะรับข้อมูลผ่านทาง stdin
และให้ output กลับมาผ่านทาง stdout, stderr
ในกรณี xmp นี้ ก็จะใช้ข้อมูลจาก stderr เป็นหลัก

ตัวอย่าง stderr ที่ได้จาก program ข้างบน
!XMP1133102283_175558![1] => "Jim"
!XMP1133102283_175558![2] => "Tim"


ขั้นต่อไปก็เป็นการแปลความผลลัพท์ที่ได้จาก stderr
XMPRE = Regexp.new("^" + Regexp.escape(MARKER) + '\[([0-9]+)\] => (.*)')
results = Hash.new{|h,k| h[k] = []}
output.grep(XMPRE).each do |line|
result_id, result = XMPRE.match(line).captures
results[result_id.to_i] << result
end

จะเห็นว่ามีการ new Hash โดยใช้ block
(ที่เคยเขียนไปใน post เรื่อง Cache with Ruby Hash)
ถ้ามีการ access hash เมื่อไร ก็ให้สร้าง empty array เตรียมไว้ให้เลย
จะเห็นว่าใน code ก็จะ grep หาบรรทัดที่มีผลลัพท์
แล้วก็ทำการ put ผลลัพท์ลงไปใน hash (<< คือการ add ลง array)
ที่ต้องใช้ array มาเก็บ ก็เพราะบรรทัดที่อยู่ใน loop
มันจะถูก evaluate ได้หลายครั้ง
เช่น
i = 0
4.times {
i+=1 # => 1, 2, 3, 4
}

จะเห็นว่าบรรทัดใน loop มันจะมีค่าได้หลายค่า

ขั้นถัดไปก็ง่ายแล้ว ก็แค่ อ่าน code ขึ้นมาใหม่
จากนั้นก็ทำการ append ผลลัพท์ที่ได้ลงไป
ส่วนที่เหลือ ก็เป็นกรณี warning กับกรณี error แล้ว
ก็ใช้ technique แบบเดียวกัน เพียงแต่ง่ายกว่า
ตรง pattern ของ warning มันจะมีเลขบรรทัด
บอกมาเสร็จ ก็แค่แทรกลงไปให้ถูกบรรทัด
ส่วนกรณี error ถ้ามีก็ให้ต่อท้าย output ได้เลย

การได้ไล่โปรแกรมอย่างนี้ เป็นการเรียนรู้ที่ดีมาก
ผมว่าการจะเขียนโปรแกรมได้ดีนั้น ส่วนหนึ่งก็คือ
ต้องอ่าน source code คนอื่นเยอะๆ

Related link from Roti

Trip พายเรือ #2

ต่อจาก Trip พายเรือ #1



13/12/2540
วันนี้เจอฝายเพิ่มอีก 3 ฝาย แต่คราวนี้ยุ่งยากขึ้น
เพราะไม่สามารถทำคนเดียวได้
ต้องมีการไหว้วาน และมีค่าตอบแทนให้
(สมัยนั้นเรือแคนนูแบบเบาๆยังไม่เข้ามาในไทย)

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

ทิวทัศน์วันนี้ บ้านคนเริ่มบางตาแล้ว
แต่ก็ยังมีถนนเลียบไปกับแม่น้ำตลอดทางอยู่

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

Related link from Roti