Thursday, June 22, 2006

Erlang + Yaws 101 ตอน 1

ลองมาเข้าใจ syntax ของ erlang กันก่อน
แต่แทนที่จะเริ่มจากนิยามต่างๆ ลองมาดูตัวอย่างการใช้งานจริงเลยดีกว่า

เริ่มด้วยโจทย์ที่ว่า ถ้ามี http get เข้ามา แล้ว
เราจะ parse เอาค่า parameter ออกมาจาก query String ได้อย่างไร
(น่าเศร้าที่ yaws ไม่มีคำสั่งประเภท request.getParameter
หรือน่าเศร้าตรงที่มันอาจจะมี แต่ผมไม่รู้)

สมมติ url ที่เข้ามาคือ

http://localhost:8080/x.yaws?name=pok&lang=en


ใน yaws เราจะใช้คำสั่ง yaws_api:parse_query(Arg)
ในการ parse query string.
ให้สังเกตดูคำสั่ง จะเห็นว่ามันประกอบด้วย 2 ส่วน คั่นด้วยเครื่องหมาย :
ส่วนแรกคือชื่อ module ซึ่งก็คือ yaws_api
ส่วนหลังคือชื่อ function ใน module นั้นๆ

ผลลัพท์ที่ได้จากคำสั่ง parse_query ก็คือ
list ของ tuple หน้าตาแบบนี้
[{name,"pok"},{lang,"en"}]

list คือ Datatype ชนิดหนึ่ง ถ้าเปรียบเทียบกับสิ่งที่เราคุ้นเคย นั่นก็คือ Array
แต่ implement ของมันจริงๆแล้วเป็นพวก link list

tuple คือ Datatype อีกแบบหนึ่ง มีความหมายคล้ายๆพวก Record
แต่ primitive กว่า, ตัว Record เราสามารถ access by name ได้
แต่ tuple access ได้โดยใช้การระบุลำดับที่

ยกตัวอย่าง
{x,y,z} ถ้าเราต้องการ z เราต้องใช้คำสั่งแบบนี้
Result = element(3, {x,y,z}).

ประโยคนี้บอกอะไรเกี่ยวกับ syntax ของ erlang บ้าง
  1. ชื่อตัวแปร ให้ขึ้นต้นด้วยตัวใหญ่ (พวกเขียน Java จะรู้สึกไม่ชอบใจ)
    ข่าวร้ายตัวแปรสามารถ assign ได้ครั้งเดียว
    assign แล้วห้ามเปลี่ยนแปลงแก้ไข
    ข่าวดี โปรแกรมปราศจาก side-effects
    มี bug น้อย (ถ้าเขียนสำเร็จ)
  2. พวกที่ขึ้นต้นด้วยตัวเล็ก (ในกรณีนี้คือ x หรือ y หรือ z)
    เราเรียกว่า atom, มันเป็นกึ่งๆพวก constant
    ถ้าเปรียบเทียบกับ ruby, มันก็คือ symbol
    ส่วนใน java ไม่มี concept นี้
  3. เราปิด statement ด้วยเครื่องหมาย .


เพื่อให้พิศดารมากขึ้น ประโยคข้างบน สามารถเขียนในลักษณะ pattern matching ได้ดังนี้
{_,_,Result} = {x,y,z}.

เครื่องหมาย _ หมายถึง match เข้ากับ อะไรก็ได้

ย้อนกลับไปที่ผลลัพท์ที่ได้จาก yaws_api:parse_query ใหม่
[{name,"pok"},{lang,"en"}]

จะเห็นว่าชื่อ parameter ได้ออกเป็น Atom
ส่วน value ได้ออกมาเป็น String

ที่นี้ถ้าเกิดเราต้องการ value ของ parameter ที่ชื่อ Name หล่ะ
จะทำได้อย่างไร
เอาวิธี primitive ก่อน
getParameter(_, []) ->
"not found";

getParameter(Name ,L) ->
[H|T] = L,
Key = element(1, H),
if
Key == Name ->
element(2, H);
true ->
getParameter(Name, T)
end.

ดูซับซ้อน แต่อธิบายได้ดังนี้
function หนึ่งๆสามารถเขียนแยกกลุ่มได้ โดยใช้เครื่องหมาย ;
จากข้างบนจะเห็นว่า มันแยกเป็น 2 ส่วนคือ
getParameter(_, [])

กับ

getParameter(Name ,L)

ณ ขณะ runtime erlang จะใช้ pattern matching เพื่อ match parameter
ไล่จากบนลงมาล่าง ถ้าเจอ function ที่ match ได้ ก็จะเข้าไปทำงาน

กรณีของเรา getParameter(_, [])
หมายถึง ตัวแปรแรก match เข้ากับอะไร ข้าไม่สน
แต่ถ้าตัวแปรที่สองเป็น empty list ก็ให้ return "not found" กลับไป

บรรทัดที่เริ่มพิสดาร ก็คือบรรทัดที่ว่า
[H|T] = L

ลองดูตัวอย่าง syntax ของเครื่องหมาย | ก่อน

[1|[]] ได้ผลลัพท์เป็น [1]
[1|[2|[]]] ได้ [1,2]
[1|[2|[3|[]]]] ได้ [1,2,3]

[H|T] = [1]
H ก็คือ 1 ส่วน T คือ []
[H|T] = [1,2,3]
H ก็คือ 1 ส่วน T ก็คือ [2,3]

เจ๋งไหม
ดังนั้น [H|T] = L ก็คือการ extract element แรกสุดของ List นั่นเอง

