在 Elixir 进程之间传递状态值的问题 - 如何正确存储它?
Problem with passing state value between Elixir processes - how to store it correctly?
嗨,
我正在尝试编写 CLI - 日历。
问题是,当我通过 IEx 发送消息时,结果如预期的那样。但是,当我在其他函数的接收块中一条一条地发送这些消息时,输出显示好像一个列表(存储为进程中的状态[我猜..?])是空的。代码(示例消息将在后面):
第一个模块
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
第二个模块
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
用法
所以我的目标是创建一个 year_manager
流程,然后打印一个示例月份。
这是我得到的:
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 |
+-----+-----+-----+-----+-----+-----+-----+
| | |
+-----+-----+-----+-----+-----+-----+-----+
当我尝试“手动”执行此操作而不使用 year_manager
过程时,输出没问题:
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 |
+-----+-----+-----+-----+-----+-----+-----+
如果我尝试取消注释第一个模块中 {:print} ->
块中的行,我也会收到错误消息,这意味着我试图在最后一个模块中发送 {:create_printable}
和 {:set_printable}
消息, {:print}
.
欢迎任何提示,包括那些与我的问题没有直接关系的提示。我只是一个厌倦了无处不在的 OOP 的 CS 学生,在我的大学学习了 Erlang 课程后,我尝试了 Elixir。
我知道只用生成过程来做所有事情并不是最好的方法,但这是我的第一个 Elixir 项目,我现在更愿意继续使用它。
① 在消息传递中不需要用元组包裹单个原子,任何术语都适用
② 参考文献provided by Adam in the comments绝对值得一读,全书
③ 如果您将收到的错误消息与已通过的未注释消息分享,那将非常有帮助
问题是 Kernel.send/2
sends is normal message, :info
in elixir 术语,它是异步的。
每个进程都有一个邮箱,在调用 receive/1
时会逐条处理邮件。在进程内部,所有代码都是同步执行的。因此,您的 :print
处理程序基本上不会按照您的想法去做,而是在 VM 有机会传递消息之前向 self()
发送几条消息并立即执行 print_month/3
到流程。
我建议不要为所有事情都发送消息,而是按照需要完成的同步部分:使用函数。有点像这样。
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
嗨,
我正在尝试编写 CLI - 日历。 问题是,当我通过 IEx 发送消息时,结果如预期的那样。但是,当我在其他函数的接收块中一条一条地发送这些消息时,输出显示好像一个列表(存储为进程中的状态[我猜..?])是空的。代码(示例消息将在后面):
第一个模块
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
第二个模块
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
用法
所以我的目标是创建一个 year_manager
流程,然后打印一个示例月份。
这是我得到的:
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 |
+-----+-----+-----+-----+-----+-----+-----+
| | |
+-----+-----+-----+-----+-----+-----+-----+
当我尝试“手动”执行此操作而不使用 year_manager
过程时,输出没问题:
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 |
+-----+-----+-----+-----+-----+-----+-----+
如果我尝试取消注释第一个模块中 {:print} ->
块中的行,我也会收到错误消息,这意味着我试图在最后一个模块中发送 {:create_printable}
和 {:set_printable}
消息, {:print}
.
欢迎任何提示,包括那些与我的问题没有直接关系的提示。我只是一个厌倦了无处不在的 OOP 的 CS 学生,在我的大学学习了 Erlang 课程后,我尝试了 Elixir。
我知道只用生成过程来做所有事情并不是最好的方法,但这是我的第一个 Elixir 项目,我现在更愿意继续使用它。
① 在消息传递中不需要用元组包裹单个原子,任何术语都适用
② 参考文献provided by Adam in the comments绝对值得一读,全书
③ 如果您将收到的错误消息与已通过的未注释消息分享,那将非常有帮助
问题是 Kernel.send/2
sends is normal message, :info
in elixir 术语,它是异步的。
每个进程都有一个邮箱,在调用 receive/1
时会逐条处理邮件。在进程内部,所有代码都是同步执行的。因此,您的 :print
处理程序基本上不会按照您的想法去做,而是在 VM 有机会传递消息之前向 self()
发送几条消息并立即执行 print_month/3
到流程。
我建议不要为所有事情都发送消息,而是按照需要完成的同步部分:使用函数。有点像这样。
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