Extending Functionality: an expedient and questionable approach
Shawn Pearce
spearce@REDACTED
Thu Mar 13 23:56:45 CET 2003
Chris Pressey <chris_pressey@REDACTED> wrote:
> i.e. an hour ago, I would have believed you... but I've been
> intentionally introducing errors into rectangle.erl and square.erl just
> to see what happens...
>
> And I'm suprised. I really, really, REALLY thought that calling an
> undefined function from square:undefined_function/2 (or something it
> calls) would throw the runtime into an infinite loop (or worse). But
> so far, I have yet to crash the emulator in a bad way - I just get the
> usual error reports (I can even catch them in the usual way - from the
> shell or with -run on the commandline)
Chris, I think this would do it:
-module(square).
-export([undefined_function/2]). % Delegate to parent
undefined_function(F,A) ->
apply(rectangle, F, A).
-module(rectangle).
% We forget to implement the shape:area call.
-export([undefined_function/2]). % Delegate to parent
undefined_function(F,A) ->
apply(shape, F, A).
-module(shape).
-export([undefined_function/2]). % Delegate everything to child
undefined_function(F, [A | Args]) ->
Type = element(1, A),
apply(A, F, [A | Args]).
square:area({square, 4, 2}).
Where the function called (area) gets kicked up to the parent, who
calls back into shape, which calls back into the child as it is just
a generic abstract call point for all 'shape' calls.
Its really a problem when both modules implement undefined_function/2
and somehow enter into each referring to the other. Rare in OO cases,
but possible if someone makes a mistake in one or the other module.
My example above may be odd, but it makes some sense: its like
declaring all methods abstract virtual in a base class 'shape' in C++
and defering all calls to the subclasses, but allowing the user to
use shape to invoke the methods.
I still prefer the compiler-based -inherit I suggested in my last
email, as it is very explicit to both the compiler, runtime and
developer(s) about what is happening, what is available, etc. The
'special' handling through undefined_function is cute, as you can
now do:
-module(os).
undefined_function(F, [A]) ->
os:cmd(atom_to_list(F) ++ " " ++ A).
os:rm("the file.txt").
os:ls("mydir").
os:find("mydir -type f").
etc.
But now you have no idea of what is available in the os module, what
it doesn't provide, etc.
What -inherit would prevent would be from doing a generic shape module
that would push all calls down, but that could be done like so:
-module(shape).
-export([area/1, width/1, height/1]).
-define(VIRTUAL(Name), Name(Shape) -> delegate(Shape, Name)).
?VIRTUAL(area).
?VIRTUAL(width).
?VIRTUAL(height).
delegate(Object, Function) when is_tuple(Object) ->
Type = element(1, Object),
Type:Function(Object).
And if square uses rectangle's area, we can still say:
shape:area({square, 1, 1}).
and have the system execute:
shape:area/1 ->
square:area/1 ->
rectangle:area/1 ->
1.
which is what we are looking for, but should we forget to implment
rectangle:area/1:
shape:area/1 ->
square:area/1 ->
exit({undef, {rectangle, area, [{square, 1, 1}]}})
Which is what we really want. Of course, this means the root level
shape must define all calls and push them down.
Of course, my idea of -inherit could possibly just be a macro locally
defined, but compiler support through a parse transform would be nicer.
--
Shawn.
You like to form new friendships and make new acquaintances.
More information about the erlang-questions
mailing list