Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
157 views
in Technique[技术] by (71.8m points)

Problem with passing state value between Elixir processes - how to store it correctly?

Hi,

I'm trying to write a CLI - calendar. The problem is, when I send messages through IEx, the result is as expected. However, when I send those messages one by one in other function's receive block, the output shows as if one list(stored as a state in a process [i guess..?]) was empty. The code (example messages will follow):

First module

defmodule ConcCalendar.Formatter do
  use Timex, TableRex

  def print_month(days, month, year) do
    header = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    months = %{1 => "January", 2 => "February", 3 => "March", 4 => "April", 5 => "May", 6 => "June", 7 => "July",
    8 => "August", 9 => "September", 10 => "October", 11 => "November", 12 => "December"}
    TableRex.quick_render!(days, header, months[month])
    |> IO.puts()
  end

  def year_manager(year, months) do

    receive do
      {:create_months, sender} ->
        months = for month <- 1..12 do spawn(ConcCalendar.Formatter, :month_manager, [month, year, [], []]) end
        for month_id <- months do
          send month_id, {:create_days}
        end
        year_manager(year, months)

      {:print_month, month} ->
        month_pid = Enum.at(months, month - 1)
        send month_pid, {:create_printable}
        send month_pid, {:set_printable}
        send month_pid, {:print}
        year_manager(year,months)
    end
  end


  def month_manager(month, year, days, printable) do

    receive do
      {:ok, day} ->
        printable = List.insert_at(printable, -1, day)
        month_manager(month, year, days, printable)

      {:create_days} ->
        days_in_month = 1..:calendar.last_day_of_the_month(year, month)
        days = Enum.map(days_in_month, &ConcCalendar.DayCreator.create_day_tuple/1)
        month_manager(month, year, days, printable)

      {:create_printable} ->
        for day_id <- days do
          send day_id, {:get_date, self()}
        end
        month_manager(month, year, days, printable)

      {:set_printable} ->
        Enum.sort(printable)
        first = :calendar.day_of_the_week(year, month, 1)
        offset = first - 1
        offset_list = []
        offset_list = for _x <- 1..offset do offset_list ++ [""] end
        printable = offset_list ++ printable
        printable = Enum.chunk_every(printable, 7)
        month_manager(month, year, days, printable)

      {:print} ->
        #send self(), {:create_printable}
        #send self(), {:set_printable}
        print_month(printable, month, year)
        month_manager(month, year, days, printable)
      end
    end
end

Second module

defmodule ConcCalendar.DayCreator do
  use Timex

  def create_day_tuple(day) do
    day_data = {day, "", ""}
    spawn(ConcCalendar.DayCreator, :day_tuple, [day_data])
  end

  def day_tuple(day_data) do
    {day, schedule, note} = day_data

    receive do
      {:schedule_a_meeting, sender} ->
        if schedule == "" do
          send sender, {:ok, "Successfully created a meeting"}
        else
          send sender, {:ok, "There is a meeting scheduled already"}
        end

        day_tuple({day, "*", note})

      {:cancel_a_meeting, sender} ->
        send sender, {:ok, "The meeting was canceled"}
        day_tuple({day, "", note})

      {:make_a_note, new_note, sender} ->
        send sender, {:ok, "Succesfully created a note"}
        day_tuple({day, schedule, new_note})

      {:delete_note, sender} ->
        send sender, {:ok, "The note was deleted"}
        day_tuple({day, schedule, ""})

      {:get_note, sender} ->
        send sender, {:ok, note}
        day_tuple(day_data)

      {:get_date, sender} ->
        send sender, {:ok, day}
        day_tuple(day_data)
    end
  end
end

Usage

So my goal is to create a year_manager process and then print an example month. This is what I get:

iex(1)> year1 = spawn(ConcCalendar.Formatter, :year_manager, [2021, []])
#PID<0.253.0>
iex(2)> send year1, {:create_months, self()}
{:create_months, #PID<0.251.0>}
iex(3)> send year1, {:print_month, 3}
{:print_month, 3}
+-----------------------------------------+
|                  March                  |
+-----+-----+-----+-----+-----+-----+-----+
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
+-----+-----+-----+-----+-----+-----+-----+
|     |     |
+-----+-----+-----+-----+-----+-----+-----+

When I try to do this "by hand", without using the year_manager process the output is okay:

iex(1)> month1 = spawn(ConcCalendar.Formatter, :month_manager, [1, 2021, [], []])
#PID<0.241.0>
iex(2)> send month1, {:create_days}
{:create_days}
iex(3)> send month1, {:create_printable}
{:create_printable}
iex(4)> send month1, {:set_printable}
{:set_printable}
iex(5)> send month1, {:print}
{:print}
+-----------------------------------------+
|                 January                 |
+-----+-----+-----+-----+-----+-----+-----+
| Mon | Tue | Wed | Thu | Fri | Sat | Sun |
+-----+-----+-----+-----+-----+-----+-----+
|     |     |     |     | 1   | 2   | 3   |
| 4   | 5   | 6   | 7   | 8   | 9   | 10  |
| 11  | 12  | 13  | 14  | 15  | 16  | 17  |
| 18  | 19  | 20  | 21  | 22  | 23  | 24  |
| 25  | 26  | 27  | 28  | 29  | 30  | 31  |
+-----+-----+-----+-----+-----+-----+-----+

I also get an error if I try to uncomment the lines in {:print} -> block in first module, meaning I'm trying to send {:create_printable} and {:set_printable} messages inside the last, {:print}.

Any tips are welcome, including those not directly related to my question. I'm just a CS student fed up with ubiquitous OOP and I gave Elixir a try after taking Erlang course at my uni.

I'm aware that doing everything with just spawning processes is not the best approach but this is my first Elixir project and I prefer just to stay with that for now.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

① You don’t need to wrap a single atom with a tuple in message passing, any term works well
② The reference provided by Adam in the comments is definitely worth reading, as well as the whole book
③ It could be extremely helpful if you were sharing the error message you’ve got with the uncommented messages passed


The issue is Kernel.send/2 sends is normal message, :info in terminology, which is asynch.

Each process has a mailbox, that gets processed one-by-one message when receive/1 is called. Inside the process, all the code is executed synchronously. So your :print handler basically does not do what you think it does, instead it sends a couple of messages to self() and immediately executes print_month/3, before VM had ever any chance to pass the messages to the process.

I would suggest instead of sending messages for everything, do synchronous parts as they are to be done: with functions. Somewhat along these lines.

create_printable = fn ->
  # code
end

set_printable = fn ->
  # code
end

receive do
...
  :create_printable ->
    create_printable() # if still needed
  :set_printable ->
    set_printable()    # if still needed
  :print ->
    create_printable()
    set_printable()
    print_month(printable, month, year)
    month_manager(month, year, days, printable)
end

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...