Thursday, April 12, 2007

หน้าจอทำงาน

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

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

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

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

workspace แรก เปิด eclipse



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



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



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



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

Related link from Roti

Wednesday, April 11, 2007

MandelBrot Set ด้วย Haskell

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

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

iter = 0
maxiter = 30

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

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

end


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

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


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

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

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

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


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

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

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

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

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

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

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


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

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

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


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

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

Related link from Roti

Tuesday, April 10, 2007

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

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

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

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

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

Related link from Roti

Monday, April 09, 2007

Thrift

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

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

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

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

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

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

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


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


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

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

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

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

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


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

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

require 'PersonService'

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

transport.open()

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

client.create(p)

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


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

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

from person import PersonService
from person.ttypes import *

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


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

client = PersonService.Client(protocol)

transport.open()

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

Related link from Roti