Barrack - A simple type checking library

Showcase your libraries, tools and other projects that help your fellow love users.
Post Reply
User avatar
DanielPower
Citizen
Posts: 50
Joined: Wed Apr 29, 2015 5:28 pm

Barrack - A simple type checking library

Post by DanielPower »

This library is meant to make it easier to track down errors when a variable of the wrong type is passed to a function.
When enabled, barrack will check the arguments of a function against a list of definitions to ensure that invalid arguments do not get passed.
If an argument fails the definition test, barrack will raise a verbose error explaining what type of variable was expected, and what was received instead.

Example:

Code: Select all

barrack = require('barrack')

-- Barrack is disabled by default. barrack.check is a blank function unless enabled.
-- This is to remove any performance hit in production, as barrack is mainly meant for debugging.
-- You can disable barrack with barrack.disable().
barrack.enable()

function createInstance(x, y, name, info)
    barrack.check(  {  x,        y,        name,     info   }, 
                    { 'number', 'number', 'string', 'table' })

    -- Continue writing the rest of your function as you normally would.
    -- Barrack will raise an error if any of the arguments do not match the required type.
end
ToDo: Allow multiple definitions for a single argument. For example:

Code: Select all

function doSomething(x, y, info)
    barrack.check({x, y, info}, {'number', 'number', {'table', 'string'})
    -- Do things
end
In this case, 'x' and 'y' must be numbers. But 'info' can be either a table or a string.

https://github.com/danielpower/barrack
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Barrack - A simple type checking library

Post by airstruck »

Interesting idea.

Here's something you might consider. You could remove some redundancy and completely take the module out of the equation when disabled by wrapping the entire function instead of invoking the type checker from within the function. Here's how it would look:

Code: Select all

local TypedFunc = require 'typedfunc'

TypedFunc.enable()

local createInstance = TypedFunc(
    { 'number', 'number', 'string', 'table' },
    function (x, y, name, info)
        -- ...
    end
)
Here's a quick example of how the module could look (not tested):

Code: Select all

local enabled = false

return setmetatable(
    {
        enable = function () enabled = true end,
        disable = function () enabled = false end
    },
    {
        __call = function (_, t, f)
            if not enabled then
                return f
            end
            return function (...)
                for i = 1, #t do
                    local paramType = t[i]
                    local argType = type(select(i, ...))
                    if paramType ~= argType then
                        error(('Parameter %i expected %s but received %s')
                            :format(i, paramType, argType))
                    end
                end
                return f(...)
            end
        end
    }
)
Notice that you can't retroactively disable it, it has to already be disabled when the function is wrapped, but I don't see an issue with that under normal circumstances. I used __call for convenience, but it could be a function named "barrack.wrap" or something if you don't like metamethods. Anyway, just a suggestion.
User avatar
Plu
Inner party member
Posts: 722
Joined: Fri Mar 15, 2013 9:36 pm

Re: Barrack - A simple type checking library

Post by Plu »

I've also created a validation-type library, which also allows extensions and user defined types. It's a little more verbose as a result, but it might also be more powerful. Maybe it has something you can use? Feel free to scavenge whatever you want from it. I'm always a fan of tools that improve error messages, they are perhaps one of the best tools at a programmer's disposal. :)

