The Elixir philosophy is “let it crash”; this makes a great deal of sense for a telecommunications system (which is what Elixir was first designed for). Hardware is going to fail. When it does, you just replace it or restart it. The person using the phone system is unaware of this; her phone just continues to work.
This philosophy, however, is not the one you want to employ when you have (atypical for Elixir) programs that ask for user input. You want to those to crash infrequently and catch as many input errors as possible.
In this étude, you will write a module named ask_area
, which prompts you
for a shape and its dimensions, and then returns the area by calling
Geom.area/3
, which you wrote in Étude 4-1.
Your module will ask for the first letter of the shape (in either upper or lower case), then the appropriate dimensions. It should catch invalid letters, non-numeric input, and negative numbers as input. Here is some sample output.
iex(1)> c("ask_area.ex") [AskArea] iex(2)> c("geom.ex") [Geom] iex(3)> AskArea.area() R)ectangle, T)riangle, or E)llipse: r Enter width > 4 Enter height > 3 12 iex(4)> AskArea.area() R)ectangle, T)riangle, or E)llipse: T Enter base > 3 Enter height > 7 10.5 iex(5)> AskArea.area() R)ectangle, T)riangle, or E)llipse: w Unknown shape w :ok iex(6)> AskArea.area() R)ectangle, T)riangle, or E)llipse: r Enter width > -3 Enter height > 4 Both numbers must be greater than or equal to zero. :ok iex(7)> AskArea.area() R)ectangle, T)riangle, or E)llipse: e Enter major radius > 3 Enter minor radius > 2 18.84955592153876
Here are the functions that I needed to write in order to make this program work.
char_to_shape/1
R
, T
, or E
in either upper or lower case),
return an atom representing the specified shape (:rectangle
,
:triangle
, :ellipse
, or :unknown
if some other character is entered). Hint: use String.first/1
to get the first character of
the user input, and use String.upcase/1
to make it upper case.
get_number/1
Given a string as a prompt, displays the string
"Enter
and returns the number that was input.
This involves the following steps:
prompt
> "
String.strip/1
to get rid of the trailing newline character
string_to_integer/1
to convert the string to a number.
When you write the -spec
for this function (you have been
writing documentation for your functions, haven’t you?), the type
you will use for the parameter is String.t()
. You cannot use
string()
, because that is a built-in type for Erlang, and there
would be a conflict. You can see a complete
list of the built-in types at
http://www.erlang.org/doc/reference_manual/typespec.html
get_dimensions/2
get_number/1
twice. Returns a tuple {n1, n2}
with the dimensions.
calculate/3
:unknown
, or the first or second dimension isn’t numeric,
or either number is negative, the function displays an
appropriate error message. Otherwise, the function calls
Geom.area/3
to calculate the area of the shape.
The code you wrote in the previous étude is still sensitive to bad input; if you enter a floating point number or a word instead of a number, you will get an error message.
iex(1)> AskArea.area() R)ectangle, T)riangle, or E)llipse: r Enter width > 3.5 ** (ArgumentError) argument error :erlang.binary_to_integer("3.5") /Users/elixir/code/ch05-01/ask_area.ex:50: AskArea.get_number/1 /Users/elixir/code/ch05-01/ask_area.ex:60: AskArea.get_dimensions/2 /Users/elixir/code/ch05-01/ask_area.ex:18: AskArea.area/0 erl_eval.erl:569: :erl_eval.do_apply/6 src/elixir.erl:133: :elixir.eval_forms/3 /bin/elixir/lib/iex/lib/iex/server.ex:19: IEx.Server.do_loop/1
In this étude, you will use regular expressions to make sure that input is numeric and to distinguish integers from floating point numbers. You need to do this because binary_to_float/1
will not accept a string like "1812"
as an argument. If you aren’t familiar with regular expressions, there is a short summary in Appendix B.
The function you will use is the Regex.match?/2
. It takes a regular expression pattern as its first argument and a string as its second argument. The function returns true
if the pattern matches the string, false
otherwise. Here are some examples in IEx.
iex(1)> Regex.match?(~r/e/, "hello") true iex(2)> Regex.match?(~r/[0-9]/, "h3llo") true iex(3)> Regex.match?(~r/b[aeiou]g/, "beagle") false
You will need to change get_number/1
to test input against patterns that match integers and floating point numbers. Presume (as Elixir does) that floating point numbers must have at least one digit before and after the decimal points. Extra credit for handling exponential notation. If neither pattern matches, have the function return :error
. You will then need to change calculate/3
to handle the errors. (I did this by adding clauses.)
Write a module named Dates
that contains a function
date_parts/1
, which takes a string in ISO date format
("yyyy-mm-dd"
) and
returns a list of integers in the form
[yyyy, mm, dd]
. This function does not need to do any error checking.
iex(1)> c("dates.ex") [Dates] iex(2)> Dates.date_parts("2013-06-15") [2013,6,15]
Use the String.split/3
function to accomplish this task.
How, you may ask, does that function work? Ask Elixir!
In IEx, type h String.split
and you will see the online documentation for
that function.
Yes, I know this étude seems pointless, but trust me: I’m going somewhere with this. Stay tuned.
3.145.174.253