Stream: ideas

Topic: zero-arg functions & sugar


view this post on Zulip Luke Boswell (Jan 15 2025 at 23:54):

#ideas > static dispatch - tuple accessors and zero-arg functions @ 💬

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.

view this post on Zulip Luke Boswell (Jan 15 2025 at 23:54):

@Richard Feldman

view this post on Zulip Richard Feldman (Jan 15 2025 at 23:55):

correct!

view this post on Zulip Sam Mohr (Jan 15 2025 at 23:55):

I agree, which means this is paired with all of the Dict.empty({})'s having their signatures changed

view this post on Zulip Luke Boswell (Jan 15 2025 at 23:57):

Yeah so changing the builtins

# from
Dict.empty : {} -> ...

# to
Dict.empty : () -> ...

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:16):

Working on the new syntax I realized we might be in trouble with zero arg functions on the expression side

view this post on Zulip Sam Mohr (Jan 16 2025 at 00:18):

Can't we do the same thing that we do where seeing something that is obviously a type, like :, halts expression parsing?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:18):

The args for a zero arg closure with the new syntax is || and …. Well there is a certain Binop that also is is ||

view this post on Zulip Sam Mohr (Jan 16 2025 at 00:18):

Oh, I see

view this post on Zulip Sam Mohr (Jan 16 2025 at 00:18):

Is this a problem without whitespace parsing?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:19):

We might be able to help this with removal of WSA

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:20):

But I think a closure with new syntax after another expression will require parens

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:20):

And we’ll have to something about this issue where we try to continue parsing a operator chain regardless of indentation

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:21):

And that requires removal of |>

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:22):

Can we change new lambda syntax to \arg1\ … ?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 00:22):

:joy:

view this post on Zulip jan kili (Jan 16 2025 at 00:46):

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)

view this post on Zulip Sam Mohr (Jan 16 2025 at 00:48):

For sale
@JanCVanB 's hopes
never worn

view this post on Zulip jan kili (Jan 16 2025 at 00:50):

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:

view this post on Zulip jan kili (Jan 16 2025 at 00:52):

We're already on the good Roc timeline.

view this post on Zulip Sam Mohr (Jan 16 2025 at 00:52):

That Roc exists

view this post on Zulip jan kili (Jan 16 2025 at 00:53):

That us Rocstars/Rocriders/Roclings already soar so high

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:07):

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?

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:08):

run! : () => Result {} _
run! =
    stream = Tcp.connect!("127.0.0.1", 8085)?

    Stdout.line!("Connected!")?

    loop!(
        {},
        \_ -> Result.map(tick!(stream), Step),
    )

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:10):

exec_example! : () => Result {} _
exec_example! =
    Cmd.exec!("echo", ["EXEC"])

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:11):

Effectful functions can be zero args

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:11):

If you have multiple statments

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:12):

If you have multiple statements

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:12):

It’s a thunk

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:13):

If Odin and JAI can use :: for assignment why can’t we use \\ for function args?

view this post on Zulip Sam Mohr (Jan 16 2025 at 01:14):

Leaning tower of args just doesn't look as good

view this post on Zulip Sam Mohr (Jan 16 2025 at 01:14):

I'd much rather change || and && to or and and

view this post on Zulip Sam Mohr (Jan 16 2025 at 01:15):

Is that an option now? We shot it down before, but that was like 6 months ago

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:16):

Heres the design discussion #ideas > ✔ lambda syntax @ 💬

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:19):

What if we switched logical OR to just |? -- not necessarily proposing that, just would that "fix" this issue?

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:23):

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|

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:27):

what's the problem?

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:28):

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 ||

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:29):

= || 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:

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:30):

|| 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")

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:30):

everything else is "or"

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:31):

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"

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:31):

What is : ||? is that a typo?

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:31):

which means it's always unambiguously one or the other

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:32):

like a record field

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:32):

{ foo: || ... }

view this post on Zulip Luke Boswell (Jan 16 2025 at 01:33):

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.

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:49):

Well I’m actually reading Harry Potter to my daughter right now, but I’ll be looking at this when the kids are asleep

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:52):

@Richard Feldman the issue is that to avoid the collision with || is not exactly a small change to the parser

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:53):

And it’s made harder by the way we parse |> and allow it to be on a newline without an indent

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:54):

(And WSA makes some things a little more difficult)

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:55):

my thinking was to special-case it in each of those scenarios

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:55):

Even with having args it’s hard to have it win a battle with an operator without backtracking and some hackery

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:56):

