Friday, December 22, 2006

โจทย์ Kata #3

P03 (*) Find the K'th element of a list.
The first element in the list is number 1.
Example:
* (element-at '(a b c d e) 3)
C


clisp
(defun element-at (lst pos)
(if (= pos 1)
(car lst)
(element-at-2 (cdr lst) (1- pos))))


haskell
elt :: [a] -> Int -> a
elt (x:_) 1 = x
elt (_:xs) n | n > 1 = elt xs (n-1)

Note: เครื่องหมาย '|' เรียกว่า Guard
Note: '_' คือ mathcing เข้ากับอะไรก็ได้ (pattern matching)

erlang
-module(p03).
-export([elt/2]).

elt([X|_],1) -> X;
elt([_|XS],N) when N > 1 -> elt(XS,N-1).

Note: when N > 1 ก็คือ Guard แบบเดียวกับ haskell

Note: ธรรมชาติคนเรามักจะเคยชินกับนับเลขไปข้างหน้า
คิดครั้งแรกผมก็ใช้วิธี +1 แล้วตรวจว่าได้เท่ากับค่าที่ต้องการหรือยัง
ซึ่ง code ที่ได้ ยาวต่างกันอย่างเห็นได้ชัด
ลองดู haskell แบบ + ไปข้างหน้า
element_at ::  [a] -> Int -> a
element_at xs pos = element_at_helper xs pos 1

element_at_helper :: [a] -> Int -> Int -> a
element_at_helper (x:xs) pos i
| pos == i = x
| otherwise = element_at_helper xs pos (1 + i)

Related link from Roti

Head On

ปกติผมไม่ค่อยชอบงาน installation เท่าไร
แต่งาน Head On ของ Cai Guo-Qiang นี่สะดุดตาดีนะ



Note: ชอบงานนี้อีกชิ้นหนึ่ง
Same Word, Same Seed, Same Root
ใช้ดอกไม้ไฟช่วยระบาย

Related link from Roti

Thursday, December 21, 2006

Hibernate Lucene Search

ผมมี Idea เรื่องที่จะใช้ Lucene ทำ search index
ของ Domain model มานานแล้ว
แต่ก็ไม่ได้โอกาสทำเสียที
มาตอนนี้ Hibernate 3.2 เขาทำ feature นี้ออกมาให้ใช้เรียบร้อยแล้ว
แถม integrate ได้เนียนดีด้วย

ลองดูตัวอย่างการใช้งาน

ขั้นแรกสุด ก็คือ "การระบุว่า domain ไหนที่ต้องการทำ index"
ตรงนี้เขาใช้ annotation Indexed เข้ามาช่วย
@Indexed(index="indexes/people")
@Entity
public class Person {
...
}

lucene มองสิ่งที่ถูก index ว่าเป็น document
ดังนั้นก็เลยต้องมีการระบุ tag DocumentId
ไว้ที่ primary key ด้วย
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@DocumentId
public Long getId() {
return id;
}

field ไหนที่ต้องการทำ index ก็ต้องระบุด้วย tag Field
@Field(index=Index.UN_TOKENISED)
public String getName() {
return name;
}


เพื่อให้ lucene ทำ index โดยอัตโนมัติ หลังจากที่เรา hibernate insert,update หรือ delete
เราก็ต้อง config add listenter เข้าไป
<event type="post-update">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>

<event type="post-insert">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>

<event type="post-delete">
<listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
</event>


การ config ระบุว่าจะเก็บ index file ที่ไหน ทำผ่าน directory_provider
hibernate.search.default.directory_provider org.hibernate.search.store.FSDirectoryProvider
hibernate.search.default.indexDir=/usr/lucene/indexes


ส่วนการสืบค้นข้อมูล จะเห็นว่าเขาพยายามดึงให้เข้าไปใช้ org.hibernate.Query
ลองดูตัวอย่าง
Session session = HibernateUtil.getSession();
FullTextSession fullTextSession = Search.createFullTextSession(session);
QueryParser parser = new QueryParser("title", new StopAnalyzer());
org.apache.lucene.search.Query query = parser.parse("name:bunn");
org.hibernate.Query query2 = fullTextSession.createFullTextQuery(query);
List results = query2.list();

for (Person tmp : results) {
System.out.println("id =" + tmp.getId() + ",name = " + tmp.getName());
}

Related link from Roti

Wednesday, December 20, 2006

My Kata

PragDave เขาเคยพูดถึงประเด็น Code Kata ไว้
Kata คือ การฝึกรูปแบบการเคลื่อนที่ (patterns of movements practiced)
เป็นศัพท์ที่ใช้ในวงการพวก ศิลปะการป้องกันตัว
(ไม่รู้ว่านำไปเทียบกับ Drill ของพวกว่ายน้ำได้หรือเปล่า)

Dave เขาพูดถึงประเด็นเรื่องการฝึกเขียนโปรแกรม หรือฝึกคิดแก้โจทย์
โดยคุณสมบัติที่เขากำหนดไว้ ก็คือ
  • session ของการฝึกต้องไม่ยาวนัก (~= 30 ถึง 60 นาที)
  • scope ของเรื่องที่ฝึก ก็ต้องน้อยๆไม่เยอะนัก
  • code (ที่ได้จากฝึกนี้) เขียนแล้วโยนทิ้งไปเฉยๆ ไม่ได้เอาไปทำอะไรต่อ
  • ทำซ้ำ และไม่ต้องพะวงกับความผิดพลาด,
    เน้นให้ความสนใจไปที่ feedback เพื่อที่จะนำมาปรับปรุง technic ให้ดีขึ้น


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

อย่างกรณีของผม
ยิ่งผมเรียนรู้ ภาษา, framework หลายๆแบบยิ่งขึ้น
สัดส่วนของความชำนาญก็จะลดลง
ตอนนี้ก็เลยต้องให้ความสำคัญกับ
"การสร้างความชำนาญ" มากขึ้น

เบื้องต้นก็คือ พยายามหาโจทย์ มาฝึกทำ
อย่างโจทย์ของที่นี่ ก็น่าสนใจดี
L-99: Ninety-Nine Lisp Problems
วันนี้ก็เลยเลือกข้อง่ายสุด มาทำ 2 ข้อ


P01 (*) Find the last box of a list.
Example:
* (my-last '(a b c d))
(D)


เริ่มด้วย clisp
(defun find-last (lst)
(if (null lst)
nil
(if (null (cdr lst) 1)
lst
(find-last (cdr lst)))))

haskell
find_last (x : []) = x
find_last (x : xs) = find_last xs

erlang
-module(p1).
-export([find_last/1]).

find_last([X | []]) ->
X;
find_last([X | XS]) ->
find_last(XS).


โจทย์แรกนี่ technic การใช้ pattern matching ชนะแฮะ
haskell ดู clean สุด
ส่วน erlang ถึงแม้จะใช้วิธีเดียวกับ haskell
แต่ syntax ไม่ clean เท่า

โจทย์ข้อต่อไป, โจทย์นี้ไม่ต่างจากโจทย์แรกเท่าไร

P02 (*) Find the last but one box of a list.
Example:
* (my-but-last '(a b c d))
(C D)


clisp
(defun but-last (lst)
(if (null lst)
nil
(if (null (cdr (cdr lst)))
lst
(but-last (cdr lst)))))

haskell
but_last (x : []) = [x]
but_last (x : y : []) = [x, y]
but_last (x : xs) = but_last xs

erlang
-module(p02).
-export([but_last/1]).

but_last([X | []]) ->
[X];
but_last([X | [Y | []]]) ->
[X,Y];
but_last([X | XS]) ->
but_last(XS).

Related link from Roti

Monday, December 18, 2006

Counter Component, Tapestry vs. Seaside

สืบเนื่องจากงาน NJUG (narisa java user group)
ที่ผมไปทำ workshop เรื่อง Tapestry
วันนี้ก็เลยยกตัวอย่างที่ทำ workshop ของ Tapestry
มาเปรียบเทียบกับ Seaside ที่ใช้ smalltalk เขียน

เรื่องด้วย Counter Component ก่อน



เริ่มที่ tapestry
การเขียน Tapestry Component ต้องใช้ 2 file
เป็น java class กับ Html template
public abstract class Counter extends BaseComponent {

@Persist
public abstract int getCount();
public abstract void setCount(int value);

public void increase() {
setCount(getCount() + 1);
}

public void decrease() {
setCount(getCount() - 1);
}
}

<div>
<h3><span jwcid="@Insert" value="ognl:count">0</span></h3>
<a href="#" jwcid="@DirectLink" listener="listener:increase">++</a>
<a href="#" jwcid="@DirectLink" listener="listener:decrease">--</a>
</div>


ฝั่งของ Seaside จะมีแค่ file เดียว
เพราะ Seaside ไม่ได้ใช้ template
(อันนี้เป็นประเด็นทางศาสนา ฝ่ายสนับสนุน
เชื่อว่า pure smalltalk มันงามแท้ๆ)

WAComponent subclass: #WACounter
instanceVariableNames: 'count'
classVariableNames: ''
poolDictionaries: ''
category: 'Seaside-Examples-Test'

initialize
super initialize.
self session registerObjectForBacktracking: self.
count := 0

count
^ count

decrease
count := count - 1

increase
count := count + 1


renderContentOn: html
html heading: count.
html anchor callback: [self increase]; text: '++'.
html space.
html anchor callback: [self decrease]; text: '--'

rendererClass
^ WARenderCanvas

ลองดูความสวยงาม(ที่เขาว่ากัน) ของการ render html ใน method renderContentOn:

ถ้าตัดประเด็นส่วน syntax ที่ไม่เหมือนกัน
กับ Technique การ implement ที่แตกต่างกันออกไป
ก็จะเห็นว่าทั้งสองฝ่ายมีแนวคิดไปในทำนองเดียวกัน นั่นคือ
  • ประเด็นเรื่องการ maintain state บนฝั่ง server,
    ที่ให้ framework เป็นคนจัดการให้เรา
    ทำให้เรามุ่งไปที่ กับ business logic ได้เต็มที่
  • การพยายามทำให้ event ที่เกิดที่ฝั่ง client กลืนเข้าเป็น
    เนื้อเดียวกับ script ที่ฝั่ง server,
    ซึ่งในแง่ของการร้อย event, ทางฝั่ง smalltalk ทำใด้เนียนกว่า


ลองดูการนำ component มาใช้งานบ้าง
ในตัวอย่างนี้ ก็คือการนำ Counter Component มาวางเรียงกันหลายๆอัน



(ซึ่งเวลาทำงานแล้ว แต่ละ component ก็จะ maintain state ของตัวเอง)
เริ่มด้วย Tapestry
เนื่องจากหน้าจอเราไม่ซับซ้อน ดังนั้นทางฝั่ง tapestry จึงใช้แค่ file เดียว
นั่นคือ html template file
<html>
<head>
<title>Multiple Component</title>
</head>
<body>
<span jwcid="@Counter">counter</span>
<hr/>
<span jwcid="@Counter">counter</span>
<hr/>
</body>
</html>

จะเห็นว่าเราใช้ Counter 2 อันมาวางเรียงกัน

ลองกลับไปดูที่ฝั่ง smalltalk บ้าง
ฝั่ง smalltalk จะยุ่งยากกว่าหน่อย
ตรงที่เราต้อง maintain ว่า component MultiCounter ของเรา
มี sub component อะไรบ้าง
โดยการ maintain, seaside กำหนดให้เราต้อง
implement method children
ที่ต้อง return sub component ทั้งหมดที่อยู่ภายใต้ component เรา

WAComponent subclass: #WAMultiCounter
instanceVariableNames: 'counters'
classVariableNames: ''
poolDictionaries: ''
category: 'Seaside-Examples-Test'

initialize
super initialize.
counters _ (1 to: 2) collect: [:i | WACounter new]

children
^ counters

renderContentOn: html
counters
do: [:ea | html render: ea]
separatedBy: [html horizontalRule]


สิ่งที่แตกต่างกันมากที่สุด สำหรับ seaside กับ tapestry
ก็คือเรื่อง flow ระหว่าง page
ในฝั่ง tapestry flow ระหว่าง page
ลองดู code ฝั่ง tapestry เวลาที่เรา flow เปลี่ยน page
@InjectPage("ShowEmployee")
public abstract ShowEmployee getShowEmployee();

public IPage doSubmit() {
ShowEmployee page = getShowEmployee();
page.setEmployee(getEmployee());
return page;
}

จะเห็นว่า tapestry กำหนดไว้ว่า ถ้า method ที่ถูกเรียกใช้
return page ไหนออกมา ก็ให้ render page นั้นแทนที่ page ปัจจุบัน

ดูฝั่ง smalltalk บ้าง ลองดูตัวอย่าง การ flow ของหน้าจอขายของ
self call: (WAStoreFillCart new cart: cart)
cart := self call:
((WAStoreCartConfirmation new cart: cart)
addMessage: 'Please verify your order:')
shipping := self call:
(WAStoreAddressEditor new
validateWith: [:a | a validate];
addMessage: 'Please enter your shipping address:';
yourself)
...

WAStoreFillCart, WAStoreCartConfirmation, WAStoreAddressEditor
คือหน้าจอย่อยๆ แต่ละหน้าจอ
จะเห็นว่าเราสามารถผูกหน้าจอต่างๆเข้าเป็น flow ได้
เวลาจะกระโดดไปหน้าจอไหน ก็ใช้ method call
แถมยังรับ return ค่ากลับจากหน้าจอนั้นได้อีก
ซึ่งถือว่าเป็น feature ที่ดูเด่นกว่า tapetry อย่างเห็นได้ชัด

Related link from Roti