Mascasc
Mascasc

Reputation: 85

Printing to Console from Elixir Process

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

Answers (1)

Dogbert
Dogbert

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

Related Questions