Suggestion: "usertype()" function

classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

Suggestion: "usertype()" function

Tim Hill
The ability to attach a metatable to a userdata value is, I feel, a great feature of Lua as it allows C code to integrate new types into the language cleanly and efficiently. With a correctly setup metatable, a userdata item can behave as a first-class citizen in Lua code.

However, it seems to me that there is one hole in this clean model: the inability of a metatable to project a "type" into the Lua code space. Specifically, regardless of metatable, the value of type(someuserdata) is always "userdata". This means if I have several different userdata "types" in Lua code I cannot use them polymorphically without tracking their type manually (using, say, an ephemeron table). While tostring() can be used as a workaround (as it is for example with file handles) this seems too much like a hack to me; tostring() is supposed to return a value, not a type.

To my mind, the most correct way for this to work would be for the type() function to return the user-defined type by returning the value of __metatable (like getmetatable()). However, this would of course break existing code, so i feel we need a new function, usertype() that behave like type() except when there is a __metatable field in the metatable, in which case it returns this instead. For example:

function usertype(v)
local mt = getmetatable(v)
if mt ~= "table" return mt else return type(v)
end

An alternative would be to create a new metatable entry, such as __type, that if present is used as the "type" of the userdata/table in much the same way __tostring() is used by the tostring() function.

This allows a userdata metatable to return a string from usertype() in a way that extends the type() namespace. Of course this has the potential disadvantage that adding new types to Lua is harder, as the private namespace of the type() function is now opened up to add-in types, but this can be handled by (for example) decorating the user-defined types in usertype() in some manner. For example:

function usertype(v)
local ty = getmetatable(v).__type
return ty and "_" .. ty or type(v)
end

Of course, it's not necessary to add this to the language; after all I just wrote the code in the above example. However, I feel that this is a generic feature that needs to always be available for people who are writing addon libraries or packages for Lua.

Thoughts anyone?
--Tim

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Cosmin Apreutesei
On Wed, May 30, 2012 at 10:37 PM, Tim Hill <[hidden email]> wrote:

> The ability to attach a metatable to a userdata value is, I feel, a great
> feature of Lua as it allows C code to integrate new types into the language
> cleanly and efficiently. With a correctly setup metatable, a userdata item
> can behave as a first-class citizen in Lua code.
>
> However, it seems to me that there is one hole in this clean model: the
> inability of a metatable to project a "type" into the Lua code space.
> Specifically, regardless of metatable, the value of type(someuserdata) is
> always "userdata". This means if I have several different userdata "types"
> in Lua code I cannot use them polymorphically without tracking their type
> manually (using, say, an ephemeron table). While tostring() can be used as a
> workaround (as it is for example with file handles) this seems too much like
> a hack to me; tostring() is supposed to return a value, not a type.
>
> To my mind, the most correct way for this to work would be for the type()
> function to return the user-defined type by returning the value of
> __metatable (like getmetatable()). However, this would of course break
> existing code, so i feel we need a new function, usertype() that behave like
> type() except when there is a __metatable field in the metatable, in which
> case it returns this instead. For example:
>
> function usertype(v)
> local mt = getmetatable(v)
> if mt ~= "table" return mt else return type(v)
> end
>
> An alternative would be to create a new metatable entry, such as __type,
> that if present is used as the "type" of the userdata/table in much the same
> way __tostring() is used by the tostring() function.
>
> This allows a userdata metatable to return a string from usertype() in a way
> that extends the type() namespace. Of course this has the potential
> disadvantage that adding new types to Lua is harder, as the private
> namespace of the type() function is now opened up to add-in types, but this
> can be handled by (for example) decorating the user-defined types in
> usertype() in some manner. For example:
>
> function usertype(v)
> local ty = getmetatable(v).__type
> return ty and "_" .. ty or type(v)
> end
>
> Of course, it's not necessary to add this to the language; after all I just
> wrote the code in the above example. However, I feel that this is a generic
> feature that needs to always be available for people who are writing addon
> libraries or packages for Lua.
>
> Thoughts anyone?
> --Tim
>

