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

First 10 of 14 Posts Switch mode

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

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 15136 120
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 11030 135
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
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
woylie
Phoenix components for pagination, sortable tables and filter forms with Flop and (optionally) Ecto. pagination cursor pagination sorta...
New
kip
Please say hi to a new lib, Astro that aims to deliver easy-to-consume astronomy calculations of practical use. For now it only calculat...
New

Other Trending Topics Top

akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
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
matt-savvy
Is there a word for the ~> symbol used in Version strings? Do you also just call it a Squiggle Arrow™ ?!
New

We're in Beta

About us Mission Statement