Most people would tell you not to use a while loop in Elixir. And most people are right. Since data is immutable, they generally won’t do you much good. But I am not most people.
while loop looks something like this:
This wouldn’t quite work in Elixir. The value of
x can't change, so depending on the value of
x, the loop would either continue forever, or finish immediately. Not so helpful is it? "But Wait!" you proudly shout, "We can just reassign the value of
x and loop to our hearts content". But thats not really a loop in the traditional sense. Using a reassigned variable would be the equivalent of calling a function recursively, which incidentally, is how you "loop" in Elixir.
And voilà, you have iterative code. Since true
while loops require mutable data, it's understandable why they weren't included in the language. But I've always been a rebel at heart. Who is José Valim to tell me what I can and can't do? While loops are my God given right!
Dealing with State
Here’s the hard truth. State happens. To prove it, I’ve written a stoplight server.
This is a simple GenServer that updates it’s internal traffic light state every millisecond (I’m told this is how it works in Los Angeles). We’re also given a
check_light function to view the state at any time. Now, if I want to consume this information, I'll need to interact with a stateful world. I've also written a bit of self driving car software that interacts with the stoplight server. Like Elon, I've decided to open source my code for an autonomous vehicle. You're welcome mankind.
This will repeatedly check the stoplight to see if it’s green. If it is not green, it will tell you to wait (yes, even on yellow lights you freewheelin’ maniac). Simple stuff. The kind of stuff that seems perfect for a while loop, so I’m going to use one. Let’s try writing us an old fashioned
We created a function that will check a given value, and if true, will perform the given function recursively until that value isn’t true anymore.
But running this against our stateful server will highlight a problem.
Our loop will either continue forever, or finish immediately. The stateful expression is computed before being passed into the function. The
check_light function is only called once on the initial
while invocation. If we want to re-evaluate the predicate on every iteration, we'll have to wrap it inside yet another function. But I think we're starting to stray too far from the while loop I want. Lets crank the volume up to 11 and utilize some macros.
A Macro While
Now most people will tell you not to use macros unless absolutely necessary. And I’m going to have to agree with most people here. But this is absolutely necessary, nothing gets between me and my while loops. Here’s
while written as a macro.
This does some pretty gnarly stuff. We define an anonymous recursive function, which accepts a function that will be called if the predicate evaluates to true. After defining this function, we pass it into itself. This is just a trick to create a recursive function while avoiding the
def keyword, allowing us to call our
while outside of a module definition.
Despite all this ugliness, we actually get a pretty slick interface.
This is almost perfect, but the syntax isn’t quite right. While.while? This ain’t the while while west. I just want a plain and simple, no frills
while loop. I also want the title of this post to be clever, so we're going to use the
__using__ keyword. When a module uses
use with another module, it will immediately call the
__using__ callback from the module being used. We're going to use this to define a macro in the Car module. Have I used the word "use" enough in this paragraph? I need the SEO points.
And there you have it! When the
Car module is compiled, it will call the
__using__ macro, which defines it's own
while macro inside of
Car. And now we can call
while straight from our
Even after all this, we’re still using recursion. We just gave it an unnecessary paint job. You don’t need
while loops. Use recursion, it's cleaner and more fun.
Originally published at https://codingwithalchemy.com on May 4, 2019.