So this is just a dumb little post regarding syntax.
I’ve been pairing with a colleague who is new to Elixir lately and today we were briefly discussing how do/end is really syntactic sugar that essentially gets converted into a keyword list as [{:do, block}] and gets passed as the last argument to whatever is calling it. I was showing them how—while fairly useless—it even works with regular functions (not just macros) and I was creating modules with a single function in IEx to demonstrate. Afterwards this got me thinking if the following syntax would work:
foo = fn args ->
args[:do]
end
foo.() do
"bar"
end
Lo-and-behold it does!
Again, this is pretty useless in practice (and many of you are likely thinking “Sure, why wouldn’t that work?”) but it slightly expanded my own understanding of the ast/sugar ergonomics. Mostly I just thought it was cool and I’m home alone on a Friday night and I had to tell someone
If anyone has a better/different/(or even worse) way that they describe how do/end works, please feel free to share.
…and actually, are there practical uses of do/end for plain function functions I’m not thinking of?
Personally, I’d find a do block passed to a plain function really confusing because I’ve written Ruby for years where that’s the syntax (more or less) for passing an anonymous function that the recipient can call.
Other keywords like else would be confusing to any Elixir developer, since they evaluate earlier than expected:
foo = fn args ->
args[:else]
end
foo.() do
IO.puts("something")
else
IO.puts("something else")
end
Oh absolutely, which is why I said it was useless. But often when I say stuff like that people come back with an “aaaaccctually, in this one case…” but ya, I was pretty sure there was no good reason for it. I was just using it to illustrate to my coworker there is nothing particularly “special” with how do works.
All of those different clauses that seem like language keywords that you can pass to the def macro, such as rescue, catch, else, after can be used in that implicit last keyword list argument. I’m pretty sure the parser limits to those atoms, which means you can’t pick arbitrary ones, however, knowing how that works could open up possibilities of cool macro features.
It is correct, the parser does seem to limit it, at least I did not succeed to use other keywords, even tough the macro definition does not seem to have such limitations. I didn’t go too deep down the rabbit hole. Probably with a lot of AST manipulation you might still be able to do it.
Is it good or bad, that I couldn’t do it? At the time I was (of course) very sad that I didn’t manage to do it and it did limit me in my creativity. I created my own DSL where I wanted to us a different keyword for the code block. I even wanted to have two code blocks (similar to a do-else- end block). On the other hand, I’m not sure whether it would have helped, because it might have lead me to create an unnatural, awkward, complex monster.
Are you talking about using any random word here? The “sugar” words are definitely reserved as they cannot be used as identifiers. Even though you can do some pretty wild stuff, Elixir doesn’t let you make up completely random syntax. For example, it only allows a fixed set of infix operators beyond the ones that are already in use. There are some other limitations that prevent you from too hog wild that I can’t remember off the top of my head.
There may be some way you could wrangle it but it would be super confusing since:
foo do
true
bar
false
end
Is normally just parsed as:
foo do
true
bar()
false
end
You could treat everything between bar and end differently, but Elixir isn’t a whitespace significant language so it would just be really confusing. The formatter would always rewrite it to the latter form, for example.
Yes, that was exactly the initial plan to have something like
foo a: 1 bar
# some code
end
Even though it does limit the DSL you can create from it. It does help to keep DSLs still somewhat consistent and I do think (in the meantime) that it’s a good thing