Yuescript

Yuescript

เป็นภาษาที่พัฒนาต่อยอดจาก MoonScript ซึ่งเป็นภาษาที่แปลงเป็นภาษา Lua อีกทีหนึ่งเช่นเดียวกับ Fennel ในบทความก่อน โดยแต่เดิมใช้ชื่อว่า MoonPlus หรือ Moon+ ภายหลังเปลี่ยนชื่อเป็น Yuescript โดยคำว่า Yue มาจากเสียงอ่านคำว่าดวงจันทร์ในภาษาจีน ( 月 ) ซึ่งใน Logo ก็ใช้สัญลักษณ์ตัวอักษร Yue กับรูปจันทร์เสี้ยวกลางหมู่เมฆให้อารมณ์จีนดี เป็นการออกแบบที่ลงตัวผมชอบนะ

MoonScript กับ Lua

มาทำความรู้จักกับ MoonScript ซึ่งเป็นภาษาต้นแบบก่อน แต่เดิมภาษา Lua นั้นเป็นภาษาที่มีโครงสร้างเรียบง่ายและมีความยืดหยุ่นแต่ในขณะเดียวกันก็ดูเยิ่นเย้อ MoonScript จึงถูกสร้างขึ้นเพื่อทำให้การเขียนโปรแกรมสะดวกขึ้น เหมือนอย่าง CoffeeScript ที่เคยนิยมเขียนแทน JavaScript อยู่พักหนึ่ง โดยเอาคุณลักษณะของหลายๆ ภาษามาใช้เช่นเปลี่ยนการใช้ keyword กำหนดบล๊อก มาใช้การย่อหน้าเหมือน Python แทน ลดการใช้วงเล็บเหมือนใน Ruby เป็นต้น นอกจากนี้ยังเพิ่มการอำนวยความสะดวกเช่น
  • ให้ตัวแปรเป็น Local โดยปริยายกลับกันกับ Lua
  • เพิ่ม Update assignment operator เช่น " += ", " -= ", " *= ", " /= ", " %= ", " ..= ", " or= ", " and= "
  • มี string interpolation เหมือนใน Ruby
    
    print "1 + 1 = #{1+1}"                -- print("1 + 1 = " .. tostring(1 + 1))
                                          -- "1 + 1 = 2"
    name = "Sam"
    print "My name is #{name}."           -- print("My name is " .. tostring(name) .. ".")
                                          -- "My name is Sam."
    
  • มีการจัดการการเขียนแบบ OOP ที่เป็นระบบมาตรฐาน มีระบบ class ให้ใช้ไม่ต้องมากำหนด metatable เอง
    class Animal
      new: (color) => @color = color      -- constructor method
    
    class Cat extend Animal               -- สืบทอดจากคลาส Animal
      leg: 4                              -- static property
      new: (name, color) =>               -- constructor method
        super color                       -- เรียก constructor method ของ Animal
        @name = name
      getname: => @name
    
    mao = Cat "Mao", "black"
    print mao\getname!                    -- "Mao"
  • เปลี่ยนรูปแบบของ function และ method ให้กระชับ
    add = (a, b) -> a + b                 -- local add
                                          -- add = function(a, b)
                                          --   return a + b 
                                          -- end
    
    fact = (n) ->                         -- local fact
      f = 1                               -- fact = function(n)
      for i = 2, n                        --   local f = 1
        f *= n                            --   for i = 2, n do
      f                                   --     f = f * n
                                          --   end
                                          --   return f
                                          -- end
    
    mod.set_x = (x) => @x = x             -- mod.set_x = function(self, x)
                                          --   self.x = x
                                          -- end
    
    mod\set_x 10                          -- mod:set_x(10)
    mod.set_x mod, 10                     -- mod.set_x(mod, 10)
  • คืนค่านิพจน์สุดท้ายในฟังก์ชันเสมอโดยไม่ต้องกำหนด return แต่ยังสามารถใส่ return เพื่อคืนค่าในตำแหน่งอื่นที่ต้องการได้
    fact = (n) ->                         -- local fact
      if n < 0 then return nil            -- fact = function(n)
      if n == 0 then return 1             --   if n < 0 then
      if n < 3                            --     return nil
        return n                          --   end
      else                                --   if n == 0 then
        n * fact n - 1                    --     return 1
                                          --   end
                                          --   if n < 3 then
                                          --     return n
                                          --   else
                                          --     return n * fact(n - 1)
                                          --   end
                                          -- end
  • if และ for สามารถใช้เป็นนิพจน์ได้
    
    if a > b                              -- if a > b then
      print "A"                           --   print("A")
    else                                  -- else
      print "B"                           --   print("B")
                                          -- end
    -- สามารถเขียนเป็นนิพจน์ได้
    print if a > b then "A" else "B"      -- print((function()
                                          --   if a > b then
                                          --     return "A"
                                          --   else
                                          --     return "B"
                                          --   end
                                          -- end)())
    -- แต่ถ้าเป็นเงื่อนไขเดียว
    local res                             -- local res
    if a > b                              -- if a > b then
      res = "A"                           --   res = "A"
                                          -- end
    -- สามารถเขียนสองบรรทัด
    local res
    if a > b then res = "A"
    -- หรือสั้นกว่านั้น
    res = if a > b then "A"
    -- หรือสั้นกว่านั้นอีก
    res = "A" if a > b
    
    -- for ก็เป็นนิพจน์ได้เช่นกัน
    table1 = for i=2, 4 do i*3            -- table1 == {6, 9, 12}
    table2 = i*3 for i=2, 4               -- table2 == 12
    table3 = [i*3 for i=2, 4]             -- table3 == {6, 9, 12}
    print i, v for i, v in ipairs(table1) -- for i, v in ipairs(table1) do
                                          --   print(i, v)
                                          -- end
                                          -- ผลลัพธ์
                                          -- 1    6
                                          -- 2    9
                                          -- 3    12
    
  • สามารถกำหนดค่าในเงื่อนไขได้
    if f = io.open "abc.txt"              -- do
      text = f\read!                      --   local f = io.open("abc.txt")
      f.close!                            --   if f then
                                          --     local text = f:read()
                                          --     f.close()
                                          --   end
                                          -- end
    
  • เพิ่ม unless เหมือนการใช้ if not และเพิ่ม continue ในลูป
  • มี switch case
    switch exp                            -- local _exp_0 = exp
      when 2                              -- if 2 == _exp_0 then
        print "Two"                       --   print "Two"
      when "greet"                        -- elseif "greet" == _exp_0 then
        print "Hello"                     --   print "Hello"
      else                                -- else
        print "What?"                     --   print "What?"
                                          -- end
    -- เช่นเดียวกับ if และ for ที่สามารถเป็นนิพจน์ได้
    grade = switch score                  -- local grade
      when 80                             -- local _exp_1 = score
        "A"                               -- if 80 == _exp_1 then
      when 70                             --   grade = "A"
        "B"                               -- elseif 70 == _exp_1 then
      else                                --   grade = "B"
        "F"                               -- else
                                          --   grade = "C"
                                          -- end
  • มี list comprehension, table comprehension และ slicing คล้ายใน Python
    a = {2, 3, 4, 5, 6, 7}
    b = [x for x in *a when x % 2 == 0]   -- b == {2, 4, 6}
    c = [x for x in *a[2,6,2]]            -- c == {3, 5, 7}
    d = [a[i]*2 for i=1,#a]               -- d == {4, 6, 8, 10, 12, 14}
    
    e = {i,a[i] for i=1,#a,2}             -- e == {2, [3]=4, [5]=6}
    f = {i,v for i,v in ipairs(a) when i<4} -- f == {2, 3, 4}
  • มี  destructuring คล้ายใน JavaScript
    {axis: {x, y}} = axis: {3, 5, 8}, v: 5 -- x == 3, y == 5
  • มี with statement
    
    with Person "John", "Hello "          -- do
      print \getname!                     --   local _with_0 = Person("John", "Hello ")
      print \say "World."                 --   print(_with_0:getname())
      print .word                         --   print(_with_0:say("World."))
                                          --   print(_with_0.word)
                                          -- end
    
  • มี import คล้าย Python แต่ไม่ได้ใช้โหลดมอดูลแทน require แต่ใช้ช่วยให้แตก method มาเก็บในตัวแปร local
    
    lpeg = require "lpeg"                 -- local lpeg = require("lpeg")
    import P, S, R from lpeg              -- local P, S, R
                                          -- P, S, R = lpeg.P, lpeg.S, lpeg.R
    import sub, rep from string           -- local sub, rep
                                          -- do
                                          --   local _obj_0 = string
                                          --   sub, rep = _obj_0.sub, _obj_0.rep
                                          -- end
    import C, Ct from require "lpeg"      -- local C, Ct
                                          -- do
                                          --   local _obj_0 = require("lpeg")
                                          --   C, Ct = _obj_0.C, _obj_0.Ct
                                          -- end
    -- ใช้กับ method ใน class หรือ instance ก็ได้
    import \say from Person "John", "Hi." -- local say
                                          -- do
                                          --   local _base_0 = Person("John", "Hi.")
                                          --   local _fn_0 = _base_0.say
                                          --   say = function(...)
                                          --     return _fn_0(_base_0, ...)    -- Person.say(self, ...)
                                          --   end
                                          -- end
    
เห็นได้ว่า MoonScript ช่วยให้สามารถเขียนโค้ดได้สั้นและสะดวกขึ้นมาก ( แต่ก็อ่านได้ยากขึ้นเช่นกัน ) แต่ภาษานี้สร้างมาเพื่อใช้ใน Lapis ที่เป็น web framework บน OpenResty เป็นหลักซึ่งใช้กับ LuaJIT ที่ไม่รองรับ syntax ของ Lua รุ่นใหม่และภาษาก็ไม่ค่อยมีการพัฒนาต่อมานานแล้วจึงทำให้มี Yuescript เกิดขึ้นมาโดยเน้นสำหรับการทำงานทั่วไปซึ่งสามารถใช้ได้กับ Lua 5.1 จนถึง 5.4 ตัวล่าสุดและปรับปรุงเพิ่มความสามารถใหม่ๆ เพิ่มเติมเข้ามา

สิ่งที่เปลี่ยนไปจาก MoonScript

  • เขียนด้วยภาษา C++ แทนการใช้ Lua เพื่อเพิ่มประสิทธิภาพในการแปลภาษา
  • ปรับปรุงการแปลงโค้ดให้กระชับขึ้น
  • เปลี่ยนรูปแบบคำสั่ง import สามารถใช้โหลดมอดูลได้เหมือน require ใน Lua
    
    import "lpeg"                         -- local lpeg = require("lpeg")
    import P, S, R from lpeg              -- local P, S, R = lpeg.P, lpeg.S, lpeg.R
    import sub, rep from string           -- local sub, rep = string.sub, string.rep
    import "inspect" as insp              -- local insp = require("inspect")
    import "lpeg" as :C                   -- local C = require("lpeg").C
    import "lpeg" as {:Ct, Cmt:cmt}       -- local Ct, cmt
                                          -- do
                                          --   local _obj_0 = require("lpeg")
                                          --   Ct, cmt = _obj_0.Ct, _obj_0.Cmt
                                          -- end
    import "person" as {:Person:{:word}}  -- local word
                                          -- do
                                          --   local _obj_0 = require("person")
                                          --   word = _obj_0.Person.word
                                          -- end
    import V from require "lpeg"          -- local V
                                          -- do
                                          --   local _obj_0 = require("lpeg")
                                          --   V = _obj_0.V
                                          -- end
    
  • เพิ่ม operator ใหม่ๆ เช่น metatable, existence, และ piping
    -- Metatable และ Metamethod
    a = #: m                              -- local a = setmetatable({}, m)
    b = #: m, key1: "val1", key2: "val2"  -- local b = setmetatable({key1 = "val1", key2 = "val2"}, m)
    c = :add#                             -- local c = setmetatable({}, {__add = add})
    d = :add#, :add, sub#: fn1, sub: fn2  -- local d = setmetatable({
                                          --             add = add,
                                          --             sub = fn2
                                          --           }, {
                                          --             __add = add,
                                          --             __sub = fn1
                                          --           }
    e = ["key"]: "val"                    -- local e = setmetatable({}, {key = "val"})
    f = a.#                               -- local f = getmetatable(a) ( f == m )
    g = c.add#                            -- local g = getmetatable(a).__add
    
    -- Existence ก็คือ nil safe operator เพื่อป้องกันข้อผิดพลาดเวลาเรียกฟังก์ชันหรือสมาชิกใน table ที่ไม่มีอยู่
    func!                                 -- func()
    func?!                                -- if func ~= nil then
                                          --   func()
                                          -- end
    h = tab?.val                          -- local h
                                          -- if tab ~= nil then
                                          --   h = tab.val
                                          -- end
    
    -- Piping เหมือนใน Fennel ใช้จัดลำดับการทำงานเพื่อให้โค้ดที่มีการเรียกฟังก์ชันซ้อนกันดูง่ายเป็นระเบียบขึ้นไม่ซับซ้อน
    a |> print 2                          -- print(a, 2)
    
    2 |> print 1, _, 3                    -- print(1, 2, 3)
    
    1 |> math.sin                         -- print(math.atan(2, math.sin(1)), "asdf")
      |> math.atan 2, _
      |> print "asdf"
  • เปลี่ยนใช้ global แทน export สำหรับตัวแปร global แล้วไปใช้ export สำหรับการส่งออก module แทน
    -- ของเดิมใน MoonScript
    MY_CONSTANT = "hello"
    export GLOBAL_CONSTANT = 10           -- ใช้ export ประกาศตัวแปรแบ global
    private_function = -> print "private"
    my_function1 = -> print "the function"
    my_function2 = -> 
      private_function!
      print "another function"
                                          -- เอาตัวแปรที่ต้องการส่งออกใส่ใน table
    { :my_function1, my_func2: my_function2, :MY_CONSTANT}-- แปลงเป็น Lua
    local MY_CONSTANT = "hello"
    GLOBAL_CONSTANT = 10
    local private_function
    private_function = function()
      return print("private")
    end
    local my_function1
    my_function = function()
      return print("the function")
    end
    local my_function2
    my_second_function = function()
      private_function()
      return print("another function")
    end
    return {
      my_function1 = my_function1,
      my_func2 = my_function2,
      MY_CONSTANT = MY_CONSTANT
    }
    -- ของใหม่ใน Yuescript
    export MY_CONSTANT = "hello"          -- ใช้ export ในการส่งออกตัแปรในมอดูล
    global GLOBAL_CONSTANT = 10           -- ใช้ global สำหรับตัวแปรแบ global
    private_function = -> print "private"
    export my_function1 = -> print "the function"
    export my_function2 = -> 
      private_function!
      print "another function"-- แปลงเป็น Lua
    local _module_0 = { }
    local MY_CONSTANT = "hello"
    _module_0["MY_CONSTANT"] = MY_CONSTANT
    GLOBAL_CONSTANT = 10
    local private_function
    private_function = function()
      return print("private")
    end
    local my_function1
    my_function = function()
      return print("the function")
    end
    _module_0["my_function1"] = my_function1
    local my_second_function
    my_second_function = function()
      private_function()
      return print("another function")
    end
    _module_0["my_function2"] = my_function2
    return _module_0
  • เพิ่มระบบ macro ช่วยดัดแปลง syntax ของภาษาเหมือนที่มีใน Fennel
    macro or_not = (...)-> "not (#{ table.concat {...}, ' or ' })"
    if $or_not f1!, f2!, f3!
      print "OK"-- แปลงเป็น Lua
    if not(f1() or f2() or f3()) then
      print "OK"
    end
  • เพิ่ม repeat until และ goto syntax เหมือนที่มีใน Lua
  • ปรับปรุง syntax ในการสร้าง table
    -- ของเดิมใน MoonScript
    inventory =
      equipment: {                        -- table แบบ hash ไม่ต้องใส่วงเล็บ
        "sword"                           -- table แบบ list ต้องใส่วงเล็บ
        "shield"
      }
      items: {
        {                                 -- ถ้ามี table ซ้อนก็ต้องใส่วงเล็บ
          name: "potion"
          count: 10
        }, {                              -- บรรทัดเดียวกันใส่ "," คั่น
          name: "bread"
          count: 3
        }
      }
    
    -- ของใหม่ใน Yuescript
    inventory =
      equipment:
        * "sword"                         -- ใส่ "*" หน้าแต่ละรายการใน list ย่อย
        * "shield"
      items:
        * name: "potion"                  -- ถ้าเป็น hash ใส่เฉพาะรายการแรก
          count: 10
        * name: "bread"
          count: 3-- แปลงเป็น Lua
    inventory = {
      equipment = {
        "sword",
        "shield"
      },
      items = {
        {
          name = "potion",
          count = 10
        },
        {
          name = "bread",
          count = 3
        }
      }
    }
  • รองรับ attribute cons และ close ใน Lua 5.4
  • Fixed bug และอื่นๆ
จบไปอีกหนึ่งภาษาสำหรับภาษาที่แปลงเป็น Lua นะครับพบกันใหม่คราวหน้า

ความคิดเห็น