no function clause matching became no case clause matching exception

560 views
Skip to first unread message

Frans Schneider

unread,
Dec 10, 2021, 9:52:12 AM12/10/21
to Erlang Questions
Dear list,

The following list comprehension used to give a no function clause
matching exception in previous version of Erlang. Now it will emit a no
case clause matching exception. Is this expected behavior?

    [(fun(a) -> "a" end)(R) || R <- [a, b, c]]

NB: I use this construct to process lists with different record types.
In the unit tests I was testing explicitly on this type of exception.

Frans


Roger Lipscombe

unread,
Dec 10, 2021, 9:58:34 AM12/10/21
to Frans Schneider, Erlang Questions
On Fri, 10 Dec 2021 at 14:52, Frans Schneider <fchsch...@gmail.com> wrote:
> The following list comprehension used to give a no function clause
> matching exception in previous version of Erlang. Now it will emit a no
> case clause matching exception. Is this expected behavior?

Which versions? I've tested on OTP-24.1.7, and it gives me a function
clause error:

Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:12:12]
[ds:12:12:10] [async-threads:1] [jit]

Eshell V12.1.5 (abort with ^G)
1> [(fun(a) -> "a" end)(R) || R <- [a, b, c]].
** exception error: no function clause matching
erl_eval:'-inside-an-interpreted-fun-'(b)

Eckard Brauer

unread,
Dec 10, 2021, 10:15:34 AM12/10/21
to erlang-q...@erlang.org
Just a bit curious, what is the lambda fun intended to do?

fun(a) -> "a" end

looks to me as it would only evaluate if called with a single symbol a
as arg and (silently) fail otherwise. If I extend a littl I get:

10> [(fun F(a) -> "a"; F(_) -> nothing end)(R) || R <- [a, b, c]].
["a",nothing,nothing]

Don't mind if the question is stupid, I'm still learning...
E.

Frans Schneider

unread,
Dec 10, 2021, 10:16:42 AM12/10/21
to Roger Lipscombe, Erlang Questions

Op 10-12-2021 om 15:58 schreef Roger Lipscombe:
Same version here:

Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:2:2] [ds:2:2:10]
[async-threads:1] [jit]

Eshell V12.1.5  (abort with ^G)

In the REPL it does give the expected exception indeed, but when I
compile, it gives the no case clause exception:

-module(t).
-compile(export_all).
t() ->
    [(fun(a) -> "a" end)(R) || R <- [a, b, c]].

5> t:t().
** exception error: no case clause matching {b}
     in function  t:'-t/0-lc$^0/1-0-'/1 (/home/frans/pm/src/t.erl, line 4)
     in call from t:'-t/0-lc$^0/1-0-'/1 (/home/frans/pm/src/t.erl, line 4)

Dieter Schön

unread,
Dec 10, 2021, 10:26:46 AM12/10/21
to erlang-q...@erlang.org
Hi Frans,


I tested all my installed versions, from 19. onwards, with this neat
feature of asdf:

for v in $(asdf list erlang); do

  asdf shell erlang $v

  echo $v

  erlc lico.erl

  erl -s lico -s init stop

done

It crashes all the time.

I think the reason for the crash is that atoms b and c do not
pattern-match with the atom a in the fun definition.


So probably you had different data in your previous, successful tests?

A question from me: is there a way to define a lambda with different
function clauses?

In general, I would think, it is not advisable. When the lambda gets
complicated, I use a statically defined function..


Kind regards,

Dieter

Roger Lipscombe

unread,
Dec 10, 2021, 10:27:12 AM12/10/21
to Eckard Brauer, erlang-q...@erlang.org
On Fri, 10 Dec 2021 at 15:15, Eckard Brauer <eckard...@gmx.de> wrote:
> fun(a) -> "a" end
>
> looks to me as it would only evaluate if called with a single symbol a
> as arg and (silently) fail otherwise.

It only fails silently if used in a list comprehension (or in a guard,
I suspect).

> what is the lambda fun intended to do?

Not a lot. I suspect that Frans has actually got more complicated code
where he's found this potential bug. He's pared it down to the minimum
reproduction steps necessary. Kudos for that: it makes it easier to
test and fix.

> Don't mind if the question is stupid, I'm still learning...

Not a stupid question. Thanks for asking.

Frans Schneider

unread,
Dec 10, 2021, 10:30:37 AM12/10/21
to Eckard Brauer, erlang-q...@erlang.org
The construct is used to process a list which should contain a mixture
of #o{} and #oa{} records. If the list contains other types of records
--which should never be the case--, it must fail. This is the actual code:

