3
$\begingroup$

This question already has an answer here:

I want to write a function using string as explanation for its arguments so that the function becomes intuitive for new users. For example, I want to write a function to compute BMI (just for illustration, not the real problem):

bmi[h_, w_] := (703*w)/(12*h)^2
bmi[5.4, 142]

23.7735

I can modify this function as the following and it works but assigns values.

bmi2[heightinFeet = h_, weightinLB = w_] := (703*w)/(12*h)^2
bmi2[5.4, 142] == bmi2[heightinFeet = 5.4, weightinLB = 142]
bmi2[heightinFeet = 5.4, weightinLB = 142]
heightinFeet

True

23.7735

5.4

But I want to define the function with explanation of the arguments without assignment. That is, I want to modify this function as follows and not assign values:

bmi3["height in Feet" = h_, "weight in LB" = w_] := (703*w)/(12*h)^2
bmi3[5.4, 142]

23.7735

I guess bmi3 might work but not sure how this assignment is working. Is there a better way to address this issue?

$\endgroup$

marked as duplicate by Roman, rhermans, garej, Öskå, Henrik Schumacher Jul 24 at 13:57

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    $\begingroup$ if it is ok to use Rule instead of Set, you can do bmi0[("height in Feet" -> h_) | (h_), ("weight in LB" -> w_) | (w_)] := (703*w)/(12*h)^2 $\endgroup$ – kglr Jul 19 at 23:31
  • $\begingroup$ @ Roman, there might be misunderstanding regarding my question and what I want to accomplish. I have edited the question to reflect what I want to accomplish. $\endgroup$ – ramesh Jul 22 at 2:14
3
$\begingroup$

Here's one way. By overriding the action of Set/= by making bmi3 have the Attribute HoldAll, we can take apart the arguments before Set is executed:

ClearAll[bmi3];
SetAttributes[bmi3, HoldAll]; 
bmi3[("heightinFeet" = h_) | h_, ("weightinLB" = w_) | w_] := 
 With[{map = {"heightinFeet" -> h, "weightinLB" -> w}},
  ((703*"weightinLB")/(12*"heightinFeet")^2 /. map)];

bmi3["heightinFeet" = 5.4, "weightinLB" = 142]
(*  23.7735  *)

bmi3[5.4, "weightinLB" = 142]
(*  23.7735  *)

bmi3[5.4, 142]
(*  23.7735  *)

A more Mathematica-like way would be to use options, somewhat like @kglr's suggestion in a comment:

ClearAll[bmi];
Options[bmi] = {"heightinFeet" -> $Failed, "weightinLB" -> $Failed};
bmi::hw = "Specify both \"heightinFeet\" and \"weightinLB\".";
bmi[OptionsPattern[]] := 
  With[{map = Thread[{"heightinFeet", "weightinLB"} -> 
       OptionValue[{"heightinFeet", "weightinLB"}]]},
   ((703*"weightinLB")/(12*"heightinFeet")^2 /. map) /; 
    FreeQ[map, $Failed]
   ];
bmi[___] := Message[bmi::hw];

Now the order doesn't matter:

bmi["heightinFeet" -> 5.4, "weightinLB" -> 142]
(*  23.7735  *)

bmi["weightinLB" -> 142, "heightinFeet" -> 5.4]
(*  23.7735  *)

bmi["heightinFeet" -> 5.4]

bmi::hw: Specify both "heightinFeet" and "weightinLB".

If you want to also have bmi[h, w], insert this definition after the definition of bmi::hw:

bmi[h : Except[_Rule | _RuleDelayed], w : Except[_Rule | _RuleDelayed]] := 
  bmi["heightinFeet" -> h, "weightinLB" -> w];

In this definition, order matters, however. If you want to restrict this form to numeric inputs, then use this definition instead:

bmi[h_?NumericQ, w_?NumericQ] := bmi["heightinFeet" -> h, "weightinLB" -> w];
$\endgroup$
  • $\begingroup$ @ Michael, thank you for your answer. My goal is to introduce user defined functions to new users. Therefore, making function arguments intuitive is a primary goal. Your answer provides an additional help for me. Thank you so much. $\endgroup$ – ramesh Jul 20 at 21:19
2
$\begingroup$

Michael E2's way of using options is a neat way if you have to handle many arguments and if you can specify good default values for them. If the user is always forced to enter all options, then this becomes very clunky, (and the options are not optional anymore!).

Think of

Sin["Angle" -> Pi/3]

vs.

Sin[Pi/3] 

So, for that few number of variables that are all mandatory, the classical thing to do is to write a short documentation of bmi, e.g.,

bmi::usage = "bmi[h,w] compute the body masss index for a person of height h and weight w."

Then the user can learn about bmi by executing

?bmi
$\endgroup$
  • $\begingroup$ @ Henrik, thank you. $\endgroup$ – ramesh Jul 24 at 15:18
  • $\begingroup$ @ramesh, you're welcome. $\endgroup$ – Henrik Schumacher Jul 24 at 15:47

Not the answer you're looking for? Browse other questions tagged or ask your own question.