6
\$\begingroup\$

I've made a little mechanism for creating 2-D vectors in Lua. Vectors have two components i and j, and support addition, dot product, magnitude, and conversion to strings.

Vector = {
  new = function (i, j)
    return setmetatable({i = i, j = j}, {
      __index = Vector,
      __add = function (self, v)
        return Vector.new(self.i + v.i, self.j + v.j)
      end,
      __mul = function (self, v)
        return self.i*v.i + self.j*v.j
      end,
      __tostring = function (self)
        return string.format('<%g,%g>', self.i, self.j)
      end
    })
  end,
  magnitude = function (self)
    return math.sqrt(self.i*self.i + self.j*self.j)
  end
}

u = Vector.new(3, 4)
v = Vector.new(-5, 10)
print(u.i)
print(u.j)
print(u:magnitude())
print(u + v)
print(u * v)

While I get expected results, I don't like the fact that each time I create a vector table, a brand new metatable is created. It would be nice to only have one metatable for all vectors. Also if there any non-idiomatic aspects to the code, those would be nice to know about as well.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ I'm not a Lua expert but wouldn't defining a separate metatable and then using setmetatable accomplish what you want? \$\endgroup\$
    – wvxvw
    Mar 11, 2015 at 15:59
  • \$\begingroup\$ Yes but it would nice to actually hide the metatable so it is not globally available. \$\endgroup\$
    – Ray Toal
    Mar 11, 2015 at 20:17

1 Answer 1

4
\$\begingroup\$

I would recommend creating a separate module/package for the Vector types.

  1. Create a file named vector.lua. It'll return the custom Vector data structure.
  2. Use a local do...end block to wrap your meta-table. This will make the meta table private.
  3. magnitude is basically square-root of the result from self * self. Use this. ;-)
  4. I prefer to use __call meta-method instead of writing a new .new call. This is of course a personal preference.

The resulting vector.lua would be like:

local Vector = {}
do
    local meta = {
        _metatable = "Private metatable",
        _DESCRIPTION = "Vectors in 2D"
    }

    meta.__index = meta

    function meta:__add( v )
        return Vector(self.i + v.i, self.j + v.j)
    end

    function meta:__mul( v )
        return self.i * v.i + self.j * v.j
    end

    function meta:__tostring()
        return ("<%g, %g>"):format(self.i, self.j)
    end

    function meta:magnitude()
        return math.sqrt( self * self )
    end

    setmetatable( Vector, {
        __call = function( V, i ,j ) return setmetatable( {i = i, j = j}, meta ) end
    } )
end

Vector.__index = Vector

return Vector

Now, to use it as a package, just put a require call:

local Vector = require 'vector'
u = Vector(3, 4)
v = Vector(-5, 10)
\$\endgroup\$
1
  • \$\begingroup\$ Excellent advice. __call was new to me. It looks more idiomatic than new. \$\endgroup\$
    – Ray Toal
    Oct 11, 2015 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.