2
$\begingroup$

I defined a function which owns many options. In addtion, these options have a common characteristic. Namely, only accepting boolean value True or False. Here is a screenshot as below:

enter image description here

Obviously, I check the values of these options step-by-step. Now I want to check them with a auxiliary function called checkBoolValues[].

Update: thanks for jkuczm and Sjoerd C.deVries, I add the HoldRest attribute to a Function[] body

General::invbool = 
  "Value of option `1` \[Rule] `2` should be a valid boolean value of True or False.";

checkBoolValues[func_, optsNameValue_List] :=
 Scan[
  Function[{optName, optValue},
    If[! MemberQ[{True, False}, optValue], 
     Message[func::invbool, optName, optValue];
     optValue = False
    ], 
  HoldRest] @@ # &, 
  optsNameValue
 ]

Following is the code boloc that used in my function.

Options[BSplineCurveInterpolate] = 
 {OriginPoints -> False, ControlPoints -> False, 
  SplineClosed -> False, TangentVector -> False};

BSplineCurveInterpolate[
  pts_, opts : OptionsPattern[]] := 
 Module[{op, cp, tv, sc},
  (*achieve the value of options*)
  op = OptionValue[OriginPoints];
  cp = OptionValue[ControlPoints];
  tv = OptionValue[TangentVector];
  sc = OptionValue[SplineClosed];
  (*check the values of options*)
  checkBoolValues[
   BSplineCurveInterpolate, 
   {{OriginPoints, op}, {ControlPoints, cp}, 
    {TangentVector, tv}, {SplineClosed, sc}}
  ];
  (*return the values of options*)
  {op, cp, tv, sc}
 ]

However, the test case shows that the checkBoolValues[] cannot achieve the effect like step-by-step.

BSplineCurveInterpolate[
  {{1, 2}, {3, 4}}, 
  OriginPoints -> aaa, 
  ControlPoints -> bbb, 
  SplineClosed -> True, 
  TangentVector -> False]

enter image description here

Here, I discovered that the Head of aaa and bbb are Symbol, owing to the HoldRest attribute of Function[] body, aaa and bbb will be assigned to a value False, so when I executed the BSplineCurveInterpolate[...] in the sceond time, there is no warning information occurs.

However, when the option value is a number, Mathematica will throw the following Set::setraw info.

BSplineCurveInterpolate[
  {{1, 2}, {3, 4}}, 
  OriginPoints -> 11, 
  ControlPoints -> 22, 
  SplineClosed -> True, TangentVector -> False]

enter image description here

So I would like to know how to refactor the auxiliary function checkBoolValues[].

$\endgroup$
  • $\begingroup$ Did you run posted code on clean kernel? I do get error messages when running your code and get desired output, but additionally I get StringForm::sfr messages since you only pass one message argument to BSplineCurveInterpolate::invbool while it expects 2. Also False values are really assigned to aaa and bbb symbols and not local op and cp symbols. You've surely seen Attempting to make an assignment to the argument of a function? It applies to your checkBoolValues and to pure function used inside it in Scan. $\endgroup$ – jkuczm Nov 29 '15 at 9:02
  • $\begingroup$ Are you sure you may pass the function name in a variable func to Message? I can imagine that Message actually is looking for a definition of func itself. Additionally, the message string has two slots, yet you provide a single argument only. $\endgroup$ – Sjoerd C. de Vries Nov 29 '15 at 9:02
  • $\begingroup$ There's already a built-in message to report desired error: General::opttf. $\endgroup$ – jkuczm Nov 29 '15 at 9:12
  • $\begingroup$ @SjoerdC.deVries Thanks, I made a mistake. In addition, did you mean the Message[]will look for the defintion of func::invbol rather than BSplineCurveInterpolate::invbol? $\endgroup$ – xyz Nov 29 '15 at 9:26
  • $\begingroup$ @jkuczm Thanks :) I have updated the question. $\endgroup$ – xyz Nov 29 '15 at 9:27
4
+500
$\begingroup$

Why your function doesn't work

Your checkBoolValues does not hold any of its arguments, so when you pass to it, inside BSplineCurveInterpolate, for example op local symbol, it gets evaluated. Inside body of checkBoolValues you really use value of this symbol not the symbol itself.

This behavior was discussed many times on this site, in particular in Attempting to make an assignment to the argument of a function answer to What are the most common pitfalls awaiting new users? question.

That's why in first evaluation of:

BSplineCurveInterpolate[
  {{1, 2}, {3, 4}}, 
  OriginPoints -> aaa, 
  ControlPoints -> bbb, 
  SplineClosed -> True, TangentVector -> False]

in which you get desired messages and output, you also get undesired side effect. False values that, as I think, you intended to assign to local symbols op and cp, are really assigned to global aaa and bbb symbols that where passed as options.

That's why, in second usage of BSplineCurveInterpolate with aaa and bbb as option values, there are no errors, related to them, reported, because those symbols evaluate to False which is an acceptable value.


Overriding OptionValue

If you want to make sure that values of certain BSplineCurveInterpolate options are tested wherever they are accessed, you can override OptionValue by setting appropriate UpValues on BSplineCurveInterpolate.

