I/O from binaries and an I/O behaviour

Chris Pressey cpressey@REDACTED
Tue Nov 13 22:52:29 CET 2001


On Thu, 08 Nov 2001 10:56:14 +0100
Robert Virding <rv@REDACTED> wrote:

> Chris Pressey <cpressey@REDACTED> writes:
> >On Mon, 5 Nov 2001 08:17:24 +0100 (MET)
> >
> >I'll go one step further, and ask: is there any reason both the io
> >module
> >and the bin_io module shouldn't both adhere to an 'io' behaviour?
> >[...]
> 
> No, real reason.  I did look into to it many years ago, long before
> behaviours in fact, and found that it wasn't trivial.  The problem is
> that such a generic server would need to define a generic way of
> getting bytes from and putting bytes to some device.  Some devices,
> like files and binaries, work best with an synchronous call method
> while others like terminals work best with a completely asynchronous
> method.
> 
> Another complication is that for things like terminals you really need
> to be able to interlace input and output requests in a sensible way,
> like the terminal does now.
> 
> The best way I then found was to define a simple generic synchronous
> io server which uses defined get and put handlers.  Other more
> complicated devices like terminals would then need a separate process
> to do their tricky bits.  This went against the current trend,
> however, towards fewer processes as the BEAM is not really efficient
> in sending lists (unified heap rules).
> 
> Actually the basic io protocol is pretty simple so I have not really
> felt the need to be great.  I will look into it, however, when I get
> the time.

Perhaps I/O isn't the best example, and the word 'behaviour' is perhaps
misleading.  There are several other examples in the standard libraries of
modules which can and do expose the same 'interface' - dict and orddict,
for example.

What I'm getting at is probably closer to this suggestion from the
programming guidelines:

"3.9 [...] a consistent system where different modules do things in a
similar manner will be much easier to understand than a system where each
module does things in a different manner."

One way for modules to do things in a similar manner is for these modules
to share an interface.  I see interfaces as a subset of behaviours.  A
behaviour specifies common functionality, plus callback points.  An
interface just specifies callback points *without* providing any common
functionality.

behaviour_info/1 gives Erlang an automated way to check that a module
adheres to an interface.  Without it, we resort to manually documenting
the interface, for example from the docs for Tobbe's epop:

"CHANGING DATABASE
If another database or mail storage facility is going to be used together
with the epop server, then it has to export the following functions:
start/0, get_passwd/1, stat/1, scan_list/1, get_mail/2, lock_maildrop/1,
unlock_maildrop/1, delete_mail/3], add_user/2, rm_user/1, change_passwd/2,
msg_limit/2, store_mail/3."

This "contract between modules" could be expressed in Erlang like so:

-module(gen_epop_db_interface).
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
[
  {start,0}, {get_passwd,1}, {stat,1}, {scan_list,1}, {get_mail,2},
  {lock_maildrop,1}, {unlock_maildrop,1}, {delete_mail,3}, {add_user,2},
  {rm_user,1}, {change_passwd,2}, {msg_limit,2}, {store_mail,3}
].

I like the Erlang version better because it's tighter.  The compiler and
other applications know about the interface, not just the programmer, so
it's less error-prone.

Chris



More information about the erlang-questions mailing list