The pattern I keep seeing is to provide a type() in the namespace of
your library similar to io.type() so users can dispatch on that.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Dirk Laurie-2
In reply to this post by Tim Hill
2012/5/30 Tim Hill <[hidden email]>:

>
> To my mind, the most correct way for this to work would be for the type()
> function to return the user-defined type by returning the value of
> __metatable (like getmetatable()). However, this would of course break
> existing code, so i feel we need a new function, usertype() that behave like
> type() except when there is a __metatable field in the metatable, in which
> case it returns this instead. For example:
>
> function usertype(v)
> local mt = getmetatable(v)
> if mt ~= "table" return mt else return type(v)
> end
>

> function usertype(v)
> local ty = getmetatable(v).__type
> return ty and "_" .. ty or type(v)
> end
>
> Of course, it's not necessary to add this to the language; after all I just
> wrote the code in the above example. However, I feel that this is a generic
> feature that needs to always be available for people who are writing addon
> libraries or packages for Lua.
>
> Thoughts anyone?

Actually your sentence starting "Of course" describes my thoughts
pretty much exactly.  I'm much too set in my habits to start writing
"usertype" now.  But you can have you cake and eat it, thus:

origtype = type
function type(obj)
  local m=getmetatable(obj)
  local t=origtype(obj)
  if not m or not m.type then return t end
  return m.type(obj)
end

print(type"abcdefghijklmnopqrstuvxyz") --> string
print(type(io.stderr)) --> userdata

getmetatable"".type = function(s) return 'string'..#s end
getmetatable(io.stdin).type = io.type

print(type"abcdefghijklmnopqrstuvxyz") --> string25
print(type(io.stderr)) --> file

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Tomás Guisasola-2
In reply to this post by Tim Hill
  Hi Tim

On Wed, 30 May 2012, Tim Hill wrote:
> However, it seems to me that there is one hole in this clean
> model: the inability of a metatable to project a "type" into the
> Lua code space. Specifically, regardless of metatable, the value
> of type(someuserdata) is always "userdata".
  This is consistent with type(sometable).  The inability also
affects tables with metatables.

> function usertype(v)
> local ty = getmetatable(v).__type
> return ty and "_" .. ty or type(v)
> end
>
> Of course, it's not necessary to add this to the language; after all I just wrote the code in the above example. However, I feel that this is a generic feature that needs to always be available for people who are writing addon libraries or packages for Lua.
>
> Thoughts anyone?
  I would suggest to return a second value:

-- Untested!
function usertype(v)
  local tv = type(v)
  if tv == "table" or tv == "userdata" then
  local mt = getmetatable(v)
  return tv, mt and mt.__type or mt
  else
  return tv
  end
end

  It can be used in place of type(), but it has extra cost...
  In fact, I discarded type-checking in my Lua code (I experimented
a library to check types and a tool to remove it from production code,
but gave up: the time spent on it were bigger than the bugs it warned),
which is one use of this kind of function.  In other situations, when I
need to check the type to avoid problems, a generic test like that is an
overkill (for me, at least) then I use a direct solution (like checking
an especific field of the value or something like that).

  Regards,
  Tomás
Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Geoff Leyland
In reply to this post by Tim Hill
On 31/05/2012, at 7:37 AM, Tim Hill wrote:

> Of course, it's not necessary to add this to the language; after all I just wrote the code in the above example.

Exactly.

> However, I feel that this is a generic feature that needs to always be available for people who are writing addon libraries or packages for Lua.

Some agreement on policy might help for interoperability between modules, but I'm wouldn't count on it being easy to get any agreement.

> Thoughts anyone?

Sometimes I'm not smart enough to avoid dealing with inheritance chains.  In these cases a type name doesn't tell you all you might want to know about a type.  So if type B is a type A then and a is an instance of A and b is an instance of B then:

typename(a) == "A"
typename(b) == "B"

typeinfo(a).A == true
typeinfo(a).B == nil

typeinfo(b).A == true
typeinfo(b).B == true




Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Miles Bader-2
In reply to this post by Tim Hill
Tim Hill <[hidden email]> writes:
> However, it seems to me that there is one hole in this clean model:
> the inability of a metatable to project a "type" into the Lua code
> space.
...
> Thoughts anyone?


I think the entire concept of a "type()" function is sort of fragile,
and really not what you want as a general interface.

Something like type predicates would be much better.  So the trick is,
assuming a standard set of type predicates, how do you extend them to
work on user types?

-Miles

--
Carefully crafted initial estimates reward you not only with
reduced computational effort, but also with understanding and
increased self-esteem.         -- Numerical methods in C,
  Chapter 9. "Root Finding and Nonlinear Sets of Equations"

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Miles Bader-2
Miles Bader <[hidden email]> writes:

>> However, it seems to me that there is one hole in this clean model:
>> the inability of a metatable to project a "type" into the Lua code
>> space.
> ...
>> Thoughts anyone?
>
> I think the entire concept of a "type()" function is sort of fragile,
> and really not what you want as a general interface.
>
> Something like type predicates would be much better.  So the trick is,
> assuming a standard set of type predicates, how do you extend them to
> work on user types?

... and of course they need not be real predicates, e.g. something like
"istype(OBJ, TYPENAME)" can offer the same functionality, maybe.

-miles

--
Conservative, n. A statesman enamored of existing evils, as opposed to a
Liberal, who wants to replace them with new ones.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Coda Highland
In reply to this post by Miles Bader-2
On Wed, May 30, 2012 at 11:42 PM, Geoff Leyland
<[hidden email]> wrote:

> On 31/05/2012, at 4:00 PM, Miles Bader wrote:
>
>> Something like type predicates would be much better.  So the trick is,
>> assuming a standard set of type predicates, how do you extend them to
>> work on user types?
>
> Does the typeinfo function I suggested earlier count as a predicate?  You could call it "istype" if that helped: istype(obj).typename.  One feature of returning at table is that the return value of typeinfo can be cached:
>
> local t = typeinfo(obj)
>
> if t.string then
>  ...
> elseif t.number then
>  ...
> elseif t.mytype then
>  ...
> end

I'd like it better if it were t.is(string), because then you're not
using strings for type matching but instead confirming the actual type
object.

/s/ Adam

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Rena
In reply to this post by Miles Bader-2
On Wed, May 30, 2012 at 10:03 PM, Miles Bader <[hidden email]> wrote:

> Miles Bader <[hidden email]> writes:
>>> However, it seems to me that there is one hole in this clean model:
>>> the inability of a metatable to project a "type" into the Lua code
>>> space.
>> ...
>>> Thoughts anyone?
>>
>> I think the entire concept of a "type()" function is sort of fragile,
>> and really not what you want as a general interface.
>>
>> Something like type predicates would be much better.  So the trick is,
>> assuming a standard set of type predicates, how do you extend them to
>> work on user types?
>
> ... and of course they need not be real predicates, e.g. something like
> "istype(OBJ, TYPENAME)" can offer the same functionality, maybe.
>
> -miles
>
> --
> Conservative, n. A statesman enamored of existing evils, as opposed to a
> Liberal, who wants to replace them with new ones.
>

I give my objects _type fields, with names like "mylib.foo.bar",
identifying a type bar which is a more specific type of foo. Wrapping
type() to check for these fields is pretty simple.

Though, I've always felt like the other direction is the way to go:
make "isindexable" and "iscallable" functions (or if you prefer,
"istable" and "isfunction", though that can be confusing, since e.g.
istable("a") == true), so that you don't have to concern yourself with
types at all. If you expect an indexable type, go ahead and index it.
These helper functions then just provide convenience if you want to
handle indexable objects differently from callable ones or vice-versa,
or throw an error if given the wrong thing.