Let's define a helper function that overrides OptionValue for given function and for options matching given pattern. Overriden OptionValue will extract value of given option, test it using given testFunc function, and if test returns False it'll return result of given defaultFunc called with option name and option value as arguments.

ClearAll[$useOverriddenOptionValue, setOptionValueTest]
$useOverriddenOptionValue = True;
SetAttributes[setOptionValueTest, HoldFirst]
setOptionValueTest[f_Symbol, optNamePatt_, testFunc_, defaultFunc_] := (
    f /: OptionValue[f, opts_ | PatternSequence[], optNames_List] /;
            $useOverriddenOptionValue :=
        OptionValue[f, opts, #] & /@ optNames;
    f /: ov : OptionValue[f, opts_ | PatternSequence[], optName : optNamePatt] /;
            $useOverriddenOptionValue :=
        If[TrueQ@testFunc[#], #, defaultFunc[optName, #]] &@
            Block[{$useOverriddenOptionValue = False}, ov]
)

Now let's define options for BSplineCurveInterpolate, and set appropriate tests for their values.

ClearAll[BSplineCurveInterpolate]
Options[BSplineCurveInterpolate] = {
    OriginPoints -> False, ControlPoints -> False,
    SplineClosed -> False, TangentVector -> False
};
setOptionValueTest[BSplineCurveInterpolate,
    OriginPoints | ControlPoints | TangentVector | SplineClosed,
    MatchQ[True | False],
    (Message[BSplineCurveInterpolate::opttf, #1, #2]; False) &
]

Now whole option value testing will be performed in ordinary call of OptionValue, so we just use it without any additional code (except when using fourth argument of OptionValue since then returned value can be arbitrary and defined test function might be inappropriate):

BSplineCurveInterpolate[pts : {{_, _} ..} | {{_, _, _} ..}, OptionsPattern[]] :=
    Module[{n, op, cp, ss, tv, sc},
        {op, cp, tv, sc} = OptionValue[
            {OriginPoints, ControlPoints, TangentVector, SplineClosed}
        ];
        (*return the values of options*)
        {op, cp, tv, sc}
    ]

Now BSplineCurveInterpolate gives desired results:

Off[General::stop]
ClearAll[aaa, bbb, Trtttue]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> True, TangentVector -> False]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> Trtttue, TangentVector -> False]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> Trtttue, TangentVector -> 222]
On[General::stop]

BSplineCurveInterpolate results


(Old answer) Refactoring checkBoolValues

Instead of playing with holding arguments and assigning False to local symbols inside checkBoolValues, I'd use a function that simply returns list of desired option values and prints messages when appropriate.

We start with simple helper function checking option value for True or False, returning value if it's correct. Printing message and returning False if value is incorrect. I'm using built-in General::opttf instead of a custom message.

ClearAll[checkBoolOption]
checkBoolOption[_][_, val : True | False] := val
checkBoolOption[func_][name_, val_] := (
    Message[func::opttf, name, val];
    False
)

Now a function that works like second and third documented usage of built-in OptionValue, but, in addition to returning option values, it checks whether they are booleans.

ClearAll[boolOptionValue]
boolOptionValue[func_, opts:{OptionsPattern[]}:{}, optName : _Symbol | _String] :=
    checkBoolOption[func][optName, OptionValue[func, opts, optName]]
boolOptionValue[func_, opts:{OptionsPattern[]}:{}, optNames:{(_Symbol | _String)...}] :=
    MapThread[
        checkBoolOption[func],
        {optNames, OptionValue[func, opts, optNames]}
    ]

Example usage in BSplineCurveInterpolate:

ClearAll[BSplineCurveInterpolate]
Options[BSplineCurveInterpolate] = {
    OriginPoints -> False, ControlPoints -> False,
    SplineClosed -> False, TangentVector -> False
};

BSplineCurveInterpolate[pts : {{_, _} ..} | {{_, _, _} ..}, opts:OptionsPattern[]] :=
    Module[{n, op, cp, ss, tv, sc},
        (*check and assign values of options*)
        {op, cp, tv, sc} = 
            boolOptionValue[BSplineCurveInterpolate, {opts},
                {OriginPoints, ControlPoints, TangentVector, SplineClosed}
            ];
        (*return the values of options*)
        {op, cp, tv, sc}
    ]

Now BSplineCurveInterpolate gives desired results:

Off[General::stop]
ClearAll[aaa, bbb, Trtttue]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> True, TangentVector -> False]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> Trtttue, TangentVector -> False]
BSplineCurveInterpolate[{{1, 2}, {3, 4}}, OriginPoints -> aaa, 
    ControlPoints -> bbb, SplineClosed -> Trtttue, TangentVector -> 222]
On[General::stop]

BSplineCurveInterpolate results

$\endgroup$
  • $\begingroup$ Like the helper function checkBoolOption[] and it is the firtst time for me to see the usage of OptionValue[func, {opts}, optName]. THX:) $\endgroup$ – xyz Nov 29 '15 at 11:04
  • $\begingroup$ @ShutaoTANG I'm glad you like it. As to OptionValue[func, opts, optName], variant with single option name is documented, passing list of option names is not, but I think I've seen it couple of times on this site. $\endgroup$ – jkuczm Nov 29 '15 at 12:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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