Higher Order Modules

ingo.schramm <>
Mon Dec 14 17:43:45 CET 2009


I wonder if parameterized modules are really intended to do OOP stuff
only. It seems to be possible to use this feature to implement
something like higher order modules, modules parameterized by other
modules.

As an example I could write a monoid module like this:

>>>
-module(monoid,[Impl]).
-export([op/2,cmp/2,get_id/0,get_member/0,is_member/1,proof/0]).

%% ------ INTERFACE -----

% apply monoid operation
op(A,B) ->
    case members([A,B]) of
        true  -> Impl:op(A,B);
        false -> undef
    end.

% compare two values if they are equal or not
cmp(A,B) ->
    case members([A,B]) of
        true  -> Impl:cmp(A,B);
        false -> undef
    end.

% get the identity or neutral element
get_id() ->
    Impl:get_id().

% get an arbitrary member element
get_member() ->
    Impl:get_member().

% test whether A is a member or not
is_member(A) ->
    case Impl:is_member(A) of
        true ->
            Impl:is_member(Impl:op(A,Impl:get_id()));
        false ->
            false
    end.

% give proof of monoid laws
proof() ->
    prove_all()

%% ------ PRIVATE ------

prove() ->
    I = Impl:get_id(),
    A = Impl:get_member(),
    B = A,
    Op = prove_closed(A,B),
    C = op(A,B),
    Assoc = prove_assoc(A,B,C),
    Id = prove_identity(N,A),
    neg(lists:member(false,[Op,Assoc,Id])).

prove_closed(A,B) ->
    Impl:is_member(Impl:op(A,B)).

prove_assoc(A,B,C) ->
    Impl:op(Impl:op(A,B), C) =:= Impl:op(A, Impl:op(B,C)).

prove_identity(I,A) ->
    0 =:= Impl:cmp(A,Impl:op(I,A)).

neg(true) -> false;
neg(false) -> true.

% empty set is always member
members([]) ->
    true;
members(L = [A|_]) ->
    neg(lists:member(false, [ is_member(X) || X <- L])).

<<<

Next I could write a module representing the additive monoid of
natural numbers (including zero):

>>>
-module(n_add).
-export([op/2,cmp/2,get_id/0,get_member/0,is_member/1]).

op(A,B) ->
    A + B.

cmp(A,B) when A =/= B ->
    -1;
cmp(A,B) when A =:= B ->
    0.

get_id() ->
    0.

get_member() ->
    1.

is_member(A) when is_integer(A), A < 0 ->
    false;
is_member(A) when is_integer(A), A >= 0 ->
    true;
is_member(_A) ->
    false.
<<<

And then I use it:
---
1> M = monoid:new(n_add).
{monoid,n_add}
2> M:proof().
true
3> M:op(1,2).
3
---

Another implementation may do matrix operations. It is also easy to
add more structure, for example to turn the monoid into a group by
adding op_inverse/2.

Does that make any sense?

Ingo




More information about the erlang-questions mailing list