How To Debug Undef Errors And Add Functionality In An Erlang Chat App?

I’m working on a simple Erlang chat app that runs across multiple shells. My goal is to let people connect, send messages, and cleanly disconnect. Along the way, I ran into an annoying undef error and decided to turn this into a learning moment. In this post, I’ll walk you through:

Error in Erlang Chat Code

Here’s the starting point of my chat module:

-module(chat).
-compile(export_all).

%% Start the chat by registering and spawning the handler
init_chat() ->
In = io:get_line("Name please: "),
Name = string:trim(In),
register(chat_handle, spawn(chat, chat_handle, [Name])).

%% Main receive loop for incoming messages and disconnects
chat_handle(Name) ->
spawn(chat, message_handle, [Name]),
receive
{message, Sender, Message} ->
io:format("~p: ~p~n", [Sender, Message]),
chat_handle(Name);
{dc, Sender} ->
io:format("~p has disconnected.~n", [Sender]),
chat_handle(Name);
quit ->
io:format("Disconnecting...~n"),
erlang:halt()
end.

%% Read user input and dispatch accordingly
message_handle(Name) ->
Message = io:get_line("You: "),
if
Message == "bye\n" ->
disconnect(nodes(), Name);
true ->
send_message(nodes(), Name, Message)
end.

%% Broadcast a message to all nodes (including self)
send_message([Head | Tail], Name, Message) ->
{chat_handle, Head} ! {message, Name, Message},
send_message(Tail, Name, Message);
send_message([], Name, _Message) ->
message_handle(Name).

%% Notify peers of disconnect and then signal local shutdown
disconnect([Head | Tail], Name) ->
{chat_handle, Head} ! {dc, Name},
disconnect(Tail, Name);
disconnect([], Name) ->
{chat_handle, node()} ! quit.

This code lets me:

  • Ask for a user’s name
  • Spawn a process (chat_handle/1) that both reads input and handles incoming messages
  • Broadcast messages to all connected nodes
  • Clean up when someone types bye

Encountering the Undef Error

As soon as I ran init_chat() on two Erlang shells and typed my name, I saw:

=ERROR REPORT==== 15-Nov-2021::08:13:11.849169 ===
Error in process <0.91.0> on node kei@osboxes
with exit value: {undef,
[{chat, chat_handle, "Jeano", []}]}

That told me something was off with my function calls.

Understanding the Undef Error

  • undef means “undefined function.” Erlang can’t find a function with the name and number of arguments you used.
  • The tuple {chat, chat_handle, "Jeano", []} means:
    • Module: chat
    • Function: chat_handle
    • Arguments: the string "Jeano" plus an extra empty list

But I only defined chat_handle/1 (one argument). Somewhere, a call tried to invoke chat_handle/2.

Common causes

  • A typo that adds or drops an argument
  • Recursively calling the wrong arity, e.g. chat_handle(Name, [])
  • Using spawn(Module, Function, Args) with the wrong Args list length

Fix the Undef Error

I scanned my code for any chat_handle( calls and made sure each one had exactly one argument. In my case, the mistake was here:

%% WRONG: accidentally passing two arguments
chat_handle(Name, [])

I corrected it to:

%% RIGHT: only one argument
chat_handle(Name)

Once all calls matched chat_handle/1, the undef error went away and the basic chat worked as expected.

Enhancing the Chat Project

To make the project more useful and fun, I added:

  1. Slash commands (/nodes, /private, /help, bye)
  2. Logging of every join, message, and quit to a file
  3. A cleaner receive loop with a single chat_loop/1 function

Here’s the enhanced version:

-module(chat).
-compile(export_all).

%% API
init_chat() ->
In = io:get_line("Name please: "),
Name = string:trim(In),
register(chat_handle, spawn(chat, chat_loop, [Name])),
io:format("~nWelcome ~p! Type /help for commands.~n", [Name]).

%% Main loop: spawn reader, then handle incoming events
chat_loop(Name) ->
log(io_lib:format("~p joined.~n", [Name])),
spawn(chat, message_reader, [Name]),
receive
{message, Sender, Text} ->
io:format("~p: ~s", [Sender, Text]),
log(io_lib:format("~p: ~s", [Sender, Text])),
chat_loop(Name);
{dc, Sender} ->
io:format("~p has disconnected.~n", [Sender]),
log(io_lib:format("~p disconnected.~n", [Sender])),
chat_loop(Name);
{nodes_request, From} ->
From ! {nodes_reply, nodes()},
chat_loop(Name);
quit ->
io:format("Disconnecting...~n"),
log(io_lib:format("~p quit.~n", [Name])),
erlang:halt()
end.

%% Read input, handle commands or broadcast
message_reader(Name) ->
io:format("You: "),
line_to_string(io:get_line("")) = Msg,
case string:tokens(string:trim(Msg), " ") of
["/nodes"] ->
chat_handle ! {nodes_request, self()},
receive {nodes_reply, List} ->
io:format("Connected nodes: ~p~n", [List])
end,
message_reader(Name);
["/private", Target|Rest] ->
Text = string:join(Rest, " "),
send_private(Target, Name, Text),
message_reader(Name);
["/help"] ->
io:format("/nodes - list peers~n/private Node Message - send privately~n/bye - exit~n"),
message_reader(Name);
["bye"] ->
disconnect(nodes(), Name);
_ ->
send_broadcast(nodes(), Name, Msg),
message_reader(Name)
end.

send_broadcast([H|T], Name, Msg) ->
{chat_handle, H} ! {message, Name, Msg},
send_broadcast(T, Name, Msg);
send_broadcast([], _, _) -> ok.

send_private(TargetStr, Name, Msg) ->
case list_to_atom(TargetStr) of
Atom ->
Atom ! {message, Name, "[private] " ++ Msg};
_ ->
io:format("Unknown recipient.~n")
end.

disconnect([H|T], Name) ->
{chat_handle, H} ! {dc, Name},
disconnect(T, Name);
disconnect([], _) ->
chat_handle ! quit.

log(Data) ->
File = "/tmp/erlang_chat.log",
{ok, Io} = file:open(File, [append]),
io:format(Io, "~s~n", [Data]),
file:close(Io).

line_to_string({ok, Bin}) -> binary_to_list(Bin);
line_to_string(Other) -> Other.

Practice Exercises

To deepen your Erlang skills, try these:

  1. Chat Rooms
    Let users pick or create a room name. Only broadcast within that room.
  2. OTP Supervision
    Convert the chat into a gen_server and supervise it so it restarts on failure.
  3. Message History
    Keep the last N messages in memory and replay them when a user joins.
  4. Load Testing
    Write a script that spawns dozens of chat clients automatically and measures message latency.

Final Thought

I’m glad I turned a simple undef error into a learning opportunity. By fixing the arity mismatch and then adding new features, I not only got my chat working smoothly but also explored Erlang’s strengths in concurrency, messaging, and fault tolerance. I hope this walkthrough inspires you to build your own real‑time projects in Erlang and to enjoy the process of finding and fixing errors along the way.

Related blog posts