luau/rfcs/table-destructuring.md
2022-08-05 22:49:39 -05:00

5.3 KiB

Table Destructuring

Summary

A feature present and highly-used in JavaScript, table destructuring allows the developer to extract data from tables and assign them to variables in a clean, shortened manner, thus avoiding the need for excessive index calls and several individual assignments, most of which being expendable.

Motivation

As of today, the only way to "dynamically" assign table values is via unpack/select and variable list assignments. With that being said, these functions cannot be used on dictionaries and limit control over what values are being retrieved.

With the introduction of destructuring assignments, we'll be able to substantially reduce redundant variables, index calls, and bloat in our code. Instead, using a clean syntax that allows the intuitive selection and assignment of values from arrays and dictionaries.

Destructuring allows for shortened code with increased flexibility. The expected outcome__s__ are endless.

-- Example using HTTP Service
-- Based on example from https://developer.roblox.com/en-us/api-reference/class/HttpService

local response = HttpSevice:GetAsync("http://api.open-notify.org/astros.json")
local data = HttpService:JSONDecode(response)

local { message, number, people } = data

if message == "success" then
    print("There are currently", data.number, "astronauts in space:")
    for i, { name, craft } in people do
        print(i .. ": " .. name .. " is on " .. craft)
    end
end
-- Example using Fusion

local { New, Children } = Fusion

return New "ScreenGui" {
    -- ...
}
-- Example using a RemoteFunction callback

RemoteFunction.InvokeServer = function(player, { target, damage })
    print("Dealt " .. damage .. " to " .. target.Name)
end

Design

Variable Assignments for Arrays

With the ability to use unpack to destructure arrays, the need for this feature can seem negligible. Nevertheless, unpack cannot be used on dictionaries.

-- Current
local foo = {1, 2, 3}

local one, two, three = unpack(foo) -- 1, 2, 3
-- New
local foo = {1, 2, 3}

local {one, two, three} = foo -- 1, 2, 3

Variable Assignments for Dictionaries

-- Current
local foo = {
    red = "good";
    green = "better";
    blue = "best";
}

local red, green, blue = foo.red, foo.green, foo.blue
print(red) -- good
print(green) -- better
print(blue) -- best
-- New
local foo = {
    red = "good";
    green = "better";
    blue = "best";
}

local {red, green, blue} = foo
print(red) -- good
print(green) -- better
print(blue) -- best

Variable Assignments for Function Returns

-- Current
local function f()
    return {
        name = "John";
        age = 25;
        gender = "male";
    }
end

local data = f()
local name, age, gender = data.name, data.age, data.gender

print(name) -- John
print(age) -- 25
print(gender) -- male
-- New
local function f()
    return {
        name = "John";
        age = 25;
        gender = "male";
    }
end

local {name, age, gender} = f()
print(name) -- John
print(age) -- 25
print(gender) -- male

Variable Assignments for UserData & Vectors

-- Current
local Part = workspace.Part
local Position = Part.CFrame.Position
local X, Y, Z = Position.X, Position.Y, Position.Z

print(X, Y, Z) -- 0, 0, 0
-- New
local Part = workspace.Part
local {Position} = Part.CFrame
local {X, Y, Z} = Position

print(X, Y, Z) -- 0, 0, 0

Variable Assignments for Indexing Children (???)

-- Current
local Baseplate = workspace.Baseplate
print(Baseplate) -- Baseplate
-- New
local {Baseplate} = workspace
print(Baseplate) -- Baseplate

Destructuring Function Parameters

-- Before
local function f(data)
    local name = data.name
    local age = data.age
    local gender = data.gender

    print(name, age, gender) -- John, 25, male
end

f({
    name = "John";
    age = 25;
    gender = "male";
})
-- New
local function f({ name, age, gender })
    print(name, age, gender) -- John, 25, male
end

f({
    name = "John";
    age = 25;
    gender = "male";
})

Defining Alternative Identifiers (Questionable?)

In this example, fizz-buzz is not a valid identifier. You should be able to instead define an alternative identifier, like so.

local foo = {
    ["fizz-buzz"] = true;
}

local { fizzbuzz = "fizz-buzz" } = foo
print(fizzbuzz) -- fizz-buzz

More Complex Example

This example is a direct port of the JavaScript example I gave on issue #617.

local obj = {
    name = "John";
    records = {
        {
            date = "7/29/2022";
            text = "Hello World!";
        }
    }
}

local {
    name,
    records = {
        { text }
    }
} = obj

print(name .. " says " .. text) -- John says Hello World!

Drawbacks

From my perspective, this would be a rather large addition to the language. Some of the drawbacks are listed below:

  • Increased compiler complexity
  • Considered language bloat
  • New, unknown syntax
  • Not necessarily friendly to new programmers

With that being said, these drawbacks are all subjective, and likely won't matter to people who choose not to use destructuring in their code.

Alternatives

The only other alternatives are unpack/select. If this weren't implemented, it would just lead to longer code.