Proposal:
Introduce syntax sugar to allow abbreviated function definitions for single-variable functions. Currently, if I want to make a little anonymous helper function, e.g. I want a function that cubes the input, I need to write
\x -> x*x*x
which feels bad and has low information density on the screen, but doesn't majorly improve readability and arguably harms it. I propose the following syntax -
\x*x*x
to be valid exclusively for single-argument functions. The body of the function would be the first expression following the slash, assuming that infix operators take precedence over the slash, with the single undefined variable implicitly being the function argument.
While this could be abused by writing absurdly long expressions where the function argument only shows up somewhere in the middle, I do not think this would be a problem in practice. Most long functions are more ergonomically written over multiple lines, at which point most would name it, and at that point you'd probably follow the conventions of how that's written with an explicit argument at top (it'd look very odd otherwise, and looking bad is an underestimated force in policing language use) which would probably be enforced by opinionated IDEs.
It may be worth looking to other places where function definitions can be abbreviated (see also the .element notation for \x -> x.element), but this proposal is designed to be conservative, requiring extremely small changes to the compiler and no fundamental language changes, being mere syntax sugar.
What will you name the variable? Always x?
Or always the first non-const value that appears?
Can you give some concrete examples of where you would expect this to come up? (preferably in both syntaxes)
The first undefined variable in the expression. If more than one appear, then it will be invalid just the same as elsewhere. Given the that the slash will clearly provide a context to the following expression, the compiler error in such a case will be able to clearly explain "You tried to use this syntax but you used multiple arguments", to paraphrase what it might say.
I mean, in many exactly two languages (that I'm aware of) the "unnamed variable" is _
scala, perl
So I was just curious which name for the variable you were proposing.
Brendan Hansknecht said:
Can you give some concrete examples of where you would expect this to come up? (preferably in both syntaxes)
The biggest cases are function composition, and especially functions like List.map. So instead of
sensorOutput
|> List.map \val -> (val*val + 2.0)
you may instead have
sensorOutput
|> List.map \(val*val + 2.0)
I feel that even in this simple case, the syntax sugar is desirable. For far longer backpassing expressions (which I understand is the semi-canonical way Roc does long strings of function composition) this could significantly help readability. The main benefit ergonomically is it takes functions that act like a single expression and makes them visually match that, becoming a single visual unit. This may sound small, but I personally find it helps readability and the change is otherwise minor and very self-contained.
Lakin Wecker said:
I mean, in
manyexactly two languages (that I'm aware of) the "unnamed variable" is_
In Roc, _ means "discard/ignore this", and is generally a wildcard in case matching when we want a default behaviour.
Which is how it's conventionally used in dynamic languages like Python. Part of Roc's design philosophy is trying to make a statically typed, very safe language that feels as light and simple as dynamic languages, so some of the conventions flow from there
Declan Joseph Maguire said:
Part of Roc's design philosophy is trying to make a statically typed, very safe language that feels as light and simple as dynamic languages
Out of all of the languages I've used, Scala does this extremely well. It's basically a functional python
All I can say is that this looks super strange to me, but I get the base sentiment. I don't think I have any real feedback past that
I'm fine with whatever choice. I just wanted to know if you're suggesting that \var * 2 means var is the name of the first variable. And does it work for \2 * var?
Perhaps my pure maths background is showing, but in the formal logic space it's common for expressions with unbound variables to implicitly be universally quantified, whereupon you may substitute concrete values to build concrete propositions. Plus I always just kinda resent the pomp of writing all the initial stuff for super short anonymous functions, which Roc exacerbates due to how wordy a nicely spaced arrow is (4 characters!)
Lakin Wecker said:
I'm fine with whatever choice. I just wanted to know if you're suggesting that
\var * 2meansvaris the name of the first variable. And does it work for\2 * var?
Assuming infix operators have priority, \2 * var ought to be interpreted as \(2*var) and thus be unambiguous.
While I could see this syntax being convenient in some cases, I _really_ like that there is exactly one way to define a function and I would rather that not become two. Even if small, it would then be another unfamiliar thing that a beginner has to look up when learning the language
Second that. Elixir has & &1 * &2, which is an anonymous function that multiples two variables. I use it because it's frequently a nice shorthand, but it's one of those things you have to go over with a new dev some time in their first couple weeks.
I see that argument, however from a visual perspective the slash ends up being the "this is a function definition" marker. Also, in practice it just means that you omit the argument bit and just include the function body, so it can easily be explained as a mere abbreviation.
Elias Mulhall said:
Second that. Elixir has
& &1 * &2, which is an anonymous function that multiples two variables. I use it because it's frequently a nice shorthand, but it's one of those things you have to go over with a new dev some time in their first couple weeks.
Thats why I chose to restrict it to the single variable case. Once you have multiple variables, you need to track the order, which gives janky and unintuitive notation like that. I definitely don't want that added to Roc!
Don't get me wrong, if there's an elegant and consistent way to do that, I'd love to have it. I just think that it already exists, and it's called a regular lambda expression.
I absolutely love _ as lambda shortcut in scala.
someList.map(_.toString)
is so much cleaner than
someList.map(x => x.toString())
Like, why am I forced to name the variable?
_ is a name. Though I do think there's good reason to ask people to use x as the conventional name, just as i j k are conventionally loop count variable in imperative languages. While it would match scala to make it _, it conflicts with existing Roc conventions plus I think it's a magic symbol as far as the compiler is concerned. Also, who wants to write \_*_*_?
Declan Joseph Maguire said:
_is a name.
Yes, and I prefer it over other ones. :grinning:
Like, anyone who's done high school maths will instantly understand "oh, x is our argument/the thing you substitute into".
Lakin Wecker said:
Declan Joseph Maguire said:
_is a name.Yes, and I prefer it over other ones. :D
Yes, but it'd introduce a really nasty inconsistency semantically.
The first undefined variable in the expression.
I believe this would noticeably complicate the desugaring.
I also like _ from scala although I believe we've discussed that before and decided against it.
I can't find the old conversation unfortunately.
Being able to pick a name would be nice. One reason I avoid using the & &1 syntax in elixir is that you can't use a name, and that makes the little functions so much more mysterious. So I usually use the longer fn bar -> foo(bar) end syntax so I can choose a good name unless it's really clear what the argument is. Something that's short and lets you pick a name would be the best of both worlds.
Given lambdas are so short why is this needed? Just \x -> ... pick any single letter variable. Only save 4 characters and a few spaces.
@Anton I don't know if it would change the implications for the desugaring process, but it would be the ONLY undefined variable in the expression body, which would be the first one, because the sugar may only work on single variable lambdas.
@Brendan Hansknecht For me it's more about reading than writing. It turns the function into a single visual unit, or close to, which I find useful when dealing with little helper functions - I like the idea of having an expression, and a function which computes that expression, look basically the same. Plus, I thought it would be easy to implement and have few consequences elsewhere.
However, given more time to think on it, it might introduce a readability problem if people decide to choose their free term to have some long descriptive name that looks like it came from elsewhere - you'd need to stop and think about where the variable names (didn't) come from. This could tip things towards the scala _ thing, where it's always the function argument, although I still don't love it visually or how it conflicts with the use of underscore in match statements. Perhaps that last part could partially resolved by a shift in perspective - not "the place to throw something out", but "an unnamed value", whereever it came from.
I agree with focusing in readability but I think it's easier to read code in general if there's only one syntax for all lambdas, rather than having a convenience that only works in specific conditions. Those conditions have to be learned.
From a compiler dev point of view, I think the trickiest part would be implementing good error messages for when people do something wrong, because it's such an ad-hoc edge case.
The two big cases I see it being nice for, are when you need to pass a trivial operation to something like List.map, and when you have a long chain of pipe operators - it would help the visual flow of "do a thing, do a thing, then this, then this".
@Brian Carroll very true. I thought when I posted this that the benefits well outweighed the harms, but I'm a lot more ambivalent about it now. The reason why I was less worried about it is that I thought of it as an abbreviation, like doing an if-statement on one line when it makes sense. Then again I understand many people loathe that, and other useful tools like continue or return in a loop body
I think it makes simple code a little more compact (stealing the simplicity from it if someone reads it who does no know the syntax). Simple code is good. Compact code feels good to write, but not neccesarily better than simple.
There is also the case where ppl would use it to define their big, 1 arg functions. V8 had to optimize their closures after ES6. They didn't think ppl would write
const topLvlFunc = (a) => {...}
all over their codebase. But they do.
This sugar would not come with performance drawbacks, but it would get abused, just like any other feature. I also really like that top level functions have the same syntax as closures. 1 goto way to do things is nice.
Well damn. I'd have hoped that it's be too awkward for long functions and people would naturally gravitate to the unabreviated version except for little helper functions. But I guess real life experience seals it.
Still annoyed by the lack of compactness, but your ES6 example sounds good enough to throw out my proposal, or at best totally overhaul it.
Honestly breaking it over 2 lines makes it more visually compact, at the cost of hogging a line. Like this
\x ->
x*x*x + 1
looks appealing to me, but not if you stuck it in the middle of a bunch of pipes
I agree with the new line part. This is a good solution if you don't want to pipe.
Hmm. Maybe with some cleverness we can omit the need for extra lines
arbitraryVar |>
funcOne |>
funcTwo |> \x->
x*x*x + 1 |>
finalFunc
Hmm. The lack of alignment on |> looks pretty bad, but I like how it makes the first line obviously feed to somewhere else, while leaving the final function on the bottom without the extra operator. Works decently well with split lambda too.
Definitely would benefit from syntax highlighting. I thought Zulip already had that, I thought I got the syntax codeblock notation right.
So, to complain about the color of the bikeshed, I would do the same as in other languages, except we have |> here and not a dot
arbitraryVar
|> funcOne
|> funcTwo
|> \x -> x*x*x + 1
|> finalFunc
I use elm for syntax highlighting in zulip :)
Just realised my idea looks awful when you have extra arguments.
arbitraryVar
|> funcOne
|> funcTwo auxVarOne
|> \x,y ->
x*x*x + y auxVarTwo
|> finalFunc
Okay that looks decent. The extra variable in the lambda looks kinda jank, but it's a weird scenario anyway (why wouldn't you just bake it into the lambda?). I mean by this point we've lost the ostensible topic of this thread and are just playing with lambdas and pipes.
I like having the function body match where the first argument goes, it makes it 2 spaces without the pipe and 4 with. Or you can just have it always indent by 4 and the pipe case solves itself.
Do we have a good way of destructuring a function output, such that one element is passed to the next function, with others optionally bound to defs for later use? Obviously if you're just getting a single value out you can just use .foo on the record to pull out the foo field, but I'm more curious about the case where you pull other values out for later use.
For tuples you also have the .0 syntax
Otherwise, for tags and such, you would have to use a lambda to destructure.
There is no way to bind a value. I don't think that is possible at all within a pipeline even with a verbose syntax.
Really? That seems like a strong claim. Like I can think of introducing some ugly ad hoc thing to the language that acts as a function but lets you do a binding thing. Something like
arbitraryVal
|> funcOne
|> SPECIAL_THING {$x, y, z:q}
|> funcTwo auxVar
|> fncThree q z
|> finalFunc
Like obviously that's awful, but it's technically a syntax that would allow record destructuring in a pipeline
Here I'm imagining the $ as a special marker that tells SPECIAL_THING which value to pass. Fugly, verbose, but technically possible.
Wait unless you meant not possible at all within Roc's existing syntax
So like you can do:
someVal
|> func
|> .someField
|> OtherFunc
But imagine that you have the same pipeline, but there is also a field .x that you want to bind for use after the pipeline.
That is not possible without breaking the pipeline
Do you mean within Roc as it presently exists, or in general?
someVal
|> func
|> \val ->
# x is a local, fails to bind in a way to escape the pipeline
x = val.x
val.someField
|> otherFunc
I mean anything can be done with changes, though not sure that some way to bind in the middle of a function is likely to get added.
If you wanted to save x in this case, you would have to split the pipeline or use a subpipline to update the other values while keeping around x.
I guess you could also make all following functions just pass through x, ignoring it
I'm not saying add changes, I was just confused because I thought your earlier comment was about pipelines in any concievable form and not just Roc.
While part of me now wants to figure out what would need to be done to make it work, I suspect it'd take some radical changes to the syntax and cost a whole lot to gain a whole little.
To me it kinda feels like a pipeline with side effects, which makes me nervous, but idk, probably not really a concern cause we don't have shadowing, so all names would be newly bound
What's a "prime" in this context? And yes I was only imagining new bindings
Typo, meant pipeline
If I ever think of a good way to do it I'll be sure to post it, but I doubt there's a worthwhile solution. I think part of what bugs me is the assymetry - I can put extra arguments into my function, so why can't I pull them back out of an output?
Totally fair. Yeah, no great way today, just some ok versions.
But nothing really ergonomic in many cases
Probably the most common would just be to break the pipe, save the temporary, and then start a new pipe
I have a lot of experience with diagrammatic notation, mathematics where the elements are boxes with a series of upwards and downwards pointing lines sticking out from it, and lines can be hooked up. Think visual programming, except it's mathematically rigourous and the boxes and lines constitute the mathematics in the same way symbols do in algebra. And when I see a pipe, I see a bunch of boxes hooked together in a row, with optional up-pointing extra legs that can be "contracted" with extra values. But what about when some legs stick down out of intermediate functions?
In a general loopless diagram, it should end up looking like a braid, with some legs terminating in a discard.
Last updated: Jun 16 2026 at 16:19 UTC