dominicletz

dominicletz

Creator of Elixir Desktop

Hi Guys,
one more time I had this need for a simple while loop and so I played with this macro idea: while | Hex

While on globals

  import While

  ref = :counters.new(1, [:atomics])
  while :counters.get(ref 1) < 10 do
    :counters.add(ref 1, 1)
  end

  IO.puts("Current value is #{:counters.get(ref, 1)}")

Now this only works when you’re working with globals, references or pids, which in fact in my case is useful sometimes. But to make it more useful in local scopes I created a more reduce like shortcut:

While on locals

When providing an additional parameter, this is interpreted as a variable name and imported into the scope of the while expression and the while body.

    import While

    cnt = 1
    cnt = while cnt, cnt < 10 do
      cnt + 1
    end

    IO.puts("Current value is #{cnt}")

Explanation:
The while/3 binds the variable name cnt to both the expression cnt < 10 and to the body. The body result (last line of body) always becomes the new value for the bound variable, like in a Enum.reduce.

   cnt - 1

Danger Area

To automate the assignment, there is another variant while_with that will automagically assign to the original variable. Consensus from this discussion seems to be: Don’t use this variant

    import While

    cnt = 1
    while_with cnt, cnt < 10 do
      cnt + 1
    end

    IO.puts("Current value is #{cnt}")

Explanation:
The while_with works exactly like while/3 but the final value, will be assigned to the bound variable of the outer scope, so that: IO.puts("Current value is #{cnt}") will print Current value is 10

Questions
The name while_with might be confusing, should this just be the same name, but a two parameter variant of while , or should it be called reduce_while or something? Curious about the communities thoughts here and whether this kind of macro has any reason to exist at all in the first place :slight_smile:

Installation
While can be installed by adding while to your list of dependencies in mix.exs:

def deps do
  [
    {:while, "~> 0.2.1"}
  ]
end

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

There’s already Enum.reduce_while, so I’m not sure how good of a name this one would be.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Can you elaborate on these concrete use cases? I really can’t think of any situation where I wouldn’t prefer a recursive function, or the use of Enum.

    cnt = 1
    while_with cnt, cnt < 10 do
      cnt + 1
    end

    IO.puts("Current value is #{cnt}")

The hidden rebinding here is particularly alarming since it changes core expectations people have about how Elixir code works. It’s also presumably inconsistent right? What if you do:

i = 0
j = 0
    while_with i, i < 10 do
      i + 1
      j + 1
    end

Is i incremented to 10, but j not?

mischov

mischov

I don’t think this is a great idea.

There are more language appropriate ways of approaching the problem (see: Write while loop equivalent in elixir - #10 by peerreynders), and using this instead of them would actually add complexity for most Elixir programmers.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

It’s also worth noting that for takes a reduce option now:

for <<x <- "AbCabCABc">>, x in ?a..?z, reduce: %{} do
  acc -> Map.update(acc, <<x>>, 1, & &1 + 1)
end
%{"a" => 1, "b" => 2, "c" => 1}
dominicletz

dominicletz OP

Creator of Elixir Desktop

In my case I’m only using the global version of this while loop, and I use them in tests to start processes and wait for them to come up:

01 test "doing some stuff with workers" do
02   while workers_online() < target do
03      start_new_worker()
04   end
05   ...
06 end

I just prefer being able to write this in-line in the function that needs it than externalizing this every time into a recursive function, especially when it’s not reused:

01 test "doing some stuff with workers" do
02    ensure_workers(target)
03    ....
04 end
05 
06 def ensure_workers(target) do
07   if worker_online() < target do
08     start_new_worker()
09     ensure_workers(target)
10   end
11 end

That’s my reason to create it: Laziness of creating a recursive function for each while use case.

Totally agreed, the hidden rebinding is alarming. That’s why I added the _with postfix instead of just overloading the while macro with two parameters for this. Maybe a name like while_bind would indicate that binding easier. First I looked at i = while_with i, i < 10 do: i + 1 but felt it’s just one i too much…

Just for completeness the two, or more variable version would look like this:

i = 0
j = 0
while_with {i, j}, i < 10 do
  {i + 1, j + 1}
end
benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Normally I’d expect something like this for starting workers in tests:

list_of_workers |> Enum.each(&start_worker/1)

Your version seems to indicate that it does some kind of polling to see if workers are online, but a more idiomatic solution would be to just block until you receive a message indicating that the worker is online.

dominicletz

dominicletz OP

Creator of Elixir Desktop

Yeah, it just appeared to me that a ‘conceptual while’ is basically a Enum.reduce_while without an enum, (or it could be at least):

defmodule Enum do
  def reduce_while(enumerable, acc, fun)
end

defmodule While do
  def reduce_while(acc, fun) do
    case fun.(acc) do
      {:halt, new} -> new
      {:continue, new} -> reduce_while(new, fun)
    end
  end
end

Then you could use it as a normal function:

    i = 1
    i =
      reduce_while(i, fn i ->
        if i < 10 do
          {:continue, i + 1}
        else
          {:halt, i}
        end
      end)

The presented while is just a shorthand / syntactic sugar for this to turn this into:

i = 1
i = while i, i < 10 do
  i + 1
end
dominicletz

dominicletz OP

Creator of Elixir Desktop

Thanks all for the feedback. I’ve removed while_with from the examples and marked it deprecated - because the hidden assignment indeed seems too dangerous.

For those who still want to use while the way to go is the while/3 macro that returns the value, so the assignment is visible in the code:

    cnt = 1
    cnt = while cnt, cnt < 10 do
      cnt + 1
    end
sasajuric

sasajuric

Author of Elixir In Action

You don’t need to write an explicit recursive function. The same can be achieved with e.g.:

Stream.repeatedly(fn -> worker_online() == target end)
|> Enum.find(& &1)
dominicletz

dominicletz OP

Creator of Elixir Desktop

This is a pretty awesome solution @sasajuric thanks for that - I didn’t think of this combination. It’s not calling start_new_worker() but I guess it would do it like this:

Stream.repeatedly(&start_new_worker/1)
|> Enum.find(fn _ -> worker_online() < target end)

While this looks correct I personally find that the while construct much better transports the meaning, at the same effect.

while workers_online() < target do
  start_new_worker()
end

Cheers

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
CodeSync
:microphone: ElixirConf 2026 - Call for Talks is open! We’re heading to Chicago :united_states: :round_pushpin: In person + virtual :d...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews