Reputation: 85
So, I started playing around with Elixir, and I wanted to make two modules. One that would prompt you to enter a message, and then send the message to the other program which would acknowledge that it got the message, which would cause the first program to print out the message.
Well, I've got to problems.
The sender is below:
defmodule PracticeCaller do
def start do
spawn(&sloop/0)
end
def sloop do
pid = :erlang.spawn(&PracticeServer.start/0)
message = IO.gets "Enter a message:"
send(pid, {self, message})
receive do
{_caller, reply} -> IO.puts "#{reply}"
end
sloop
end
end
And the receiver is here:
defmodule PracticeServer do
def start do
spawn(&loop/0)
end
defp loop do
receive do
{caller, "kill"} -> send(caller, {self, "Process Dead"})
Process.exit(self, :kill)
{caller, _} -> send(caller, {self, "Thank you for your message!"})
end
loop
end
end
My first problem is that when I start my sender loop, the terminal prompts me to enter a message, but it does not print the received message.
Second, when I enter "kill", the terminal freezes, because I am not sure how to handle a :kill response.
Any help on fixing these issues?
Upvotes: 0
Views: 1447
Reputation: 222388
The first problem is:
pid = :erlang.spawn(&PracticeServer.start/0)
and the fact that PracticeServer.start/0
calls spawn
again. This means that pid
here does not refer to the process running PracticeServer.loop/0
, but a process that spawned PracticeServer.loop/0
and immediately exited.
If I change:
pid = :erlang.spawn(&PracticeServer.start/0)
to just:
pid = PracticeServer.start
Some things start working:
Enter a message:hello
Thank you for your message!
Enter a message:world
Thank you for your message!
But:
Enter a message:kill
Thank you for your message!
This is because IO.gets/1
includes the trailing newline character, which means the {caller, "kill"}
pattern in PracticeServer.loop/0
never matches.
This can be fixed by changing:
message = IO.gets "Enter a message:"
to
message = IO.gets("Enter a message:") |> String.trim_trailing
Now:
Enter a message:kill
Process Dead
Enter a message:kill
Process Dead
Another problem is that you're currently leaking processes. Instead of reusing the spawned server, you're spawning a new server for every message. Also, the idiomatic way to handle a recursive receive
that can be "killed" is to not do the recursive call when you want the process to die. Here's some refactored code:
defmodule PracticeCaller do
def start do
spawn(fn -> loop(PracticeServer.start) end)
end
def loop(server) do
message = IO.gets("Enter a message: ") |> String.trim_trailing
send(server, {self, message})
receive do
{_caller, reply} ->
IO.puts "#{reply}"
# Only continue if process is not dead.
# Comparing against strings is usually not idiomatic, you may want to
# change that.
if reply != "Process Dead" do
loop(server)
end
end
end
end
defmodule PracticeServer do
def start do
spawn(&loop/0)
end
def loop do
receive do
{caller, "kill"} ->
send(caller, {self, "Process Dead"})
{caller, _} ->
send(caller, {self, "Thank you for your message!"})
loop
end
end
end
PracticeCaller.start
:timer.sleep(:infinity)
Note that this will not halt the VM even after you send "kill". If this is just a script, you can add :erlang.halt
to PracticeCaller.loop/1
when the message is "Process Dead" to immediately halt the VM.
Upvotes: 3