From what I can tell, Roc function call arguments are separated with whitespace, however, arguments in a function definition, and items in lists and records are separated with commas. Why are these different? I understand that not having commas in definitions, lists and records would be more annoying because there would need to be parens instead, but why not keep everything uniform and have commas for function call arguments as well?
great question! It's mainly because of what happens when function calls happen inside lists - for example, in our static site generator for roc-lang.org:
today, this can be:
a [id "home-link", href "/", title "Roc"] [rocLogo]
...but if we required commas to separate arguments in function calls, then it would have to be this instead:
a [(id "home-link"), (href "/"), (title "Roc")] [rocLogo]
...because the first way would become ambiguous; we couldn't tell if [id "home-link", href
meant "I want to call id
passing "home-link"
and href
in the usual comma-separated way" or "I want to call id
passing "home-link"
and then href
begins the next element in the list"
that said, I actually think it's an interesting idea to revisit that decision
originally I assumed that pattern would come up often (because it came up all the time in Elm, specifically for DSLs like this) but to date that one specific DSL is the only place I've seen this come up :big_smile:
actually tuples would be another place - I don't have an example to link to offhand, but for example (foo bar, baz blah)
would have to become ((foo bar), (baz blah))
we haven't tried it, but another thing that's possible in the HTML DSL is to use optional record fields - in which case the above code could be:
a { id: "home-link", href: "/", title: "Roc" } [rocLogo]
although the same consideratoin happens for the list of child nodes - e.g. https://github.com/roc-lang/roc/blob/7b1f2d2ac1f6e0b7a283cfbe932318f37a0a6861/www/wip_new_website/main.roc#L109
would go from this:
div [id "top-bar-links"] [
a [href "/wip/tutorial"] [text "tutorial"],
a [href "/wip/install"] [text "install"],
a [href "/wip/community"] [text "community"],
a [href "/wip/docs"] [text "docs"],
a [href "/wip/donate"] [text "donate"],
],
...to this:
div [id "top-bar-links"] [
(a [href "/wip/tutorial"] [text "tutorial"]),
(a [href "/wip/install"] [text "install"]),
(a [href "/wip/community"] [text "community"]),
(a [href "/wip/docs"] [text "docs"]),
(a [href "/wip/donate"] [text "donate"]),
],
also worth noting that the optional record fields design raises the design question of what to do with custom attributes (such as data-
attributes) - might need a custom:
field which takes a list of tuples or something
all that said, I could see an argument for the extra commas being worth it for other reasons (e.g. readability, being able to pass if
s or when
s as middle arguments without needing parens, etc.)
actually https://github.com/roc-lang/roc/blob/98df325d76b6b743c6b01f399bbbeaaf0d8108fa/www/wip_new_website/main.roc#L107 is a better example of the downside, would need to have parens across multiple lines if homeLink
was moved ahead one spot:
nav [ariaLabel "primary"] [
(div [id "top-bar-links"] [
(a [href "/wip/tutorial"] [text "tutorial"]),
(a [href "/wip/install"] [text "install"]),
(a [href "/wip/community"] [text "community"]),
(a [href "/wip/docs"] [text "docs"]),
(a [href "/wip/donate"] [text "donate"]),
]),
homeLink,
],
so in general DSLs that wanted to use function calls to make the elements would get a lot more parens!
I see, that makes sense.
I'm looking over the syntactic design choices of programming languages and this particular choice in Roc seemed quite interesting, the lack of "uniformity" does seem to be massively outweighed by the improvements to succinctness.
Thanks for your reply!
absolutely, thanks for the question! :smiley:
Personally, I would rather consider removing commas from the type definition than adding them to the function call site.
That said, I'm pretty sure that would lead to a mess and don't currently have a good alternative.
I guess we could remove commas and require parens around more complex types just like with nested function calls currently. I wonder how that would look.
In this case. Could u just use a semicolon as the separator? Are the only conflicts because you want to use the same symbol for separation in lists?
A few examples of instead changing the type to match the calling syntax. Tried to pick more complex looking signitures from current roc code:
Note, I left off parens around the output type given it is never ambiguous, but they could be added
update : (Dict k v) k ([Present v, Missing] -> [Present v, Missing]) -> Dict k v where k implements Hash & Eq
write : (List U8) Stream -> Task {} [TcpWriteErr StreamErr]
andMap : (Parser a) (Parser (a -> b)) -> Parser b
walkTryHelp : (List elem) state (state elem -> Result state err) Nat Nat -> Result state err
Could u just use a semicolon as the separator? Are the only conflicts because you want to use the same symbol for separation in lists?
Yes, though I don't think many people would want different separates that they have to remember based on context.
I do like the way lean has type annotations, very clean, but can get long sometimes. The parenthesis do make things very clear and organized though.
Brendan Hansknecht said:
Could u just use a semicolon as the separator? Are the only conflicts because you want to use the same symbol for separation in lists?
Yes, though I don't think many people would want different separates that they have to remember based on context.
Yeah thats kinda annoying if all you're getting is that the call site has commas.
Another way to use commas for function calls would be to use f(x, y, z)
syntax, which would be more familiar to people coming from imperative languages. Is there a benefit unique to functional programming that causes what seems like every functional language (Elm, Haskell, Lean, Roc, etc) to use f x y z
syntax instead?
I suppose the juxtaposition syntax makes a lot of sense in curried languages. But Roc isn't curried
yeah Roc uses it because of its practical benefits (in terms of avoiding parentheses in DSLs like the above example), not because of consistency or conceptual elegance :big_smile:
is unparenthesized if
or when
in the middle of a list a real win though? I'd be a bit concerned about readability in such a case, and in more recent years, I've tended to favor breaking down complex expressions into simpler ones by using intermediate variables.
Yes, naming things is hard, but also, to some extent, if it's hard to give a reasonable name, then it might not be the best way to achieve what you're trying to build.
I have found commas to be a poor visual delimiter for use in complex expressions... It's great for a list of numbers or a list of strings, or even a list of lists, but strings and lists have their own, stronger delimiters. In terms of function calls, more than a few times I've been confused about which tokens are arguments to a call, and which are elements in the list.
These are certainly hard tradeoffs to make. I do rather appreciate the style choice of (f x)
over f(x)
when parens are needed to avoid ambiguity. I do greatly prefer writing f : (List x) y
instead of f : List x, y
because the former is more approachable and less visually ambiguous, and because non-trivial parameters will require parens anyways.
Side note: I'd certainly be happy writing f : (List x), y
but I'd probably parenthesize all trailing params if any of the preceding required it, just for balance and visual clarity, i.e. f : (List x) (y)
I think the upside is much more for function calls than if
or when
, e.g.
Richard Feldman said:
actually https://github.com/roc-lang/roc/blob/98df325d76b6b743c6b01f399bbbeaaf0d8108fa/www/wip_new_website/main.roc#L107 is a better example of the downside, would need to have parens across multiple lines if
homeLink
was moved ahead one spot:
2 messages were moved from this topic to #beginners > compiling for wasm by Brendan Hansknecht.
hmmm. yeah, that's true. Some DSLs seem to benefit from this a lot, and for others, it may be more neutral either way
Last updated: Jul 06 2025 at 12:14 UTC