Proposed change to libraries

Richard A. O'Keefe ok@REDACTED
Thu Feb 10 00:49:53 CET 2005

Kostis Sagonas <kostis@REDACTED> wrote:

	First let me enumerate the things we (all?) agree upon:
	 1. Something needs to be done in libraries to make them more consistent
	    with their documentation and allow more effective type checking.

I disagree, in the sense that it is not *JUST* the libraries that come
with Erlang/OTP.  What I would like is something *easy* that people can
do to their *own* code to make it better fit the intended types.

The important difference here is that one's view of the scope of the
problem affects one's judgement about how much extra effort it is
tolerable to impose.

If you think it is _just_ the libraries, and the libraries are "owned"
by a small disciplined group of people, then even something fairly
heavyweight could be tolerated and we could have confidence that the
job would be completed.

If, with me, you think that we *all* write code that would benefit from
better checking, you want a solution that is very very easy to apply.

Also, we need a new edition of the Erlang book and the style guides which
explain the new recommended style.  If the new style is not to drive people
away in fits of contemptuous laughter, it again has to be a solution that
is very very easy to apply.  (Like Common Lisp adding ENDP so that you can
"fix" a list iteration simply by changing (NULL x) to (ENDP x).)

	 2. Tuples-as-funs need to be handled somehow but only for a
	    transitional period (of currently unspecified length ;-).

If we agree on that, then there should be NOTHING in the code which
explicitly tests for {M,F} tuples.

	 3. The checks that will be performed for tuples-as-funs cannot be as
	    strong as (at least some of us) would like them to be.
	 4. Some new built-in guards have to be introduced.  If they handle
	    tuples-as-funs, this handling will be taken out when their support
	    is discontinued.
	 5. It is probably not a good idea to modify the semantics of existing
	    guards (e.g. is_function/1) to also handle tuples-as-funs only to
	    take these modifications away some fine day.

Agreed on those.

	So, here is an alternative proposal that satisfies the above
	constraints and allows us to do more effective type checking.
	Modify map/2 to look as follows:
	    map({M,F}, L) -> map(fun(X) -> M:F(X) end, L);
	    map(F, [H|T]) -> [F(H) | map(F, T)];
	    map(F, []) when is_function(F,1) -> [].
Presumably this is a mistake for

	map({M,F}, L) when atom(M), atom(F) ->
	    map1(fun(X) -> M:F(X) end, L);
	map(F, L) ->
	    map1(F, L).

	map1(F, [H|T]) -> [F(H) | map1(F, T)];
	map1(F, []) when is_function(F, 1) -> [].

But that does NOT satisfy the above constraints.  Not only does it not
satisfy constraint 2, it jumps up and down on it and then spits on the
bleeding corpse.  It does exactly the same thing to my modified
constraint 1:  the solution must be very easy to apply.

A solution which requires splitting existing functions into two and
adding at least two more clauses is a heavyweight solution.  The OTP
team may be disciplined enough to do this, but most other people will
say "I've been managing without type checking up to now, this is a
huge amount of boring and error prone work that gives _me_ very little
immediate benefit."

The proposed change to library functions is
(1) heavyweight.  One function turns into two.  Two new clauses are added.
    (More if there's more than one function argument.)
(2) error prone.  It's far too easy to write map (or map1) instead of
    map1 (or map).  It's FAR too easy to forget to change _all_ the
    existing recursive calls to map.
(3) a huge roadblock in the way of removing support for {M,F} pairs;
    every single function that has been modified in this style is another
    member of the resistance fighting against such a change.  Remove it
    from the libraries, and it will still be there in some user code.
(4) a *double* cost.  All such functions have two be modified on two
    occasions.  Now, in order to add the is_function/2 guard without
    removing support for {M,F} pairs.  And later, to remove support for
    {M,F} pairs.  Every single higher order function.  Change to every
    line of every such function.  On two separate occasions.

Now if we adopt is_applicable/[1,2], we find
(1) the change is lightweight; a guard is _added_ to base case clauses
    of higher-order functions but it's only a matter of inserting text,
    not altering any single text.  No functions are split, no clauses added.
(2) Not particularly error prone.  The most likely error is forgetting to
    make the change.
(3) A positive help when support for {M,F} pairs is removed.  Just change
    is_applicable/[1,2] to be the same as is_function/[1,2] and that *one*
    alteration makes the change *everywhere*.
(4) A *single* cost.  The code is changed once.  If it is decided to
    replace is_applicable/[1,2] by is_function/[1,2], that can be
    automated, but there really isn't any need to do that.

	and then is_function/2 only handles proper fun objects.  Besides being
	much cleaner, I think, it also has the extra advantage that calls to
	map/2 with a tuple-as-fun also work when compiled in native code (which
	currently they do not).

Ah.  So this is really all about patching the source code to work around
a bug in the compiler!

	Then taking out support for tuples-as-funs is
	just a matter of removing the extra clause -- the semantics of all guards,
	current or new, remains unchanged.
Replacing one guard (is_applicable/2) by another (is_function/2) is
easy to automate reliably.  Removing the extra clauses is _not_ so
easy to automate reliably.

More information about the erlang-questions mailing list