Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support table keys destructuring and spread #126

Closed
rimuy opened this issue Nov 4, 2021 · 2 comments
Closed

Support table keys destructuring and spread #126

rimuy opened this issue Nov 4, 2021 · 2 comments
Labels
enhancement New feature or request

Comments

@rimuy
Copy link

rimuy commented Nov 4, 2021

Concept

Recently the language has gained support to if-then-else expressions, which shortened the way we deal with conditional values, rather than using if statements. On that note, I'd like to suggest adding support to tables destructuring and spread the same way typescript does with objects.

That way it would not only bring a key into the scope without needing to create a variable for the table and for every key we want to use, but also make our code a lot smaller. Of course, in Luau's case, that would only apply to string indexes, since we can make a tuple via table.unpack and reference the returned values likewise.

Examples

Merging dictionaries

Currently there is not way to destructure string indexed values in a table, and we cannot call unpack to do so, since that's used for number indexed values to return tuples, like said before. If we wanted to do so, we would need to create an utility function for that case. (e.g. merge, assign)

To resolve that problem, we could use ... as the spread operator, just like we do when handling varargs, but instead placing it before a table reference, inside a table.

For example, here we want to create a new table that contains all the keys from t1 and t2:

Current

local t1 = { a = 5 }
local t2 = { b = 10, c = 20 }

local newTbl = {}

local function assign(t)
    for k, v in pairs(t) do
        newTbl[k] = v
    end
end

assign(t1)
assign(t2)

print(newTbl) -- { ["a"] = 5, ["b"] = 10, ["c"] = 20 }

With spread

local t1 = { a = 5 }
local t2 = { b = 10, c = 20 }

print({ ...t1, ...t2 }) -- { ["a"] = 5, ["b"] = 10, ["c"] = 20 }

For the record, we could also do the same above without variables:

{ ...{ a = 5 }, ...{ b = 10, c = 20 } }

Referencing Keys

Let's say we have the functions foo and bar. Both takes as a parameter a destructured table.

  • foo prints the result of the sum of x and y.
  • bar prints the result of x * y * z, but x is the only key that is being directly referenced.
type Point = { x: number, y: number }
type ExtendedPoint = { x: number, y: number, z: number }

Current

local function foo(point: Point)
    print(point.x + point.y)
end

local function bar(point: ExtendedPoint)
    print(point.x * point.y * point.z)
end

foo({ x = 1, y = 2 })
bar({ x = 10, y = 20, z = 30 })

With destructuring

local function foo({ x, y }: Point)
    print(x + y)
end

local function bar({ x, ...rest }: ExtendedPoint)
    print(x * rest.y * rest.z)
end

foo({ x = 1, y = 2 })
bar({ x = 10, y = 20, z = 30 })

Using spread inside a table that is already being destructured, creates a new table that contains all the keys that were not referenced like x.

Nested Tables

local props = {
    a = { name = "fizz" },
    b = { name = "buzz" },
    c = { name = "fizzbuzz" }
}

local {
    a = { name = fizzVar },
    b = { name = buzzVar },
    c = { name = fizzbuzzVar }
} = props

print(fizzVar, buzzVar, fizzbuzzVar) -- fizz buzz fizzbuzz

Key Assignment

In addition, it would also be nice to assign the reference of a key to a new one:

local { foo = a, bar = b, baz = c } = { foo = "luau", bar = 3, baz = true }

assert(a == "luau")
assert(bar == 3) -- Should error, because the key reference was assigned to "b"

This would be a great alternative to creating variables for aliases when working with libraries that returns a dictionary (e.g. roact):

local { Component, createElement = e } = require(Packages.Roact)

local Button = Component:extend("Button")

function Button:render()
    return e("TextButton", ...)
end
@rimuy rimuy added the enhancement New feature or request label Nov 4, 2021
@Halalaluyafail3
Copy link
Contributor

Halalaluyafail3 commented Nov 5, 2021

The proposed spread syntax is ambiguous: {...-t1,...-t2} could be interpreted as merging the results of -t1 and -t2, or it could be interpreted as doing subtraction with the first variable argument and t1 and t2. Currently it is interpreted as doing subtraction.

Would {...{1},...{2}} be {2}, {1}, or {1,2}?
{2} and {1} makes sense if it is just mapping keys into the table (the order of assignments is undefined).
{1,2} makes sense if it doing array concatenation.

I believe that having functions do these tasks is better than having a built in syntax.
You mentioned unpack, but that isn't a good function to use for copying arrays. {table.unpack(t)} will work fine if t is small, but if it is too large then it'll error.

You said that want support destructuring the same way Typescript does, but in the examples you use = for property renaming while Typescript uses = for default value.

local k = 1
local {x=k} = {} -- declare x as 1 or declare another k with nil?
local c = 2
local {x=v=c} = {} -- declare v with 2, confusing

@zeux
Copy link
Collaborator

zeux commented Jan 4, 2022

This issue by itself isn't actionable so I'm going to close this.

Both features would require an RFC to be considered. OTOH spread is unlikely to be accepted at this point in time because it's difficult to remove implicit spread in tail position so explicit spread makes the spread story "fragmented", and there's some implementation challenges - an ideal proposal for spread would come up with some idea of how to move away from implicit spread... unsure how.

Destructuring is much more straightforward (don't treat this as a promise that we'll get that feature...). I think Quenty had plans to submit an RFC for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Development

No branches or pull requests

3 participants