3
\$\begingroup\$

I've made a script (Triangle.m) to calculate the area of a triangle in MATLAB:

function Triangle() % Main function clc; clear variables; close all; % Set function values base = force_positive_finite("Base"); height = force_positive_finite("Height"); area = tri_area(base,height); fprintf("Area: %2.3f m²\n",area); end function output = force_positive_finite(var_name) % Ask/force the user to enter a positive number output = -1; while true output = input("Enter in "+var_name+" (m): "); if input_error_response(output) % If no error, leave loop break end end end function response = input_error_response(in) % Warns user if input is invalid if isempty(in) || in < 0 || in==inf % Value must be a non-empty positive number disp("Enter a value: {0 <= VALUE < inf}"); response = false; % This signals the loop to continue else response = true; % This means we can exit the loop end end function area = tri_area(base,height) % Calculate the area of a triangle arguments base {mustBeNumeric, mustBeFinite}; height {mustBeNumeric, mustBeFinite}; end area = base*height*0.5; end 

It contains force_positive_finite to ensure the input is >= 0 and finite, then is fed into tri_area where it is checked to be numeric and finite, before calculating the area.

I am new to MATLAB, so any pointers on convention, optimisation and general improvements are appreicated.

\$\endgroup\$
2
  • \$\begingroup\$"clear variables;" What?!? You do three clears for no perceptible reason? I won't be calling your function anytime soon, for fear I lose that giant matrix that took ten minutes to create. More generally, think about whether you're writing a (clean! pure!) function that maps from these numbers to those numbers, versus writing an application that satisfies some customer's Story. The three clears is an extreme example, but even a side effect like an input() interaction is usually something you want to confine to the very top-level application, and keep it out of utility functions.\$\endgroup\$
    – J_H
    CommentedMar 7, 2023 at 0:26
  • \$\begingroup\$@J_H this is literally my first script, I didn't really know how potent the 3 clears were. I'll reduce it to a clc, thank you\$\endgroup\$CommentedMar 7, 2023 at 1:51

1 Answer 1

3
\$\begingroup\$

The function Triangle() is clearly a script that you turned into a function. I personally don't like putting clc, clear and close at the top of my scripts, but I see this a lot, and it's not a terrible thing to do. In a function they are very much out of place.

A function has its own workspace (it cannot see variables defined outside of it), so there really is no need for a clear. This is the point of using functions instead of scripts: each function has its own workspace ("scope" in other programming languages), which is cleared the moment the function ends. The base workspace doesn't get cluttered with variables if you use functions instead of scripts, and the code inside the function will not accidentally be using variables from a different scope (which makes finding bugs easier).

Given that Triangle() asks for user input, a clc is not terrible, but you could accomplish the same effect (make the input prompt more visible) by first printing some empty lines.

close all closes all figure windows. Your program doesn't deal with figure windows, why attack any existing ones? Just let them be!


force_positive_finite() is fine. Of course there are other ways to implement it. Instead of break, for example, you could return. This makes it a bit easier (IMO) to read the function, because I don't need to look for additional code below the loop. I know this is where the function ends. But simpler would be:

function output = force_positive_finite(var_name) % Ask/force the user to enter a positive number output = -1; while ~input_error_response(output) output = input("Enter in "+var_name+" (m): "); end end 

while tests a condition. Instead of invalidating that test by testing true, and adding an additional if statement, use that test to do what your if statement does.


Similarly, input_error_response() can be simplified a bit. The construct if <test>, v = true, else, v = false is a typical anti-pattern, better written as v = <test>. You do a bit more inside your if statement, still you can avoid assigning true or false to your response variable:

function response = input_error_response(in) % Warns user if input is invalid response = ~isempty(in) && in >= 0 && isfinite(in) if ~response disp("Enter a value: {0 <= VALUE < inf}"); end end 

Note that your comment "Value must be a non-empty positive number" comes next to a test for the number being non-negative, which is not the same as positive. Positive: in > 0. Non-negative: in >= 0. [Your original test, in < 0 is negated as in >= 0, as I put into my version of the function.]

I changed the in == inf test to ~isfinite(in). This catches also the case where in == -inf (which you already tested for with the non-negativity test), and the case where in is NaN.

Finally: What if the user inputs "foo"? What if the user inputs [4, 6]? You should probably also test that the input is a double array (isnumeric(in) or isfloat(in) or isa(in,'double')), and scalar (isscalar(in)).

\$\endgroup\$
2
  • \$\begingroup\$Thank you for such a detailed response, I have updated with the relevant changes. How would I go about implementing isdouble()? do I need to convert the input to an fi object?\$\endgroup\$CommentedMar 13, 2023 at 22:11
  • 1
    \$\begingroup\$@FreddyMcloughlan Sorry, my mistake. I didn't pay attention to the details when I looked up if isdouble existed. isnumeric is probably the way to go, but you can be more detailed if you like (see updated answer). Oh, and take a look at isreal as well, in case the user enters a complex number.\$\endgroup\$CommentedMar 13, 2023 at 22:17

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.