I was actually pondering earlier today the idea of making *all*
objects both indexable and callable. Calling a number, boolean or nil
value would just return their value (i.e. 4() == 4). Calling a string
would do the same as load()ing it. (Whether the resulting function
should be returned or executed on the spot is something I hadn't
decided.) Calling a table or userdata which doesn't have a __call
would throw an error, or maybe do nothing? Then, all of these
behaviours would be defined as functions in their respective
metatables, so that you could change them if you liked; e.g. if you
want calling nil to be an error: getmetatable(nil).__call = error.
Numbers, booleans and nil would share metatables like strings do.
As for indexing, perhaps any assignment to fields of such objects
automatically creates a table which emulates them, e.g.:
n = 4; n.i = 3 --identical to:
n = setmetatable({4, i=3}, {...various magic to make n work like a number...})

I was busy with work, so I didn't have time to fully think about the
fine details of this idea...

--
Sent from my Game Boy.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Geoff Leyland
In reply to this post by Coda Highland
On 31/05/2012, at 4:52 PM, Coda Highland wrote:

> I'd like it better if it were t.is(string), because then you're not
> using strings for type matching but instead confirming the actual type
> object.

What's your objection to strings?  I've tried variations on the type.isa(object) and isa(type, object) and ended up with what I suggested earlier.

That said, there's no reason not to store metatables in the typeinfo table:

myclass = { __typeinfo = { myclass = true} }
myclass.__typeinfo[myclass] = true
myobject = setmetatable({}, myclass)

function typeinfo(obj)
  return getmetatable(obj).__typeinfo
end

if typeinfo(myobject)[myclass] then
  ...
end

The implementation at [1] stores both metatables and strings, but I think I've only ever used the strings.


[1] https://github.com/geoffleyland/rima/blob/master/lua/rima/lib/object.lua


Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Miles Bader-2
In reply to this post by Miles Bader-2
Geoff Leyland <[hidden email]> writes:
>> Something like type predicates would be much better.  So the trick is,
>> assuming a standard set of type predicates, how do you extend them to
>> work on user types?
>
> Does the typeinfo function I suggested earlier count as a predicate?

As long as it's flexible enough to offer the same functionality... :)

Hmm, and I guess it can:

  function istype (obj, type)
    return typeinfo (obj)[type]
  end

"typeinfo" does look like a nice idea, and seems to offer a clear way
to make the system extensible to user types, e.g. with a "__typeinfo"
meta-method.

> You could call it "istype" if that helped: istype(obj).typename.

I think "istype" intuitively takes two arguments, so better keep the
"typeinfo" name if that interface is thought preferable.

Since "istype" can be easily implemented in terms of "typeinfo", one
could offer the former function as an alternative interface to
typeinfo (which might be more readable in much code).

> One feature of returning at table is that the return value of typeinfo
> can be cached:
>
> local t = typeinfo(obj)
>
> if t.string then
>   ...
> elseif t.number then
>   ...
> elseif t.mytype then
>   ...
> end

Yeah, that's nice.

-miles

--
Virtues, n. pl. Certain abstentions.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Jim Pryor-2-3
In reply to this post by Tim Hill
I know there are many existing implementations of custom type predicates
floating around. But I thought I'd point out my own, which readers of
this thread may find of interest. From
https://github.com/dubiousjim/luafiveq/blob/master/BENEFITS-LUA:

