Erlang generic behaviors

In this article you will learn basic concepts about Behaviours in Erlang programming language with some code examples.

Abstraction

As an Erlang newbie or as an experienced OOP programmer who wants to write some Erlang/OTP compatible code, Sometimes it’s boring to understand  behaviors. Because we just go and learn which functions should we export, Which arguments each function accepts, etc without understanding what a behavior is and how it works.

What a behavior is?

In Erlang, a behavior is a design pattern implemented in an Erlang module. If you know OOP, It provides functionality in a fashion similar to inheritance in OOP. When there is a generic parts for doing something, We can implement those generic parts in a module and for specific parts, we can call some functions from other module called callback-module. Those specific functions that behavior module calls, are callback-functions.

Let’s write a simple code snippet in a human-readable OOP language Python.

#! /usr/bin/env python3
 
 
def start(callback, init_argument):
	# First, We need to initialize an state value. This generic part of code
	# does not know what state is, It depends on callback module.
	# init callback-function should yield an state value for its own.
	# Callback-function init sometimes needs an init argument for making an state.
	state = callback.init(init_argument)
 
	# This behavior module reads input data from stdin in an infinite loop
	#  and after some checks calls callback-functions of callback-module
	#  for handling input data:
	while True:
		try:
			# Get message from stdin:
			message = input("Send me a message: ")
 
			# If message is in form of: "request "
			if message.startswith("request "):
				# Get request itself:
				request = message[8:]
 
				# Call handle_request callback-function with request and its state.
				# Callback function should yield a reply and its new state
				(reply, state) = callback.handle_request(request, state)
				print( "Reply: {}".format(reply) )
 
			# So message is not a request
			else:
				# Also handle_other_message callback-function should yield its new state
				state = callback.handle_other_message(message, state)
 
		# If we got a Ctrl-C: 
		except KeyboardInterrupt:
			# We want to call terminate callback-function as last call
			# This termination is normal
			reason = None
			callback.terminate(reason, state)
			print( "Terminated normally in state '{}'".format(state) )
			# We want to stop it normally
			break
 
		# It seems that there is an error in one of callback-functions:
		except Exception as reason:
			callback.terminate(reason, state)
			print( "Terminated in state {} because of {}".format(state, reason) )
			# So we want to raise the error:
			raise

I saved above code in a file named behavior.py. This is our behavior module. I suggest to read above code and its comments to understand what it does. I do not use inheritance because I don’t want to explain ‘self’, etc to someone who does not know Python well.

As an example, I want to write a simple counter on top of it. Our behavior needs a callback-module with 4 callback-functions init, handle_request, handle_other_message and terminate. See our counter.py code in below:

def init(init_argument):
	# Our init callback accepts an string argument which is an integer
	# We use this as state value, but we need this in integer type:
	return int(init_argument)
 
 
def handle_request(request, state):
	if request == "increase":
		reply = "Increased"
		new_state = state + 1
		return (reply, new_state)
	elif request == "decrease":
		reply = "Decreased"
		new_state = state - 1
		return (reply, new_state)
	# We have unknown request, So raise an error
	else:
		raise ValueError( "Unknown request {}".format(request) )
 
 
def handle_other_message(message, state):
	if message == "show":
		print( "Number is {}".format(state) )
	# We did not change the state, but the behavior does not know
	# So we yield last state as new state
	return state
 
 
def terminate(reason, state):
	if reason == None: # terminated normally
		print("Goodbye!")

Done! Let’s run the code inside python3 interpreter:

>>> import behavior
>>> import counter
>>> behavior.start(counter, "0")
Send me a message: show
Number is 0
Send me a message: request increase
Reply: Increased
Send me a message: show
Number is 1
Send me a message: request increase
Reply: Increased
Send me a message: request increase
Reply: Increased
Send me a message: show
Number is 3
Send me a message: request decrease
Reply: Decreased
Send me a message: show
Number is 2
Send me a message: # Ctrl-C
Goodbye!
Terminated normally in state '2'
 
>>> behavior.start(counter, "100")
Send me a message: request foo
Terminated in state 100 because of Unknown request foo
Traceback (most recent call last):
  File "", line 1, in 
  File "/home/pouriya/Desktop/behavior.py", line 26, in start
    (reply, state) = callback.handle_request(request, state)
  File "/home/pouriya/Desktop/counter.py", line 19, in handle_request
    raise ValueError( "Unknown request {}".format(request) )
ValueError: Unknown request foo

Above example helps you understand basic concepts of behavior. In Erlang a behavior just needs a callback-module with callback-function(s) exported and an init argument to run. Sometimes you can specify some start options too.

to be continued …

2 Replies to “Erlang generic behaviors”

    1. Actually this is the first part. In next parts I will explain how gen, proc_lib and sys work in behavior modules. Your posts are helpful. Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *

Enter Captcha Here : *

Reload Image