e.g. right after a then, eagerly look for exactly || (with optional spaces before it) and if we find it, consume it and continue

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:56):

then repeat for those other cases

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:57):

This is the fuzzer input that caused the issue

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:58):

oar # c
|datfo| ts # ^D
urA

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:59):

this is an expression?

view this post on Zulip Richard Feldman (Jan 16 2025 at 01:59):

(the whole code snippet)

view this post on Zulip Anthony Bullard (Jan 16 2025 at 01:59):

The problem is that after “oar” we continue looking for a operator because we don’t demand the indent

view this post on Zulip Richard Feldman (Jan 16 2025 at 02:00):

right

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:00):

Yes it’s a Defs

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:00):

So it wants |d to be a valid operator

view this post on Zulip Richard Feldman (Jan 16 2025 at 02:00):

do we consider | as an operator in an expression? :face_with_raised_eyebrow:

view this post on Zulip Richard Feldman (Jan 16 2025 at 02:01):

it is in a pattern but not in an expression

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:01):

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

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:02):

So I need to say “hey in the case that the chomped bytes starts with | we should back out gracefully and try something else

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:03):

This is actually good we are talking it out because I think I just came up with a plan

view this post on Zulip Anthony Bullard (Jan 16 2025 at 02:06):

I hope it works out in code the way it is in my brain

view this post on Zulip Anthony Bullard (Jan 16 2025 at 03:18):

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")) ], ... }

view this post on Zulip Anthony Bullard (Jan 16 2025 at 03:32):

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

view this post on Zulip Richard Feldman (Jan 16 2025 at 03:37):

I'd be fine with saying operators always indent

view this post on Zulip Richard Feldman (Jan 16 2025 at 03:37):

|> used to

view this post on Zulip Richard Feldman (Jan 16 2025 at 03:37):

(because that's how it's formatted in Elm)

view this post on Zulip Richard Feldman (Jan 16 2025 at 03:38):

we just talked about it and decided to go with non-indented because it seemed like the extra indentation wasn't necessary

view this post on Zulip Richard Feldman (Jan 16 2025 at 03:38):

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

view this post on Zulip Luke Boswell (Jan 16 2025 at 03:40):

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.

view this post on Zulip Luke Boswell (Jan 16 2025 at 03:41):

Although I (and a few others I think) had a preference for the |> being on the same line

view this post on Zulip Kilian Vounckx (Jan 16 2025 at 08:46):

With static dispatch |> will be gone, so I think the new question would be, how do we format that. Should method calls be indented?

view this post on Zulip Sam Mohr (Jan 16 2025 at 09:32):

Seems like everyone was pushing for zero indentation last time we discussed it

view this post on Zulip Anthony Bullard (Jan 16 2025 at 10:50):

That’s kind of out of line with the style in every other language I can think of with PNC and methods

view this post on Zulip Anthony Bullard (Jan 16 2025 at 10:52):

But if we are OK with operators requiring indent, the question becomes: how do we cross the bridge? Just rip the bandaid off?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 10:53):

Ie, requiring the indent from here on out and format that way, but current usages of |> and such with no indent breaking?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 10:54):

Actually I guess only || on a newline breaks today

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:01):

We should maybe start by auto formatting an indent for all binops

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:01):

On newlines

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:01):

And only require it for || for now

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:02):

And then in the future, change to require the indent on newlines for all binops

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:02):

Yeah, that's kind of what I was thinking

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:02):

I don't think its possible to make || ever work without the indent

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:02):

Yeah, not now

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:03):

Would it work sans WSA?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:03):

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"

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:03):

I don't think so

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:05):

That sounds generally good to me, but then methods are indented again after the first "start a block indent"

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:05):

Unless we make an exception for method chains

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:06):

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

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:07):

But I think we can allow it for methods

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:07):

Yeah, but now there's 8 spaces between the start of my_var = and the method dot

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:07):

I'm okay with it for consistency

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:07):

But we should consider formatting the first method onto the first line of the expr if we do that

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:08):

You mean

my_var =
    foo.method(arg)
        .other_method(arg)
        .third_method(arg)

?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:08):

I think that's consistent with other languages with methods

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:08):

To avoid having

my_var =
    x
        .method(arg)
        .other_method(arg)

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:09):

Hell, that's even consistent with Elm and pipelines with WSA?

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:09):

Oh

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:09):

I see, I think we could do that

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:10):

Ok, so I'm going to start by saying that || has to be indented

view this post on Zulip Sam Mohr (Jan 16 2025 at 11:10):

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

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:10):

