- this in turn means we could say that
() -> Stris actually a zero-arg function, because()is no longer a type (or a value), so it could be used exclusively and unambiguously to mean "a function of zero arguments" - which would have nice symmetry with how they're called, e.g. withfoo()
I'm losing track of the conversations a little.. but just want to clarify if we would like to get the () thing in with these changes?
My understanding is that we add support for fn! : () -> ... meaning this is a zero arg function. Then we can use fn!() everywhere and not have the ({})s spattered everywhere.
@Richard Feldman
correct!
I agree, which means this is paired with all of the Dict.empty({})'s having their signatures changed
Yeah so changing the builtins
# from
Dict.empty : {} -> ...
# to
Dict.empty : () -> ...
Working on the new syntax I realized we might be in trouble with zero arg functions on the expression side
Can't we do the same thing that we do where seeing something that is obviously a type, like :, halts expression parsing?
The args for a zero arg closure with the new syntax is || and …. Well there is a certain Binop that also is is ||
Oh, I see
Is this a problem without whitespace parsing?
We might be able to help this with removal of WSA
But I think a closure with new syntax after another expression will require parens
And we’ll have to something about this issue where we try to continue parsing a operator chain regardless of indentation
And that requires removal of |>
Can we change new lambda syntax to \arg1\ … ?
:joy:
JanCVanB said:
Oh! I'm used to deprecated things in Roc being gone within a couple of months. Is this expected to be a long deprecation?
...1 hour later...
Anthony Bullard said:
We might be able to help this with removal of WSA
:laughing: I won't get my hopes up (jk, I know you're brainstorming)
For sale
@JanCVanB 's hopes
never worn
I've gotten to use Roc for longer than 99% of all Elm+Rust-populated universes ever got to see it for. I'm happy no matter what happens. :heartbeat:
We're already on the good Roc timeline.
That Roc exists
That us Rocstars/Rocriders/Roclings already soar so high
Anthony Bullard said:
The args for a zero arg closure with the new syntax is
||and …. Well there is a certain Binop that also is is||
Have we discussed this somewhere? I think we have... but maybe there is another syntax we want.
Like the first thing that pops into my head is why are we including || for a zero arg function. Isn't that just the same thing as an expression?
run! : () => Result {} _
run! =
stream = Tcp.connect!("127.0.0.1", 8085)?
Stdout.line!("Connected!")?
loop!(
{},
\_ -> Result.map(tick!(stream), Step),
)
exec_example! : () => Result {} _
exec_example! =
Cmd.exec!("echo", ["EXEC"])
Effectful functions can be zero args
If you have multiple statments
If you have multiple statements
It’s a thunk
If Odin and JAI can use :: for assignment why can’t we use \\ for function args?
Leaning tower of args just doesn't look as good
I'd much rather change || and && to or and and
Is that an option now? We shot it down before, but that was like 6 months ago
Heres the design discussion
What if we switched logical OR to just |? -- not necessarily proposing that, just would that "fix" this issue?
I guess using keywords and or would also solve the problem -- but that seems like a much bigger change than the intent of \args -> to |args|
what's the problem?
Anthony Bullard said:
Working on the new syntax I realized we might be in trouble with zero arg functions on the expression side
Anthony Bullard said:
The args for a zero arg closure with the new syntax is
||and …. Well there is a certain Binop that also is is||
= || and : || and (|| and |...| || and then || and else || and -> || (in a when) are all unambiguously 0-arg functions, because an expression is valid in those positions but a BinOp isn't
also if || and when || are unambiguous but doomed to be type mismatches later :big_smile:
|| as the final expression in a def is trickier but still unambiguous in that if it were "or" it would be an error ("defs missing final expression")
everything else is "or"
unless I'm missing something, seems like in all the situations where it would be valid as a 0-arg function, it's already invalid as "or"
What is : ||? is that a typo?
which means it's always unambiguously one or the other
like a record field
{ foo: || ... }
I'm going to stop throwing ideas into the wild, and wait for an update from @Anthony Bullard who appears to be looking at this rn.
Well I’m actually reading Harry Potter to my daughter right now, but I’ll be looking at this when the kids are asleep
@Richard Feldman the issue is that to avoid the collision with || is not exactly a small change to the parser
And it’s made harder by the way we parse |> and allow it to be on a newline without an indent
(And WSA makes some things a little more difficult)
my thinking was to special-case it in each of those scenarios
Even with having args it’s hard to have it win a battle with an operator without backtracking and some hackery
e.g. right after a then, eagerly look for exactly || (with optional spaces before it) and if we find it, consume it and continue
then repeat for those other cases
This is the fuzzer input that caused the issue
oar # c
|datfo| ts # ^D
urA
this is an expression?
(the whole code snippet)
The problem is that after “oar” we continue looking for a operator because we don’t demand the indent
right
Yes it’s a Defs
So it wants |d to be a valid operator
do we consider | as an operator in an expression? :face_with_raised_eyebrow:
it is in a pattern but not in an expression
No but because of the lack of indent its expecting us to complete the expression here - and the only thing it could be is an operator
So I need to say “hey in the case that the chomped bytes starts with | we should back out gracefully and try something else
This is actually good we are talking it out because I think I just came up with a plan
I hope it works out in code the way it is in my brain
I think we are going to need to special case the "no indent" behavior to just |>
Taking the above input and making a slight edit:
oar # c
|| ts # ^D
urA
Will always parse as Defs { .., value_defs: [ Stmt(BinOp(Var("oar"), Or), Var("urA")) ], ... }
If we require the min_indent before the operator, we would automatically cut "oar" off as a Expr::Stmt(Var("oar")) and move on correctly
I'd be fine with saying operators always indent
|> used to
(because that's how it's formatted in Elm)
we just talked about it and decided to go with non-indented because it seemed like the extra indentation wasn't necessary
but if there's a reason to require indentation there, I don't see any problem with requiring indentation for all binops and changing the formatting accordingly
Like this right?
echo : Str -> Str
echo = \shout ->
silence = \length -> List.repeat(' ', length)
shout
|> Str.to_utf8
|> List.map_with_index(
\_, i ->
length = (List.len(Str.to_utf8(shout)) - i)
phrase = (List.split_at(Str.to_utf8(shout), length)).before
List.concat(silence((if i == 0 then 2 * length else length)), phrase),
)
|> List.join
|> Str.from_utf8
|> Result.with_default("")
If I recall, I tried to implement that but couldn't figure out how to make the parser happy with the (old) ! operator.
Although I (and a few others I think) had a preference for the |> being on the same line
With static dispatch |> will be gone, so I think the new question would be, how do we format that. Should method calls be indented?
Seems like everyone was pushing for zero indentation last time we discussed it
That’s kind of out of line with the style in every other language I can think of with PNC and methods
But if we are OK with operators requiring indent, the question becomes: how do we cross the bridge? Just rip the bandaid off?
Ie, requiring the indent from here on out and format that way, but current usages of |> and such with no indent breaking?
Actually I guess only || on a newline breaks today
We should maybe start by auto formatting an indent for all binops
On newlines
And only require it for || for now
And then in the future, change to require the indent on newlines for all binops
Yeah, that's kind of what I was thinking
I don't think its possible to make || ever work without the indent
Yeah, not now
Would it work sans WSA?
I just think out of consistency it makes sense to say "if a statement is to span multiple lines, all consecutive lines must be indented"
I don't think so
That sounds generally good to me, but then methods are indented again after the first "start a block indent"
Unless we make an exception for method chains
Like I said above
foo.method(arg)
.other_method(arg)
.third_method(arg)
Would look very strange to the very people that like that Roc has PNC and method
But I think we can allow it for methods
Yeah, but now there's 8 spaces between the start of my_var = and the method dot
I'm okay with it for consistency
But we should consider formatting the first method onto the first line of the expr if we do that
You mean
my_var =
foo.method(arg)
.other_method(arg)
.third_method(arg)
?
I think that's consistent with other languages with methods
To avoid having
my_var =
x
.method(arg)
.other_method(arg)
Hell, that's even consistent with Elm and pipelines with WSA?
Oh
I see, I think we could do that
Ok, so I'm going to start by saying that || has to be indented
Generally yes, but you can definitely do
x
|> first_func
|> other_func
today. You have to write this to avoid that
first_func(x)
|> other_func
And formatted all binops that appear on a newline with an indent
But funnily enough, that allow doesn't solve the motivating problem for this thread
I'm sorry it does for what I put in this thread
But not the issue in #compiler development > New lambda syntax / BinOp contention
Well, maybe it could...
I have the fix
Just running all the tests and then the fuzzer for a bit before I push it
yeah all of those formatting styles look fine to me
I feel like the current indentation rules around binops and . are mostly "we wanted consistent formatting and we picked this way"
but being consistent with these slightly different indentation rules also seems fine
Yeah, this goes back to a discussion I've been trying to start a few times recently: We need our formatter to have some plain english principles spelled out, and then a spec that shows how those principles play out in practice
Maybe we could parse that out of test_fmt.rs if we had a little more rigor and convention in place around the test cases
Luke Boswell said:
Yeah so changing the builtins
# from Dict.empty : {} -> ... # to Dict.empty : () -> ...
Is anyone working on this?
Specifically @Anthony Bullard or @Sam Mohr are either of you?
I think this may be the last thing we've talked about for this set of breaking changes
I can do it when I get home, unless Anthony is free
Irrespective of whether we can unambiguously parse || body as a zero-arg function, in the context of a final expr...
Would || cause beginners confusion?
That was my line of thinking here #ideas > zero-arg functions & sugar @ 💬
But I probably proposed something completely nonsense
Like the first thing that pops into my head is why are we including
||for a zero arg function. Isn't that just the same thing as an expression?
If I'm being honest I actually like the old \args->body syntax better :grimacing:
Zero args aren't terrible there; just \->
Or, I remember we were discussing making function types have parenthesized args, e.g. x : () -> Str, why not use parentheses and arrow for declaring the function too?
e.g.
x : () -> Str
x = () -> "I'm a little teapot"
() has no ambiguities with binops
Isn't it still ambiguous with parenthesized expressions?
And tuples?
No; because -> can never start an expression
We'll initially parse () as an empty tuple, and then see a -> and turn it into the (Pattern) args of a function.
Just like we do for the left hand side of an assignment
I'll let Richard speak to it, since it was his final decision to go this direction, but I think with PNC already landing there is already () fatigue
Yeah, fair
also when the type is () => it's weird if the body is () ->
a nice thing about | for function bodies is that they don't have any arrows in them, so it works equally well with pure and effectful functions
Interesting; I think I missed that part of the discussion around |args| closures
I assume just making the body use => as well was discussed?
I was trying to make an argument - not taken seriously - for \\ instead. I think the strangeness budget issue was a problem
yeah, the concern with having the body use -> or => is that it's basically a chore for everyone to do not because we think it's helpful, but because of a syntax coincidence
But \\ is unambiguous and it's design space I don't think we'd ever be sad for not having for an operator
Joshua Warner said:
Irrespective of whether we can unambiguously parse
|| bodyas a zero-arg function, in the context of a final expr...Would
||cause beginners confusion?
it's been fine in Rust
Oh actually that's a good point.
And I still hold that if some languages can use :: for assignment/declaration \\ for funtion args isn't that weird
I would have guessed it would be a problem in Rust, and then it turned out to just not be a problem at all
@Joshua Warner I just got done upgrading basic-cli if you'd like to see it in the wild. https://github.com/roc-lang/basic-cli/pull/314
The big issue Joshua is the no-indent for binops thing we did
Actually - this isn't a problem in rust at all, because statements in a body are separated by ;
There is zero ambiguity in the rust grammar
Not so for roc
Yes we need indentation for solving the amibiguity
yeah
I don't love that part :/
And we got rid of it for binops
I've always been a "binary op at the end of the line" guy myself
But pipes are different
Last updated: Jun 16 2026 at 16:19 UTC