[erlang-questions] ** exception error: no function clause matching test_calculate:validate(["1", "+", "1"], []) (test_calculate.erl, line 11)

Richard A. O'Keefe ok@REDACTED
Thu Oct 1 05:42:58 CEST 2015


On 1/10/2015, at 4:42 am, Roelof Wobben <r.wobben@REDACTED> wrote:
> 
> -module(test_calculate).
> 
> -export([validate/1]).
> 
> scan(String) ->
>    validate(string:tokens(String, " ")).

I'm going to use an entirely informal type notation here.
I am doing this to emphasise that you not only do not need
to understand a language's type checker, there doesn't even
need to BE a type checker for the language for YOU to check
that the types make sense.

string:tokens(Source :: <string>, Separator :: <string>) :: <string> list.

You can verify this by calling string:tokens/2 in the Erlang shell.

1> string:tokens("1 + 1", " ").
["1","+","1"]

You pass the result of string:tokens/2 to validate/1, so we must have

scan(Source :: <string>) :: SOMETHING.
validate(Tokens :: <string> list) :: SOMETHING.

That is, scan/1 returns whatever validate/1 returns,
and validate/1 is given a LIST OF STRINGS as its argument.

> 
> validate(String) ->
>    validate(String, []).

Right here alarm bells go off.  We know from the call
that the argument is not a string.  It is a LIST of strings.
But the argument *NAME* says 'String'.

validate([Head | Tail], Validated_list) when Head >= $0 , Head =< $9 ->
>    validate(Tail, [Head | Validated_list]);

Here you are comparing an element of validate's argument
as if you are expecting a character.  But Head is a STRING.

It is also a concern to me that you do not have any comment
saying what it *means* for a list of strings to be valid.

2> c(test_calculate).
test_calculate.erl:4: Warning: function scan/1 is unused
test_calculate.erl:19: Warning: function test/0 is unused
{ok,test_calculate}

It looks as though you forgot to -export([test/0]).
> 
> validate([Head | Tail], Validated_list) when Head =:= $+ ->
>    validate(Tail, [Head | Validated_list]);
> 
> validate([], Validated_list) ->
>    Validated_list.
> 
> test() ->
>    ["1","+","1"]    = scan("1 + 1"),
>    ["10", "+", "1"] = scan("10 + 1"),
>    ["1", "+", "10"] = scan("1 + 10"),
>    io:format("All tests are green ~n").
> 
> but as soon as I try :
> 
> test_calculate:validate(["1","+", "1"])
> 
> I see the above error
> 
> Someone a tip where things are going the wrong way ?

The error message is pretty explicit.
You have a call
    validate(["1","+","1"], []).
No clause matches that call.

You expected that some clause *would* match it.
So what you do is compare each clause in turn with
the call to see why *that* clause did not match.

The first clause looks for a CHARACTER between
$0 and $9 but it finds the STRING "1".
4> {"1" >= $0, "1" =< $9}.
{true,false}

So that clause does not match.

The second clause looks for the CHARACTER $+
but it finds the STRING "1".

So that clause doesn't match.

The last clause looks for the empty list,
but it finds a list of three strings.

So that clause doesn't match.

And we've found out what looking at scan/1 and
validate/1 told us straight away, that you are
muddling up strings and characters.

So let's fix that.

-module(test_calculate).
-export([test/0, validate/1]).

scan(String) ->
    Tokens = string:tokens(String, " "),
    case validate(Tokens)
      of ok  -> {ok,Tokens}
       ; Err -> Err
    end.

%% A Token_List is valid if and only if it contains
%% number tokens and operator tokens, where a number
%% token is a non-empty string of digits and currently
%% an operator token is just "+".  We either report
%% 'ok' for a valid list or {error,Reason,Culprit} for invalid.

validate([]) ->
    ok;
validate(["+"|Tokens]) ->
    validate(Tokens);
validate([Num|Tokens]) ->
    case is_number_token(Num, 0)
      of true  -> validate(Tokens)
       ; false -> {error,"not a number or operator",Num}
    end;
validate(Other) ->
    {error,"not a list",Other}.

%% is_number_token(String, N) is true if and only if
%% String is a list of ASCII decimal digit characters
%% and N + length(String) > 0.

is_number_token([], N) ->
    N > 0;
is_number_token([C|Cs], N)
  when C =< $9, C >= $0 ->
    is_number_token(Cs, N+1);
is_number_token(_, _) ->
    false.

test() ->
   {ok,["1","+","1"]   } = scan("1 + 1"),
   {ok,["10", "+", "1"]} = scan("10 + 1"),
   {ok,["1", "+", "10"]} = scan("1 + 10"),
   io:format("All tests are green ~n").

The single most important step in getting that right was
WRITING THE COMMENTS.





More information about the erlang-questions mailing list