This is the actual code

    ATE_ids = [(fun(#oa{id = AT_id}) -> AT_id;
                   (#o{id = AT_id}) -> AT_id end)(AT) || AT <- ATEs],

It enables me to process different records/maps or whatever is in the
list and will fail when something is in the list that shouldn't be there.


Op 10-12-2021 om 16:16 schreef Eckard Brauer:

Roger Lipscombe

unread,
Dec 10, 2021, 10:31:14 AM12/10/21
to Frans Schneider, Erlang Questions
Confirmed. With compilation, the behaviour changed somewhere between 23.0.3:

$ erl
Erlang/OTP 23 [erts-11.0.3] [source] [64-bit] [smp:12:12]
[ds:12:12:10] [async-threads:1]

Eshell V11.0.3 (abort with ^G)
1> c(t).
t.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,t}
2> t:t().
** exception error: no function clause matching t:'-t/0-fun-1-'(b)
(t.erl, line 4)
in function t:'-t/0-lc$^0/1-0-'/1 (t.erl, line 4)
in call from t:'-t/0-lc$^0/1-0-'/1 (t.erl, line 4)

...and 24.1.7:

$ source ~/.kerl/erlangs/OTP-24.1.7/activate
$ erl
Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:12:12]
[ds:12:12:10] [async-threads:1] [jit]

Eshell V12.1.5 (abort with ^G)
1> c(t).
t.erl:2:2: Warning: export_all flag enabled - all functions will be exported
% 2| -compile(export_all).
% | ^

{ok,t}
2> t:t().
** exception error: no case clause matching {b}
in function t:'-t/0-lc$^0/1-0-'/1 (t.erl, line 4)
in call from t:'-t/0-lc$^0/1-0-'/1 (t.erl, line 4)

Roger Lipscombe

unread,
Dec 10, 2021, 10:35:29 AM12/10/21
to Frans Schneider, Erlang Questions
On Fri, 10 Dec 2021 at 15:30, Roger Lipscombe <ro...@differentpla.net> wrote:
> Confirmed. With compilation, the behaviour changed somewhere between 23.0.3:
> ...and 24.1.7:

Narrower: between 24.0-rc1 and 24.0. That's a pretty small window.

Roger Lipscombe

unread,
Dec 10, 2021, 10:36:32 AM12/10/21
to Frans Schneider, Erlang Questions
Even smaller. It changed behaviour between rc1 (function clause) and
rc2 (case clause).

Frans Schneider

unread,
Dec 10, 2021, 10:38:58 AM12/10/21
to Roger Lipscombe, Erlang Questions
Great, thanks for checking

Op 10-12-2021 om 16:36 schreef Roger Lipscombe:

Peti Gömöri

unread,
Dec 10, 2021, 11:06:18 AM12/10/21
to Frans Schneider, Erlang Questions
hi all

OTP 24 release notes says "The compiler will now inline funs that are used only once immediately after their definition." https://www.erlang.org/downloads/24
might be related

Björn Gustavsson

unread,
Dec 13, 2021, 3:46:37 AM12/13/21
to Frans Schneider, Erlang Questions
On Fri, Dec 10, 2021 at 3:52 PM Frans Schneider <fchsch...@gmail.com> wrote:
> The following list comprehension used to give a no function clause
> matching exception in previous version of Erlang. Now it will emit a no
> case clause matching exception. Is this expected behavior?
>
> [(fun(a) -> "a" end)(R) || R <- [a, b, c]]

It is an unintended consequence of the change that Peti Gömöri
mentioned (inlining of funs).

It is not a bug, because exactly which type of exception an error will
raise is not documented. Furthermore, we don't recommend matching
error reasons or stack traces (with the exception of the reason
'undef'; see the Warning box in
https://www.erlang.org/doc/reference_manual/errors.html).

That said, the 'case_clause' exception is confusing, and we might fix
that in some future release, but not in OTP 24.

/Björn

--
Björn Gustavsson, Erlang/OTP, Ericsson AB

Mikael Pettersson

unread,
Dec 13, 2021, 5:16:08 AM12/13/21
to Björn Gustavsson, Erlang Questions
On Mon, Dec 13, 2021 at 9:46 AM Björn Gustavsson <bj...@erlang.org> wrote:
>
> On Fri, Dec 10, 2021 at 3:52 PM Frans Schneider <fchsch...@gmail.com> wrote:
> > The following list comprehension used to give a no function clause
> > matching exception in previous version of Erlang. Now it will emit a no
> > case clause matching exception. Is this expected behavior?
> >
> > [(fun(a) -> "a" end)(R) || R <- [a, b, c]]
>
> It is an unintended consequence of the change that Peti Gömöri
> mentioned (inlining of funs).
>
> It is not a bug, because exactly which type of exception an error will
> raise is not documented.

I think this is unfortunate. The language specifies different errors
for different
cases, e.g. badmatch, case_clause, function_clause, and I think the error
implied by the original source should be preserved even if the compiler inlines
and rewrites code. If this is not the case there is no reason for the
language to
pretend to have this distinction.

/Mikael

Björn Gustavsson

unread,
Dec 13, 2021, 7:39:11 AM12/13/21
to Mikael Pettersson, Erlang Questions
On Mon, Dec 13, 2021 at 11:15 AM Mikael Pettersson <mikpe...@gmail.com> wrote:
>
> I think this is unfortunate. The language specifies different errors
> for different
> cases, e.g. badmatch, case_clause, function_clause, and I think the error
> implied by the original source should be preserved even if the compiler inlines
> and rewrites code. If this is not the case there is no reason for the
> language to
> pretend to have this distinction.
>

I have written an issue: https://github.com/erlang/otp/issues/5513

We will need a new BEAM instruction to implement this without code
bloat, so it can be no earlier than OTP 25.

Michael P.

unread,
Dec 13, 2021, 9:22:46 AM12/13/21
to erlang-q...@erlang.org
:-)

On Fri, 10 Dec 2021 16:30:23 +0100
Frans Schneider <fchsch...@gmail.com> wrote:

> The construct is used to process a list which should contain a mixture
> of #o{} and #oa{} records. If the list contains other types of records
> --which should never be the case--, it must fail. This is the actual code:
>
>     ATE_ids = [(fun(#oa{id = AT_id}) -> AT_id;
>                    (#o{id = AT_id}) -> AT_id end)(AT) || AT <- ATEs],

So far, to me "fail" means 'process dies' and 'no catching'.
When "function" or "case" in the exception matters, is there some `catch`?

  ATE_ids = [(fun (#oa{id = AT_id}) -> AT_id;
              (#o{id = AT_id}) -> AT_id;
(Except) -> throw({myapp_unok, Except}) end)(AT)
|| AT <- ATEs],

If you can name it, you can claim it?

:-)

~Michael

--

The oldest, largest, most buggy piece of legacy
software in constant maintenance: human society.




Reply all
Reply to author
Forward
0 new messages