[erlang-questions] how do I make print_area work
Richard A. O'Keefe
ok@REDACTED
Tue Aug 11 05:15:06 CEST 2015
Once again I'm going to focus on a peripheral issue,
which is actually quite important.
>
> area() ->
> Shape = shape_from_user(),
> {X, Y} = dimensions_from_user(Shape),
> geom:area(Shape, X, Y).
The Shape and the dimensions should not be *separate*;
they should be in a *single* data structure.
One of the reasons for this is that it is not the case
that all shapes have the same number of dimensions.
To use Haskell syntax here, imagine
data Shape
= Square Float -- side
| Rectangle Float Float -- sides
| Rhombus Float Float Float -- sides, acute angle
| Triangle Float Float Float -- sides
| Triangle2 Float Float -- base altitude
| Circle Float -- radius
| Ellipse Float Float -- semi-major & semi-minor
| Point
We see that the number of dimensions naturally varies from
0 to 3. Now this _could_ be represented as a Shape atom
and a Dimensions list, but done that way there's no
*connection* to make sure the two agree.
It's far better to use
{square,Side}
| {rectangle,Width,Height}
| {rhombus,Side1,Side2,Angle}
| {triangle3,Side1,Side2,Side3}
| {triangle2,Base,Altitude}
| {circle,Radius}
| {ellipse,SemiMajor,SemiMinor}
| {point}
So it should be
area() ->
Figure = figure_from_user(),
Area = area(Figure),
report_area_to_user(Area).
Anyone else on the list remember
"Hierarchical Input-Process-Output"?
Input, processing, and output generally *should* be separated;
putting area/1 and report_area_to_user/1 into a single operation
is, um, looking for a tactful phrase, ah got it, suboptimal.
I would expect someone posting from a .nl address
to be acutely aware of the fact that there are many
human languages in the world. (About 6000, as far as
I know, with many hanging on by their fingernails.)
Reading from the user needs to be localised.
Writing to the user needs to be localised.
The processing that happens in between does not.
As noted before, I even reject the idea of prompting
for the shape and dimensions separately, or if they
are separate, it would be good to make the separation
table-driven. Something like this:
dimensions(square) -> [side];
...
dimensions(circle) -> [radius];
dimensions(point) -> [].
figure_from_user() ->
Shape = get the shape from the user,
Dimension_Names = dimensions(Shape),
Dimension_Values = get each dimension from the user(Dimension_Names),
list_to_tuple([Shape|Dimension_Values]).
This way 'get each dimension from the user' doesn't know
ANYTHING about which shapes have what dimensions and a
new shape can be added very easily.
>
> numbers_from_user(XPrompt, YPrompt) ->
> X = number_from_user(XPrompt),
> Y = number_from_user(YPrompt),
> {X, Y}.
Make this
numbers_from_user([]) ->
[];
numbers_from_user([Prompt|Prompts]) ->
Number = number_from_user( Prompt),
Numbers = numbers_from_user(Prompts),
[Number | Numbers].
Why on earth should this function know that there are
exactly two dimensions when the number *ought* to vary?
>
> to_number(Str) ->
> try_number([fun list_to_float/1, fun list_to_integer/1], Str).
>
> try_number([Method|Rest], Arg) ->
> try
> Method(Arg)
> catch
> error:badarg -> try_number(Rest, Arg)
> end;
> try_number([], _Arg) ->
> error(bad_number).
This is overkill. Here there are two and only two functions
you ever intend to call.
More information about the erlang-questions
mailing list