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

restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 12366 136
New
type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New
gtcode
Aloha gang, There’s a new class of AI model out from TypeSafe. Jev is their flagship model. You can learn about it here: ...
New

Other Trending Topics Top

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews