คงผ่านตา ข่าวใน blognone เรื่อง "การเพิ่มพื้นที่แสดงผลช่วยเพิ่มประสิทธิภาพการทำงาน"
ผมเป็นคนหนึ่งหล่ะที่ visualize ภาพในใจไม่เก่ง
ต้องอาศัยการมองเห็น มาเป็นตัวช่วยในการคิดและเรียนรู้
เมื่อก่อน สมัยจอยังเล็กๆธรรมดาอยู่
เวลา design ก็ต้องพึ่งกระดาษแผ่นใหญ่ๆ วางให้เต็มโต๊ะ แล้วก็ sketch ขีดๆเขียนๆไป
มาปัจจุบันจอมันใหญ่มากแล้ว
ก็เลยกัดฟันลงทุนซื้อจอใหญ่ (นอนก่ายหน้าผาก คิดอยู่นานมาก)
แต่ผลของมันก็คุ้มค่านะ
ที่เห็นผมเรียนรู้เรื่อง rails, erlang, haskell, ... จนไปพูดใน blognone techday ได้
ปัจจัยหนึ่งที่ช่วยสนับสนุน ก็คือ เจ้าจอตัวนี้นั่นแหล่ะ
ว่าแล้วก็ขอ show หน้าจอทำงาน (เมื่อก่อนจะรู้สึกอาย ว่าจอมันไฮโซไปหน่อย)
ปกติ ผมจะเปิด desktop ไว้ 4 workspace
อันนี้ capture งานที่ทำวันนี้
workspace แรก เปิด eclipse
workspace สอง เป็นพวก design
workspace สาม เป็นพวก document
workspace สี่ เป็น พวก console
Note: จะสังเกตุว่าไม่มีพวก IM ใช้ เพราะแก่แล้ว (ไม่ถนัดโต้ตอบแบบ online)
Thursday, April 12, 2007
Wednesday, April 11, 2007
MandelBrot Set ด้วย Haskell
จากโจทย์ใน codenone ที่ Implement ด้วย Ruby ไปแล้ว
คราวนี้มาลอง implement ด้วย haskell บ้าง
เริ่มด้วย algorithm การคำนวณค่า
ถ้าเป็น ruby เขียนแบบนี้
ใน imperative language จะมีพวก while loop ให้ใช้
แต่ใน haskell ไม่มี loop แบบนี้
ดังนั้นเราต้องแปลงให้เป็น tail-recursive แทน
ขั้นถัดไปก็คือคำนวณว่าจะ render ภาพอย่างไร
โดยในโจทย์ เขาจะให้เรากำหนด coordinate ของมุมบนซ้าย กับมุมล่างขวา
จากนั้นก็ให้เราคำนวณรูป โดยกำหนดว่า resolution ของการ plot ต้องมีอย่างน้อย 200 pixel,
ถ้าเขียนด้วย python จะเขียนดังนี้ (code นี้คุณสุกรีเป็นคนเขียน)
จะเห็นว่ามี for loop ซ้อนกัน 2 ชั้น
haskell ก็ไม่มี for loop ให้ใช้เหมือนเดิม
ดังนั้น เราจะเปลียนแนวคิดไปเป็น List แทน
เริ่มโดยหาจุดที่เป็นไปได้บน แกน X ก่อน
โดยต้องการ list หน้าตาแบบนี้
[(-2,0),(-1.98,1),(-1.96,2),....]
ตัวแรกใน tuple คือ ค่าที่จะนำไปคำนวณสมการ
ส่วนตัวที่สองใน tuple ก็คือค่าที่ใช้ plot จุดบนแกน x
take คือการดึงค่าออกจาก list ตามจำนวนที่ระบุ เช่น
ส่วน iterate มันอธิบายยาก ลองดูตัวอย่าง
ดังนั้น code ข้างบน ก็อ่านได้ว่า
"ดึง element ออกมาจาก list ตามจำนวน pixel ที่เราจะ plot"
ซึ่งก็คือ 200 element
จากนั้นก็หาจุดที่เป็นไปได้บน แกน Y
นำ list ของแกน X และ แกน Y
มาทำ cartesial product โดยใช้ List Comprehension
โดย sx คือ x ที่มุมบนซ้าย
ex คือ x ที่มุมล่างขวา
sy คือ y ที่มุมบนซ้าย
ey คือ y ที่มุมล่างขวา
ถึงขั้นนี้ เราก็ได้ผลลัพท์ออกมาเป็น list ของ tuple แบบนี้
ซึ่งสามารถนำไป plot ได้แล้ว
ขั้นตอนที่ยากอีกขั้นของ Haskell ก็คือ จะเลือกใช้ Graphic library อันไหนดี
เพราะมันมีให้เลือกเยอะ,
แต่ที่เหมือนกันหมดทุกเจ้าก็คือ
ถ้าอยากรู้ว่าทำงานอย่างไร ก็ให้อ่าน source code ของ example เอา
ผมตกลงเลือกใช้ Graphics.UI.GLUT
(หลังจากเลือกไปตัวหนึ่งแล้ว แต่มัน run ช้ามาก,
กับอีกตัวหนึ่งที่ install ไม่ผ่าน)
ปัญหาของโปรแกรมในส่วนของ UI ก็คือ State
เนื่องจาก Haskell มันเป็น pure functional
การจัดการกับ state ก็เลยต้องใช้ Monad เข้ามาช่วย
ลองดูการ define state
โดยผมจะเก็บค่า มุมบนซ้ายกับมุมล่างขวา
ซึ่งค่านี้จะเปลี่ยนไปเรื่อยๆ เมื่อ user กด zoom
เวลาจะ get ค่า ก็ทำแบบนี้
ส่วนเวลาจะ set ค่า ก็ต้องทำแบบนี้
code ที่เหลือเป็น code ที่เกี่ยวกับ open-gl แล้ว
มีหน้าตาประมาณนี้ (ไม่ได้แสดงส่วนที่เกี่ยวกับการ zoom)
คราวนี้มาลอง implement ด้วย haskell บ้าง
เริ่มด้วย algorithm การคำนวณค่า
ถ้าเป็น ruby เขียนแบบนี้
def is_mandelbrot(x,y)
x0 = x
y0 = y
x2 = x*x
y2 = y*y
iter = 0
maxiter = 30
while ( x2 + y2 < 4 && iter < maxiter )
y = 2*x*y + y0
x = x2 - y2 + x0
x2 = x*x
y2 = y*y
iter += 1
end
if iter == maxiter || iter == 0
0.0
else
iter.to_f * 100 / maxiter
end
end
ใน imperative language จะมีพวก while loop ให้ใช้
แต่ใน haskell ไม่มี loop แบบนี้
ดังนั้นเราต้องแปลงให้เป็น tail-recursive แทน
max_iter = 30
is_mandel x y = is_mandel' x y max_iter x y
is_mandel' x y cnt x0 y0
| cnt == 0 = 0.0
| exceed = (max_iter - cnt) / max_iter
| otherwise = is_mandel' (x2-y2+x0) (2*x*y+y0) (cnt-1) x0 y0
where x2 = x * x
y2 = y * y
exceed = x2 * y2 > 4
ขั้นถัดไปก็คือคำนวณว่าจะ render ภาพอย่างไร
โดยในโจทย์ เขาจะให้เรากำหนด coordinate ของมุมบนซ้าย กับมุมล่างขวา
จากนั้นก็ให้เราคำนวณรูป โดยกำหนดว่า resolution ของการ plot ต้องมีอย่างน้อย 200 pixel,
ถ้าเขียนด้วย python จะเขียนดังนี้ (code นี้คุณสุกรีเป็นคนเขียน)
def mandelbrot(ul,lr,callback,step=(200,200),maxiter=255,limit=2):
xs,ys = step
x0,y0 = ul.real,ul.imag
x1,y1 = lr.real,lr.imag
xd,yd = (x1-x0)/xs,(y1-y0)/ys
i = 0
y = y0
for i in range(ys):
x = x0
for j in range(xs):
color = mandelbrot_point(x,y,maxiter,limit)
callback(i,j,x,y,color)
y += yd
จะเห็นว่ามี for loop ซ้อนกัน 2 ชั้น
haskell ก็ไม่มี for loop ให้ใช้เหมือนเดิม
ดังนั้น เราจะเปลียนแนวคิดไปเป็น List แทน
เริ่มโดยหาจุดที่เป็นไปได้บน แกน X ก่อน
โดยต้องการ list หน้าตาแบบนี้
[(-2,0),(-1.98,1),(-1.96,2),....]
ตัวแรกใน tuple คือ ค่าที่จะนำไปคำนวณสมการ
ส่วนตัวที่สองใน tuple ก็คือค่าที่ใช้ plot จุดบนแกน x
loopX startX endX = take w $ iterate (\(x,x') -> (x + stepX, x' + 1)) (startX,0)
where stepX = (endX - startX) / width
w = floor width
take คือการดึงค่าออกจาก list ตามจำนวนที่ระบุ เช่น
take 3 [1,2,3,4,5]
ก็จะได้ [1,2,3]
ส่วน iterate มันอธิบายยาก ลองดูตัวอย่าง
iterate (\x -> x + 1) 1
ได้ผลลัพท์คือ [1,2,3,4,... จน infinity]iterate (\x -> x + 2) 1
ได้ผลลัพท์คือ [1,3,5,7,...]ดังนั้น code ข้างบน ก็อ่านได้ว่า
"ดึง element ออกมาจาก list ตามจำนวน pixel ที่เราจะ plot"
ซึ่งก็คือ 200 element
จากนั้นก็หาจุดที่เป็นไปได้บน แกน Y
loopY startY endY = take h $ iterate (\(y,y') -> (y - stepY, y' + 1)) (startY,0)
where stepY = (startY - endY) / height
h = floor height
นำ list ของแกน X และ แกน Y
มาทำ cartesial product โดยใช้ List Comprehension
โดย sx คือ x ที่มุมบนซ้าย
ex คือ x ที่มุมล่างขวา
sy คือ y ที่มุมบนซ้าย
ey คือ y ที่มุมล่างขวา
mandelset sx sy ex ey = [(x',y',is_mandel x y) | (x,x') <- (loopX sx ex), (y,y') <- (loopY sy ey)]
ถึงขั้นนี้ เราก็ได้ผลลัพท์ออกมาเป็น list ของ tuple แบบนี้
[(x coordinate,y coordinate, ค่าที่ได้จากการคำนวณว่าอยู่ใน set ของ mandelbrot )]
ซึ่งสามารถนำไป plot ได้แล้ว
ขั้นตอนที่ยากอีกขั้นของ Haskell ก็คือ จะเลือกใช้ Graphic library อันไหนดี
เพราะมันมีให้เลือกเยอะ,
แต่ที่เหมือนกันหมดทุกเจ้าก็คือ
ถ้าอยากรู้ว่าทำงานอย่างไร ก็ให้อ่าน source code ของ example เอา
ผมตกลงเลือกใช้ Graphics.UI.GLUT
(หลังจากเลือกไปตัวหนึ่งแล้ว แต่มัน run ช้ามาก,
กับอีกตัวหนึ่งที่ install ไม่ผ่าน)
ปัญหาของโปรแกรมในส่วนของ UI ก็คือ State
เนื่องจาก Haskell มันเป็น pure functional
การจัดการกับ state ก็เลยต้องใช้ Monad เข้ามาช่วย
ลองดูการ define state
โดยผมจะเก็บค่า มุมบนซ้ายกับมุมล่างขวา
ซึ่งค่านี้จะเปลี่ยนไปเรื่อยๆ เมื่อ user กด zoom
data State = State { sx, sy, ex, ey :: IORef Float }
makeState :: IO State
makeState = do
sx <- newIORef (-2.0)
sy <- newIORef 2.0
ex <- newIORef 2.0
ey <- newIORef (-2.0)
return $ State { sx = sx, sy = sy, ex = ex, ey = ey }
เวลาจะ get ค่า ก็ทำแบบนี้
sx <- get (sx state)
ส่วนเวลาจะ set ค่า ก็ต้องทำแบบนี้
writeIORef (sx state) val
code ที่เหลือเป็น code ที่เกี่ยวกับ open-gl แล้ว
มีหน้าตาประมาณนี้ (ไม่ได้แสดงส่วนที่เกี่ยวกับการ zoom)
display :: State -> DisplayCallback
display state = do
clear [ ColorBuffer ]
sx <- get (sx state)
sy <- get (sy state)
ex <- get (ex state)
ey <- get (ey state)
renderPrimitive Points $ do
mapM_ plot $ mandelset sx sy ex ey
swapBuffers
reshape :: ReshapeCallback
reshape size@(Size w h) = do
viewport $= (Position 0 0, size)
matrixMode $= Projection
loadIdentity
ortho2D 0 (fromIntegral w) 0 (fromIntegral h)
scale 1 (-1) (1::GLfloat)
translate (Vector3 0 (-400) (0 :: GLfloat))
keyboardMouse :: State -> KeyboardMouseCallback
keyboardMouse state (MouseButton b) Down _ (Position x y) = case b of
LeftButton -> zoom state x y
_ -> return ()
keyboardMouse _ _ _ _ _ = return ()
main :: IO ()
main = do
(progName, _args) <- getArgsAndInitialize
initialDisplayMode $= [ DoubleBuffered, RGBMode ]
initialWindowSize $= Size (floor width) (floor height)
createWindow progName
myInit
state <- makeState
displayCallback $= display state
reshapeCallback $= Just reshape
keyboardMouseCallback $= Just (keyboardMouse state)
mainLoop
Related link from Roti
Labels:
haskell
Tuesday, April 10, 2007
สะพานลอยหน้า Major
7 เดือนก่อน :
ผม: วันก่อนผมแบบจักรยานข้ามสะพานลอย Major,
เห็นพวก california fitness ~50 คน
กำลังปั่นจักรยานกันใหญ่เลย, ได้ feeling มากเลย
(ประมาณว่า เราขี่จักรยานจริงๆ พวกนั้นทำอะไรกันอยู่, โลกอยู่ข้างนอกนี่)
revolution: (เจ้าของร้านจักรยาน และเจ้าของ host server ที่ support rails) ยิ้ม...
5 เดือนก่อน :
roofimon: พี่ ผมเห็นพี่บนสะพานลอยคนข้ามหน้า Major ด้วย
ผม: เออ ผมกำลังแบบจักรยานกลับบ้านน่ะ
เมื่อวาน :
ขณะกำลังแบกจักรยานข้ามสะพานลอยหน้า Major
RerngIt: พี่ป๊อก พี่ป๊อก
ผม: เฮ้ยมาได้ยังไง
RerngIt: ผมย้ายมาอยู่แถวนี้แล้วพี่
เมื่อเช้า :
ไม่ห่างจากสะพานลอยหน้า Major
ตำรวจ 2 คนยืนเฝ้าตู้โทรศัพท์ที่โดนระเบิด
ทหารอีกคนกำลังกึ่งเดินกึ่งวิ่ง พร้อมกับพูด ว. ไปด้วย
เศษกระจกแตกเกลื่อนพื้น
ผมประคองจักรยานผ่านหน้าตู้ด้วยความระมัดระวัง ด้วยความกลัวยางแตก
จากนั้นก็ลงเดินแล้วแบกจักรยานข้ามสะพานลอย
ผม: วันก่อนผมแบบจักรยานข้ามสะพานลอย Major,
เห็นพวก california fitness ~50 คน
กำลังปั่นจักรยานกันใหญ่เลย, ได้ feeling มากเลย
(ประมาณว่า เราขี่จักรยานจริงๆ พวกนั้นทำอะไรกันอยู่, โลกอยู่ข้างนอกนี่)
revolution: (เจ้าของร้านจักรยาน และเจ้าของ host server ที่ support rails) ยิ้ม...
5 เดือนก่อน :
roofimon: พี่ ผมเห็นพี่บนสะพานลอยคนข้ามหน้า Major ด้วย
ผม: เออ ผมกำลังแบบจักรยานกลับบ้านน่ะ
เมื่อวาน :
ขณะกำลังแบกจักรยานข้ามสะพานลอยหน้า Major
RerngIt: พี่ป๊อก พี่ป๊อก
ผม: เฮ้ยมาได้ยังไง
RerngIt: ผมย้ายมาอยู่แถวนี้แล้วพี่
เมื่อเช้า :
ไม่ห่างจากสะพานลอยหน้า Major
ตำรวจ 2 คนยืนเฝ้าตู้โทรศัพท์ที่โดนระเบิด
ทหารอีกคนกำลังกึ่งเดินกึ่งวิ่ง พร้อมกับพูด ว. ไปด้วย
เศษกระจกแตกเกลื่อนพื้น
ผมประคองจักรยานผ่านหน้าตู้ด้วยความระมัดระวัง ด้วยความกลัวยางแตก
จากนั้นก็ลงเดินแล้วแบกจักรยานข้ามสะพานลอย
Related link from Roti
Monday, April 09, 2007
Thrift
Note:
ไปเจอจาก bookmark ของ bact' บน del.icio.us
น่าสนใจดี ตรงตามที่อยากได้
เลยชิงเขียนถึงก่อน
Thrift มีแนวคิดคล้ายๆ COBRA นั่นคือ เข้ามาช่วยเราในเรื่อง Distributed Application
แต่ไม่ over spec เหมือน COBRA
ตัวมันจะเล็กๆ เบาๆ กว่า
ที่ผมชอบก็คือ มันเปิดโอกาสให้ผม เชื่อม ruby, python, java, php
เข้าหากันในลักษณะ remote procedure call ได้
วิธีการใช้ ก็คือ เราต้องเขียน definition ของ interface, parameter ที่เราต้องการใช้ ด้วย syntax ของ Thrift เองก่อน
(แบบเดียวกับที่เราต้องเขียน IDL file ใน COBRA)
จากนั้นก็สามารถจะ generate stub สำหรับฝั่ง server หรือฝั่ง client
บนภาษาที่เราต้องการได้
โดยขณะนี้ Thrift support การ generate java,cpp,php,python,ruby
ลองดูตัวอย่าง
เริ่มด้วยการเขียน definition file
ลอง implement server ด้วย java
ลอง implement client ด้วย ruby
สุดท้ายลอง implement client ด้วย python
ไปเจอจาก bookmark ของ bact' บน del.icio.us
น่าสนใจดี ตรงตามที่อยากได้
เลยชิงเขียนถึงก่อน
Thrift มีแนวคิดคล้ายๆ COBRA นั่นคือ เข้ามาช่วยเราในเรื่อง Distributed Application
แต่ไม่ over spec เหมือน COBRA
ตัวมันจะเล็กๆ เบาๆ กว่า
ที่ผมชอบก็คือ มันเปิดโอกาสให้ผม เชื่อม ruby, python, java, php
เข้าหากันในลักษณะ remote procedure call ได้
วิธีการใช้ ก็คือ เราต้องเขียน definition ของ interface, parameter ที่เราต้องการใช้ ด้วย syntax ของ Thrift เองก่อน
(แบบเดียวกับที่เราต้องเขียน IDL file ใน COBRA)
จากนั้นก็สามารถจะ generate stub สำหรับฝั่ง server หรือฝั่ง client
บนภาษาที่เราต้องการได้
โดยขณะนี้ Thrift support การ generate java,cpp,php,python,ruby
ลองดูตัวอย่าง
เริ่มด้วยการเขียน definition file
#!/usr/local/bin/thrift -cpp -java -py -php -rb -r
struct Address {
1: string province
2: string zipCode
}
struct Person {
1: i32 id,
2: string name,
3: Address address
}
service PersonService {
Person getById(1:i32 id),
void create(1:Person person)
}
ลอง implement server ด้วย java
public class MyServer implements PersonService.Iface {
static Hashtable<Integer, Person> map = new Hashtable<Integer, Person>();
public void create(Person person) throws TException {
map.put(person.id, person);
}
public Person getById(int id) throws TException {
return map.get(id);
}
public static void main(String[] args) throws TTransportException {
PersonService.Processor proc = new PersonService.Processor(new MyServer());
TServerSocket tss = new TServerSocket(9090);
TThreadPoolServer server = new TThreadPoolServer(proc, tss);
server.serve();
}
}
ลอง implement client ด้วย ruby
#! /usr/bin/env ruby
require 'thrift/transport/tsocket'
require 'thrift/protocol/tbinaryprotocol'
require 'PersonService'
transport = TBufferedTransport.new(TSocket.new('localhost',9090))
protocol = TBinaryProtocol.new(transport)
client = PersonService::Client.new(protocol)
transport.open()
p = Person.new
a = Address.new
a.zipCode = '10900'
a.province = 'Bangkok'
p.address = a
p.name = 'pok'
p.id = 1
client.create(p)
np = client.getById(1)
puts np.name
puts np.address.zipCode
สุดท้ายลอง implement client ด้วย python
#!/usr/bin/env python
import sys
sys.path.append('..')
from person import PersonService
from person.ttypes import *
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
transport = TSocket.TSocket('localhost',9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = PersonService.Client(protocol)
transport.open()
p = client.getById(1)
print p.name, p.address.zipCode
Related link from Roti
Subscribe to:
Posts (Atom)