As I’m working on my programming language, syntax is sometimes a struggle. There’s a lot of inspiration one can get from existing languages, but things from different languages a lot of times don’t match. Sometimes it even feels like things from the same language don’t match. And it’s super hard to discuss this as it comes down to familiarity and taste. So I know my mental models are all biased. Please excuse any generalisations that don’t apply to you.
When working with some code in some IDE, something I see developers do frequently, is to write some variable name, press “.” and then see what auto-complete suggests. I do it myself as well. This is a big deal in exploring what once can do with a variable of some type.
I also like the end result. Out of the following two examples I prefer the first one:
var1.concat(var2)
concat(var1, var2)
The problem in most languages is that you can only do the first one when `concat` is defined in the same place as the type for `var1`. There’s two approaches for enabling this feature:
Extensions don’t allow the reader of the code to know if it’s an extension or not without checking the definition and/or imports. I think I would prefer something that acts more like syntactic sugar.
The other approach seems fine. And while I don’t like to read `var1|>concat(var2)` (with a space it becomes better), I do like `var1->concat(var2)`.
So I can just adopt what rescript has, right? If a user of my language wants to see what functions take the type of `var1` as an argument, they can write `var1->` and let the IDE do its magic! And if they want to look at the fields, they use “.” instead of “->”.
Problem solved?
Unfortunately not yet.
In order to add new syntax, it needs to match what is already there. Let me show you some example code, so you get an idea of the existing syntax:
import tenecs.list.map
struct Todo(
title: String,
done: Boolean
)
struct TodoFilter(
predicateFunction: (Todo) -> Boolean
)
exampleFunctionUsingFilter := (): Void => {
todoList := [](
Todo("finish blog post", false),
Todo("publish blog post", false)
)
filterThatRejectsAll := TodoFilter((todo) => false)
emptyList := map(todoList, filterThatRejectsAll.predicateFunction)
}
You can see in that example that `->` is being used to declare a function type. I searched for another symbol that would also work, so I don’t have to mess with existing syntax, but haven’t found anything as satisfying as `->`. So, if I want to use that, then I need another symbol for the functions. I can use `=>`. But I was using that as part of the syntax for declaring a function. And that one I can drop without replacement, if I no longer allow defining single-expression functions without the brackets around them.
Let’s try those changes:
import tenecs.list.map
struct Todo(
title: String,
done: Boolean
)
struct TodoFilter(
predicateFunction: (Todo) => Boolean // changed -> to =>
)
exampleFunctionUsingFilter := (): Void { // dropped =>
todoList := [](
Todo("finish blog post", false),
Todo("publish blog post", false)
)
filterThatRejectsAll := TodoFilter((todo) { false }) // dropped =>
emptyList := map(todoList, filterThatRejectsAll.predicateFunction)
}
And, for completeness’ sake, let’s apply our new syntactic sugar:
import tenecs.list.map
struct Todo(
title: String,
done: Boolean
)
struct TodoFilter(
predicateFunction: (Todo) => Boolean
)
exampleFunctionUsingFilter := (): Void {
todoList := [](
Todo("finish blog post", false),
Todo("publish blog post", false)
)
filterThatRejectsAll := TodoFilter((todo) { false })
// doesn't look too bad, I think?
emptyList := todoList->filterThatRejectsAll.predicateFunction()
}
I’m nearly happy. I don’t mind too much that we lost the `=>` on exampleFunctionUsingFilter` but on `filterThatRejectsAll`… There I’m not a fan at all. It’s missing something. I might end up having to introduce a third, weirder arrow, like `+>` or `~>` to not have ambiguity there.
I suspect there’s a best case scenario here that I just haven’t yet puzzled together. I’ll think about it some more.