It lets you do things like below. (This is by far the most complex validation schema I'm using, it shows a lot of the available features.
Also perhaps of note is that most of these are default validators, but "color" is a special case in that it's defined as a compound-validator; the system lets you define things like "a color is a table with 4 numeric keys that are numbers between 0 and 255" and then automatically unpacks the "color" validator into its normal schema (recursively, so you can also use existing compound validators to make new ones))

Code: Select all

        schema = {
          width = { required = true, schemaType = "oneOf", possibilities = {
            { schemaType = "fromList", list = {"wrap", "fill"} },
            { schemaType = "number" }            
          }},
          height = { required = true, schemaType = "oneOf", possibilities = {
            { schemaType = "fromList", list = {"wrap", "fill"} },
            { schemaType = "number" }            
          }},
          padding = { required = false, schemaType = "table", options = {
            left = { required = true, schemaType = "number" },
            right = { required = true, schemaType = "number" },
            bottom = { required = true, schemaType = "number" },
            top = { required = true, schemaType = "number" },
          }},
          backgroundColor = { required = false, schemaType = "color" },
          gravity = { required = false, schemaType = "table", options = {
            { schemaType = "fromList", list = { "start", "center", "end" } },
            { schemaType = "fromList", list = { "start", "center", "end" } },
          }},
          border = { required = false, schemaType = "table", options = {
            color = { required = true, schemaType = "color" },
            thickness = { required = true, schemaType = "number" }
          }},
          weight = { required = false, schemaType = "number" },
          visibility = { required = false, schemaType = "fromList", list = { "visible", "cloaked", "gone" } },
          externalSignalHandlers = {
            required = false,
            schemaType = "dict",
            keyValidator = { schemaType = "string" },
            valueValidator = { schemaType = "oneOf", possibilities = {
              { schemaType = "function" },
              { schemaType = "fromList", list = { "o", "c" } },
              { schemaType = "number" },
              { schemaType = "table" },
            }},
          },
          childSignalHandlers = {
            required = false,
            schemaType = "dict",
            keyValidator = { schemaType = "string" },
            valueValidator = { schemaType = "oneOf", possibilities = {
              { schemaType = "function" },
              { schemaType = "fromList", list = { "o", "c" } },
              { schemaType = "number" },
              { schemaType = "table" },
            }},
          }          
        }
This is how the "color" validator is defined:

Code: Select all

  validator:addPartialSchema("color", { schemaType = "table", options = {
      { required = true, schemaType = "betweenNumbers", min = 0, max = 255 },
      { required = true, schemaType = "betweenNumbers", min = 0, max = 255 },
      { required = true, schemaType = "betweenNumbers", min = 0, max = 255 },
      { required = true, schemaType = "betweenNumbers", min = 0, max = 255 },
    }
  })
You can find the validator here, if you want to have a look.
https://github.com/ErikRoelofs/renderer ... idator.lua
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Barrack - A simple type checking library

Post by airstruck »

That's pretty cool, Plu. Reminds me vaguely of json-schema.

In general I wouldn't use something like this (any of the approaches mentioned here including my own), it seems like too much work for something that will essentially have no effect when everything's working properly, but a proper schema definition could be really useful for some special cases (like a restful web server, maybe).
User avatar
Plu
Inner party member
Posts: 722
Joined: Fri Mar 15, 2013 9:36 pm

Re: Barrack - A simple type checking library

Post by Plu »

I've noticed that after implementing this schema, things started to work properly considerably more often and a lot earlier in development than before I had it :P
User avatar
DanielPower
Citizen
Posts: 50
Joined: Wed Apr 29, 2015 5:28 pm

Re: Barrack - A simple type checking library

Post by DanielPower »

Thanks for the feedback!

airstruck:
Could you give me an example of how wrapping the functions would be more desirable compared to running the type checker at the beginning of the function? I'm not sure of the performance difference, but I prefer the syntax of barrack to your proposed solution.

I didn't know about :format. Does that work with other print functions? I'm definitely going to use that!

Plu:
That's interesting. I've never used json, and have no experience with type checking in lua or other languages. So I really don't know what features barrack is missing other than allowing an argument to have multiple possible definitions and allowing definitions to have arguments. I just had the idea while folding boxes at work, and thought it would be something useful to write for myself. I'll give your library a look! Could you give an example of the syntax? How you would implement it on a function?

The most important thing for me is that it has to look clean and be very quick to insert into a function. I want to have type checking on most of my major functions so that errors are easier to find, but I also want to make sure I can add type checking very quickly, so I can spend my time focusing on the rest of my code.

I had a better syntax in mind initially, where barrack would use debug.getlocal to get the arguments from the function that called it without having to manually pass them. Unfortunately luajit doesn't support debug.getlocal, so performance was terrible. Every time barrack was called, it would revert to interpreted mode. The syntax was like this:

Code: Select all

function createInstance(x, y, name, info)
    barrack.check('number', 'number', 'string', {'string', 'table'})
    -- do stuff
end
It would have been fine for vanilla lua, but luajit doesn't like it very much :(
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Barrack - A simple type checking library

Post by airstruck »

DanielPower wrote:Could you give me an example of how wrapping the functions would be more desirable compared to running the type checker at the beginning of the function? I'm not sure of the performance difference, but I prefer the syntax of barrack to your proposed solution.
The idea with wrapping the function was that when the module is disabled, it just returns the original function unaltered, so there's not even a no-op function call at the beginning of it. The only added overhead would be when the functions are declared, not when they're called (when the module is disabled). With your current setup, even though the function just no-ops, two tables still have to be constructed to be passed into it. Of course, it's very unlikely you'd actually notice the tiny difference in performance.

That, and you wouldn't have to write the parameter list twice (like what you were going for in the last example you gave). It's probably not worth it if it's more important to you to keep everything localized to one or two lines so you can drop it in or take it out very quickly, though.
User avatar
Plu
Inner party member
Posts: 722
Joined: Fri Mar 15, 2013 9:36 pm

Re: Barrack - A simple type checking library

Post by Plu »

The library has a validate function, which you pass a table and a pre-defined schema. You would have to change it to work better for validating functions, it's currently designed to validate whether tables are in the correct format. It works like this:

Code: Select all

-- define a schema to check against later
validator:addSchemaType( 
"someValidator"
{
  x = { required = true, schemaType = "number",
  y = { required = true, schemaType = "betweenNumbers", min = 100, max = 200 },
  name = { required = false, schemaType = "string" }
} )

-- have a table
t = { x = 600, y = 150, name = "Plu" }

-- validate the table's structure
validator:validate("myTable", "someValidator", t )
-- this is valid, no output

-- try a broken table
t = { x = 500, y = 300, name = "Plu" }
validator:validate("myTable", "someValidator", t )
-- this will raise an error saying "Value 300 for field y of myTable should be between 100 and 200"
Like I said; feel free to take any parts that you might need. It's made to solve a somewhat different problem, but it might have parts you can use :)
Post Reply

Who is online

Users browsing this forum: Google [Bot], slime and 218 guests