OK, first define the function:
printEven(I,N) when I >= N -> ok;
printEven(I,N) ->
if
I rem 2 == 0 -> io:format("~p~n",[I]), printEven(I+1,N);
I rem 2 == 1 -> printEven(I+1,N)
end.
Erlang is a functional programming language and (by definition) functions 'have' a value so you are going to get 'something' back. By convention the thing you get back on completion of a function that you are using for side-effects is the atom 'ok', that's the best to use here.
You can 'silently discard' the return value if you want. You do that when you invoke the function by pattern matching to the 'don't care' variable (which is underscore):
_ = printEven(3,9),
or by calling the function without a pattern match:
printEven(3,9),
However, you are much better to always check return values by pattern matching when you invoke a function:
ok = printEven(3,9),
This is a a really good habit to get into because you will be using a lot of library functions that return error codes as you can see from their specs:
@spec funky(X) -> [ok | {error, bad_op} | {error, wig_out}]
If funky has side effects you want to know it has failed now by invoking it with a pattern match so it will crash here and now if funky fails:
ok = funky(99),
If you match it to '_'
or ignore the return value it will crash 268 lines later when your mojo expects funky to have done his thang, and then it is much harder to find.
This is happy path programming which is the done thing in Erlang. "Let it crash" is the motto. If you are new to Erlang you will find this very disconcerting - like walking about naked. Don't worry embrace it, it is a good thing. It leads to lots of code 'not being written'.
(You should also get in the habit of putting the clause that ends the recursion as the top clause as shown here - it makes reading the code sooo much easier when you have a multi-clause function.)