บทความก่อนหน้า
Module
Environment ใน Lua
ภาษา Lua มีตัวแปรพิเศษอยู่ตัวหนึ่งชื่อว่า _G เป็น table ที่เก็บตัวแปร global, standard library และฟังก์ชันภายในทุกตัวเช่น print() ก็คือ _G.print() เป็นต้น
ตัวอย่าง Lua interpreter
Lua 5.3.5 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> a = 10
> a
10
> _G.a
10
> _G.a = 20
> a
20
> a == _G.a
true
>
เราสามารถแก้ไขสมาชิกใน _G ได้อิสระซึ่งอาจเกิดปํญหากระทบกับฟังก์ชันภายในได้ ใน Lua 5.1 เรามีฟังก์ชัน getfenv() กับ setfenv() ไว้คอยจัดการกับสภาพแวดล้อมทำให้สร้างเป็น sandbox ที่ไม่กระทบกับสภาพแวดล้อมหลักได้แต่ใน Lua 5.2 ขึ้นไปเรามีวิธีที่เรียบง่ายกว่าโดยอาศัยตัวแปรพิเศษอีกตัวคือ _ENV โดยค่าเริ่มต้นคือ _ENV = _G ซึ่งในบล๊อกคำสั่งของ Lua จะไม่ไปค้นหาใน _G โดยตรงแต่จะไปดูที่ _ENV แทนดังนั้นเราสามารภเปลี่ยน _ENV ในบล๊อกเป็นอีก table เพื่อป้องกันการเข้าถึงสภาพแวดล้อมหลักจากภายในบล๊อกได้
a = {p = print} -- สร้างสภาพแวดล้อมใหม่มีสมาชิก "p" ชี้ไปที่ฟังก์ชัน "print"
do -- บล๊อกของโปรแกรม
local _ENV = a -- กำหนดให้ใช้สภาพแวดล้อมใหม่ในบล๊อกโปรแกรม
a = 10 -- ตัวแปร global ในบล๊อก
p(a) -- พิมพ์ค่าในตัวแปร
end -- จบบล๊อกกลับสู่สภาพแวดล้อมหลัก
print(a) -- พิมพ์ค่าตัวแปร global ในสภาพแวดล้อมหลัก
print(a.a) -- พิมพ์ค่าสมาชิก "a" ใน table "a"
-- ผลลัพธ์
--[[
10
table: 0x1edb840
10
]]
local pr = print -- เก็บฟังก์ชัน "print" ไว้ในตัวแปร local "pr"
function my_env(t) -- สร้างฟังก์ชัน "my_env"
t.pa = pairs -- เก็บฟังก์ชัน "pairs" เป็นสมาชิกของ table "t"
local _ENV = t -- กำหนดให้ "t" เป็นสภาพแวดล้อมใหม่
pr("inner_ENV =", _ENV) -- พิมพ์ค่าสภาพแวดล้อมปัจจุบันในฟังก์ชัน
for k, v in pa(_ENV) do -- วนลูปแสดงสมาชิกในสภาพแวดล้อมปัจจุบัน
pr(k, v)
end
end
local env = {} -- สร้างตัวแปร "env"
print("ENV=", _ENV, "env=", env) -- พิมพ์ค่าสภาพแวดล้อมปัจจุบันเทียบกับ "env"
my_env(env) -- เรียกฟังก์ชัน "my_env"
print"+++++"
for k, v in pairs(env) do -- วนลูปแสดงสมาชิกใน "env"
print(k, v)
end
-- ผลลัพธ์
--[[
ENV= table: 0x13709f0 env= table: 0x1378b00
inner_ENV = table: 0x1378b00
pa function: 0x41b570
+++++
pa function: 0x41b570
]]
การนำเข้าโปรแกรมจากไฟล์อื่น
ภาษา Lua สามารถแปลโค้ดในตัวเองได้โดยใช้ฟังก์ชัน load() ดังนั้นเราสามารถอ่านไฟล์เป็นข้อความส่งเข้าฟังก์ชันก็ได้หรือใช้ loadfile() เรียกไฟล์นั้นโดยตรง ซึ่ง dofile() ก็ทำได้เช่นกันแต่ dofile() จะทำงานในไฟล์นั้นเลยขณะที่ loadfile() กับ load() จะส่งค่าเป็นฟังก์ชันต้องเรียกใช้งานอีกที
-- Ex.1 เนื้อหาใน mycode1.lua ไฟล์ที่ไม่มีการคืนค่า
print("Hello")
.......
-- การใช้วิธี Load
local code = io.open("mycode1.lua"):read("a") -- โหลดไฟล์มาเป็นข้อความ
local f = load(code) -- โหลดโค้ดจากข้อความไปเป็นฟังก์ชัน
f() -- เรียกให้โค้ดที่โหลดมาทำงาน
.......
-- การใช้วิธี Loadfile
local f = loadfile("mycode1.lua") -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชัน
f() -- เรียกให้โค้ดที่โหลดมาทำงาน
.......
-- การใช้วิธี Dofile
dofile("mycode1.lua") -- โหลดโค้ดจากไฟล์และสั่งทำงาน
.......
-- ผลลัพธ์
--[[
Hello
]]
-- Ex.2 เนื้อหาใน mycode2.lua ไฟล์ที่มีการคืนค่า
return 2 + 3
.......
-- การใช้วิธี Load
local code = io.open("mycode2.lua"):read("a")
local f = load(code) -- โหลดโค้ดจากข้อความไปเป็นฟังก์ชัน
local result = f() -- เรียกให้โค้ดที่โหลดมาทำงานเอาผลลัพธ์เก็บใน "result"
print(result)
.......
-- การใช้วิธี Loadfile
local f = loadfile("mycode2.lua") -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชัน
local result = f() -- เรียกให้โค้ดที่โหลดมาทำงานเอาผลลัพธ์เก็บใน "result"
print(result)
.......
-- การใช้วิธี Dofile
local result = dofile("mycode2.lua") -- โหลดโค้ดจากไฟล์และสั่งทำงานเอาผลลัพธ์เก็บใน "result"
print(result)
.......
-- ผลลัพธ์
--[[
5
]]
-- Ex.3 เนื้อหาใน mycode3.lua การเข้าถึงตัวแปรและฟังก์ชันที่เป็นแบบ global และ local
abc = "ตัวแปร global"
local def = "ตัวแปร local" -- ไม่สามารถเข้าถึงตัวแปร local จากนอกไฟล์ได้
function add(x, y)
return x + y
end
local function sub(x, y) -- ไม่สามารถเข้าถึงฟังก์ชัน local จากนอกไฟล์ได้
return x - y
end
.......
-- การใช้วิธี Load
local code = io.open("mycode3.lua"):read("a")
local f = load(code) -- โหลดโค้ดจากข้อความไปเป็นฟังก์ชัน
f() -- เรียกให้โค้ดที่โหลดมาทำงาน
print(abc, def)
print(add(1, 2), sub)
.......
-- การใช้วิธี Loadfile
local f = loadfile("mycode3.lua") -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชัน
f() -- เรียกให้โค้ดที่โหลดมาทำงาน
print(abc, def)
print(add(1, 2), sub)
.......
-- การใช้วิธี Dofile
dofile("mycode3.lua") -- โหลดโค้ดจากไฟล์
print(abc, def)
print(add(1, 2), sub)
.......
-- ผลลัพธ์
--[[
ตัวแปร global nil
3 nil
]]
นอกจากจะสามารถโหลดไฟล์โปรแกรมแล้วยังสามารถใช้กับไฟล์ที่ผ่านการแปลงเป็น bytecode ด้วยโปรแกรม luac ได้ด้วย
การสร้าง Module
โดยทั่วไปในการเขียนโปรแกรมไฟล์หนึ่งๆ อาจมีการนำเข้าฟังก์ชันจากไฟล์อื่นเก็บไว้เรียกใช้ในโปรแกรม การนำเข้าไฟล์ตามตัวอย่างข้างต้นเราสามารถเข้าถึงตัวแปร global ได้เลยแต่จะมีปัญหาเวลาที่เราโหลดหลายไฟล์และมีชื่อตัวแปรหรือฟังก์ชันซ้ำซ้อนกัน เราสามารถใช้การคืนค่าของไฟล์ในตัวอย่าง Ex.2 ข้างบนโดยใช้ return ในการส่งออกฟังก์ชันที่ใช้งาน
-- Ex.4 เนื้อหาใน mycode4.lua
local function add(x, y)
return x + y
end
return add -- ส่งออกฟังก์ชัน add ไปให้ไฟล์ที่เรียกใช้งาน
.......
-- การใช้วิธี Load
local code = io.open("mycode4.lua"):read("a")
local f = load(code) -- โหลดโค้ดจากข้อความไปเป็นฟังก์ชัน
-- "f" เทียบได้กับ function() return add end
-- "f()" จึงได้ "add" ดังนั้น "f()(1,2)" จึงเท่ากับ "add(1,2)"
local result = f()(1, 2) -- เรียกให้ฟังก์ชัน "add" ที่เก็บอยู่ใน "f" ทำงานเอาผลลัพธ์เก็บใน "result"
print(result)
.......
-- การใช้วิธี Loadfile
local f = loadfile("mycode4.lua") -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชัน
local add = f() -- เรียก "f" ทำงานคืนค่าฟังก์ชัน "add" ไปเก็บในตัวแปร "add"
print(add(1, 2))
-- หรือ
local add = loadfile("mycode4.lua")() -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชันและเรียกใช้ฟังก์ชันเอาผลลัพธ์ฟังก์ชัน "add" เก็บในตัวแปร "add"
-- loadfile("mycode4.lua") == function() return add end
-- (function() return add end)() จะได้ "add"
local result = add(1, 2)
print(result)
.......
-- การใช้วิธี Dofile
local add = dofile("mycode4.lua") -- โหลดโค้ดจากไฟล์และสั่งทำงานเอาผลลัพธ์ฟังก์ชัน "add" เก็บในตัวแปร "add"
print(add(1, 2))
.......
-- ผลลัพธ์
--[[
3
]]
ในกรณีที่ต้องการส่งออกฟังก์ชันหลายตัวในหนึ่งไฟล์ เราสามารถใช้ตัวแปร table ในการเก็บฟังก์ชันทั้งหมดที่ใช้งานได้และเรียกไฟล์ที่เก็บฟังก์ชันที่ให้ไฟล์อื่นใช้งานว่า module เหมือนเป็นการกำหนด namespace ให้ฟังก์ชันป้องกันการสับสนกับฟังก์ชันชื่อเหมือนกันจากมอดูลอื่น
-- Ex.5 เนื้อหาใน mycode5.lua
local M = {}
function M.addi(x, y)
return x + y
end
function M.subi(x, y)
return x - y
end
return M
.......
-- การใช้วิธี Load
local code = io.open("mycode5.lua"):read("a")
local f = load(code) -- โหลดโค้ดจากข้อความไปเป็นฟังก์ชัน
local result = f().addi(1, 2) -- เรียกฟังก์ชัน "addi" ที่อยู่ในมอดูล "M" ในฟังก์ชัน "f" ทำงาน
print(result)
local mymod = f() -- เรียกให้โค้ดที่โหลดมาทำงานเอาผลลัพธ์มอดูล "M" เก็บในตัวแปร "mymod"
local func_sub = mymod.subi -- เอาฟังก์ชัน "subi" ที่อยู่ในมอดูล "mymod" ไปใส่ตัวแปร "func_sub"
result = func_sub(5, 2) -- เรียกฟังก์ชัน "func_sub" ทำงาน
print(result)
.......
-- การใช้วิธี Loadfile
local f = loadfile("mycode5.lua") -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชัน
local mymod = f() -- เรียกให้โค้ดที่โหลดมาทำงานเอาผลลัพธ์ "M" เก็บในตัวแปร "mymod"
print(mymod.addi(1, 2))
print(mymod.subi(5, 2))
-- หรือ
local mymod = loadfile("mycode5.lua")() -- โหลดโค้ดจากไฟล์ไปเป็นฟังก์ชันและเรียกใช้ฟังก์ชัน
local result = mymod.addi(1, 2) -- เรียกใช้ฟังก์ชัน "addi" ในมอดูล "mymod" เก็บผลลัพธ์ในตัวแปร "result"
print(result)
print(mymod.subi(5, 2)) -- เรียกใช้ฟังก์ชัน "subi" ในมอดูล "mymod"
.......
-- การใช้วิธี Dofile
local mymod = dofile("mycode5.lua") -- โหลดโค้ดจากไฟล์และสั่งทำงานเอาผลลัพธ์เก็บในตัวแปร "mymod"
print(mymod.addi(1, 2))
print(mymod.subi(5, 2))
-- หรือ
local func_add = dofile("mycode5.lua").addi -- โหลดโค้ดจากไฟล์และสั่งทำงานเอาเฉพาะฟังก์ชัน "addi" เก็บในตัวแปร "func_add"
print(func_add(1, 2))
local func_sub = dofile("mycode5.lua").subi -- โหลดโค้ดจากไฟล์และสั่งทำงานเอาเฉพาะฟังก์ชัน "subi" เก็บในตัวแปร "func_sub"
print(func_sub(5, 2))
.......
-- ผลลัพธ์
--[[
3
3
]]
แต่การใช้ load(), loadfile(), dofile() ยังมีปัญหาบ้างเช่นต้องระบุพาธเต็มของไฟล์ที่ต้องการโหลดกรณีที่ไฟล์ไม่ได้อยู่ที่เดียวกันและกรณีที่มีการโหลดไฟล์เดิมซ้ำก็จะได้มอดูลใหม่ทำให้เปลืองหน่วยความจำ
local mod1 = dofile("mycode5.lua")
local mod2 = dofile("mycode5.lua")
print(mod1, mod2)
-- ผลลัพธ์
--[[
table: 0x2559ef0 table: 0x2475a30
]]
ดังนั้น Lua จึงมีอีกฟังก์ชันที่ใช้จัดการกับมอดูลโดยตรงคือ require() โดยจะไปค้นหามอดูลใน package.loaded ในกรณีที่มีการโหลดมอดูลนั้นมาก่อนก็จะไม่โหลดซ้ำแต่จะใช้มอดูลที่เก็บไว้ในนี้แทนถ้าไม่มีค่อยใช้ชื่อไปค้นในพาธที่เก็บใน package.path โดยไปหาไฟล์ .lua ชื่อเดียวกับชื่อมอดูลหรือหาไฟล์ init.lua ในโฟล์เดอร์ชื่อเดียวกับชื่อมอดูลแทน ถ้าไม่เจอค่อยไปหาไฟล์นามสกุล .so ( หรือ .dll ใน Windows ) ที่เป็นไฟล์ไบนารีที่เขียนจากภาษา C จากพาธที่เก็บใน package.cpath แทนตามลำดับกรณีที่โหลดสำเร็จก็จะเก็บมอดูลนั้นไว้ที่ package.loaded
local mod1 = require("mycode5")
local mod2 = require("mycode5")
print(mod1, mod2)
print(package.loaded.mycode5)
-- ผลลัพธ์
--[[
table: 0x26d0eb0 table: 0x26d0eb0
table: 0x26d0eb0
]]
ดังนั้นจึงไม่มีปัญหาการซ้ำซ้อนเช่นในมอดูล B มีการเรียกใช้มอดูล A และในมอดูล C ก็มีการเรียกใช้มอดูล A เช่นกันและในโปรแกรมของเรามีการใช้งานทั้งมอดูล B และ C ก็จะมีการโหลดมอดูล A แค่ตัวเดียว
กรณีที่ต้องการโหลดมอดูลที่คอมไพล์เป็น lua bytecode ก็ไปเพิ่มพาธใน package.path เอาได้
กรณีที่ต้องการโหลดมอดูลที่คอมไพล์เป็น lua bytecode ก็ไปเพิ่มพาธใน package.path เอาได้
$ ls # "ls" เป็นคำสั่งดูไฟล์ใน Linux mycode5.lua test.lua # มีสองไฟล์คือไฟล์มอดูลกับไฟล์ทดสอบ $ luac -o mymod.luac mycode5.lua # คอมไพล์ไฟล์ "mycode5.lua" ไปเป็น Lua bytecode ชื่อ "mymod.luac" $ ls mymod.luac mycode5.lua test.lua # ได้ไฟล์ที่เป็น bytecode เพิ่มมา $
-- ไฟล์ test.lua package.path = package.path .. ";./?.luac" -- เพิ่มให้หาไฟล์นามสกุล .luac ในโฟลเดอร์เดียวกัน ( Linux/Mac ถ้าเป็น Windows พาธจะเป็น Drive: กับ \ แทน / ) local mymod = require("mymod") print(mymod.subi(10, 2)) -- ผลลัพธ์ --[[ 8 ]]
Sub module
เราสามารถสร้างมอดูลที่ประกอบไปด้วยกลุ่มของมอดูลได้จากตอนที่แล้วเรื่อง OOP เราจะเอาตัวอย่างตอนที่แล้วมาทำเป็นมอดูลดังนี้
โครงสร้างไฟล์ |_ person -- มอดูลหลัก "person" เป็นโฟลเดอร์ไปเรียกไฟล์ person/init.lua | |_ init.lua | |_ person.lua -- มอดูลย่อย "person" person/person.lua | |_ employee.lua -- มอดูลย่อย "employee" person/employee.lua |_ main.lua -- โปรแกรมหลัก
-- init.lua local M = {} M.Person = require("person.person") -- โหลดคลาส "Person" ( "person.person" == person/person.lua ) M.Employee = require("person.employee") -- โหลดคลาส "Employee" ( "person.employee" == person/employee.lua ) return M ----------------------- -- person.lua local Person = {} function Person.new(n, w) local obj = {} local class = "Person" local name = n obj.word = w function obj.getclass() return class end function obj.getname() return name end function obj.say(w) if w then obj.word = w end return name.." say: "..obj.word end return obj end return Person -- ส่งออกมอดูล Person ----------------------- -- employee.lua local Person = require("person.person") -- โหลดคลาส "Person" ( "person.person" == person/person.lua ) local Employee = {} function Employee.new(n, w, s) local obj = Person.new(n, w) local class = "Employee" local salary = tonumber(s) or 0 obj.getbaseclass = obj.getclass function obj.getclass() return class end function obj.setsalary(s) if type(s) == "number" then salary = s end end function obj.getsalary() return salary end function obj.say(s) obj.setsalary(s) return obj.word..", I'm "..obj.getname().."\nMy salary is "..salary.."." end return obj end return Employee -- ส่งออกมอดูล Employee ----------------------- -- main.lua local Employee = require("person.employee") -- "Employee" == person/employee.lua local Person = require("person").Person -- "Person" == "Person" ใน person/init.lua local john = Person.new("John", "Hello") local jack = Employee.new("Jack", "Hi.", 20) print(john.say()) print(jack.say()) print(Person, Employee) print(package.loaded.person["Person"]) -- "Person" ใน person/init.lua print(package.loaded["person.person"]) -- person/person.lua print(package.loaded.person.Employee) -- "Employee" ใน person/init.lua print(package.loaded["person.employee"]) -- person/employee.lua local P = require("person") -- "P" == person/init.lua local jane = P.Person.new("Jane", "Bye. :)") print(jane.say()) print(P.Person, P.Employee) -- ผลลัพธ์ --[[ John say: Hello Hi., I'm Jack My salary is 20. table: 0x9f88e0 table: 0x9f8fd0 table: 0x9f88e0 table: 0x9f88e0 table: 0x9f8fd0 table: 0x9f8fd0 Jane say: Bye. :) table: 0x9f88e0 table: 0x9f8fd0 -- package.loaded["person"] == {Person, Employee} -- package.loaded["person"].Person == package.loaded["person.person"] -- package.loaded["person"]["Employee"] == package.loaded["person.employee"] ]]
จบแล้วครับสำหรับเรื่องมอดูลในภาษา Lua นอกจากนี้ยังสามารถใช้มอดูลที่เป็น binary ที่เขียนในภาษา C เช่นพวกไฟล์ .so หรือ .dll ได้แต่ต้องเขียนโดยใช้ C API ของ Lua ซึ่งเราจะไม่กล่าวถึงเพราะจะเกินเนื้อหาสำหรับมือใหม่หัดเขียนโปรแกรมไป
บทความถัดไป
ความคิดเห็น
แสดงความคิดเห็น