[erlang-questions] how do I make print_area work
Garrett Smith
g@REDACTED
Tue Aug 11 14:42:04 CEST 2015
Hi Richard,
On Mon, Aug 10, 2015 at 10:15 PM, Richard A. O'Keefe <ok@REDACTED> wrote:
> Once again I'm going to focus on a peripheral issue,
> which is actually quite important.
I was secretly hoping to get a critique from you - one of the benefits
of any given post :)
>> 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.
The alternative form that occurs to me, which calls the two functions
directly is:
to_number(Str) ->
try
list_to_float(Str)
catch
error:badarg ->
try
list_to_integer(Str)
catch
error:badarg ->
error(bad_number)
end
end.
Not only is this hard to read, it's longer in line and character count
than my version!
More importantly, generalizing to_number/1 here as a series of
attempts clearly spells my intention.
As a nice side effect, if I wanted to add try_octal (as to follow your
own argument, a number from a user is not inherently two possible
things) it's a trivial and coherent change.
Garrett
More information about the erlang-questions
mailing list