Inspired by the recent discussion about backpassing, I wrote a Gist exploring different ways of calling functions. It helped me get a clearer picture of the pipe operator and backpassing, and I'd be glad if it can help others too!
I'd also be interested in further thoughts you have on the topic.
that all looks accurate to me! :100:
btw regarding this:
At the time of this writing, executing
callbackPipe2leads to a run-time error, but executing the originalcallbackPipedoes not.
would you mind opening an issue for that? :smiley:
Will do!
I removed the footnote because the error had nothing to do with callbackPipe2, just with combining the results of the different definitions. I created a new issue with a minimal example program.
awesome, thank you!
Hi guys, regarding function definition vs function calls I think it not very symetric (and can be confusing) that functions parameters are separated by comma, but called without comma...
This is definitely something you get used to pretty quick, but I definitely can see the want for it to be symmetric.
I think the current trade off is kind of (maybe not totally intentionally) built to avoid extra parenthesis and aid in readability.
If you remove the commas from a type definition, you have to add parens around complex types
base:
func: \List U8, T Nat Str, Str -> U8
would be:
func: \(List U8) (T Nat Str) Str -> U8
I think this second example is very strange to read.
If on the other hand, you add commas to calling a functions, it requires extra parens, can be confused with tuple and record syntax, and looks really strange with pipeling.
base:
x =
Str.joinWith ["test", "one", "2", "three"] ", "
|> Str.concat "\n some new line"
y = {
val1: Num.addWrap 3 7,
val2: SomeTupleFunc (3, 4, 5) "test",
val3: (List.repeat 0 8, x),
}
would become:
x =
Str.joinWith(["test", "one", "2", "three"], ", ")
|> Str.concat("\n some new line") # This really feels like Str.concat takes only 1 param
y = {
val1: Num.addWrap(3, 7),
val2: SomeTupleFunc((3, 4, 5), "test"),
val3: (List.repeat(0, 8), x),
}
Of course you could still do this without paren, but that is also strange:
x =
Str.joinWith ["test", "one", "2", "three"], ", "
|> Str.concat "\n some new line"
y = {
val1: Num.addWrap 3, 7, # Should a third param go here or is this the next record field
val2: SomeTupleFunc (3, 4, 5), "test",
val3: (List.repeat 0, 8, x), # Does List.repeat take 1, 2, or 3 params. No clear way to tell.
}
Note: tuple syntax is not in the language yet, but is a planned feature.
FWIW I don't currently love the space-delimited call syntax, and I think I'd actually prefer if the syntax were the same as rust/java/c/etc function calls - funcName(arg1, arg2, arg3). Maybe it'll grow on me some day. I think having commas on the def side but not on the call side does make it particularly awkward.
Spaces for applying functions work well for the curried languages (from which ROC borrow it's historical origins) but don't have that much sense for the uncuried ones and is much harder to grasp for users of mainstream languages. I would be quite cautious when mixing different ideas from different language families since having simple consistent syntax is very important for new users to immediately get a language and not to run away searching for the other one in abundant space of new languages. I think Koka (from which ROC borrowed some implementation ideas - Perceus Reference Counting etc.) get that very well...
On the other hand, I understand that if ROC tries to be also good shell language that breaking that convention is very hard. I've spent a lot of time thinking how to make the best universal syntax for both of these worlds of programming and shell scripting and it's very hard to come up with something that is simple and consistent...
Advantage of the parrens for separating complex inner expressions are that they are needed just in case there is complex inner expresion and not all the time but it's harder to make such simple consitent rule for separators (as opposed to wrappers)...
As a note, i am not sold to the current syntax or saying it should stick around. Just noting why I think it currently is in the form it is.
Personally, I think it would be a great idea to remove the commas from type definitions and function definition. I just don't know how to do that in a way that doesn't lose readability.
On the other hand, after writing a significant amount of roc, i definitely do not want commas in functions calls or a regular c style call syntax.
Related aside, i think i figured out why I dislike this syntax:
func: \(List U8) (T Nat Str) Str -> U8
Even though it isn't possible in roc, i want to read the input as one type somehow. Either like some sort of higher kinded type where (List U8) would be a type that takes two other types as type variables. Similar to writing:
func: \Dict (T Nat Str) Str -> U8
This second example is valid but is only one input type. It is a function that takes a dictionary with a key of T Nat Strand value of Str. The function returns a U8.
Otherwise, i kinda want to read it as a type level function. List is a function that takes U8. It then returns a function that takes (T Nat Str) Str to define the final input type.
Yes, I totally understand that, I personally very much like the idea to have a space separated call syntax so that it's compatible with the shell (where most beginner programmers used to start (I don't know how it is today though :)) But space separated call syntax and space separated type parametrization syntax is probably too much :). Regarding that type parametrization is something totally different than function application, maybe separating those two syntaxes makes more sense...
And it would still make a great shell language, since when using the shell, you mostly just call things but don't that often define new things (where you would use types or constructors)
Because if you don't strictly limit the use of space separated syntax for evaluation of some kind, it would be very hard for newcommers to know where to stop following that rule and don't get lost in mental evaluation :)
IMO trying to be both a good shell language and a good general purpose language is basically impossible.
I think there’s fundamental incompatibility there
I don't think it's impossible, if you take e.g. haskell it's quite compatible with the shell syntax...
But even a good shell language don't have to follow the current convetions, sometimes convetions must be broken to make some progress happen...
It's just that shell syntax must be concise and simple enough (without much use of puntuation etc), so that non-programmers wouldn't be scared of it :)
At least for usual stuff like executing a program or calling a function (which should definitely look the same for them (at least in some shell sublanguage), so that they don't have to think about the difference)
Last updated: Jun 16 2026 at 16:19 UTC