จากนั้น เราก็ใช้คำสั่ง Key = element(1, H)
assumption ของเราก็คือ H มันมี datatype เป็น tuple แน่นอน
ให้ดึงตัวแรกใน tuple ออกมาเสีย

สุดท้ายก็ใช้ if เปรียบเทียบ
    if 
Key == Name ->
element(2, H);
true ->
getParameter(Name, T)
end.

กรณีที่ Key เท่ากับ Name ของ parameter ที่ต้องการ
ก็ให้ return element ที่สองออกมา
ส่วน true -> เทียบได้กับ else นั่นเอง
ถ้าไม่เจอ ก็ให้หาใน list ที่เหลือ (ที่ได้จากการ map [H|T])

อาจจะดูซับซ้อนนิดหนึ่ง แต่ถ้าเข้าใจแล้ว
มันจะเรียบง่ายอย่างยิ่ง

นี่คือวิธีแบบ primitive
แต่จริงๆแล้วรูปแบบที่ parse_query return กลับมา
มันเป็น pattern ที่ใช้บ่อยๆ (ใน clisp เราเรียก pattern แบบนี้ว่า Alists)
ใน erlang ก็เลยมีคำสั่งที่ช่วยดึงค่าออกมาได้ง่ายขึ้น
getPara(Name, L) ->
V = lists:keysearch(Name, 1, L),
case V of
{value,{_,Result}} ->
Result;
false ->
"not found"
end.


เหนื่อย ยิ่งอธิบายก็ยิ่งลงลึก วันนี้ติดไว้แค่นี้ก่อนครับ

Related link from Roti

5 comments:

Pol said...

สวัสดีครับ ผมเป็นคนนึงที่ ชอบเรียนรู้ภาษาใหม่ๆ erlang ก็ดีนะครับ แต่ผมไม่เคยเขียนโปรแกรมประเภท fucctional มาก่อนเลยน่ะครับ เลยไม่มีความรู้เรื่องนี้เท่าไรนัก ขนาด จะเขียนยังไง ยัง ไม่รู้เลยอ่ะครับ แล้วอย่างนี้จะรู้ยังไงล่ะครับ หาหนังสือก็ไม่ค่อยมีเท่าไรนักน่ะครับ เห็นแต่พวก C C++ java แล้วที่ผมเขียนมาก็พวกนี้ทั้งนั้น ผมเลย ใจท้อนิดๆ อยากรู้ภาษานี้นะครับ

polawat phetra said...

ผมเริ่ม functional language จาก
opencouseware ของ MIT ก่อนนะ
http://icampustutor.csail.mit.edu/6.001-public/
สอนดีมากเลย

พอเป็นสักตัวหนึ่ง ก็จะเร่ิมจับ idea ได้
ค่อยขยายไป erlang

Pol said...

แล้วอย่างนี้ผมจะพอมีทางเข้าใจ Erlang แบบ เร็วๆใหมครับ เพราะผมกะว่าจะเอา vehicle tracking system มาเขียนเป็น ภาษา C นะครับ

ผมเคยไปคุยกับพี่ทีนึงแล้วที่งาน codefest นะครับ เรื่องนี้ พอได้ idea แล้ว แต่ยังไม่เข้าใจหลายส่วนเลยครับ

ถ้ามีเรื่องที่จะปรึกษา ขอให้ช่วยผมหน่อยนะครับ

polawat phetra said...

ถ้าจะ implement ด้วย C
pol จะใช้แนวคิดของ vts ที่เขียนด้วย erlang ไม่ได้

ภาษาแต่ละภาษาจะมี กรอบหรือสิ่งที่ตัวเองทำได้ดี กับทำไม่ได้ดีอยู่
ตัว erlang ออกแบบมาโดยคำนึงถึง concurrency เป็นหลัก
พี่ก็เลยสามารถใช้คุณสมบัติต่างๆของ erlang
เข้ามาเขียน vts ได้ง่ายๆ

แต่ถ้าเป็น C ต้องคิดในแบบที่ C ทำได้ดีหรือทำได้ง่ายแทน

พล said...

ขอโทษนะครับที่หายไปนานพอดีมีงานเปิดเทคนะครับมีหลายๆ เรืองนะครับที่ยังไม่ค่อยเข้าใจอยู่ เช่นเรื่อง java script ผมให้เพื่อส่งที่พี่ส่งมาให้ มาให้ผมต่อ มันก็เป็นตัว ยึกยือหมดเลยอ่ะครับเหลือแต่ link เลยไม่ค่อยเข้าใจเลย

ผมลองจะมาเขียนเป็นภาษา C แล้วนะครับ แต่เรื่องการติดต่อกันระหว่างรถกับ process มันติดต่อไปทางชื่อยากนะครับ เพราะ fork ของ c มัน ตั้งชื่อ process ไม่ได้นะครับ (หรือผมไม่รู้) แต่ลองอ่านๆ หนังสือแล้วยังไม่เจอนะครับ

พี่ช่วยส่ง E-mail ที่ส่งให่เพื่อนผมมาให้ผมหน่อยได้ใหมล่ะครับ ที่ sripaoeam@hotmail.com นะครับ เพราะอยากจะลองรันแบบพี่ก่อนแล้วศึกษาดูนะครับ แต่ตอนนี้ยังทำแบบพี่ไม่ได้เลย พวก yawn อ่ะครับ ยังไม่ค่อยเข้าใจเท่าไร จะลองศึกษาต่อไปครับ ขอบคุณที่ตอบครับ