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 m2\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.
1 Answer 1
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)
).
-
\$\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 anfi
object? \$\endgroup\$Freddy Mcloughlan– Freddy Mcloughlan2023年03月13日 22:11:49 +00:00Commented Mar 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 atisreal
as well, in case the user enters a complex number. \$\endgroup\$Cris Luengo– Cris Luengo2023年03月13日 22:17:33 +00:00Commented Mar 13, 2023 at 22:17
input()
interaction is usually something you want to confine to the very top-level application, and keep it out of utility functions. \$\endgroup\$clc
, thank you \$\endgroup\$