In the simplest terms I think this would be similar to your code above:
def while(fun) when is_function(fun) do
if (name = fun.() && exists?(name)), do: while(fun), else: name
end
# Call it as:
name = while(&generate_name/1)
Your code example here implies mutation, so âreal lifeâ may look a little differently in a functional language. For example if you want to generate a list of new names until they stop existing you can do something like:
There are a hundred little variations on that you can do, combining various stream generators like repeatedly/1, unfold/1 and so forth with Enum.take* variants.
If those patterns arenât a good fit, manual recursion is always a simple function a way.
Well if you want to limit the depth, you can always pass along a depth counter. But you probably wonât want to limit depthâother than to make sure that the recursive call is the last thing your function does, so that tail call optimization can limit the actual stack depth to 1.
In a typical case I need one name, which is not yet takenâŠ
⊠and I want to limit the number of checks so that if I tried - say - sixteen times and still donât get a valid, non-yet-existing one, I can stop and notify that somethingâs probably wrong somewhere (namespace exhausted, misconfiguration, generator bug, etc.), instead of trying âforeverâ.
This will return the first generated name that isnât taken, OR quit after 16 tries. It will only generate as many names as are needed. Enumerable works great here too. Of course, you can always use manual recursion.
Huh, thatâs all so different from what I am well used to that after many years of successfully doing it for a living (using too many languages/stacks to list) I feel like I am learning programming from scratch again Thatâs both refreshingly challenging and intimidating at the same time. For example looking at the line, I wouldnât guess that it will generate âonly as many as neededâ. Thanks.
Thatâs the beauty of lazy streams the original stream is actually infinite, but it only gets realized in a finite number of elements due to Stream.take and Enum.find.
Yes, this is commonly known as âstreamsâ or âlazy iteratorsâ. This mean that the values will be computed only as needed and this is common pattern in languages that have âfunctionalâ feel (Haskell is lazily evaluated in general, Rust iterators are lazy by default, etc.). Even some âoldâ languages are getting such features (like Stream in Java).
One good âbrain hackâ is to think of Enum as âthe result of this will be immediately calculated and passed to the next functionâ and of Stream as âthis will be calculated at the first occurence of Enum down the lineâ.
I would understand that the ârepeatedlyâ is infinite, but then my first guess would be that the second part âtakesâ 16 instances from the infinite stream and passes to the third part, which enumerates over them, or so. But it seems like backward thinking in the context.
Key is that the docs for Stream.take start off âLazily takesâŠâ, meaning elements not evaluated until requested. So it immediately returns, yes, and the return is an enumerable that will yield 16 values, and that enumerable is passed into the Enum.find? call, but the values themselves are not evaluated by the enumerable until requested, if ever.
import While
total = 10
while_with total, total != 0 do
IO.puts "hello"
total - 1
end
IO.puts "total is #{total}"
Explanation:
The while_with binds the variable name total to both the expression total != 0 and the body. The body result (last line of body) always becomes the new value for the bound variable, like in a Enum.reduce
IO.puts "hello"
total - 1
Addendum:
If youâre only working with globals (or processes) you can also use the simple while without binding a name:
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)}")
An equivalent with potentially infinite running time (useful when polling an API for results in a quick Mix.install/2 script):
Stream.unfold(1, fn
acc ->
:timer.sleep(1_000)
IO.puts("Waiting...")
# TODO here: poll API and decide based on output status whether to leave or not
if acc >= 5, do: nil, else: {acc, acc + 1}
end)
|> Stream.run()
Thanks @LostKobrakai for the inspiration, I wouldnât have thought about Stream.unfold for this!