Skip to content

Commit e47a7d3

Browse files
authored
feat(option): add support for variadics
* feat(option): add variadic support * test(option): add tests for option * docs(option): update options docs for variadics * feat(option): add join function * docs(option): add reference for join * docs(option): add section about joining options
1 parent 40761b3 commit e47a7d3

File tree

6 files changed

+642
-247
lines changed

6 files changed

+642
-247
lines changed

.lune/test-lib/randomTuple.luau

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- see .lune/test.luau
2+
3+
export type TupleFunctions = {
4+
map: (...any) -> ...any,
5+
other: () -> ...any,
6+
assertEq: (...any) -> (),
7+
assertEqMapped: (...any) -> (),
8+
assertEqOther: (...any) -> (),
9+
assertEqOtherMapped: (...any) -> (),
10+
}
11+
12+
-- selene: allow(unused_variable)
13+
return function(f: (tuple: TupleFunctions, ...any) -> ())
14+
return nil :: any
15+
end

.lune/test.luau

+131
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,137 @@ local function createTestLib(blocks: { TestBlock })
109109
return true
110110
end
111111

112+
local randomTuple = require("./test-lib/randomTuple")
113+
local possibleTypes = { "number", "string", "boolean" }
114+
local alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
115+
116+
local function createRandomTuple(types: { string }): { any }
117+
local t: { any } = {}
118+
for i, type in types do
119+
if type == "number" then
120+
t[i] = math.random(0, 999)
121+
elseif type == "string" then
122+
local s = ""
123+
for _ = 1, math.random(5, 10) do
124+
local randomChar = math.random(#alphabet)
125+
s ..= alphabet:sub(randomChar, randomChar)
126+
end
127+
t[i] = s
128+
elseif type == "boolean" then
129+
t[i] = math.random() > 0.5
130+
else
131+
error(`Unknown type: {type}`, 2)
132+
end
133+
end
134+
return t
135+
end
136+
137+
local function createAssertEqTuple(name: string, tuple: { any }): (...any) -> ()
138+
local s = ""
139+
140+
s ..= "local "
141+
for i = 1, #tuple do
142+
s ..= `v{i}`
143+
if i < #tuple then
144+
s ..= ", "
145+
end
146+
end
147+
s ..= " = ...\n"
148+
149+
s ..= "if\n"
150+
s ..= ` select("#", ...) ~= {#tuple} or\n`
151+
152+
local tupleStr = ""
153+
for i, v in tuple do
154+
local valueStr = if typeof(v) == "string" then `"{v}"` else `{v}`
155+
s ..= ` v{i} ~= {valueStr}`
156+
tupleStr ..= valueStr
157+
158+
if i < #tuple then
159+
s ..= " or\n"
160+
tupleStr ..= "\t"
161+
end
162+
end
163+
s ..= "\nthen\n"
164+
s ..= ' local s = ""\n'
165+
s ..= ' for i = 1, select("#", ...) do'
166+
s ..= " local v = select(i, ...)\n"
167+
s ..= ' s ..= if typeof(v) == "string" then `"{v}"` else `{v}`\n'
168+
s ..= ' if i < select("#", ...) then\n'
169+
s ..= ' s ..= "\\t"\n'
170+
s ..= " end\n"
171+
s ..= " end\n"
172+
s ..= ` error(\`Equality Failed\\n Expected: \\t{tupleStr}\\n Received: \\t\{s}\`, 2)\n`
173+
s ..= "end"
174+
175+
return luau.load(s, { debugName = name })
176+
end
177+
178+
local function createMapTuple(name: string, types: { string }): (...any) -> ...any
179+
local s = ""
180+
181+
s ..= "local "
182+
for i = 1, #types do
183+
s ..= `v{i}`
184+
if i < #types then
185+
s ..= ", "
186+
end
187+
end
188+
s ..= " = ...\n"
189+
190+
s ..= "return "
191+
for i, type in types do
192+
if type == "string" then
193+
if math.random() > 0.5 then
194+
s ..= `v{i}:upper()`
195+
else
196+
s ..= `v{i}:reverse()`
197+
end
198+
elseif type == "number" then
199+
if math.random() > 0.5 then
200+
s ..= `v{i} + {math.random(5)}`
201+
else
202+
s ..= `v{i} - {math.random(5)}`
203+
end
204+
elseif type == "boolean" then
205+
s ..= `not v{i}`
206+
else
207+
error(`Unknown type: {type}`, 2)
208+
end
209+
210+
if i < #types then
211+
s ..= ", "
212+
end
213+
end
214+
215+
return luau.load(s, { debugName = name })
216+
end
217+
218+
function testLib.randomTuple(f: (randomTuple.TupleFunctions, ...any) -> ())
219+
local types = {}
220+
for i = 1, math.random(10) do
221+
types[i] = possibleTypes[math.random(#possibleTypes)]
222+
end
223+
224+
local originalTuple = createRandomTuple(types)
225+
local otherTuple = createRandomTuple(types)
226+
227+
local map = createMapTuple("map", types)
228+
229+
local tupleFunctions = {
230+
map = map,
231+
other = function()
232+
return unpack(otherTuple)
233+
end,
234+
assertEq = createAssertEqTuple("assertEq", originalTuple),
235+
assertEqMapped = createAssertEqTuple("assertEqMapped", { map(unpack(originalTuple)) }),
236+
assertEqOther = createAssertEqTuple("assertEqOther", otherTuple),
237+
assertEqOtherMapped = createAssertEqTuple("assertEqOtherMapped", { map(unpack(otherTuple)) }),
238+
}
239+
240+
f(tupleFunctions, unpack(originalTuple))
241+
end
242+
112243
return testLib
113244
end
114245

docs/docs/optional-values.md

+47-4
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@ To create a `Some` option, we can use `Option.Some`:
1818
local opt = Option.Some(5)
1919
```
2020

21+
An option can contain multiple values:
22+
23+
```lua
24+
local opt = Option.Some(5, "hello", true)
25+
```
26+
2127
To create a `None` option, we can use `Option.None`:
2228

2329
```lua
2430
local opt = Option.None
2531
```
2632

27-
## Accessing the inner value
33+
## Accessing the inner values
2834

29-
There are multiple ways to access the inner value of an option.
35+
There are multiple ways to access the inner values of an option.
3036

31-
The easiest way is to use the `unwrap` method, which will return the inner value
32-
if it exist, or throw an error if it does not:
37+
The easiest way is to use the `unwrap` method, which will return the inner
38+
values if it exist, or throw an error if it does not:
3339

3440
```lua
3541
local some = Option.Some(5)
@@ -161,6 +167,43 @@ calling them using `:` syntax. [Learn more](/reference/option#typechecking).
161167

162168
:::
163169

170+
## Joining options
171+
172+
Sometimes you have multiple options and you want to combine them into a single
173+
option.
174+
175+
For example, lets say you had two options, and you wanted to get the sum of
176+
their values. You may achieve this using the `andThen` and `map` methods:
177+
178+
```lua
179+
local optA = Option.Some(5)
180+
local optB = Option.Some(10)
181+
182+
local sum = optA:andThen(function(a)
183+
return optB:map(function(b)
184+
return a + b
185+
end)
186+
end)
187+
188+
print(sum) -- Option::Some(15)
189+
```
190+
191+
However, this introduces multiple levels of nesting, which may be undesirable.
192+
193+
Instead, we can use the `join` method to turn both options into a single option,
194+
which we can map over:
195+
196+
```lua
197+
local optA = Option.Some(5)
198+
local optB = Option.Some(10)
199+
200+
local sum = optA:join(optB):map(function(a, b)
201+
return a + b
202+
end)
203+
```
204+
205+
If either option is `None`, the result will be `None`.
206+
164207
## Learn more
165208

166209
There are more methods available on the `Option` type, this was just a small

0 commit comments

Comments
 (0)