Stream: ideas

Topic: Warning for unannotated exposed symbols


view this post on Zulip Sam Mohr (Dec 17 2024 at 18:42):

The static dispatch proposal has been generally accepted as a good move for Roc in providing a more discoverable and user-friendly syntax that requires less provision of context by devs when writing Roc code. Instead of writing out the types you're using:

countValidElements = \allElems ->
    allElems
    |> List.mapTry \elem -> validate elem
    |> List.len

now you'd write:

countValidElements = allElems ->
    allElems.mapTry(.validate()).len()

This is more concise, and pretty legible, but it doesn't tell you want data structure it's acting on. Maybe it's a Dict? It's probably a List because the arg's name starts with "all". This isn't a problem if we annotate the function type.

countValidElements : List a -> U64
countValidElements = allElems ->
    allElems.mapTry(.validate()).len()

With the addition of the CLI command + LSP action for auto type annotations, it'll soon be very easy for devs to get annotations on these functions. Because it'll be so easy to annotate types for top-level defs, I propose we require them to be type annotated by adding warning for exposed top-level defs without a type annotation.

Why not all top-level defs?

It's nice to be able to have "guerilla" private functions, and if a function is private, it's less likely to be important to people reading the code. I'm open to requiring them to be type annotated as well.

Why not have the formatter annotate everything automatically?

While you're writing a function, you may not have decided what type it should return, or may change your mind halfway through. I think this would over-constrain the dev.

Isn't it annoying to have warnings for every function you have defined?

We already have this for unexposed defs. Every top-level def that's not exposed will continuously have a warning on it until it's used by something or exposed from the module. You can fix said warning quickly by exposing the function before writing the body, and you can avoid the type annotation warning by adding the annotation before you write the body as well.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 18:54):

What about intentionally generic function?

Also, in many cases, the lsp will pick an overly generic type if it types the arguments for you

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 18:54):

So the user experience may not be great

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 18:55):

I say this as someone who types most type level functions in anything that isn't a quick script

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:21):

You're right that the LSP will by default annotate the principle type for the function, which is even less sane for static dispatch.

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:22):

Which puts pressure on the user to write a specific type.

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:23):

But that feels more like a problem with what we render inferred types as rather than whether type annotations are good to pressure users to include.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:23):

Yeah, I'm not sure here.

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:24):

I'm having trouble making a type annotation for the above example... I think the type would be:

countValidElements : a -> e
    where
        a.mapTry(b -> c) -> d,
        b.validate() -> c,
        d.len() -> e
countValidElements = allElems ->
    allElems.mapTry(.validate()).len()

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:24):

I'm not sure if this is the equivalent of a good clippy error that should always be on or one of the clippy warnings that it regularly annoying and you would rather turn off.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:24):

Especially given that scripting is a use case for roc

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:25):

Yeah, that type looks right I think

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:27):

Unfortunately, we don't have clippy by design. I know your point is "would people just ignore this by default?" but we've already committed to "we know at least a little bit more than you about what's good for you"

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:28):

Oh, I'm specifically thinking about the too many args for a function clippy warning. Whenever it is hit we just turn it off cause it is not useful.

In this case, typing when we know the exact static type feels fine. But if it automatically generates signatures like the above, it is just an annoyance. I would rather see no signature than the signature above.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:29):

So I guess I would be fine with the formatter automatically adding types to all tope levels if there is no where clause required.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:30):

When a where clause is involved I don't think it will pick a useful signature.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:32):

This all made harder by static dispatch not supporting higher kinded types which limits how nice the signature can be

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:34):

Brendan Hansknecht said:

So I guess I would be fine with the formatter automatically adding types to all tope levels if there is no where clause required.

I don't think we can automatically add type signatures via formatter because people save code while writing it. We could have the formatter overwrite incorrect type signatures with the latest one to fix this, but then people can't say "here's my type, Roc, I'm gonna keep writing until you tell me we agree on that being the right type"

view this post on Zulip Luke Boswell (Dec 17 2024 at 19:35):

I personally like annotating everything.

But I think this specific proposal is in the wait it out and see space for me.

I just don't think we have seen enough roc (certainly in static dispatch format) to know if this is an issue yet, or to really inform the discussion.

So I think it's good to note as a concern, but I don't think we should pre-emptively make it a warning or enything.

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:38):

I would be okay with tabling this until later if there's a metaphorical table to put this on. Do we have a "for later" doc? Maybe https://www.roc-lang.org/plans?

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:48):

My suggestion was more, if there is no type annotations for a top level and the type annotion doesn't need a where clause, add it.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:49):

Wouldnt do anything for a wrong type annotation

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:50):

Though I guess formatting is before type checking....so :shrug:

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:52):

Then we get to the scenario of prematurely annotating things. I expect this is a pretty common case:

countValidElements = elems -> List.mapTry(elems, .validate())

If I save this now, it gets annotated

countValidElements : List a -> List b where ...
countValidElements = elems -> List.mapTry(elems, .validate())

But I didn't finish adding the .count(), which would've made it

countValidElements : List a -> U64 where ...
countValidElements = elems -> List.mapTry(elems, .validate()).count()

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:53):

Ah

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 19:53):

Yeah

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:54):

