Giter Club home page Giter Club logo

blog's People

Contributors

originalfoo avatar

Watchers

 avatar  avatar

blog's Issues

type(...)

Neat little trick... Find out what type the first item in a varargs list is...

function foo(...)
  print( type(...) )
end

foo( 1, 'a', true ) -- 'number'

This is really useful in methods that take different parameter combinations, for example:

function newArray( ... )
   if select('#', ...) == 1 and type(...) == 'table' then
     return (select(1, ...))
  else
     return {...}
  end
end

a = newArray{ 1, 2, 3 }

b = newArray( 1, 2, 3 )

The benefit of that approach is that we're not creating any additional tables unless absolutely necessary (in other words, we're avoiding unnecessary heap allocations).

If only ... was a first class citizen...

Why can't we do this in Lua?

function foo( ... )
   print( #... ) -- size of `...`

   local a = ...[2] -- set `a` to second value in `...`

   ...[#... + 1] = 'bar' -- add a new value to `...`
end

Instead of this...

function foo( ... )
  print( select( '#', ... ) )

  local a = select( 2, ... )

  local temp = {...}
  temp[#temp + 1] = 'bar'
  return table.unpack( temp ) -- can't actually add to `...`
end

I guess, if you think about it, the ... varargs is actually Lua's native implementation of an array? Sadly we can't treat it as such because it's nerfed.

require(...)

A neat way to load a library based on command line parameters...

local class = require(...)

First seen in 30log performance tests, example syntax for loading the script being:

lua performance\test.lua 30log

Metatable events are broken

Update: As correctly pointed out on reddit, __tostring() isn't like other metatable events. That being said the other metatable events suffer the exact same issues so it doesn't really matter.

Let's do some OOP. I'll use a 'class' based naming convention in these examples as it seems to be what most people immediately think about when using OOP and inheritance, but in reality this is just a hierarchical metatable chain.

superclass = {}
superclass.__index = superclass

class = {}
class.__index = class

instance = {}

Well, that's not going to do much, let's wire up our metatables...

setmetatable( class, superclass )

setmetatable( instance, class )

So now instance inherits from class, and class inherits from superclass. Now let's add some stuff to the classes...

function superclass:hello()
  return 'my name is: ' .. self.name
end

instance.name = 'Bob'

print( instance:hello() ) -- 'my name is: Bob'

This is awesome, everything is working exactly how you'd expect. The method hello() isn't found on instance so an __index event is triggered on its metatable, class. As the method isn't found on class another __index event is triggered, this time on superclass and finally the method hello() is found.

Because we've used colon notation (which most Lua newbies struggle with as it's not explained very well in the Lua docs) we pass a self reference in to the method. Of vital importance is that self points to instance, and that's why the superclass:hello() method is able to find the instance.name property. Well hello there, Bob.

It would be nice if we could just tostring( instance ) and get the same result - to do that we'll need to use the __tostring() metatable event...

function superclass:__tostring()
  return self:hello()
end

print( tostring( instance ) ) -- 'table: 0x215b360'

Uhm, what just happened?

setmetatable( instance, nil )

print( tostring( instance ) ) -- 'table: 0x215b360'

This is the first major problem you run in to with metatable events.

Unlike method calls, event handlers are only sought on the metatable of the table that triggered the event. When we do tostring( instance ) we trigger the __tostring event on it's metatable, so Lua looks for the event handler in just once place: class:__tostring(). If it's not found there, then it assumes there's no event handler and just outputs the table type and hash (table: 0x215b360) instead.

Let's just prove that by adding a class:__tostring() handler:

setmetatable( instance, class )

function class:__tostring()
  return 'it works on class'
end

print( tostring( instance ) ) -- 'it works on class'

What we really want is for events to work the same way as methods. Despite it's unusual name, superclass:__tostring() is in fact just a method, so we can call it ourselves just like any other method:

class.__tostring = nil -- erase the `class:__tostring()` event handler

print( instance:__tostring() ) -- 'my name is: Bob'

See? Calling the event handler as a method works just fine. So why doesn't Lua do that?

If Lua treated events as if they were methods, most of the problems would instantly be resolved.

I might write another post at some point to explain why I put "most" in italics (hint: __index() and __newindex() are even more broken). But, for now, I just want finish this current post by busting some Lua mythology -- specifically every time people talk about OOP in Lua you eventually get words along these lines being used:

Lua doesn't implement OOP out of the box, but it provides everything you need to implement your own. That's why there's so many OOP libraries, it's something of a cottage industry.

As we say here in England: "What a load of bollocks".

The reason there are so many OOP libraries in Lua is because so many people have tried to use metatables to implement inheritance only to be thwarted by the broken event system, and then they try and write yet another OOP library to work around the problems caused by the event system. Others mistakenly try to turn Lua in to a strictly-typed class-based programming language, the exact opposite of what Lua should be.

In most OOP libraries this takes the form of scanning the metatable hierarchy and then cloning superclass event handlers on to the class. This trivial task is what actually messes everything up. It's why the smallest OOP libraries are still 50 lines long, and most are well over 100 lines long. Others try and use functional __index() event handlers and incur even more pain and verbosity.

Let me put that in perspective. If the events worked like methods, OOP in Lua would look like this:

setmetatable( instance, class )

One. Single. Line. Of. Code.

The fact that there are so many OOP libraries available for Lua is not a good thing, it's a damning indictment of an architectural flaw that few seem willing to talk about. OOP libraries are evidence of programmers fighting symptoms of problems that the core devs should be fixing within Lua itself.

if ... then

Another neat trick with varargs - you can use them in an if statement at the top-level of a lua script to determine whether it's been included as a package or loaded with parameters etc.

if ... then
   -- probably loaded as a package, or loaded with command line params
else
  -- most likely loaded directly as local script
end

First saw this in the Lupy example scripts which use it to run tests when the file is loaded directly rather than as a module/package.

Lua 5.3 shims for Lua 5.2 and 5.1

While they don't implement everything (eg. bitwise operators) they do a very good job of brining earlier versions of Lua up to 5.3 capabilities... Kelper Project's Lua Compatibility 5.3

As an example, here's a table.move() shim:

function table.move(a1, f, e, t, a2)
  assert( f > 0 , "table.move(): initial position must be positive" )
  a2 = a2 or a1
  if e >= f then
    local m, n, d = 0, e-f, 1
    if t > f then m, n, d = n, m, -1 end
    for i = m, n, d do
      a2[t+i] = a1[f+i]
    end
  end
  return a2
end

Module descriptor tables

I've adopted Kikito's descriptor headers and never looked back.

I used a slightly different format, which allows for some easier automation...

local module = {
  _NAME    = 'blua.utils',
  _VERSION = '0.1.0', -- http://semver.org/
  _SUMMARY = 'Snippets of code used by various Blua libraries',
  _URL     = '',
  _LICENSE = 'MIT/X11: https://github.com/aubergine10/blua/blob/master/LICENSE',
  _EXPORTS = { '__call', 'callable', '_callableFnOnly', '_defStart', '_defEnd',
               '_pack', '_unpack', '_move', '_maxint', '_clamper', 'Nil', 'Default' }
}

The _EXPORTS in particular can be used in interesting ways. For example...

function module:__add( left, right )
  for _, export in ipairs( module._EXPORTS ) do
    if export:sub(1,1) ~= '_' then left[export] = module[export] end
  end
  return left
end

When including the script in a project...

_G = _G + require 'blua.utils'

Note also that the module in the example above exports a __call() method, allowing the package itself to be used as a function. I always put the method that will be called directly after __call() in the _EXPORTS list, in this case the callable() function.

local callable = require 'blua.utils'

function foo() print('hello') end

if callable(foo) then foo() end -- 'hello'

If only we could do this with functions...

Some things I wish we could do with functions in Lua...

function foo( bar, baz )
 -- stuff
end

print( #foo ) -- 2 (foo has 2 named arguments)

function bar( ... )
 -- stuff
end

print( #bar ) -- -1 (bar has varargs)

function baz()
  -- stuff
end

print( #baz ) -- 0

Being able to determine number of args from outside the function, without resorting to debug library, would open up some interesting possibilities for currying functions.

And it would be so nice to set default argument values in the function declaration:

function foo( bar or 'fish', baz or 0 )
  -- stuff
end

One last thing that would be useful on occasion - the ability to wrap initial args in to a varargs and collect a few named args at the end of the parameter list:

function foo( ..., bar, baz )
  print( select('#',...), bar, baz )
end

foo( 1, 2, 3, 4, 5, 6 ) -- 4  5  6

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.