|
| 1 | +local M = {} |
| 2 | + |
| 3 | +---Takes a function of two arguments and flips their parameters |
| 4 | +---@generic T, U |
| 5 | +---@param fun fun(lhs: `T`, rhs: `U`): any Function to flip |
| 6 | +---@return fun(rhs: U, lhs: `T`): any # Flipped function |
| 7 | +function M.flip(fun) |
| 8 | + return function(lhs, rhs) |
| 9 | + return fun(rhs, lhs) |
| 10 | + end |
| 11 | +end |
| 12 | + |
| 13 | +---Partially applies some function arguments |
| 14 | +---@param fun fun(...: any): any Function to apply args to |
| 15 | +---@param ... any Args to apply |
| 16 | +---@return fun(...: any): any # Function with args applied |
| 17 | +function M.partial(fun, ...) |
| 18 | + local passed = { ... } |
| 19 | + return function(...) |
| 20 | + return fun(unpack(vim.list_extend(passed, { ... }))) |
| 21 | + end |
| 22 | +end |
| 23 | + |
| 24 | +---Partially applies some function arguments to the back |
| 25 | +---@param fun fun(...: any): any Function to apply args to |
| 26 | +---@param ... any Args to apply |
| 27 | +---@return fun(...: any): any # Function with args applied to the back |
| 28 | +function M.partial_(fun, ...) |
| 29 | + local passed = { ... } |
| 30 | + return function(...) |
| 31 | + return fun(unpack(vim.list_extend({ ... }, passed))) |
| 32 | + end |
| 33 | +end |
| 34 | + |
| 35 | +---Returns the nth argument passed to this function |
| 36 | +---@param n number Which argument to return |
| 37 | +---@param ... any Args |
| 38 | +---@return any # Nth arg |
| 39 | +function M.nth(n, ...) |
| 40 | + return ({ ... })[n] |
| 41 | +end |
| 42 | + |
| 43 | +---Returns the first argument passed to this function |
| 44 | +---@generic T |
| 45 | +---@param a `T` The first arg |
| 46 | +---@return T # The first argument |
| 47 | +function M.first(a) |
| 48 | + return a |
| 49 | +end |
| 50 | + |
| 51 | +---Returns the second argument passed to this function |
| 52 | +---@generic T |
| 53 | +---@param _a any First arg, discarded |
| 54 | +---@param b `T` Second arg, returned |
| 55 | +---@return T # Second arg |
| 56 | +function M.second(_a, b) |
| 57 | + return b |
| 58 | +end |
| 59 | + |
| 60 | +---Returns a function that compares its argument to `value` |
| 61 | +---@generic T |
| 62 | +---@param value `T` Value to compare against |
| 63 | +---@return fun(x: T): boolean |
| 64 | +function M.equal(value) |
| 65 | + return function(x) |
| 66 | + return x == value |
| 67 | + end |
| 68 | +end |
| 69 | + |
| 70 | +---If it's a function, calls it, otherwise returns id |
| 71 | +---@generic R, V |
| 72 | +---@param value fun(any...): `R`|V Value to call/return |
| 73 | +---@param ... any Possible values to pass to the function |
| 74 | +---@return R|V |
| 75 | +function M.eval(value, ...) |
| 76 | + if type(value) == 'function' then |
| 77 | + return value(...) |
| 78 | + else |
| 79 | + return value |
| 80 | + end |
| 81 | +end |
| 82 | + |
| 83 | +---Returns the result of a function wrapped in an array |
| 84 | +---@generic Ts |
| 85 | +---@param f fun(any...): `Ts` Function to wrap |
| 86 | +---@return fun(...): [Ts] |
| 87 | +function M.arraified(f) |
| 88 | + return function(...) |
| 89 | + return { f(...) } |
| 90 | + end |
| 91 | +end |
| 92 | + |
| 93 | +---Returns the args untouched |
| 94 | +---@param ... any Args to return |
| 95 | +---@return ... any |
| 96 | +function M.id(...) |
| 97 | + return ... |
| 98 | +end |
| 99 | + |
| 100 | +---Returns `id` or the passed in function |
| 101 | +---@generic Rs, Ts |
| 102 | +---@param value fun(...: `Ts`): `Rs`|nil Value to call/return |
| 103 | +---@return fun(...: Ts): `Rs`|fun(...: any): ... |
| 104 | +function M.maybe_fn(value) |
| 105 | + assert(type(value) == 'function' or value == nil) |
| 106 | + |
| 107 | + if type(value) == 'function' then |
| 108 | + return value |
| 109 | + else |
| 110 | + return M.id |
| 111 | + end |
| 112 | +end |
| 113 | + |
| 114 | +---Reduces the result over a function |
| 115 | +---@generic T, U, R |
| 116 | +---@param f fun(arg1: T, arg2: U): `R` The applied function |
| 117 | +---@param acc R The accumulator and initial value |
| 118 | +---@return R |
| 119 | +function M.foldl(f, acc, ...) |
| 120 | + if select('#', ...) == 0 then |
| 121 | + return acc |
| 122 | + end |
| 123 | + local l = ... |
| 124 | + return M.foldl(f, f(acc, l), select(2, ...)) |
| 125 | +end |
| 126 | + |
| 127 | +---Composes two functions f, g and into `g(f(...))` |
| 128 | +---@generic FirstR, SecondR |
| 129 | +---@param f fun(...: any): `FirstR` |
| 130 | +---@param g fun(...: FirstR): `SecondR` |
| 131 | +---@return fun(...: any): SecondR |
| 132 | +function M.compose(f, g) |
| 133 | + return function(...) |
| 134 | + return g(f(...)) |
| 135 | + end |
| 136 | +end |
| 137 | + |
| 138 | +---Chains several functions invocations together |
| 139 | +---chain(a, b, c) results in c(b(a(...))) |
| 140 | +---@param ... fun(...: any): ... |
| 141 | +---@return fun(...: any): ... |
| 142 | +function M.chain(...) |
| 143 | + return M.foldl(M.compose, M.id, ...) |
| 144 | +end |
| 145 | + |
| 146 | +---Returns `not f(...)` |
| 147 | +---@param f fun(...: any): boolean func to inverse |
| 148 | +---@return fun(...: any): boolean |
| 149 | +function M.nah(f) |
| 150 | + return function(...) |
| 151 | + return not f(...) |
| 152 | + end |
| 153 | +end |
| 154 | + |
| 155 | +---Formats the arguments |
| 156 | +---@param str string The format string |
| 157 | +---@return fun(...: any): string |
| 158 | +function M.fmt(str) |
| 159 | + return function(...) |
| 160 | + return str:format(...) |
| 161 | + end |
| 162 | +end |
| 163 | + |
| 164 | +---@package |
| 165 | +function M.__test() |
| 166 | + describe('`flip()`', function() |
| 167 | + it('Flips a two arg function', function() |
| 168 | + local sub = function(a, b) |
| 169 | + return a - b |
| 170 | + end |
| 171 | + |
| 172 | + assert.are.same(sub(5, 3), 2) |
| 173 | + assert.are.same(M.flip(sub)(5, 3), -2) |
| 174 | + end) |
| 175 | + |
| 176 | + it('Makes a one arg function accept only the second argument', function() |
| 177 | + assert.are.same(M.id(5), 5) |
| 178 | + assert.are.same(M.flip(M.id)(5, 3), 3) |
| 179 | + end) |
| 180 | + end) |
| 181 | + |
| 182 | + describe('`partial()`', function() |
| 183 | + it('Applies the first argument', function() |
| 184 | + local sub = function(a, b) |
| 185 | + return a - b |
| 186 | + end |
| 187 | + |
| 188 | + assert.are.same(M.partial(sub, 5)(3), 2) |
| 189 | + end) |
| 190 | + |
| 191 | + it('Applies all arguments', function() |
| 192 | + local sub = function(a, b) |
| 193 | + return a - b |
| 194 | + end |
| 195 | + |
| 196 | + assert.are.same(M.partial(sub, 5, 3)(), 2) |
| 197 | + end) |
| 198 | + end) |
| 199 | + |
| 200 | + describe('`partial_()`', function() |
| 201 | + it('Applies the last argument', function() |
| 202 | + local sub = function(a, b) |
| 203 | + return a - b |
| 204 | + end |
| 205 | + |
| 206 | + assert.are.same(M.partial_(sub, 5)(3), -2) |
| 207 | + end) |
| 208 | + |
| 209 | + it('Applies all arguments', function() |
| 210 | + local sub = function(a, b) |
| 211 | + return a - b |
| 212 | + end |
| 213 | + |
| 214 | + assert.are.same(M.partial_(sub, 5, 3)(), 2) |
| 215 | + end) |
| 216 | + end) |
| 217 | + |
| 218 | + describe('`partial_()`', function() |
| 219 | + it('Applies the last argument', function() |
| 220 | + local sub = function(a, b) |
| 221 | + return a - b |
| 222 | + end |
| 223 | + |
| 224 | + assert.are.same(M.partial_(sub, 5)(3), -2) |
| 225 | + end) |
| 226 | + |
| 227 | + it('Applies all arguments', function() |
| 228 | + local sub = function(a, b) |
| 229 | + return a - b |
| 230 | + end |
| 231 | + |
| 232 | + assert.are.same(M.partial_(sub, 5, 3)(), 2) |
| 233 | + end) |
| 234 | + end) |
| 235 | +end |
| 236 | + |
| 237 | +return M |
0 commit comments