Hence "warnings are good because they aren't wrong, just annoying"

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:55):

But easily "fixable" with either the auto-added annotation which is verbose but perfectly correct, or manually (which most static languages make you do manually anyway)

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:57):

Brendan Hansknecht said:

Though I guess formatting is before type checking....so :shrug:

This is fixable, but not trivially. We lose a lot of info by the time canonicalization strips that info pre-typechecking, so we'd either need to carry that around (bad for perf), or be able to tie it back to the roc_parse::Ast (lots of detritus)

view this post on Zulip Luke Boswell (Dec 17 2024 at 19:57):

The auto annotation could just be something really dumb like _.

my_fn : _
my_fn = \_ -> ...

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:58):

That option works if we also have a code action along the lines of "realize inferred type" that gives the type that _ is inferring, which we should have anyway

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:58):

But it would defeat the point of this, which is to fix the bottom of the static dispatch boat

view this post on Zulip Luke Boswell (Dec 17 2024 at 19:59):

No real impact for people who dont want to annotate. But still encourages people towards annotate top-level exposed things.

I could imagine a cultural norm forming around that.

view this post on Zulip Sam Mohr (Dec 17 2024 at 19:59):

AKA static dispatch is more terse, but is confusing unless you know the type of the initial state in a pipeline, e.g. the function's arg's types

view this post on Zulip Luke Boswell (Dec 17 2024 at 20:00):

My suggestion I hope still enables frictionless dev wrokflow, but that trends things towards our desired outcome.

view this post on Zulip Sam Mohr (Dec 17 2024 at 20:01):

I would consider my_fn : _ harmful to readability, because devs will be satisfied with a zero info solution. At least no type def at all makes them aware that they don't know the type

view this post on Zulip Luke Boswell (Dec 17 2024 at 20:02):

Yeah, but that _ looks out of place or ugly or embarrassing for anything I'm publishing or sharing.

view this post on Zulip Luke Boswell (Dec 17 2024 at 20:02):

Imagine the code review...

view this post on Zulip Sam Mohr (Dec 17 2024 at 20:02):

You're one of the good ones

view this post on Zulip Sam Mohr (Dec 17 2024 at 20:04):

I think disciplined devs will write good type annotations because they care about communicating with the reader.

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 20:07):

I wonder if allowing no types, but making it easy to add types would be enough. Make it so trivial to add types that people will just do it.

view this post on Zulip Sam Mohr (Dec 17 2024 at 20:08):

If there's no warning, I think lots of people won't do it. Tons of people prefer JS to TS, even if they didn't have to write the code. Some people don't like type annotations

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 20:09):

I think that's fine. We are still raising the floor quite a lot

view this post on Zulip Brendan Hansknecht (Dec 17 2024 at 20:09):

There are always static types in the final app

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:05):

I dunno, this feels like a solution in search of a problem :sweat_smile:

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:05):

like in Elm there's a culture of annotating things and people annotate things and it's fine :shrug:

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:06):

if people want to write quick scripts where they don't annotate things, that also seems fine

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:06):

I'm fine without it. I'm just trying to encourage lazy people to make hard-to-read code more readable

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:07):

And the main problem I'm seeing is that static dispatch without arg annotations can get very hairy

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:07):

well, but as we've seen with advent of code (for example), sometimes you're being lazy because you're writing code you don't intend to maintain

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:07):

So guiding people towards at least annotating arg types helps a lot IMO

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:08):

like not everything Roc will be used for will be a production code base worked on by multiple people, and while I do want to create pits of success, I don't know that we should go out of our way to disallow doing things in a quick-and-dirty way either

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:08):

You can also solve this by requiring the first thing in a static-dispatch method chain to have a qualified module

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:08):

(and I don't think "it's a warning, just ignore it" is reasonable)

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:08):

I'm actually not considering "just ignore the warning" because Roc doesn't either.

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:09):

Since they block builds

view this post on Zulip Anthony Bullard (Dec 17 2024 at 21:09):

Couldn’t a roc version of clippy do this?

view this post on Zulip Anthony Bullard (Dec 17 2024 at 21:09):

For your enterprise use cases :joy:

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:09):

If we had a clippy, I'd much rather this be there

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:09):

But we don't have one by design, I think

view this post on Zulip Anthony Bullard (Dec 17 2024 at 21:10):

Maybe a —nits flag for roc check with verbose diagnostics?

view this post on Zulip Richard Feldman (Dec 17 2024 at 21:10):

at some point I'd like to have a builtin linting feature actually, but mostly for team-specific things - e.g. "we are moving away from A and towards B, so these current uses of A are allowed but nobody is allowed to introduce new A to this code base"

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:12):

If that's planned, then that would make me happy. I still agree that letting people configure all of their lints or suppress them inline isn't great

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:13):

But having "baseline good stuff" and "pedantic" as two lint levels is probably all we need

view this post on Zulip Sam Mohr (Dec 17 2024 at 21:13):

famous last words, maybe

view this post on Zulip Sky Rose (Dec 17 2024 at 21:48):

Then you end up with one pendantic thing you want to enforce and one you don't, so you still end up falling into the pit of wanting per-rule configuration.


Last updated: Jun 16 2026 at 16:19 UTC