For loop with custom accumulator in Elixir

Hi, I’m very new to Elixir, I’m struggling with writing loop. i have this simple code in javascript and would like to know how can I write the same in Elixir.

function print() { 
  for (var p = 990; p > 99; p -= 11) {
  console.log('Print');
}
1 Like

Elixir is a functional language.

  1. You can use Range, like 1..9 or 990..99 or 990..99//-11, for iterating over numbers.
  2. To jump over numbers, you can use Enum.take_every
  3. For logging to console, you can use IO.inspect

990..99
|> Enum.take_every(11)
|> IO.inspect()

Output:

[990, 979, 968, 957, 946, 935, 924, 913, 902, 891, 880, 869, 858, 847, 836, 825,
 814, 803, 792, 781, 770, 759, 748, 737, 726, 715, 704, 693, 682, 671, 660, 649,
 638, 627, 616, 605, 594, 583, 572, 561, 550, 539, 528, 517, 506, 495, 484, 473,
 462, 451, ...]


P.S. Elixir code is similar to writing JavaScript using Ramda.js or Lodash, in case you have used those libraries, you will be able to relate.

P.P.S. I just learnt how to write List Comprehension, that may look like for loop in JavaScript’s for, but shouldn’t be used like that!

for x <- 990..99//-11, do: IO.inspect x
3 Likes

In elixir, as in most other functional languages, we do not write loops using a for.

Elixir has special form that is often cofused by new users with loops, as its named for, though it is actually used for comprehensions.

Repeating things is usually done via recursion or comprehensions or the Enum module.

Depending on your elixir version the following might already do the correct thing:

Enum.each(990..99//-11, fn _ -> IO.puts("Print") end)
6 Likes

Hello and welcome, one quick answer…

Enum.each(990..99//-11, fn _x -> IO.puts("Print") end) 

But for some reason, trying to replicate JS code in Elixir is going to be hard.

Your code is imperative with mutation.

Elixir is descriptive and immutable.

You will have to use Functional Programming tools if You want to use Elixir

3 Likes

To highlight some problems with your code…

  • No loop, because immutability does not allow p to change
  • ‘Print’ is not equal to “Print”, beware of single quote, it has a special meaning
  • In general (almost always), function should return a value
  • In general, avoid side effect, console.log() is a side effect
3 Likes

It would be better to have shown what have you tried.

2 Likes

Bummer. I saw this title and was hoping to have a deep discussion of using custom Collectable implementations in the into option of a for comprehension. :grin:

3 Likes

Others mentioned most of the important aspects about how to write Elixir code and what mistakes to avoid
Now, on how to write the code you requested. I will use reduce because you mentioned accumulator :smiley:
“JavaScript way”:

print = fn () ->
  990..00
  |> Enum.reduce(fn x, acc -> 
  	if acc > 99, do: x - 11, else: acc 
  end)
  IO.inspect("Print")
end

Elixir way:
Already answered above :slight_smile:
PS: Using var in JS? :open_mouth:

2 Likes

Do tell, I want to learn about Custom stuff!! :face_with_monocle:

You can define your own Collectable impl and blend that into for, (or Enum.into)

Mix.install([{:ecto, ">= 0.0.0"}], consolidate_protocols: false)
defimpl Collectable, for: Ecto.Query do
  import Ecto.Query
  def into(query) do
    collect = fn
      query, {:cont, {f, v}} ->
        where(query, [], ^f == ^v)

      query, :done ->
        query

      query, :halt ->
        :ok
    end

    {query, collect}
  end
end
import Ecto.Query

filter = [{:in_stock, true}, {:on_sale, true}]
q = for {field, val} <- filter, into: from("products"), do: {field, val}
IO.inspect(q)
# => Ecto.Query<from p0 in "products", 
#               where: ^:in_stock == ^true, 
#               where: ^:on_sale == ^true>

# this can also just be written as reduce
q = for {f, v} <- filter, reduce: from("products") do
  query -> where(query, [], ^f == ^v)
end
IO.inspect(q)
# => Ecto.Query<from p0 in "products", 
#               where: ^:in_stock == ^true, 
#               where: ^:on_sale == ^true>

# reduce is probably most interesting when you (ab)use for's pattern matching too
# which can avoid an additional clause to Enum.reduce if your harddrive is low on disk space.
filters = [{:in_stock, true}, {:on_sale, false}]
q = for {f, true} <- filter, reduce: from("products") do
  query -> where(query, [], ^f == true)
end
# => Ecto.Query<from p0 in "products", 
#               where: ^:in_stock == ^true>

I don’t think I’ve actually ever done this in production, probably because I have never needed my own low-level collection type and writing it for “composite” structs seems a bit obtuse vs just manipulating fields. Or maybe I just lack imagination. (Obviously it is quite useful with the built in collections types.)

Anyone have a real-world example?

That’s a verbose way of typing q = Enum.into(filter, from("products")) :grin:

I’ve done a custom Collectable implementation that I used to good effect, but not in combination with a for comprehension. I could imagine them used more for algorithmic purposes, such as a GroupingCollector that in a single pass groups things under common map keys, or a SortingCollector that can efficiently sort things without an additional pass.

2 Likes