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