This is an anonymous function shorthand suggestion & discussion for pico8.
sum=function(a,b)return a+b end |
Right now, anonymous function definitions look like this.
This is way too long for such a simple function!
I know that Lua doesn't have its own short function; although efforts were made(as seen here), none stood out to be practical.
But as there is already a ton of shorts in pico8, why not add one more?
Of course, then we have to decide the syntaxes for this short.
Below I list three function shorts from other languages:
Verbose Lambda
As implemented in Python and Java.
lambda p1=default,p2:expression sum=lambda a,b:a+b |
(example from Python)
Allows default values, which makes the assignment of local values very concise.
From my experience of size-optimization in pico8, one pattern troubles me: nobody uses local variables.
Of course, size-optimized code is supposed to be purely procedural and I get that; still, they can be better with such a shorthand.
The arrow
Choice of Javascript.
(p1=deafault,p2=>expression) (a,b=>a+b) |
Nice and short. (Don't need 2 characters for the arrow tho, one will do the trick)
Just like the Lambda method, it also allows default values.
> Personally, I think the Elixir fn(p1)-> expression end
fits the Lua syntax better.
> But why arrow again when there are already parentheses to separate the parameters and the expression??
> Also, the additional end
fails to capture the expression-like nature of anonymous functions. It's not a control structure(although it can be used as one).
Overall, I think this is the best choice.
The & shorthand
From Elixir.
sum = &(&1 + &2) |
(example from https://elixirschool.com/en/lessons/basics/functions/)
This is the shortest so far.
Instead of naming the parameters, why not just number them?
This shorthand has several problems tho:
doesn't align with the De Bruijn index.- no default values.
- if I do
&(&2)
, does this just evaluate tofunction(x)return function(y)return y end end
? As you can see in this example, I just skipped&1
entirely. I do not think this is great at all for intuitive and readable code. It's confusing and not as elegant as shorthands should be. (Just an opinion, feel free to tell me how wrong I am.)
So I guess I won't consider this as an option.
Conclusion
A functional shorthand would make the pico8 experience even better.
I am not sure if this has been requested before, but it might shape how we code in pico8 by a lot.
Feel free to comment below on your favorite shorthand. I am sure there are even better syntaxes than the ones I have listed above.
Self Criticism: Such a shorthand might turn out to be disruptive to the Lua syntax. For now, most shorts in pico8 are alternative functions(the ?
for print
or t
for time
). These are pretty harmless because one can always assign something else to the variable. However, a function short is different and might not interact well with the rest of the Lua syntax.
(Self Rebuttal?: tbh, Lua function definitions are already very different in syntax than other structures... For the loops(except for repeat) one needs to append a "do" keyword to begin a block(of code). For if
there's at least then
. But for function
, there is nothing. Hmmm...)
PICO-8 has quite a few shorts that are more sophisticated syntactic replacements. I think simple versions of this idea, especially if syntactically limited to a single line like most PICO-8 preprocessor transforms, could replace to function(...) ... end
easily enough.
Could people explain what these shorthands bring?
Lua already gives us inline anonymous functions, which are convenient to pass to another function call like foreach
or custom sort functions. We can also define functions directly in tables. We don’t have a problem with self
in the way that javascript has with this
and that is one of the motivations for arrow functions. So what would this add? Saving a few characters?
I do use function shorthand/lambdas pretty frequently in other languages but I'm not sure it necessarily adds anything for pico-8/Lua.
Although a shorthand will save you some characters it's still just building a function underneath so it's not likely to actually save you any tokens and I feel like tokens are generally more precious than characters. That said, if you're using them a lot then the savings in characters might be substantial.
I do think a good shorthand could make code more readable, especially in the built-in editor where line length is severely limited. The shorter the definition the better chance the whole thing can fit onscreen at once. On the flip-side, overuse of lambdas can make code harder to follow but that's true whether the definition is short or not. And you can get the same readability benefits by defining a named function and passing it as a parameter rather than defining it inline. But, again, at the cost of characters and tokens.
If it were to be adopted I would probably go with some variation of the elixir example you gave (fn(p1)-> expression end
) because this is basically already Lua syntax and seems like it would be a fairly easy thing to add. Essentially fn
just needs to be translated to function
and ->
(or whatever other symbol you prefer) translates to return
. Then the whole thing becomes function(p1) return expression end
under the hood.
I don't think this would be a game changer or anything; more of a convenience than a whole new feature. But hey, convenient things are convenient so maybe worthwhile?
True, I don't think a shorthand would be able to save any tokens. I would still like something like this to happen though. I guess I'm just the kind of person who abuses lambda expressions whenever I get the chance to do so; so personally, this will be pretty game-changing for me. I guess the impact of such a shorthand mainly relies on the community - their coding style and their subjective preferences. I guess I am changing the post name to a convenience request rather than a feature request.
Even if shorthands like this didn't save any tokens, they'd still be useful for stuff like Tweetcarts and char-limited game jams.
my 2 cents, but at this point why even keep a lua-like syntax?
not sure the idea of forking pico8 lua even more from stock would help anyone but the most experienced (and multi-language) users.
I agree with @freds72.
Wow. That was unexpected. Somebody shoot me. :) But yeah, clarity has always been important to me in coding, especially when you don't need to compress code. Like for instance I don't even use a+=3
in my normal coding any more and always write it out as a=a+3
for ease of viewing.
On the other end of the stick though this suggested method might open up new TWEET CART methods. Just - don't lose sight of what we already have in current readability.
(please pardon the necro 🙏 but i'm new to pico-8, just spent ~30 mins looking for an answer, and saw you wondering about use-cases, so i figured i'd share 😅)
the main thing i would use this for is iteratee functions. these are rarely more than a short one-off expression passed into another method, but when using the built-in editor, i have to choose between using my keyboard cursor to scroll horizontally, or really skewing my code separation practices (and pushing details off-screen vertically, instead).
most of my life has been working in web dev, and finally gaining the rocket was life-changingly positive (even accounting for readability pitfalls), but i do absolutely understand the desire not to fork too far off lua tho.
and that’s a good thing
multiple statements on 1 line is usually flagged as bad code in professional coding
as per my last email, ahem, forum post, i never said anything about multiple statements on one line, but rather iteratee functions which evaluate and return a single expression.
something as mundane as an anonymous function defined within a foreach is far from the realm of "bad professional coding practices", but in the pico-8 editor, it is in the realm of "offscreen" 😅.
@develleoper can you elaborate on what you're doing? I tried to figure out what an "iteratee function" was on my own, but what wikipedia presents doesn't seem to map cleanly onto what you're describing. If it is what the wikipedia article described then I think your problem is that you're trying to approach things from a direction lua doesn't want you to- lua is not an especially functional language.
Of course at this point it's entirely possible I've just misunderstood, so take this with a grain of salt.
@ReeceGames I think they mean something like this.
--Example using arrow syntax -- Not real won't work in pico-8 objs={"apple", "orange", "banana"} foreach(objs, o->print("i eat a "..o)) --Example using anon function objs={"apple", "orange", "banana"} foreach(objs, function (o) print("i eat a "..o) end) --Example using a named function objs={"apple", "orange", "banana"} function eat(fruit) print("i eat a "..fruit) end foreach(objs,eat) |
As you can see its a significent change to how you write that type of function.
Arrow functions also tend to close over values so basically their a shorthand for an inline function as above, this can be very useful.
I like them a lot but I agree that its moving further away from the lua syntax.
I personal think, that one of the goals of pico8 is, that it should be easy readable. Like the early home computers with build in Basic.
And I must say, that I don't like any of the solutions above. They are really bad to read and to understand, way too cryptic. Without a manual it is nearly impossible to understand, what you do here.
But this:
sum=function(a,b)return a+b end
which is btw the same as:
function sum(a,b)return a+b end
is easy to understand, even when you don't know the lua-language.
When I remember correct, some Basic has a shortcut like:
function(a,b)=a+b
for
function (a,b) return a+b end
@GPI I use both of these last two styles as their what is afforded to me in the language. If arrow functions where a thing I'd only use that style.
Sometimes you just need to do something for every element in a table.
I've got used to languages supporting this type of feature and arrow functions read naturally to me.
Anonymous functions I dislike as they are noisy. If it came to that I tend to perfer the last style.
But each to their own.
@SquidLight, @GPI
Something which can alleviate, though not eliminate, the need for anonymous functions in this kind of iterative context is partial application. It's often the case that we need to use anonymous functions because we need to wrap some other function which takes a different number of parameters. For instance:
function it_takes_two(a, b) -- does stuff with a and b end lst = {1, 2, 3} foreach(lst, function(b) it_takes_two(1, b) end) |
Lua doesn't have partial application by default but we can implement it easily.
function concat(l1, l2) local r = {} foreach(l1, function(l) add(r, l) end) foreach(l2, function(l) add(r, l) end) return r end function partial(f, ...) local args = {...} return function(...) return f(unpack(concat(args, {...}))) end end |
And then the iteration becomes:
foreach(lst, partial(it_takes_two, 1)) |
Which is functionally identical to the first example but:
- Much less line noise
- Pure Lua, no weird language extensions
But of course there are disadvantages.
- You have to spend tokens to define
partial()
(and technically alsoconcat
but I personally find that a generally useful function to have around anyway so I don't really count it.) - It's only useful if you already have a function handy which pretty much does what you want. True on-offs will still need to be anonymous functions or separate named functions.
- It's only useful you're able to fix the first however-many parameters of the handy function you have. If we wanted to fix the second parameter, as below,
partial
doesn't help.
foreach(lst, function(a) it_takes_two(a, 2) end) -- partial() can't handle this |
my solution would be
function ab(a,b,c) print("got "..tostr(a)..", "..tostr(b).." and "..tostr(c)) end function multieach(lst,fn,...) for x in all(lst) do fn(x,...) end end lst={1,2,3} multieach(lst,ab,100,200) |
@SquidLight, thank you for answering, that's exactly what I meant! 🙏
@jasondelaat, I love partial application! (lodash/fp get & set are love) Actually, now that I think about it, it's technically what my ECS does. 🤔 That said, while I will absolutely remember to curry built-in functions with mismatched loop arity, my uses for iteratees so far have been fairly bespoke, and really I was just hoping to help with my horizontal-scrolling struggles.
Oh, and @ReeceGames, I am coming to realize that about Lua. I accept that I'll have to adjust a number of my habits as I become more familiar, tho the disconnect between the arguments foreach loops pass along and my expectations has been the hardest by far. 😅
tl;dr
i would LOVE some form of shorter anonymous method syntax and would materially benefit from it. I understand not wanting to fork Lua too hard, but afaict arguments against it are personal code-style opinion that could be solved by folks just choosing not to use it if added.
> are personal code-style opinion that could be solved by folks just choosing not to use it if added.
Thats not true.
On thing on Pico-8 Cards is, that you can open it and look in the source code. So you need to understand this shortcuts. Also you need to use them, because of the token-limit.
It would not be optional.
Wait how is you choosing to use a feature not optional I don't understand that.
We already have huge chunks of code encoded as strings in several carts which is much less readable that a function expression.
Partially applied functions are something that probably fits better with the FP style of coding, but I do appreciate the suggestion.
Having said this. I'm neither for nor against the suggestion. I can see that I would like and use it but I also get the keeping the purity of the api.
Also given the way the extensions to the language were done in the past I don't see this happening.
see the celeste in 5 tokens for example.
@develleoper
Honestly, most use cases i can see for a FOREACH are covered by a FOR loop- this is to the point that in proper lua, table.foreach is deprecated in 5.1+ What use cases there are are niche- if you have a sequence and don't need to know the table key it's one token lighter than FOR I IN ALL() DO
It also seems to be ever so slightly faster, but we're talking maybe a dozen cycles over more than a dozen items, and that's giving FOREACH a couple advantages, so for the most part the only practical use case I can see for a FOREACH is if you have a function you already use elsewhere, or with internal functions as a quick debugging tool- e.g. foreach(tbl, print)
or foreach(tbl, printh)
I think lua will serve you better with for loops than foreach- and not just because the syntax ends up a lot cleaner- the for loop is much more versatile. Unless you're reaaallly token crunching like a maniac, you're better off with a for loop, I think.
(edit) and to be fair, it's not your fault- lua is weird in a bunch of ways, not just its lack of a clean lambda expression.
[Please log in to post a comment]