Yuescript
Yuescript copied to clipboard
Metamethod operator
Hi!
I have a feature suggestion for Yuescript:
# metamethod operator
a = close: true, close#: => print "out of scope"
b = add#: (left, right) -> right - left
c = key1: true, #:add, key2: true
d, e = a.close, a#.close
f = a#\close(1)
Compiles to
local a = setmetatable({close = true}, {__close = function(self) return print("out of scope") end})
local b = setmetatable({}, {__add = function(left, right) return right - left})
local c = setmetatable({key1 = true, key2 = true}, {__add = add})
local d, e = a.close, getmetatable(a).__close
local f = getmetatable(a).__close(a, 1)
thanks!
How about a little modification for the syntax to make it look more consist?
a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true
d, e = a.close, a.close#
{:close#, close#: closeA} = a
f = a\close# 1
a.add# = (x, y)-> x + y
close _ = close#: -> print "out of scope"
Compiles to:
local a = setmetatable({
close = true
}, {
__close = function(self)
return print("out of scope")
end
})
local b = setmetatable({ }, {
__add = function(left, right)
return right - left
end
})
local c = setmetatable({
key1 = true,
key2 = true
}, {
__add = add
})
local d, e = a.close, getmetatable(a).__close
local close, closeA
do
local _mt_0 = getmetatable(a)
close, closeA = _mt_0.__close, _mt_0. __close
end
local f = getmetatable(a).__close(a, 1)
getmetatable(a).__add = function(x, y)
return x + y
end
local _ <close> = setmetatable({ }, {
__close = function(self)
return print("out of scope")
end
})
its implementation makes it more consistent. I liked! It got easier. The operator at the end means it is related to the metamethod. +1
The new syntax is now implemented in the commit 1df786307c1983b8ce693e3916081a8bcd4e95ae with some extra usages as:
a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true
d, e = a.close, a.close#
{:close#, close#: closeA} = a
f = a\close# 1
a.add# = (x, y)-> x + y
close _ = close#: -> print "out of scope"
-- shortcut for setting and getting the meta table
a.# = __index: tb
mt = a.#
Compiles to:
local a = setmetatable({
close = true,
}, {
__close = function(self)
return print("out of scope")
end
})
local b = setmetatable({ }, {
__add = function(left, right)
return right - left
end
})
local c = setmetatable({
key1 = true,
key2 = true
}, {
__add = add
})
local d, e = a.close, getmetatable(a).__close
local close, closeA
do
local _obj_0 = getmetatable(a)
close, closeA = _obj_0.__close, _obj_0.__close
end
local f = getmetatable(a):__close(1)
getmetatable(a).__add = function(x, y)
return x + y
end
local _var_0 = setmetatable({ }, {
__close = function()
return print("out of scope")
end
})
local _ <close> = _var_0
setmetatable(a, {
__index = tb
})
local mt = getmetatable(a)
Wow. nice! congrats for the great job.
could this be changed or does it cause conflict?
a.# = __index: tb
mt = a.#
-- TO
a# = __index: tb
mt = a#
Yes, it causes conflict. If an expression followed by # symbol means getting its meta table. Codes a.add# will be compiled to getmetatable(a.add) instead of getmetatable(a).__add. So using a.add.# can be different from a.add#.
the first syntax seemed more consistent to me. # + :, # + ., and # + \
a = close#: =>
b = #:add
c = t#.close
d = t#.close#.test --> getmetatable(getmetatable(t).__close).__test
e = t#\close 1 --> getmetatable(t):__close(1)
f = t#['close'] --> getmetatable(t).close
so it does not cause conflict in the shortcut below
a# = __index: tb
mt = a#
the two underscores could be removed and added automatically here too
a# = index: tb
mt = a#
compiles to
setmetatable(a, {
__index = tb
})
local mt = getmetatable(a)
literal keys could be added as well
a = close: true, ['close']#: => print "out of scope"
compiles to
local a = setmetatable({
close = true
}, {
close = function(self)
return print("out of scope")
end
})
that way everything is consistent and beautiful What do you think?
I really liked it
a = t#.close --> local a = getmetatable(t).__close
b = t# --> local b = getmetatable(t)
Classic Lua class using Metamethod syntax:
MyClass = call#: (...) => @.new ...
MyClass.new = (init) ->
@ = index#: MyClass
@value = init
@
MyClass.set_value = (newval) =>
@value = newval
MyClass.get_value = =>
@value
i = MyClass 5
print i\get_value!
i\set_value 6
print i\get_value!
I like the idea of ['close']#: => print "out of scope" and got it implemented. And I understand the #., #\ can be used as a new operator. But to add such syntax we have to pick another leading symbol instead of '#', or it will conflict with other syntax like:
with tb
d = t#.close
Currently compiles to:
local _with_0 = tb
local d = t(#_with_0.close) -- # is an unary operator with a high priority
-- affecting the following expression
So that the version of postfix syntax and indexing table with a '#' symbol that I proposed may work better with the former syntax. Or we have to pick another symbol other than '#' to get the syntax design work.
-- currently implemented
a = close#: =>
b = :add#
c = t.close#
d = t.close#.test# --> getmetatable(getmetatable(t).__close).__test
e = t\close# 1 --> getmetatable(t):__close(1)
f = t.#['close'] --> getmetatable(t).close
a.# = __index: tb
mt = a.#
-- maybe pick up another symbol for a new syntax?
-- for example ` is neither a binary operator nor an unary operator for the moment
a = close`: =>
b = `:add
c = t`.close
d = t`.close`.test --> getmetatable(getmetatable(t).__close).__test
e = t`\close 1 --> getmetatable(t):__close(1)
f = t`['close'] --> getmetatable(t).close
a` = __index: tb
mt = a`
The main reason for choosing # was the similarity to _ _ and it already exists in Lua, but due to conflicts and consistency it is better to change to your suggestion that looks good. :+1: . Another symbol that I thought was ~
-- set
a = close`: =>
b = `:add
-- get
c = t`.close
d = t`.close`.test --> getmetatable(getmetatable(t).__close).__test
e = t`\close 1 --> getmetatable(t):__close(1) or getmetatable(t).__close(t, 1) ?
-- literal
f = t`['close']
-- shortcut
t` = __index: tb -- alias: a` = `index: tb ?
mt = a`
workaround for the Fill operator (#39)
t1 = {0, key1: 10, key2: 20}
t2 = index`: t1, key3: 30, key4: 40 -- the "index`:t1" is the Fill operator here (uses metatable __index)
print t2[1] -- prints 0
print t2.key1 -- prints 10
print t2.key4 -- prints 40
With a little more thought, I feel the first syntax won't be a better choice because there will be 4 rules to memorize for using the metatable syntax and each has a different representation.
a = close`: => -- 1 metamethod '__close'
b = close` -- 2 metatable for close
c = `:close -- 3 metamethod '__ close' and function 'close'
d = t`.close -- 4 metamethod '__close' for t
e = (t`).close -- field 'close' of metatable for t ?
Syntax that is currently implemented won't be this confusing, a name followed by '#' will always refer to the metamethod. And metatable is using dot indexing any expression with '#'.
a = close#: => -- metamethod '__close'
b = close.# -- metatable for close
c = :close# -- metamethod '__ close' and function 'close'
d = t.close# -- metamethod '__close' for t
e = t.#.close -- field 'close' of metatable for t
Your idea is great. 2 rules make it simple!
syntax improvement:
get_mt = () -> {}
a = key: true, #: get_mt!
-- OR - by convention (my favorite) - I haven't checked if it causes conflicts
a = key: true, .#: get_mt!
compiles to
local a = setmetatable({
key = true
}, get_mt())
how to solve hybrid cases?
-- conditional 'or': (my favorite) - .# always takes precedence
a = key: true, .#: get_mt!, index#: -> print "ok" --> local a = setmetatable({key=true}, get_mt() or {__index = function() return print("ok") end})
a2 = key: true, .#: get_mt!, index#: (-> print "ok"), .#: a2_mt --> local a2 = setmetatable({key=true}, get_mt() or a2_mt or {__index = function() return print("ok") end})
-- parser error:
b = key: true, .#: get_mt!, index#: -> print "ok" --> e. g. ERROR: too many metatable declarations
-- ignore others:
c = key: true, .#: get_mt!, index#: -> print "ok" --> local c = setmetatable({key=true}, get_mt())
this syntax improvement is natural and is an alternative to the current shortcut allowing to unify the keys as well. Both syntaxes have well-defined purposes:
a = .#: mt --> local a = setmetatable({}, mt)
b.# = mt --> setmetatable(b, mt)
his proposal to leave the # symbol as a suffix allowed this improvement. And the surprising thing is that it became cohesive.
Thanks!
.#: mt and #: mt can be aliases
I like this syntax improvement and got it implemented. Hybrid cases are solved with parser error which makes things simpler.
mt = a.#
#:mt = a
a = #:mt
a = #:{__index:mt}
local index
#:{__index:index} = a
:index# = a
do #:{new:ctor, :update} = a
do {new:ctor, :update} = a.#
Now compiles to:
local mt = getmetatable(a)
mt = getmetatable(a)
local a = setmetatable({ }, mt)
a = setmetatable({ }, {
__index = mt
})
local index
index = getmetatable(a).__index
index = getmetatable(a).__index
do
local ctor, update
do
local _obj_0 = getmetatable(a)
ctor, update = _obj_0.new, _obj_0.update
end
end
do
local ctor, update
do
local _obj_0 = getmetatable(a)
ctor, update = _obj_0.new, _obj_0.update
end
end
But the new syntax breaks an old syntax of #:abc which is now compiling to setmetatable({ }, abc) instead of #{abc = abc}.
Hi Li Jin! I'm using this syntax daily in my private projects.
I think the best full syntax was this:
a = a#: true
b = :b#
c = ['c']#: true
d = .#: mt --> code.# instead of #:code here (don't break the old syntax)
e# = e
f = f#
g.# = g --> code.# instead of #:code here
h = h.#
{:i#} = i
{j#: j} = j
{['k']#: k} = k
in short: code.# related to metatable, code# related to metamethod
thanks for implementing this!
-- Still feeling the current implementation could be more consistent.
-- The old syntax is a rare case, and not to be of much uses. So I think removing it could be acceptable.
a: x = tb
x = tb.a
tb = a: x
#: x = tb
x = tb.#
tb = #: x
get
local x = tb.a
x = tb.a
local tb = {
a = x
}
x = getmetatable(tb)
x = getmetatable(tb)
tb = setmetatable({ }, x)
On second thought, the current implementation is more consistent. The old syntax can be removed. I think the syntax is complete. Thank you!
Hi,
Explicit {} could remove call to setmetatable, useful when you need to create a metatable elsewhere.
a = {close#: => print "out of scope"} --> local a = {__close = function() return print("out of scope") end}
a = close#: => print "out of scope" --> local a = setmetatable({}, {__close = function() return print("out of scope") end})
Example:
a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true
d, e = a.close, a.close#
{:close#, close#: closeA} = a
f = a\close# 1
a.add# = (x, y)-> x + y
close _ = close#: -> print "out of scope"
-- shortcut for setting and getting the meta table
a.# = {index#: tb} -- old: __index: tb
mt = a.#
Better
-- keys and metamethods:
a = b: true, c#: true -- local a = setmetatable({b = true}, {__c = true})
-- only keys:
a = b: true -- local a = {b = true}
-- only metamethods:
a = c#: true -- local a = {__c = true}
-- with metatable:
a = b: true, #: d -- local a = setmetatable({b = true}, d)
a = #: {e#: true} -- local a = setmetatable({}, {__e = true})
Currently code {key: value} is the same with key: value. So I prefer that {close#: =>} and close#: => could be the same thing.
Key name ends with '#' symbol means assigning the metatable field for a table for the moment. So a = c#: true creates an empty table with a metatable having a field named '__c' and a = b: true, c#: true creates a table with a key 'a' and a metatable having a field named '__c'.
a = c#: true -- local a = {__c = true}
-- Change the syntax this way will make it with less 'sugar'.
-- `c#` is just a shortcut for `__c`
a = c#: true -- local a = setmetatable({}, {__c = true})
-- Could save more codes for a syntax sugar
I agree. My suggestion would just expand this idea with just a syntax. Would save a lot of code, but it would be more complex. My intention is to eliminate the use of __key, replacing it with key#
Hi @pigpigyyy
with the new metatable syntax (<>), we can transform all keys, if surrounded by angle brackets, into metamethods
so we will remove the use of double underscores (__key) everywhere.
a = <index: 1, close: 2, "add": 3, [true]: 4>
compiles to
local a = { -- 1
__index = 1, -- 1
__close = 2, -- 1
["add"] = 3, -- 1
[true] = 4 -- 1
} -- 1
sample (from docs):
-- create with metatable containing field "value"
tb = <"value">: 123
tb.<index> = tb.<>
print tb.value
tb.<> = __index: {item: "hello"}
-- new suggestion:
tb.<> = <index: {item: "hello"}>
print tb.item
if angle brackets are placed inside the table again, a syntax error will occur, preventing syntax recurrences:
a = <index: 1, <close>: 2, "add": 3, [true]: 4> -- SYNTAX ERROR! after first ">"
a syntax error should occur in the following code, as it is now
a = <[b: true]>: 1 -- OK
a = <b: true>: 1 -- SYNTAX ERROR!
thanks
@1zilla It is a nice addition. I like the idea of metamethod key name checking to distinguish angle brackets table with a common table. I will try implementing this into a newer version.