I'm really impressed by the syntax. I have yet to find a piece of syntax I wouldn't like. Labelled arguments for example are delightful:
pub fn replace(
in string: String,
each pattern: String,
with replacement: String,
) {
// The variables `string`, `pattern`, and `replacement` are in scope here
}
replace(in: "A,B,C", each: ",", with: " ")
> The pipe operator will first check to see if the left hand value could be used as the first argument to the call, e.g. a |> b(1, 2) would become b(a, 1, 2).
Optimizing for one less _ typed is a really bad trade-off in terms of clarity here.
Gleam did used to have this, but the community collectively decided that it was actually quite annoying in practice to always have to write `_, ` after every function
I actually agree, this is the most questionable piece. I prefer (and have argued for in JS) Hack-style pipes, where the value "placement" always has to be specified.
That allows arbitrary expressions on the RHS of the pipe, so for example this would be valid Gleam:
Gleam doesn't have auto currying! Often folks find this (currying) a bit confusing to wrap their head around when learning functional programming and we don't feel like it really affords you much that couldn't already be achieved with just a little bit more work by the programmer.
Applicative builder APIs are the only thing we've found would be much much better if we had auto currying.
Having shipped code in nearly two dozen different programming languages, there were very few times when I wanted my internal parameter name to be different from my external parameter name. The reasons are very simple:
- if your function is simple, you just use the params immediately, like in your `replace` example. It doesn't matter what they are called as long as their names make sense
- if your function is not simple, the parameters are usually immediately transformed into something else (mapped to different data, split to different data etc.), and their original name doesn't matter.
So this leaves a small weird case of "somehow parameter names don't make sense for the caller of the function", and I can't for the life of me come up with such an example (the example with replace in Gleam docs is not convincing, to say the least).
Two dozen programming languages shipped in or not, almost no languages support this.
It is something that you start appreciating a lot more once you have used it significantly. It helps a lot with writing more self-documenting interfaces that are legible at a glance.
Another detail I did not mention is that _public names are part of the interface_. This means that two functions that differ only in the public names are distinct overloads.
To give an extremely simple example (taken from Apple's docs):
This starts becoming even more valuable when you have more complex objects, and allows you to move (e.g. on some kind of repository class) mangling of overload names into more readable named argument overloads.
Does it? Instead of a single parameter you now have two names, and a type info to boot
pub fn replace(
in string: String,
each pattern: String,
with replacement: String,
) {
// The variables `string`, `pattern`, and `replacement` are in scope here
}
replace(in: "A,B,C", each: ",", with: " ")
When argument variable names and labels are combined you have two problems:
1. Labels are accidental. If all arguments automatically create labels then the programmer has not considered and deliberately designed the API. Gleam is designed to make sure that APIs are always carefully thought about and designed as appropriate.
2. Renaming a variable becomes a breaking change. We don't believe that renaming a variable should ever result in a semver major version bump.
3. Since Gleam doesn't enforce labels or check labels in any way, it doesn't "make sure APIs are carefully thought out". Labels will be just as random and accidental as parameter names following the whims of the programmer who decides to use them
1. Every class/method/function/whatever declaration is an API, doesn't matter if it's internal or which language it's written.
2. Why do you think it's better? It looks like it tries to cater to everyone, and leads to inconsistent API usage and requires teams to agree on linting rules and use tools to enforce consistent coding standards.
3. It's a tool for _you_ to design good APIs that are clear. Languages without named arguments don't give you this tool.
> So does renaming a label.
In languages without this feature, any renaming breaks the API, unlike the ones with distinct internal and external names. This is not the same.
the idea is that within the function body you would rather refer to the variables as e.g. `for s in string.match(pattern)` than `for s in in.match(each)`
In this example, the local variables are `string`, `pattern`, and `replacement`, and are implementation details; only the names `in`, `each`, and `with` are part of the public API. Renaming the local variables doesn't break anyone. Renaming the labels would break callers, just as changing the names of keyword arguments in other languages.
Without labels—i.e., with only traditional positional arguments—the example would simply be a function taking three string parameters:
replace(String, String, String)
Of course, the documentation should specify which arguments are which, but still at call sites it would be easy to accidentally permute the arguments:
replace("https://[^/]*", "", url) // wrong!
(In this case, the problem is exacerbated since different languages/libraries pick different orders here! For instance, the incorrect ordering above is correct for Python's `re.sub`, so it's easy to see how mistakes might arise.)
Labels solve this problem by making the binding explicit at the call site:
replace(each: "https://[^/]*", with: "", in: url) // now correct!
(Looking at the Gleam docs [1], it seems that labeled arguments "can be given in any order".)
Now, how does the implementation of this function look? The obvious first approach is to require the labels to be the same as the local variable names, but this often leads to awkward "grammar", because (in the convention of Gleam and some other languages, like those in the Smalltalk/ObjC/Swift heritage) good label names tend to be more like prepositions, whereas variable names like to be nouns:
Isn't this solvable just by using types and instances rather than just calling top level/static functions? If you have a "replace" function off of String and use a Pattern type instead of a raw string, the compiler will enforce the correct type and ordering.
val p = Pattern("https://[^/]*")
val in = "https://google.com/some/path"
val path = in.replace(p, "") // /some/path
In fact this specific example is solved just by having a dedicated type, for example URI:
val path = URI("https://google/com/some/path").path; // /some/path
This feels like to me an answer in search of a problem.