หัดเขียนโปรแกรมแบบบ้านๆ ตอนที่ 3 พื้นฐานภาษา Lua

บทความก่อนหน้า

ตัวแปรและประเภทข้อมูล

ถ้าสมมุติให้หน่วยความจำในเครื่องคอมพิวเตอร๋เป็นอพาร์ทเม้น์แบ่งพื้นที่เป็นห้องๆ แต่ละห้องก็มีเลขที่ห้องเป็น address ของหน่วยความจำคนที่เข้าพักก็คือข้อมูล การประกาศตัวแปรก็คือการจองห้องหรือพื้นที่หน่วยความจำเช่นตัวแปรชื่อ x คอมพิวเตอร์ก็จะให้เลขที่ห้องว่างมาห้องหนึ่ง เมื่อมีคนเข้ามาอยู่ก็คือการกำหนดค่าก็คือการเอาข้อมูลไปเก็บในตำแหน่งหน่วยความจำนั้น

จากบทความเก่า [มารั่วกันเถอะ 1-4] ที่เคยกล่าวถึง Lua มาบ้างคร่าวๆ เราก็จะรู้จักกับข้อมูล 5 ใน 8 ประเภทรวมถึงวิธีการตั้งชื่อตัวแปรแล้วมาสรุปกันอีกที
  • ข้อมูลในภาษา Lua แบ่งได้เป็น 8 ประเภทได้แก่
    nil, boolean, number, string, table, function, userdata, thread ซึ่งเราได้ทำความรู้จักกับ 5  ประเภทแรกแล้ว
  • การตั้งชื่อตัวแปรประกอบไปด้วยตัวอักษรภาษาอังกฤษตัวเล็กตัวใหญ่ ตัวเลข และเครื่องหมาย _ โดยห้ามขึ้นต้นด้วยตัวเลข และอักษรตัวเล็กตัวใหญ่ถือเป็นคนละตัวกัน
  • การกำหนดค่าให้ตัวแปรใช้เครื่องหมาย  =  โดยตัวแปรอยู่ทางซ้ายและค่าที่กำหนดให้อยู่ทางขวา
  • ขอบเขตการเข้าถึงตัวแปร ( scope ) ในภาษา Lua มี 2 ระดับคือ global สามารถอ้างถึงตัวแปรจากตำแหน่งไหนในโปรแกรมก็ได้ กับระดับ local ที่สามารถอ้างถึงได้เฉพาะในขอบเขตที่ประกาศตัวแปรเท่านั้น ตัวแปร local จะต้องมี local หน้าชื่อตัวแปรถ้าไม่ใส่จะเป็นตัวแปร global
ประเภทข้อมูลส่วนที่เหลือได้แก่ function, userdata และ thread ในส่วนของ function นั้นจากตอนที่1 ได้อธิบายว่ามันคือส่วนของโปรแกรมย่อย และ Lua เป็นภาษาประเภท multi-paradigm ซึ่งได้เอาหลักการของ functional programming มาใช้ด้วยได้แก่  first-class function และ high-order function ทำให้เราสามารถกำหนดค่าให้ตัวแปรเป็น function รับค่า parameter เป็น function หรือส่งค่ากลับเป็น function ได้ซึ่งเราจะมากล่าวรายละเอียดกันภายหลัง ส่วน userdata เป็นประเภทข้อมูลที่รับมาจากโปรแกรมหรือ library ภาษา C และสุดท้าย thread เป็นประเภทข้อมูลที่ได้จากการสร้าง thread ด้วยมอดูล coroutine ซึ่งจะกล่าวถึงภายหลังเช่นกัน

คำสงวน

คือคำที่ภาษาโปรแกรมใช้ในนิยามคำสั่งหรือโครงสร้างของภาษาจึงห้ามนำมาใช้เป็นชื่อตัวแปร ซึ่งในภาษา Lua มีคำสงวนแค่ไม่กี่ตัวเท่านั้นดังนี้
            and    break    do      else      elseif
            end    false    for     function  goto
            if     in       local   nil       not
            or     repeat   return  then      true
            until  while

