Another nice lil functional language I came across recently is ante: https://antelang.org/docs/language/. It is way bigger than roc, but it has a couple of tasteful design choices. My favorites were:
map data (_ + 2)
.filter (_ > 5)
.max!
match foo
| Some bar -> print bar
| None -> ()
the |> is spelled . has interesting type system implications - I'm not sure how that could be implemented without either adding higher kinded types to the language or else giving up decidable principal type inference :thinking:
e.g. if you put this into roc repl today, it infers that arg is a record which stores a lambda in its map field:
\arg -> arg.map
If it requires a space before it, that shouldn't be the case, right?
Or new line
but it seems like in ante, arg is allowed to be an opaque type
oh...interesting
https://antelang.org/docs/language/#member-access-traits
interesting!
how does that work with a function that takes the trait type as an argument?
e.g. .map
I guess it would generate a trait and then say one of the type variables has to have to have it? :thinking:
that feels equivalent in power to higher-kinded types, but I could be missing something :big_smile:
or if not, I'm not sure how .map would work in practice
like if inside the same file, I wrote strings.map \before -> "\(before)!!!" and also nums.map \num -> num + 1
is it possible that the type of .map could not be higher-kinded? 🤨
well it's only dealing with first-kinds, I don't see how higher kinds play into this? As I understand it, x.f is syntax sugar (in Roc) for either x |> f or x |> .f where .f is the record access function \x -> x.f. So the point of ambiguity is which of those functions is available, but the type system can determine that either that's ambiguous or only one resolution exists
right but this is for opaque types, not records
which...I think makes a difference?
maybe not?
Oh I see. I guess the difference is, can the language find a unique resolution of .map for some type. But you don't need higher kinds for that
Personally I don't think that . is easier to read than |>, I think the "is this a record access or dispatch to a function?" question that you may need to consider makes it more difficult to grok from the Roc perspective
OTOH I could see how if your language does already have higher kinds, this provides a convenience
Tuple are coming to Roc soon! Not as an explicit syntax sugar for Pair x y though, if I recall correctly
here's an example of what that style might look like in Roc: https://gist.github.com/rtfeldman/4d7cad972e77fc7cd68a816239a11c2b
not sure how this would translate:
response
|> Str.split "\n"
|> List.mapTry movieFromLine
|> Task.fromResult
I don't think it could be this:
response
.split("\n")
.mapTry(movieFromLine)
.fromResult()
because fromResult would be associated with Task, which isn't involved in anything in the previous chain
unless maybe it does the type resolution based on type inference, since the function is returning Task
but if it's doing things like that, can you still have decidable principal type inference? :thinking:
yeah, I think as long as you can infer the output type that would work
yes
huh, interesting!
Well, I guess it depends how you're considering this feature
so I guess then theoretically you could even convert
path = Path.fromStr "output.json"
to
path = "output.json.".fromStr()
Like if split and mapTry and fromResult are in scope, from List, Result, and Task respectively, the answer is yes. But if all of those have to become abilities, it starts to become a bit wavier
I think using . as a binary operator in a functional language goes against the widely spread usage of . in existing functional languages for function composition, which is a right-to-left operation.
well here it's more like . as in field access
it's not like the . is its own operator, really
it is in haskell
I see. It's the thing which is done in Ruby and Rust, where method dispatching happens on the thing that the result of the previous operation happens to be.
I work with a lot of this kind of code in my day job, and I have found it consistently harder to read than code that is explicit with the modules. (e.g. a pipeline such as in Elixir or current-day Roc). Especially in the presence of traits it is no longer obvious where to look for a particular function.
is . as composition actually widespread?
e.g. Clean uses the letter o for it
and many newer languages pick >> and <<
yeah agreed, Marten
like compare:
response
|> Str.split "\n"
|> List.mapTry movieFromLine
|> Task.fromResult
vs
response
.split("\n")
.mapTry(movieFromLine)
.fromResult()
one of these things is more self-documenting than the other :big_smile:
also more verbose, to be fair
Yes, that is the trade-off
Folkert de Vries said:
is
.as composition actually widespread?
This is a good question to which I do not know the answer :blush:
another thing I prefer about |> is that the rules for when to use parentheses are more complex in the other style, unless you always require them (which is not ideal either)
I don't think verbosity is a bad thing here. And the nice thing is it's flexible - as a Roc author you could import split, mapTry, and fromResult unqualified, which would be similar to the . access version in terms of verbosity
e.g. because you can omit parens for foo.bar baz but need parens for foo.bar() because otherwise it's ambiguous whether you're trying to access the bar function or call it
also, to me, something like response.isError is quite ambiguous as a reader - is that trying to access the isError field on a response, or call isError response?
I guess that could be solved with response.isError() though, nevermind
right, although that gets a bit weird with backpassing
yeah
because you need to end lines in .await rather than .await() even though normally you'd add () to invoke the function
as another perspective, I could see the .foo call syntax being confusing to new Roc developers, since that style is traditionally associated with dispatch over objects and OOP-like styles. But Roc doesn't have objects in the sense that Rust/Ruby/etc. do
Folkert de Vries said:
is
.as composition actually widespread?
Haskell and friends use it to great success. See: point free functions, Lens. What i see here is |> is like Haskell's $application operator, not . function composition. I do not believe we should use . for the type system reasons/difficulties and because it would be confusing for programmers migrating from other pure functional langs.
I mean the specific use of the dot character . to denote function composition. Many languages have function composition, but I think only haskell actually uses . as the "name" for that operator
Would it not be this in Roc:
response
.Str.split "\n"
.List.mapTry movieFromLine
.Task.fromResult
If we were directly converting from the |> syntax
imo borrowing the concept of "objects with methods" from rust (even if just as syntax sugar for regular functions) would be a detractor from the simplicity you get from functional languages...
thinking about Elm, one of the best things about it is that there is virtually no syntax sugar magic so people learn the basics and - poof - everything just builds upon it.
as soon as we open this gate, people start to assume other magic are possible and so they lose the confidence in understanding everything they're seeing.
Last updated: Jun 16 2026 at 16:19 UTC