5

I am trying to define a function that takes in a Matrix and when its dimensions are not provided as input, compute these dimensions in the optional parameter d

This does not work but gives you the idea (The options parameter need be constants):

Options[DimM] = {d -> Dimensions[A]};
DimM[A_?MatrixQ, OptionsPattern[]] := OptionValue@d;

Indeed the simple way is to input an impossible value and in the function def put an if condition as in

Options[DimM] = {d -> 0};
DimM[A_?MatrixQ, OptionsPattern[]] :=If[OptionValue@d==0,Dimensions[A],OptionValue@d]

How can I accomplish this most efficiently?

Phil
  • 815
  • 1
  • 8
  • 15

2 Answers2

9

For your original formulation, @WReach gave a fine answer. However, it may make sense to reconsider your design a bit: note that you have a (dependent on input arguments) value for d in any case. Optional arguments are designed exactly for that - to be optional. In your case, a default argument seems more appropriate. You can set it up with Automatic, similarly to what @WReach suggested:

dimMAuto[a_?MatrixQ, d_: Automatic] :=
     If[d === Automatic, Dimensions[a], d];

To use this in more than one place in your code, you will however need to introduce an auxiliary variable or constant (using With or Module), to store this value. As an alternative, you can also use the following code:

Module[{dims},
  dimM[a_?MatrixQ, d_: dims] :=
      Block[{dims = Dimensions[a]}, 
          d]
] 

which has the advantage that you can use the same original parameter d everywhere in the body of your function. What happens here is rather non-trivial: Module is used to generate a unique symbol, which is then given as a default for d and used to dynamically compute the dimensions. Note that Block localizes not the symbol dims, but the unique symbol like dims$77542 produced by Module. This combination of Module and Block makes this technique completely safe. Examples of use:

In[1466]:= dimM[IdentityMatrix[3],{1,1}]
Out[1466]= {1,1}

In[1467]:= dimM[IdentityMatrix[3]]
Out[1467]= {3,3}  

I think this combination of Module and Block is an interesting technique which may find other uses. Essentially, it is a version of dynamic scoping made safe by lexical scoping (or, more precisely, its imitation in Mathematica) - since one of the main dangers of dynamic scoping is inadvertent collisions of dynamically localized symbols with the same name.

On an unrelated matter - it is best to not start your variables and functions with a capital letter, since they may collide with the system symbols.

Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • This is quite deep! my only concern here is efficiency. It seems the Module/Block combination computes Dimensions[a] whether or not a value for d is supplied which is precisely what I am trying not to do. Am I missing something? – Phil Sep 12 '11 at 22:54
  • @Phil Yes, you are right. In the case of `Dimensions`, overhead is probably not very significant, but if this is just a toy example for you, I see your point. You may make it compute the function conditionally at the price of making code a bit uglier: `Module[{dims},dimM[a_?MatrixQ, d_: dims] := Block[{dims}, If[d===dims,dims = Dimensions[a]]; function-body]]`. Perhaps, this destroys a purpose a bit, but you still have the advantage of using the original parameter `d` everywhere in the body. You can also write a macro to abstract away the `Module`-`Block` part. – Leonid Shifrin Sep 12 '11 at 23:04
  • If I understand this correctly, the use of `Block` within `Module` value is to create a dynamic default via a local, unique var which can acquire its value as needed. Clever. +1. – rcollyer Sep 13 '11 at 03:16
  • @rcollyer Yes, that's the idea. Just using `Block[{dims},...` is dangerous because in principle the body of the function may use the same global symbol `dims` for some unrelated purpose, in the chain of function calls resulting from evaluation of the body (with `dims` not necessarily lexically present in the body). This is a major problem with dynamic scoping. But, by using `Module`, we ensure that the dynamically - scoped symbol in question is unique and can not possibly collide with other symbols. AFAICT, this variation of dynamic scoping is much safer. I used it a few times before. – Leonid Shifrin Sep 13 '11 at 08:49
  • Yet another interesting dissertation, Leonid. :-) – Mr.Wizard Oct 16 '11 at 17:27
  • @Mr.Wizard I was sure you'd like this one when you see it :). Thanks for the upvote. – Leonid Shifrin Oct 16 '11 at 18:58
5

This is not really an improvement over your "simple way", but for many built-in Mathematica functions the symbol Automatic is used as the "impossible value". For example:

Options[DimM] = {d -> Automatic};
DimM[A_?MatrixQ, OptionsPattern[]] := OptionValue[d] /. Automatic->Dimensions[A]

DimM[RandomInteger[10, {2, 2}]]
(* {2, 2} *)

DimM[RandomInteger[10, {2, 2}], d -> {5, 5}]
(* {5, 5} *)
WReach
  • 18,098
  • 3
  • 49
  • 93
  • thanks. How about if I have more than one optional parameter? how do I name them all? – Phil Sep 12 '11 at 22:01
  • You can use `Automatic` for all of them. Then, inside the function you reference each option in turn thus:`a = OptionValue[A] /. Automatic -> defaultA; b = OptionValue[B] /. Automatic -> defaultB`. – WReach Sep 12 '11 at 22:09
  • Right, right! Do we still compute Dimensions[A] if a value for d is provided? I think not, since if Automatic is not found in OptionValue[d], then the replacement rule is not activated. Am I missing it? My goal is to avoid computing Dimensions[A] if in the context I already know it and can pass it on. – Phil Sep 12 '11 at 23:00
  • 1
    @Phil As written, the solution does compute `Dimensions` in any case. To avoid computing `Dimensions` in this approach, you can simply use delayed rules: `Automatic :> defaultA`, etc. – Leonid Shifrin Sep 12 '11 at 23:07
  • 1
    You are correct that the replacement rule will not be activated if the option value is anything other than `Automatic`. However, as I have written it, `Dimensions[A]` will always be evaluated. To avoid this calculation when unnecessary, the replacement rule would have to be written as a deferred rule, i.e. `Automatic :> Dimensions[A]`. – WReach Sep 12 '11 at 23:10