Attributes

เป็นของใหม่ใน Lua 5.4 ที่เพิ่งออกมาไม่นานนี้ ( 29 มิ.ย. 2563 ) เป็นการกำหนดคุณสมบัติเพิ่มเติมให้ตัวแปรมีสองชนิดได้แก่
  • <const> สำหรับกำหนดให้ตัวแปรนั้นเป็นค่าคงทีไม่สามารถเปลี่ยนแปลงค่าภายในได้อีก
      ตัวอย่าง local e <const> = 2.71828
  • <close> สำหรับกำหนดให้ตัวแปรนั้นปิดตัวเองอัตโนมัติเมื่อหลุดจาก scope การทำงานเช่นการเปิดไฟล์เพื่ออ่าน/เขียน ปกติเมื่อจบการทำงานจะสั่งให้ปิดไฟล์ แต่บางกรณีอาจมีการทำงานที่ต้องโดดออกจากลูปหรือคืนค่าออกจากฟังก์ชั่นตามเงื่อนไขซึ่งการกำหนดให้ปิดตัวเองอัตโนมัติจะทำให้เขียนโค้ดได้สะดวกขึ้น
      ตัวอย่าง local file <close> = io.open("input01.txt","r")

ตัวดำเนินการ ( Operators )

แบ่งออกเป็นตัวดำเนินการเอกภาค ( unary operator ) คือตัวดำเนินการที่ทำงานกับข้อมูลตัวเดียว ตัวดำเนินการทวิภาค ( binary operator ) คือตัวดำเนินการที่ทำกับข้อมูลสองตัว
  • ตัวดำเนินการกำหนดค่า ( Assignment operator ) มีตัวเดียวคือ
    =  เอาค่าฝั่งขวาใส่ให้ตัวแปรฝั่งซ้าย
  • ตัวดำเนินการตัวเลข ( Arithmatic operators ) ได้แก่
    +  บวก, ลบ, *  คูณ, /  หาร, ^  ยกกำลัง, //  หารตัดเศษ ( // มีเฉพาะในรุ่น 5.3 ขึ้นไป ),
    % modulo ( ผลลบระหว่างตัวตั้ง กับ (ผลหารตัดเศษระหว่างตัวตั้งกับตัวหารคูณด้วยตัวหาร) )
    ตัวอย่าง 10/6 ได้ 1.666666....  10//6 ได้ 1 แล้ว 10%6 จะได้เท่ากับ 10 - (6 * (10//6)) เท่ากับ 4
    ถ้า 10%3 ก็จะได้ 10-(3*(10//3)) เท่ากับ 1 ( หรือจะพูดได้ว่าจำนวนเต็มมากที่สุดที่คูณกับตัวหารแล้วไม่เกินตัวตั้งคือ 3 เมื่อคูณกับตัวหารก็จะได้ 9 นำไปลบจากตัวตั้งก็ได้ 1 )
    - ( unary )  กลับค่าบวกลบ เช่น a = 5; a = -a แล้วจะได้ a เท่ากับ -5
  • ตัวดำเนินการเปรียบเทียบ ( Relational operators ) ได้แก่
    ==  เท่ากับ, ~=  ไม่เท่ากับ, <  น้อยกว่า, <=  น้อยกว่าหรือเท่ากับ, >  มากกว่า,
    >=  มากกว่าหรือเท่ากับ
  • ตัวดำเนินการตรรกะ ( Logical operators ) ได้แก่
    not  ไม่ ( เช่น not (1>2) จะได้ true ), and  และ, or  หรือ
  • ตัวดำเนินการระดับบิท ( Bitwise operators ) มีเฉพาะรุ่น 5.2 ขึ้นไปและใน luajit เท่านั้นโดยในรุ่น 5.2 และ luajit จะเป็นฟังก์ชั่นในมอดูล bit32 ส่วนในรุ่น 5.3 ขึ้นไปจะเป็น operators ได้แก่
    &  bitwise and ( 1 & 2 ได้ 0 ; 1 คือ ..00000001 และ 2 คือ ..00000010 ในฐานสอง 0 และ 1 ได้ 0, 0 และ 0 ได้ 0, 1 และ 1 ได้ 1 ดังนั้น ..00000001 และ ..00000010 จะได้ ..00000000 ซึ่งก็คือ 0 ฐานสิบ
    |  bitwise or ( 0 หรือ 1 ได้ 1, 0 หรือ 0 ได้ 0, 1 หรือ 1 ได้ 1 )
    ~  bitwise xor ( Exclusive OR: 0 xor 1 ได้ 1, 0 xor 0 ได้ 0, 1 xor 1 ได้ 0 )
    ~ ( unary ) bitwise not ( ~1 ได้ -2; not 0 ได้ 1, not 1 ได้ 0
    ดังนั้น  ~ 00000000000000000000000000000001 ได้ 11111111111111111111111111111110
    )
    << bitwise left shift ( เลื่อนบิทไปทางซ้าย 1 << 1 ได้ 2; ..00000001 เลื่อนไปซ้าย 1 เป็น ..00000010 คือ 2 )
    >> bitwise right shift ( เลื่อนบิทไปทางฃวา 1 >> 1 ได้ 0; ..00000001 เลื่อนไปขวา 1 เป็น ..0000000 คือ 0 )
  • ตัวดำเนินการอักขระ ( String operators ) ได้แก่
    ..  เชื่อมต่อข้อความ ( "Hello " .. "World" จะได้ "Hello World" และยังใช้เชื่อมต่อตัวเลขได้ 1 .. 2 ได้ "12" หรือเชื่อมต่อข้อความกับตัวเลขก็ได้ แต่ต้องเว้นวรรคระหว่างตัวเลขตัวหน้ากับ .. ที่ตามหลังเช่น 1 ..2 ได้แต่ 1..2 หรือ 1.. 2 ไม่ได้ )
    #  นับจำนวนตัวอักษรในข้อความ ( เช่น a = "abcde" แล้ว #a จะเท่ากับ 5 แต่ใช้กับอักขระ unicode เช่นตัวอักษรภาษาไทยไม่ได้ a = "ฟหกด" แล้ว #a จะเท่ากับ 12 เพราะตัวอักษรไทยหนึ่งตัวเท่ากับ 3 bytes )
  • ตัวดำเนินการเทเบิล ( Table operators ) ได้แก่
    #  ใช้นับจำนวนสมาชิกใน table ที่มี index เป็นตัวเลขเรียงลำดับได้ ( a = {"a", "b", "c", 123, true} แล้ว #a จะได้ 5 แต่ถ้า index ไม่ใช่ตัวเลขหรือเป็นเลขไม่เรียงลำดับจะใช้ไม่ได้เช่น a = {"a", "b", [5]="c", x=123, true} แล้ว #a จะได้ 3 นับเฉพาะ [1]="a", [2]="b", [3]=true )
    [ ] กับ .  สำหรับการอ้างถึงสมาชิกใน table ( a = {"a", "b", [5]="c", x=123, true} แล้ว a.x หรือ a["x"] จะได้ 123 และ a[3] จะได้ true )
  • Function Call
    ( ) การเรียกใช้ฟังก์ชั่น จะเรียกชื่อฟังก์ชั่นตามด้วยค่า arguments ในวงเล็บแต่ถ้ามี argument ตัวเดียวเช่น string หรือ table สามารถเรียกโดยไม่ต้องใส่วงเล็บก็ได้ แต่ถ้าเรียกฟังก์ชั่นที่มีหลาย arguments หรือ argument ตัวเดียวแต่เป็นตัวแปรหรือฟังก์ชั่น หรือไม่มี argument ต้องมี () ด้วยเสมอ
    
    -- ฟังก์ชัน add()
    function add(x, y)
      return x + y
    end
    
    -- เรียกใช้ add()
    add(1,2)
    
    -- ฟังก์ชัน hello()
    function hello()
      print "H E L L O"
      print"world"
      -- 123    4:5:6
      print(table.concat {1,2,3}, table.concat({4,5,6}, ":")
    end
    
    -- เรียกใช้ hello()
    hello()
    
    -- ฟังก์ชัน fn()
    function fn(s)
      return s
    end
    
    -- ถ้าให้ s เป็น้อมูลประเภท string เรียกได้ 2 แบบ
    fn("asdf") หรือ fn"asdf" ก็ได้ 
    -- จะเว้นวรรคหลังชื่อฟังก์ชั่นหรือไม่ก็ได้ผลเหมือนกันหรือจะเว้นบรรทัดก็ไม่ผิด Lua ไม่ซีเรียสเรื่อง white space
    -- ถ้าให้ s เป็นข้อมูลประเภท table ก็สามารถเรียกแบบนี้ได้เช่นกัน
    fn({1,2,3}) หรือ fn{1,2,3}
    -- ถ้าให้ s เป็นตัวแปรหรือฟังก์ชั่นหรือข้อมูลประเภทอื่นต้องใส่ ()
    fn(3)
    local x = "asdf"
    fn(x)
    fn(hello())
    

กรอบการทำงานของโปรแกรม ( Blocks )

ในภาษาตระกูลภาษา C จะใช้ { } เป็นตัวกำหนดกลุ่มของคำสั่งและขอบเขตของตัวแปร local ให้อยู่เฉพาะในกรอบส่วนในภาษา Lua จะใช้
do .... end 

-- ตัวอย่าง 
a = 2
local b = 4
do
  local a
  a = 10
  local b, c = 5, 7
  local d = 2
  e = 50
  print("ในบล๊อก",a,b,c,d,e)
end
print("นอกบล๊อก",a,b,c,d,e)

-- ผลลัพธ์คือ
-- ในบล๊อก  10   5   7   2   50
-- นอกบล๊อก 2    4   nil nil 50
หรือในคำสั่งควบคุมต่างๆ รวมถึง function มักจะจบด้วย end เพื่อแสดงถึงจุดสิ้นสุดของ block นั้นและแต่ละ block สามารถซ้อนกันเป็นชั้นๆ ได้ ( nested เช่น nested blocks, nested if, nested loop, nested function ) ตัวแปรหรือฟังก์ชั่นที่ประกาศเป็น local ภายใน block นั้นจะมีขอบเขตการเข้าถึง ( scope ) เฉพาะภายใน block นั้นและ block ที่อยู่ภายใน block นั้น แต่ไม่สามารถเข้าถึงจากนอก block ได้

คำสั่งควบคุม ( Control Structures ) 

ประกอบด้วย
  • เงื่อนไขเป็นนิพจน์ ( Expression ) ที่มีค่าเป็นจริงหรือเท็จ
  • Block คำสั่ง
  • ชุดคำสั่งทำงานต่างๆ ( Statements ) ที่อยู่ภายใน block นั้น

คำสั่งเงื่อนไข ( Condition statements )

ใช้สำหรับกำหนดเงื่อนไขในการทำงานมีรูปแบบดังนี้
if เงื่อนไข then คำสั่งที่จะทำเมื่อเงื่อนไขเป็นจริง end

if เงื่อนไข then คำสั่งที่จะทำเมื่อเงื่อนไขเป็นจริง else คำสั่งที่จะทำเมื่อเงื่อนไขเป็นเท็จ end

if เงื่อนไข1 then
  คำสั่งที่จะทำเมื่อเงื่อนไข1เป็นจริง
elseif เงื่อนไข2 then
  คำสั่งที่จะทำเมื่อเงื่อนไข2เป็นจริง
elseif เงื่อนไข3 then
  คำสั่งที่จะทำเมื่อเงื่อนไข3เป็นจริง
  (สามารถใช้ elseif ได้หลายตัวตามเงื่อนไขที่เรามี)
else
  คำสั่งที่จะทำเมื่อไม่ตรงกับเงื่อนไขข้างบนเลย
end


-- ตัวอย่าง
local age = 18
if age<15 then print"เด็ก" elseif age>=15 and age<30 then print"ผู้ใหญ่" else print"แก่" end

--[[ ผลลีพธ์
ผู้ใหญ่
]]

คำสั่งทำซ้ำวนรอบ ( Loop )

While loop: จะทำคำสั่งใน block ซ้ำจนกว่าเงื่อนไขจะเป็นเท็จมีรูปแบบดังนี้
while เงื่อนไข do คำสั่งที่จะทำซ้ำ end


-- ตัวอย่าง
local counter = 0
while counter < 5 do
  counter = counter + 1  -- เพิ่มค่าทีละ 1 ในแต่ละรอบ
  print("counter is ".. counter)
end

--[[ ผลลัพธ์
counter is 1
counter is 2
counter is 3
counter is 4
counter is 5
]]
Repeat loop: ต่างกับ while ที่จะทำงานใน block ก่อนจะตรวจสอบเงื่อนไขและจะออกจากลูปเมื่อเงื่อนไขเป็นจริงมีรูปแบบดังนี้
repeat คำสั่งที่จะทำซ้ำ until เงื่อนไข


-- ตัวอย่าง
local counter = 0
repeat
  counter = counter + 1  -- เพิ่มค่าทีละ 1 ในแต่ละรอบ
  print("counter is ".. counter)
until counter == 5

--[[ ผลลัพธ์
counter is 1
counter is 2
counter is 3
counter is 4
counter is 5
]]
Numeric for loop:  จะทำซ้ำโดยกำหนดจำนวนรอบว่าจะทำกี่ครั้งมีรูปแบบดังนี้
for ตัวแปร = ค่าเริ่มต้น, ค่าสิ้นสุด do คำสั่งที่จะทำซ้ำ end

for ตัวแปร = ค่าเริ่มต้น, ค่าสิ้นสุด, step ในการเพิ่มหรือลดค่า do คำสั่งที่จะทำซ้ำ end


-- ตัวอย่าง1
for counter = 1, 5 do  -- ถ้าไม่กำหนด step จะเป็นการเพิ่มค่าทีละ 1
  print("counter is ".. counter)
end

--[[ ผลลัพธ์1
counter is 1
counter is 2
counter is 3
counter is 4
counter is 5
]]

-- ตัวอย่าง2
for counter = 10, 1, -2 do
  print("counter is ".. counter)
end

--[[ ผลลัพธ์2
counter is 10
counter is 8
counter is 6
counter is 4
counter is 2
]]
Generic for loop: จะทำซ้ำตามจำนวนค่าที่มีใน Iterator ( ฟังก์ชั่นที่สามารถส่งค่าใน iterable (หรือสิ่งที่ประกอบด้วยส่วนย่อยหลายตัวที่สามารถเอามาทำเป็นวนรอบทีละตัวได้) ออกมาทีละค่า ) พยายามจะอธิบายแต่อาจจะงงเข้าไปใหญ่ เช่น จำนวนตัวอักษรใน string หรือจำนวนสมาชิกใน table ( จะอธิบายเพิ่มภายหลัง )  มีรูปแบบดังนี้
for ตัวแปร1, ตัวแปร2, ..., ตัวแปรN in iterator[,itarable,ตัวควบคุม] do คำสั่งที่จะทำซ้ำ end


--[[
ถ้าไม่ได้เรียกใช้ฟังก์ชั่น iterator แต่เรียกชื่อฟังก์ชั่นลูป for จะส่ง iterable 
และตัวควบคุมเป็น arguments ส่งให้ iterator โดยที่ในการวนแต่ละรอบลูปจะเอาค่าแรกที่ iterator
ส่งออกมาใส่กลับเป็นตัวควบคุม
และลูปจะจบการทำงานเมื่อ iterator คืนค่ากลับมาเป็น nil
]]
-- ตัวอย่าง
local counter = {"a", "b", "c", "d", "e"}
for index, value in ipairs(counter) do     -- ipairs() เป็น iterator
  print("index is ".. index .."; value is ".."'".. value.."'")
end
-- หรือ
local counter = {"a", "b", "c", "d", "e"}
for index, value in next, counter, nil do  -- next() เป็น iterator
  -- next(counter,nil) ได้สมาชิกตัวแรกของ table เป็น 1, "a"
  -- next(counter,1) ได้สมาชิกตัวที่ 2 เป็น 2, "b"
  -- ......
  -- next(counter,4) ได้สมาชิกตัวที่ 5 เป็น 5, "e"
  -- next(counter,5) ได้ nil จบรอบ
  print("index is ".. index .."; value is ".."'".. value.."'")
end

--[[ ผลลัพธ์
index is 1; value is 'a'
index is 2; value is 'b'
index is 3; value is 'c'
index is 4; value is 'd'
index is 5; value is 'e'
]]
Break: เป็นคำสั่งให้กระโดดออกจาก loop เมื่อโปรแกรมทำงานจนถึงคำสั่งนี้โดยไม่ต้องทำซ้ำจนครบรอบ

print"start..."
for i=1,10 do       -- วนรอบ 1 ถึง 10
  print(i)          -- พิมพ์รอบที่
  if i==3 then      -- ตรวจสอบถ้าถึงรอบที่ 3 ให้ทำคำสั่งใน block นี้
    print "stop..." -- พิมพ์จบการวนรอบ
    break           -- ออกจากลูป
  end
end
print"Hello!"

-- ผลลัพธ์
--[[
start...
1
2
3
stop...
Hello!
]]
Goto และ Label: ( Lua 5.2 ขึ้นไป ) เป็นคำสั่งให้กระโดดการทำงานจากตำแหน่ง goto ไปยังตำแหน่ง label ที่ต้องการมีรูปแบบดังนี้
goto ชื่อ_label
ส่วนการกำหนดตำแหน่ง label ใช้
::ชื่อ_label::


-- ตัวอย่าง
local count = 0
::add_count::
count = count+1
-- ถ้า count อยู่ระหว่าง 5 กับ 8 หรือเท่ากับ 10 ให้ไปที่ label1
if count>5 and count<8 or count==10 then goto label1 end
print(count)
goto add_count	-- ย้อนไปที่ add_count
::label1::
if count==10 then
  goto label2	-- ไปที่ label2
end
print"a"
goto add_count	-- ย้อนไปที่ add_count
::label2::
print("b")

-- ผลลัพธ์
--[[
1
2
3
4
5
a
a
8
9
b
]]
บางภาษามีคำสั่ง continue ไว้ข้ามการทำงานในรอบนั้นแล้วเริ่มทำรอบใหม่เช่น if เงื่อนไข then continue แต่ในภาษา Lua สามารถใช้ goto ทดแทนได้เช่น if เงื่อนไข then goto ท้ายลูป end และยังทำลูปพิเศษเช่นตัวอย่างข้างบนก็ได้ หรือจะใช้แบ่งส่วนโปรแกรมเป็นส่วนย่อยๆ ก็ได้
ยาวแล้วตัดจบต่อตอนหน้าเรื่อง Function ครับผม

บทความถัดไป

ความคิดเห็น