New table function, ForRange
Fusion is currently lacking a function which covers for Count = 1, X do. Alternatives we're stuck with look something like this
Scope:Computed(function()
local Table = {}
for Count = 1, 1, 1 do
table.insert(Table, Scope:New("")({})
end
end)
Thus, im proposing syntax along these lines, to serve as utility, while also allowing fusion to add optimizations which a Computed can currently not serve, such as not recreating Children when a new one is added, since currently a computed would recreate every instance if the End state was turned from say, 1 to 2.
Scope:ForRange({Start: number, End: number, Increment: number?}, function(Current: number)
return Scope:New("")({})
end)
I'm prepared to make a PR for this after some people have looked at it and made sure I'm not saying anything abhorrently stupid, and also ensured no improvements can be made to it
Why not just have number arguments for start, end, and step, like how Spring hasit?
Why not just have number arguments for start, end, and step, like how Spring hasit?
Reason being, Increment is a niche use case, optional argument, which will be silenced 95% of the time, and since the function is the last argument but is non-optional, you'd be silencing the Increment almost every call, which seems unintuitive to me
Can't you just use a ForValues with a number array?
local function range(start, ending, step)
local arr = {}
-- i forgot the syntax but
for i = start, ending, step do
table.insert(arr, i)
end
return arr
end
scope:ForValues(range(1, 10), function(use, scope, i)
-- ...
end))
Can't you just use a ForValues with a number array?
local function range(start, ending, step) local arr = {} -- i forgot the syntax but for i = start, ending, step do table.insert(arr, i) end return arr end
scope:ForValues(range(1, 10), function(use, scope, i) -- ... end))
In response, can you not just use any combination of ForValues, ForKeys and ForPairs, to make any combination of each other?
It's purposefully tagged with enhancement. The original post points out that it's possible, but not natively, where it should be possible natively following the trend of the other iteration functions
Can't you just use a ForValues with a number array?
local function range(start, ending, step) local arr = {} -- i forgot the syntax but for i = start, ending, step do table.insert(arr, i) end return arr end
scope:ForValues(range(1, 10), function(use, scope, i) -- ... end))
I would argue that this is too much of unnecessary performance overhead, both time and memory wise. Time because you are going to iterate twice, first time to fill the table, and second time through ForValues. Memory because you are allocating space for an array that will be filled with 64-bit floating point numbers. Although the performance may be negligible, it does stack, because developer definitely won't have just a simple list of 50 numbers in a product (e.g., game, plugin). The time and memory can be saved for something else that is more important. Additionally, it will be one less utility function to write for a developer. By having a dedicated method we cut down on unnecessary performance overhead and inconvenience. I personally found this useful for prototyping UI where I didn't yet have data to fill up my list or grid.
Reason being, Increment is a niche use case, optional argument, which will be silenced 95% of the time, and since the function is the last argument but is non-optional, you'd be silencing the Increment almost every call, which seems unintuitive to me
I'm not sure if overloaded functions are against Fusion's principles, but ForRange could be written like this:
type ForRangeCallback<T> = (use: Use, scope: Scope, iteration: number) -> T
type ForRangeConstructor<T> =
& (scope: Scope, start: number, stop: number, callback: ForRangeCallback<T>) -> ForRange
& (scope: Scope, start: number, stop: number, increment: number, callback: ForRangeCallback<T>) -> ForRange
local ForRange = function(...)
local result = {}
if select("#", ...) == 4 then
local scope, start, stop, callback = ...
for i = start, stop do
-- ...
end
elseif select("#", ...) == 5 then
local scope, start, stop, increment, callback = ...
for i = start, stop, increment do
-- ...
end
end
return result
end :: ForRangeConstructor
This would allow ranges to be specified more tersely without the need to pass nil for increment in most cases.
One example use case for ForRange would be generating a list of children. An alternative I found for this case is to write an anonymous function inline, and then call it. However, it isn't pretty and requires some imperative code.
scope:New "Frame" {
[Children] = {
scope:New "UIListLayout" {},
(function()
local labels = {}
for i = 1, 10 do
labels[i] = scope:New "TextLabel" {
Text = `Item {i}`,
}
end
return labels
end)(),
},
}
Here's how I'd imagine it would look with ForRange:
scope:New "Frame" {
[Children] = {
scope:New "UIListLayout" {},
scope:ForRange(1, 10, function(use, scope, i)
return scope:New "TextLabel" {
Text = `Item {i}`,
}
end),
},
}
Definitely support this, a state object like ForRange would be so useful to have in Fusion. My main question, however, is would this use For/Disassembly under the hood? or would it use it's own custom logic for handling reusing values? I know that Disassembly only really works with tables that have already been populated, so I would assume the latter if we really to avoid any potential performance overheads.
I've already experimented with implementing this myself, with support for UsedAs values, as a ForValues wrapper and it works pretty well. This works fine for me, since I'm not too worried about performance.
local function ForRange<S, T>(
scope: memory.Scope<S>,
start: calc.UsedAs<number>,
finish: calc.UsedAs<number>,
processor: iter.ForRangeProcessor<S, T>,
step: calc.UsedAs<number>?
): iter.For<number, T>
local new_range = Range(scope, start, finish, step)
return ForValues(scope, new_range, processor)
end
I've opted to put the step parameter after the processor, since I feel like overloading functions can be quite cumbersome and can lead to code increasing in complexity very quickly. Usually if a function behaves differently based off of the number/order/type of arguments passed to it, I would opt to instead create separate functions to handle those cases, instead of doing loads of checks to change the flow of code execution.