>
> What do I get when I ... require "fiveqplus"?
> ---------------------------------------------
>
>     an additional global function `check` is provided; this is somewhat
>     akin to assert:
>             check(test, arg#, fmt, args ...)
>     One difference is that check expects a second argument `arg#`
>     that identifies the position of the checked object in an argument list.
>     If check's `test` argument is true-like, then that argument (and it
>     alone) is returned, else the third argument is used as a format string
>     together with all subsequent arguments. For example:
>
>             > function example(arg) local x = check(arg, 1, "foo %s",
>             > "bar") end
>             > example(false)
>             stdin:1: bad argument #1 to 'example' (foo bar)
>             stack traceback:
>                 [C]: in function 'check'
>                 stdin:1: in function 'example'
>                 stdin:1: in main chunk
>                 [C]: ?
>
>
>     an additional library `err` is provided with more specific
>     error/checking functions to complement error, assert, and check...
>
>
>     err.istype(obj, types ...)
>         returns the first of the type arguments that obj satisfies, or false
>         if it satisfies none of them. The type arguments can be any of:
>             * the standard return values from type(obj): "nil", "table",
>               "boolean", and so on. Both full and light userdata satisfy
>               "userdata". For "string" and "number", obj merely has to be
>               convertible to the type
>             * "positive" if obj is convertible to a positive integer
>             * "natural" if obj is convertible to a non-negative integer
>             * "negative" if obj is convertible to a negative integer
>             * "string!" if obj is literally a string; whereas "string" merely
>               requires obj to be convertible to a string
>             * "number!" if obj is literally a number, not just convertible to
>               one
>             * "integer!" if obj is literally an integer-valued number
>               (plain "integer" is also available, but anything that satisfies
>               "number" also counts as convertible to an integer, by truncation)
>             * "positive!" if obj is literally a positive integer
>             * "natural!" if obj is literally a non-negative integer
>             * "negative!" if obj is literally a negative integer
>             * "callable" if obj is a function or has a function for its __call
>               metamethod
>             * "indexable" if obj is a table or has a table or function for
>               its __index metamethod
>             * "iterable" if obj is a table or has an __singles or __pairs
>               metamethod
>             * "iterator" if obj is a function: the idea is that
>               istype({}, "iterator") returns false but
>               istype(pairs({}), "iterator") returns true. The same effect
>               can be achieved with "function", but this documents the
>               programmer's intentions more clearly.
>             * any typeobject: obj will count as satisfying this type if either
>               obj's metatable or its __type metamethod equals the typeobject
>
>     err.checktype(obj, arg#, [expected index], types ...)
>         a hybrid of check and istype: if object satisfies any of the type
>         arguments, the first one it satisfies is returned. Else an error
>         is raised complaining that the function expected an object of type
>         so-and-so, but received one of type such-and-such. You can
>         explicitly specify the index of the type that was expected (the first
>         one having index 1); else 1 is assumed. Examples:
>
>         > function example(a, b, c) checktype(c, 3, "number", "string") end
>         > example(1, 2, false)
>         stdin:1: bad argument #3 to 'example' (number expected, got boolean)
>         stack traceback:
>             [C]: in function 'checktype'
>             stdin:1: in function 'example'
>             stdin:1: in main chunk
>             [C]: ?
>
>         > function example(a, b, c) checktype(c, 3, 2, "number", "string") end
>         > example(1, 2, false)
>         stdin:1: bad argument #3 to 'example' (string expected, got boolean)
>         stack traceback:
>             [C]: in function 'checktype'
>             stdin:1: in function 'example'
>             stdin:1: in main chunk
>             [C]: ?
>
>     err.checkopt(obj, arg#, type, [default])
>         asserts that obj satisfies the single type argument, or else is nil;
>         if the assertion fails, raises an error of the same format as checktype.
>         if the assertion succeeds and obj is nil, returns default; else
>         returns obj
>
>     err.checkany(obj, arg#)
>         asserts that obj is of any non-nil type; if the assertion fails,
>         raises an error of the same format as checktype. if the assertion
>         succeeds, returns obj
>
>     err.arenil(args, ...)
>             returns true if all its arguments are nil
>
>     err.checkrange(num, arg#, [min], max)
>     err.checkrange(num, arg#, [min], -max)
>         asserts that num is a number in the inclusive range min...max;
>         min defaults to 1. If max is negative, its absolute value is used,
>         and additionally, num is also permitted to be -1, -2, and so on.
>         A value of -1 for num is converted to max; -2 is converted to max-1;
>         and so on. Raises an error of the same format as checktype if the
>         range constraints are violated; else returns the (possibly converted)
>         num.
>

There's more to the err library (and much more to fiveqplus), but these
are the elements relevant to the present discussion.

--
Dubiousjim
[hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Benoit Germain-2
In reply to this post by Tim Hill


2012/5/30 Tim Hill <[hidden email]>

However, it seems to me that there is one hole in this clean model: the inability of a metatable to project a "type" into the Lua code space. Specifically, regardless of metatable, the value of type(someuserdata) is always "userdata". This means if I have several different userdata "types" in Lua code I cannot use them polymorphically without tracking their type manually (using, say, an ephemeron table). While tostring() can be used as a workaround (as it is for example with file handles) this seems too much like a hack to me; tostring() is supposed to return a value, not a type.


I have found that I could do it by storing the type of my objects in the metatable's __metatable field:

C:\>lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> mt={__metatable="MyType"}
> o=setmetatable({},mt)
> =getmetatable(o)
MyType
>

This has the benefit of not changing the language, and also protects the metatable against unwanted access.

--
Benoit.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Coda Highland
In reply to this post by Geoff Leyland
On Thu, May 31, 2012 at 12:03 AM, Geoff Leyland
<[hidden email]> wrote:
> On 31/05/2012, at 4:52 PM, Coda Highland wrote:
>
>> I'd like it better if it were t.is(string), because then you're not
>> using strings for type matching but instead confirming the actual type
>> object.
>
> What's your objection to strings?  I've tried variations on the type.isa(object) and isa(type, object) and ended up with what I suggested earlier.

Namespace collisions. Two types could easily have the same name,
especially if they're coming from different sources. This isn't
possibly a problem with type.isa(object). Maybe it's not likely to
happen in reality, but why use strings when you don't have to?

/s/ Adam

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Geoff Leyland
On 1/06/2012, at 2:33 AM, Coda Highland wrote:

> On Thu, May 31, 2012 at 12:03 AM, Geoff Leyland <[hidden email]> wrote:
>> On 31/05/2012, at 4:52 PM, Coda Highland wrote:
>>
>>> I'd like it better if it were t.is(string), because then you're not
>>> using strings for type matching but instead confirming the actual type
>>> object.
>>
>> What's your objection to strings?
>
> Namespace collisions

Good point.  I've only used this is in code I control, so that problem didn't come up.

> but why use strings when you don't have to?

Avoiding requiring modules.  If type A is defined in module A, and you want to check for it in module B, then you have to require module A in module B before you can typeinfo(obj)[A], but with strings you can do a typeinfo(obj).A without requiring anything.

As I said before, there's no problem putting metatables and/or strings in the typeinfo table for an object.

Cheers,
Geoff
Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Leo Razoumov
In reply to this post by Tim Hill
On Wed, May 30, 2012 at 3:37 PM, Tim Hill <[hidden email]> wrote:

> To my mind, the most correct way for this to work would be for the type()
> function to return the user-defined type by returning the value of
> __metatable (like getmetatable()). However, this would of course break
> existing code, so i feel we need a new function, usertype() that behave like
> type() except when there is a __metatable field in the metatable, in which
> case it returns this instead. For example:
>
> function usertype(v)
> local mt = getmetatable(v)
> if mt ~= "table" return mt else return type(v)
> end
>

In my own version of type() function I return up to two values. The
first returned value comes from original_type() function. For userdata
the second returned value is the typeinfo taken from its metatable (if
found). Usage is something like this:

local xtype, xtypeinfo= type(x)
if x == "userdata" then do_something_with_typeinfo(xtypeinfo) end

Please note, that this new type() function is backwards compatible
with the original type() in most cases. Second returned value can be
simply discarded.

--Leo--

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Tim Hill
Well some really interesting discussions :) ... I tend to agree with Leo's approach which extends type() without breaking it by adding a 2nd return value derived from __type or whatever from the metatable.

For those saying "well, all you have to do is implement XXX" yes you are correct about how it can be done (there are dozens of ways). My point really was that the ability to project "metatype" information for userdata and tables is, imho, something that should be part of the standard base language. Such a facility is part of the basic contract between suppliers and consumers of objects, and so transcends individual local designs. Just like tostring() can be extended via the __tostring metafunction, so the type() function can be extended. This way, no new design pattern need be introduced, and Lua stays implementation neutral ... what is standardized is the contract BETWEEN the consumer and the supplier of type data.

As for extending type() versus some form of predicate, my feeling is that a predicate model belongs above the core layer; just like Lua does not have one baked-in object syntax, but instead provides the building blocks for many different OO paradigms, so the type() mechanism should be above any one paradigm. After all, any predicate system can really be built on top of an extended (two return value) type() function plus a conventionalized use of the second return value (string parsing etc etc).

--Tim


On May 31, 2012, at 5:37 PM, Leo Razoumov wrote:

On Wed, May 30, 2012 at 3:37 PM, Tim Hill <[hidden email]> wrote:
To my mind, the most correct way for this to work would be for the type()
function to return the user-defined type by returning the value of
__metatable (like getmetatable()). However, this would of course break
existing code, so i feel we need a new function, usertype() that behave like
type() except when there is a __metatable field in the metatable, in which
case it returns this instead. For example:

function usertype(v)
local mt = getmetatable(v)
if mt ~= "table" return mt else return type(v)
end


In my own version of type() function I return up to two values. The
first returned value comes from original_type() function. For userdata
the second returned value is the typeinfo taken from its metatable (if
found). Usage is something like this:

local xtype, xtypeinfo= type(x)
if x == "userdata" then do_something_with_typeinfo(xtypeinfo) end

Please note, that this new type() function is backwards compatible
with the original type() in most cases. Second returned value can be
simply discarded.

--Leo--


Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Dirk Laurie-2
2012/6/1 Tim Hill <[hidden email]>:
> My point really was that the ability to project "metatype" information for
> userdata and tables is, imho, something that should be part of the standard
> base language.
...
> After all, any predicate system can really be built on top of an
> extended (two return value) type() function plus a conventionalized use of
> the second return value (string parsing etc etc).

I'm happy with extending `type` in that way for userdata.  The second
return value could be the actual metatype associated with the metatable
in the registry.  This is already available to a resourceful user, as
Luiz pointed out here:

    http://lua-users.org/lists/lua-l/2012-01/msg00318.html

This value needs be unique anyway (it is a table key) so there is
no danger of a namespace collision.  And even though one can implement
this purely at the Lua level, searching all of the registry every time
is clearly inefficient.

But tables?  No.

Reply | Threaded
Open this post in threaded view
|

Re: Suggestion: "usertype()" function

Fabien-3
If you mainly want to use types for type-checking, we've become convinced that type() is not the best approach. We ended up with a function checks(...), which takes n arguments, and where the n-th argument describes the expected type of the n-th argument *of the calling function*. For instance, if you want function foobar to take two strings and an optional number, you'll write:

function foobar(str1, str2, n)
    checks('string', 'string', '?number')
    -- rest of foobar's body
end

This is terse and readable, it can be leveraged by static code analysis tools, it's trivial to disable in production code, and there are several fallback/extension mechanisms:
- types, as returned by type(), are allowed;
- if an object's metatable contains a string in a __type field, this name is also allowed;
- if tweaking the metatable isn't an option, there's a global table associating arbitrary type names to arbitrary predicates;
- several types can be allowed, separated with a bar, e.g. checks("number|string", "?table")
- a prefix "?" makes an argument optional ("?sometype" is equivalent to, although faster and more readable than, "nil|sometype");

The code is self-sufficient, and available here under MIT license, with a detailed luadoc manual: https://github.com/SierraWireless/luasched/blob/master/c/checks.c