And formatted all binops that appear on a newline with an indent

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:11):

But funnily enough, that allow doesn't solve the motivating problem for this thread

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:12):

I'm sorry it does for what I put in this thread

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:13):

But not the issue in #compiler development > New lambda syntax / BinOp contention

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:14):

Well, maybe it could...

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:52):

I have the fix

view this post on Zulip Anthony Bullard (Jan 16 2025 at 11:52):

Just running all the tests and then the fuzzer for a bit before I push it

view this post on Zulip Richard Feldman (Jan 16 2025 at 12:56):

yeah all of those formatting styles look fine to me

view this post on Zulip Richard Feldman (Jan 16 2025 at 12:56):

I feel like the current indentation rules around binops and . are mostly "we wanted consistent formatting and we picked this way"

view this post on Zulip Richard Feldman (Jan 16 2025 at 12:57):

but being consistent with these slightly different indentation rules also seems fine

view this post on Zulip Anthony Bullard (Jan 16 2025 at 13:58):

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

view this post on Zulip Anthony Bullard (Jan 16 2025 at 13:59):

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

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:04):

Luke Boswell said:

Yeah so changing the builtins

# from
Dict.empty : {} -> ...

# to
Dict.empty : () -> ...

Is anyone working on this?

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:04):

Specifically @Anthony Bullard or @Sam Mohr are either of you?

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:05):

I think this may be the last thing we've talked about for this set of breaking changes

view this post on Zulip Sam Mohr (Jan 17 2025 at 02:09):

I can do it when I get home, unless Anthony is free

view this post on Zulip Joshua Warner (Jan 17 2025 at 02:56):

Irrespective of whether we can unambiguously parse || body as a zero-arg function, in the context of a final expr...

Would || cause beginners confusion?

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:57):

That was my line of thinking here #ideas > zero-arg functions & sugar @ 💬

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:57):

But I probably proposed something completely nonsense

view this post on Zulip Luke Boswell (Jan 17 2025 at 02:58):

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?

view this post on Zulip Joshua Warner (Jan 17 2025 at 02:59):

If I'm being honest I actually like the old \args->body syntax better :grimacing:

view this post on Zulip Joshua Warner (Jan 17 2025 at 02:59):

Zero args aren't terrible there; just \->

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:00):

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?

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:01):

e.g.

x : () -> Str
x = () -> "I'm a little teapot"

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:01):

() has no ambiguities with binops

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:02):

Isn't it still ambiguous with parenthesized expressions?

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:02):

And tuples?

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:02):

No; because -> can never start an expression

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:03):

We'll initially parse () as an empty tuple, and then see a -> and turn it into the (Pattern) args of a function.

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:03):

Just like we do for the left hand side of an assignment

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:04):

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

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:05):

Yeah, fair

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:05):

also when the type is () => it's weird if the body is () ->

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:06):

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

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:06):

Interesting; I think I missed that part of the discussion around |args| closures

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:07):

I assume just making the body use => as well was discussed?

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:07):

I was trying to make an argument - not taken seriously - for \\ instead. I think the strangeness budget issue was a problem

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:07):

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

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:08):

But \\ is unambiguous and it's design space I don't think we'd ever be sad for not having for an operator

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:08):

Joshua Warner said:

Irrespective of whether we can unambiguously parse || body as a zero-arg function, in the context of a final expr...

Would || cause beginners confusion?

it's been fine in Rust

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:09):

Oh actually that's a good point.

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:09):

And I still hold that if some languages can use :: for assignment/declaration \\ for funtion args isn't that weird

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:09):

I would have guessed it would be a problem in Rust, and then it turned out to just not be a problem at all

view this post on Zulip Luke Boswell (Jan 17 2025 at 03:09):

@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

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:09):

The big issue Joshua is the no-indent for binops thing we did

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:09):

Actually - this isn't a problem in rust at all, because statements in a body are separated by ;

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:09):

There is zero ambiguity in the rust grammar

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:10):

Not so for roc

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:10):

Yes we need indentation for solving the amibiguity

view this post on Zulip Richard Feldman (Jan 17 2025 at 03:10):

yeah

view this post on Zulip Joshua Warner (Jan 17 2025 at 03:10):

I don't love that part :/

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:10):

And we got rid of it for binops

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:11):

I've always been a "binary op at the end of the line" guy myself

view this post on Zulip Anthony Bullard (Jan 17 2025 at 03:11):

But pipes are different


Last updated: Jun 16 2026 at 16:19 UTC