What are the different types of message passing primitives in Erlang?

In Erlang, there are two main types of message passing primitives that allow processes to communicate with each other:

1. `!` (Bang or exclamation mark operator) :
   The `!` operator is the basic message sending primitive in Erlang. It is used to send a message from one process to another. The syntax for sending a message is `SenderPid ! Message`, where `SenderPid` is the process identifier (PID) of the sending process, and `Message` is the data to be sent. The message is sent asynchronously, meaning the sending process does not wait for a response. The message is delivered to the recipient's mailbox for later processing.

   Example :
   ReceiverPid ! {self(), "Hello, world!"}.​

2. `receive` block :
   The `receive` block is used by processes to receive and handle incoming messages. It allows a process to wait for specific messages and pattern match on the received messages to perform different actions. The syntax of a `receive` block is as follows:
   receive
       Pattern1 [when Guard1] -> Action1;
       Pattern2 [when Guard2] -> Action2;
       ...
       PatternN [when GuardN] -> ActionN
   end​
   The process will wait until a matching message arrives in its mailbox. When a message matches one of the patterns, the corresponding action is executed. If no message matches any of the patterns, the process will block and wait for the next message.

   Example :
   receive
       {SenderPid, Message} -> io:format("Received message: ~p~n", [Message])
   end.​

   In the example above, the process waits to receive a message of the form `{SenderPid, Message}` and then prints the received message.

These message passing primitives enable inter-process communication in Erlang. Processes can send and receive messages asynchronously, allowing for concurrent and loosely coupled communication. The use of message passing contributes to Erlang's fault-tolerant and scalable nature, as processes can communicate across nodes in a distributed system.