Skip to content

qoocku/emixins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Introduction

Current version of Erlang/OTP has had a rather experimental feature which resembles a "mixin" concept of several OO languages. The feature is enabled by inserting -extends(a_mixin) attribute into a module code. This feature is enabled during run-time -- standard error_handler module catches all cases of undefined function call and tries to call the lacking function using the module mentioned in the extends attribute. It's rather, like err..., pretty cool BUT one may define only ONE mixin.

The emixins application makes many mixins possible BUT the drawback is the fact that the mixins "mixing" is done during module compilation using parse_transform mechanism. This means that:

  • all of the mixed-in modules SHOULD be compiled before;
  • reloading of a mixed-in module does not affect the target module exported functions list -- only the function seen in a mixin during compilation will be available; one have to recompile the target module to obtain new functions (if such has been added to a mixin) mixed-in.

Example of usage

Let's say we have a "base" module b.erl:

-module (b).
-export ([f/0]).

f () -> 1.

and another "base" module b2.erl:

-module (b2).
-export ([f2/0]).

f2 () -> 2.

The target module is called t.erl:

-module (t).
-compile ([{parse_transform, mixins_pt}]).
-mixins ([b]).

Now, the -mixins(list()) attribute defines a list of mixed-in modules that will participate in function calls of the t module. After compilation of the modules you should be able to call:

t:f().

to obtain great value of 1. If you type:

t:module_info(exports).

you should obtain something like this:

[{f, 0}, ...]

Mixing more than one module

Now, change the t code like this:

-mixins ([b, b2]).

If you look at the t:module_info(exports) result you can see this:

[{f, 0}, {f2, 0}, ...]

which mean that you may safely call f2/0 function with the t module.

Mixing parameterized modules

This is cool but it must be done carefully. If you mix parametrized (abstract) modules into a non-abstract (I mean normal) module nothing really happens. You just have to remember that a parameterized module function application need "hidden" argument (module instance value) to be passed. Obviously in rather unhidden way:

%% Abstract module
-module (p, [X]).
-export ([x/0]).

x () -> X.

So, the target module is:

-module (t).
-compile([{parse_transform, mixins_pt}]).
-mixins ([p]).

How to use it? Look:

1> X = p:new(10).
2> t:x(X).
10

or using t module which has new/1 function already mixed-in:

1> X = t:new(10).
2> t:x(X).
10

The real fun is to mix abstract modules into an abstract module:

%% Target module
-module (t, [MIXIN, T]).
-compile([{parse_transform, mixins_pt}]).
-mixins ([p]).
-export ([new/1, f/1, g/1]).

new (T0) ->
  ?MODULE:instance(p:new(T0+20), T0).
  
f (A) -> T + A.
g (A) -> THIS:x() + A + T. %% <- watch THIS!

Now, it's time to check this guy:

1> X = t:new(10).
{t,{p, 30},10}
2> X:x().
30
3> X:f(1).
11
4> X:g(1).
41    

Ok. But what if we want to mix abstract modules altogether with regular ones? Just, mix them! Let's change the mixins list in the target module:

-mixins ([b, p, b2]).

As you see, the regular b mixin is put before abstract p mixin. Just for fun.

Now, what's the value of module_info?

1> t:module_info(exports).
[... 
 {new,1},
 {f,2},
 {g,2},
 {x,1},
 {f,1},
 {module_info, 0},
 {module_info, 1},
 {module_info, 2}]

Test it:

2> T = t:new(1).
{t,{p, 21},1}
3> T:f().
1
4> T:module_info(exports).
[{instance, 1},
 {new,1},
 {f,2},
 {g,2},
 {x,1},
 {f,1},
 {module_info, 0},
 {module_info, 1}]
5> X:f2().
2
6> X:f().
1

Excluding from mixing

Sometimes it's necessary to exclude a function from a mixin (for example, I've run into this mixing a ebmachine resource module which originally included webmachine.hrl which in turn defined and exported init/1 function). This is possible using this mixins attribute form:

-mixins([{a_module, {exclude, [F/A, ...]}}]).

This tells the parser to mix everything but the functions listed after exclude atom.

Let's take a break for a while and try to summarize what we've seen already:

  • only exported functions may be mixed-in;
  • if the target module exports a function with the same name & arity as in one of the mixed-in modules the function supress them;
  • mixing abstract modules requires no additional job if the target module is regular one;
  • abstract modules mixed-into an abstract target module require additional parameter defined in order of mixing at the beginning of the module's parameter list;
  • the order of mixing is important if there are functions of the same name & arity in mixins -- this one from the last mixin on the mixins list will be chosen to be called.
  • the order of abstract mixins on the -mixin(...) list must be the same as the order of the mixins parameters of the target abstract module;
  • the order of the regular mixins "mixed" with abstract mixins is irrelevant -- only the abstract mixins order matters (in case of abstract target module)

About

Enable Using Mixins in Erlang Modules

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published