How to conditionally exclude whole clauses via Erlang's preprocessor?

Viewed 65

I am writing some module in Erlang that is supposed to be compiled on various OTP versions. In some cases I would like to use the unsupported in older versions ++ operator pattern, but only if it is available. Therefore my attempt to this task goes as follows:

f([]) -> empty;
f([_|_]) -> cons;
-if(?OTP_RELEASE >= 23).
f([] ++ _) -> empty_append;
-endif.
f(X) -> error(nah).

The workarounds to this issue I found are:

  • Move the iffed clause to the beginning. This however does not work well when:

    • I need to add more -ifs
    • The order of clauses forbids me from doing so for any reason.
  • Create a beautiful garden of ifelses with whole function copied several times. Obviously, this does not pass.

Is there some convenient way to do it? If possible, I'd appreciate a general solution, not limited to the presented case.

1 Answers

From flow control in macros: The macro directives cannot be used inside functions. So if you want to use -ifdef you need to copy several times the same function.

(I guess that it has to do with them being defined between - and .)

That being said, you can do something like:

f([]) -> empty;
f([_|_]) -> cons;
f([] ++ _) when ?OTP_RELEASE >= 23 -> empty_append;
f(X) -> error(nah).

You can verify that the clause that will never match is removed from the generated beam ASM code with erlc -S <module>. Also, the compiler will show a warning.

The warning generated during this step cannot be selectively omitted. From here:

Another class of warnings is generated by the compiler during optimization and code generation. They warn about patterns that will never match (such as a=b), guards that always evaluate to false, and expressions that always fail (such as atom+42).

Those warnings cannot be disabled (except by disabling all warnings).

If you want to avoid the warning altogether, you need to something like ( note that this flavor will not delete the branch because OtpRelease is a variable ):

f(Term) ->
    f(Term, ?OTP_RELEASE).

f([], _) -> empty;
f([_|_], _) -> cons;
f([] ++ _, OtpRelease) when OtpRelease >= 23 -> empty_append;
f(_, _) -> error(nah).

EDIT: In the last example, OTP23's code optimizer is able to remove unreachable code. In this particular case, if f/2 is not exported, its 3rd or 4th clauses will be removed depending on ?OTP_RELEASE)

Related