4

I like to use With[] with constants that I need to use in 2 different places.

Instead of typing the same long list of constants in 2 places, I have been trying to figure how to use a variable for this list, and then use this variable in the few places I want to use the list.

The problem is that I need to Hold the list and then ReleaseHold it later when time to use it, but I can't get this part right. (tried many things, nothing working for me)

Here is an example:

With[{$age = 2, $salary = 3},
 x = $age
 ]

With[{$age = 2, $salary = 3},
 y = $age
 ]

I do not want to type the same constants around (in my case, these are very long), and I was trying to do something like this:

c = HoldForm[{$age = 2, $salary = 3}]

With[Release[c],
 x = $age
 ]

With[Release[c],
 y = $age
 ]

I tried many other combinations of the above. So many version of Hold* and Release*, I find them all very confusing.

question is: Any one knows how to do the above, so I can reused the list in more than one place without actually copy it?

To put this in context, here is an example where this would be needed:

I can't do surround everything with With in Manipulate: (and I can't put a With outside Manipulate for what I am doing, everything must be inside Manipulate)

Manipulate[

 Evaluate@With[{$age = 2, $salary = 3},
   x;
   $age,

   {{x, $age, "age="}, 0, 10, 1}
   ]

 ]

Not valid syntax. (due to the "," needed to separate the Manipulate expression from the controls). (it now thinks With has 3 arguments)

I could ofcourse do

Manipulate[

 With[{$age = 2, $salary = 3},
  x;
  $age
  ],

 Evaluate@With[{$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ]

 ]

But as you see, I had to copy the list of constants around.

If I can figure how to define the list once, I can put it in the Initialization section on Manipulate and use it, like this:

Manipulate[

 With[ReleaseHold[myList],
  x;
  $age
  ],

 Evaluate@With[ReleaseHold[myList],
   {{x, $age, "age="}, 0, 10, 1}
   ],

 Initialization :>
  (
   myList = HoldAll[{$age = 2, $salary = 3}];
   )

 ]

I think what I want to do is possible, I just can't figure the correct commands to use for the Hold/Release hold part.

edit (1)

I thought I put an example of why I want to use constants in With.

I came up with this new method :) to allow me to simulate a record or a struct.

The constant values will be the named field of the record (which is just a list really).

For field name, I give it a sequential number, (I start from 1) and I use $field=number, and then in the code, I write struct[[$field]]=... to access the field.

I need to share the values of the named fields to the struct between the Manipulate expression and the control area, since both need to use the same struct.

Here is a simple example below of a Manipulate that reads the age and current salary from the UI and the expression and assigns new salary based on current salary and ago.

The record is used to communicate the data between the control area and the expression and lower level functions.

In my current demo, I have hundreds of such parameters, (I actually have now few demos, all in one demo, and I flip between different UI layouts (controls) based on which choice is selected on the UI) and using records will simplify life for me, since now I can make function calls, and just pass few parameters, records that contain the UI parameters, and not 100's of the individual parameters, which is what I have to do now. As I said many times before, Mathematica needs a real record/struct as a basic data struct, in addition to List, and Array and such, which is integrated into M.

(the UI parameters have to be send, all, to lower level functions, and there is no other choice than doing this. I do not want to use global variables. Bad design).

I also can now pass this record by reference If I want to to allow updates to happen into it inside other much lower level functions. I am still evaluating this method to see if I can actually use it in my main code.

 (*verison 1.1*)
Manipulate[

 With[{$age = 1, $salary = 2, $newSalary = 3},

  updateRecord[record_] := Module[{},

    (*update/process UI input*)

    record[[$newSalary]] = record[[$salary]] + record[[$age]]*10;

    (*return result*)
    record
    ];

  (*call lower level function to process UI parameters*)
  myRecord = updateRecord[Unevaluated@myRecord];

  (*display the result *)
  Grid[{
    {"age=", myRecord[[$age]]},
    {"current salary=", myRecord[[$salary]]},
    {"new salary=", myRecord[[$newSalary]]}
    }]
  ],

 (* build the UI controls *)
 Evaluate@With[{$age = 1, $salary = 2, $newSalary = 3},
   Grid[{

     {"age=",
      Manipulator[Dynamic[age, {age = #; myRecord[[$age]] = age} &],
       {10, 100, 1}, ImageSize -> Tiny], Dynamic[age]},

     {"salary=",
      Manipulator[
       Dynamic[salary, {salary = #; myRecord[[$salary]] = salary} &],
       {10, 10000, 10}, ImageSize -> Tiny], Dynamic[salary]}

     }
    ]
   ],

 {{myRecord, {10, 100, 100}}, None},
 {{age, 10}, None},
 {{salary, 1000}, None},
 TrackedSymbols -> {age, salary}

 ]

enter image description here

edit(2)

On trying to use Leonid below, I can use it in the Manipulate expression, but can't figure how to use in the control area.

Here is the original example using With in 2 places:

Manipulate[

 With[{$age = 2, $salary = 3},
  x + $age
  ],

 Evaluate@With[
   {$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ],
 {x, None}
 ]

now using the new Leonid method below, this is what I have so far:

Manipulate[

 env[
  x + $age
  ],

 Evaluate@With[
   {$age = 2, $salary = 3},
   {{x, $age, "age="}, 0, 10, 1}
   ],
 {x, None},

 Initialization :>
  (
   ClearAll[makeCustomEnvironment];
   SetAttributes[makeCustomEnvironment, HoldAll]; 
   makeCustomEnvironment[values : (_Symbol = _) ..] := 
    Function[code, With @@ Hold[{values}, code], HoldAll];

   env = makeCustomEnvironment[$age = 2, $salary = 3];

   )
 ]

But is it possible to use it for the control also? I can't just do this:

Manipulate[

 env[
  x + $age
  ],

 env[
  {$age = 2, $salary = 3},
  {{x, $age, "age="}, 0, 10, 1}
  ],
 {x, None},

 Initialization :>
  (
   ClearAll[makeCustomEnvironment];
   SetAttributes[makeCustomEnvironment, HoldAll]; 
   makeCustomEnvironment[values : (_Symbol = _) ..] := 
    Function[code, With @@ Hold[{values}, code], HoldAll];

   env = makeCustomEnvironment[$age = 2, $salary = 3];

   )
 ]

as the above gives many errors.

edit(3)

deleted as was incorrect

edit (4)

deleted content of edit(3) above as it included a user error on my side reporting an issue.

Here is the WRI support response to why I was getting the error

Manipulate::vsform: Manipulate argument env[{{age,100,age},10,200,1}] does
not have the correct form for a variable specification. >>

When I was writing the following code:

Manipulate[
  env[
   record[[$age]] = age;
   record[[$salary]] = 60*age;
   {record[[$age]], record[[$salary]]}
   ],

 env[
    {{age, 100, "age"}, 10, 200, 1}
    ],

  {{record, {40, 5}}, None},
  {{salary, 40000}, None},
  TrackedSymbols :> {age},
  Initialization :>
   (
    makeCustomEnvironmentAlt =
     Function[Null, Function[code, With @@ Hold[{##}, code], HoldAll],
      HoldAll];
    env = makeCustomEnvironmentAlt[$age = 1, $salary = 2];
    )

  ]

This is the support explanation of why this error came showed up:

The issue is specifically with the section:

Evaluate@env[{{age, 100, "age"}, 10, 200, 1}]

Manipulate doesn't really evaluate until it gets to the Initialization
option, but it will check its input for correct form. Mathematica reads the
main body of the Manipulate before running the Initialization option. This
is can be verified by using a Print statement:

Initialization -> (Print["Test"]; 
  makeCustomEnvironmentAlt = 
   Function[Null, Function[code, With @@ Hold[{##}, code], HoldAll], 
    HoldAll];
  env = makeCustomEnvironmentAlt[$age = 1, $salary = 2]; 
  Print["Test"])

Test does not print. 

Getting around this will be probably not be clean.
....
Having  the code for the controller for age depend on evaluation of 
some function which must be initialized does not appear to be possible 
with simply Manipulate. 

I hope this information helps. And thanks for everyone's help and also for WRI support and explanation.

Nasser
  • 12,849
  • 6
  • 52
  • 104

2 Answers2

6

What I'd do is to write a function generator to create custom (lexical) environments:

ClearAll[makeCustomEnvironment];
SetAttributes[makeCustomEnvironment, HoldAll];
makeCustomEnvironment[values : (_Symbol = _) ..] :=
   Function[code, With @@ Hold[{values}, code], HoldAll];

This takes a declaration list and creates a pure function which uses With internally with encapsulated constants. I used With@@Hold[...] to fool the renaming mechanism of Function so that it would not rename the variables inside With (instead of With, one could use the withRules function suggested by @Szabolcs, which would lead to a slightly different substitution semantics).

Now, we create our custom function:

env = makeCustomEnvironment[$age = 2, $salary = 3];

And use it like so:

In[25]:= 
env[x=$age];
x

Out[26]= 2

In[27]:= 
env[y=$age];
y

Out[28]= 2

The advantage of this construct with respect to a saved variable (with rules or whatever) is that here we encapsulate behavior rather than state. This is arguably cleaner and more along the functional programming paradigm (we create here a closure rather than instantiate a class).

EDIT

Apparently, the rules for demonstrations are pretty strict, and won't allow the sugested code. This version will hopefully be ok:

makeCustomEnvironmentAlt = 
 Function[Null,
   Function[code, With @@ Hold[{##}, code], HoldAll],
   HoldAll]

But you'll have to remember the format of input arguments (which was clear for the initial solution due to the use of suggestive pattern).

Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Thanks Leonid. I can use your construct in the Manipulate expression with no problem, but not able to figure how to use it in the control area of the Manipulate in place of the current `With` there. Please see my edit(2) to show what I mean on a simple example. – Nasser Jan 07 '12 at 12:51
  • @Nasser Sure you can use it. You should have paid a bit more attention to the original code and do the substitution to the new construct consistently: `Manipulate[env[x + $age], Evaluate@env[{{x, $age, "age="}, 0, 10, 1}], {x, None}, Initialization :> no change here `. Make sure your `x` doesn't have a global value before running this. Try also to fully absorb the ideas behind this construct - then you won't have any problems adopting it to a range of different situations. – Leonid Shifrin Jan 07 '12 at 13:06
  • thanks Leonid. It works now. I actually tried to understand your code, but had hard time, a bit too advanced for me at this stage. For example, I did not understand the where the `code` bit came from in the function, and all the Holds there. I hope one day I will fully understand your advanced code ;) . Thanks again for your help. – Nasser Jan 07 '12 at 14:03
  • @Nasser The `code` is a formal parameter to a pure function `Function`. By making this function `HoldAll` (the last argument to `Function`), I ensure that it is injected into the body of `Function` in unevaluated form. The body naively should be just `With[{values},code]`, but then the variable-renaming mechanism of `Function` messes things up. The stage `With@@Hold[{values},code]` can be thought of as a dynamically-generated variant of the naive code, and used to fool the renaming mechanism. The `HoldAll` for the main function prevents the assignments in declarations from evaluation. – Leonid Shifrin Jan 07 '12 at 14:23
  • Hello Leonid, well, we do not have take off I am afraid. The set worked great, but when I uploaded the demo, it failed due to use of 2 illegal symbols, which are `Symbol` and `SetAttributes`. `Please edit the notebook to remove the following illegal symbols: SetAttributes, Symbol.`. I tried to fix it myself, but not able without these. I was wondering if in your magic toolbox, if you have another idea to do this without these 2 symbols? otherwise, I will just type all the constants in 2 places, not much of a problem, lots of duplicated code and won't be as nice as your current method. thanks – Nasser Jan 08 '12 at 07:14
  • Just wanted to say that no need to spend time on this any more, unless as an academic exercise. This struct method makes the argument passing easier for sure, but I find that the code is now harder to read, since I have to write, inside the function called, `param[[$variableName]]` instead of just `variableName`. This made the code harder to work with. So I am back to original version, of just passing the parameters as is, and not packed in a struct. – Nasser Jan 08 '12 at 09:22
  • Hello Leonid. Thanks again for your help. I think there is a bug somewhere in the demonstration style sheet that your new method just uncovered. For it runs fine when the style sheet is default. But once I change the style sheet to Demonstration (Format->Style sheet), then I get an error when I execute the cell. I put screen shot in my edit(3) now, and also a code example of simple Manipulate in case you like to try this yourself. I also put a small animated gif file showing the steps I did. I am using V 8.04 on windows 7. I think I will send a bug report to WRI on this. – Nasser Jan 09 '12 at 08:16
  • WRI support helped me on this. It turned out to be an issue with the way I used the code in Manipulate and not the style sheet. I just needed to restart Mathematica each time to see the error in the Default style sheet as well in the Demonstration style sheet. I'll post the WRI support explanation of the problem for the interest of anyone, but no need for any one to spend more time on this set up (unless they want to), as myself is not going to use this any more. I'd rather pass each argument, even though there are so many of them, as it is easier to do argument checks on them this way. thanks – Nasser Jan 11 '12 at 23:49
1

I would recommend using Replace and Rules instead of With for this purpose. See for example the withRules function I described here, which I use for a purpose similar to what you describe: injecting a predefined list of constants into code without having to define those constants as global variables.

ClearAll[withRules]
SetAttributes[withRules, HoldAll]
withRules[rules_, expr_] :=
  Internal`InheritedBlock[
    {Rule, RuleDelayed},
    SetAttributes[{Rule, RuleDelayed}, HoldFirst];
    Unevaluated[expr] /. rules
  ]

Usage:

constants = { a -> 1, b :> 2+2 }

withRules[ constants, a+b ]

Note that you can use both -> and :> so there's no need for Holding the constants.

A word of warning:

Also note that it is different from With in that it doesn't really localize variables. Consider With[{x=1}, With[{x=2}, x]], which renames the x in the inner With to avoid conflict. withRules doesn't do this: is ruthlessly replaces every x within withRules[{x-> ...}, ...] regardless of whether it appears in another Function, With or Module or not.


Regarding your Manipulate example, I would just wrap the whole Manipulate with a With, as in

With[
  {age = 2}, 
  Manipulate[{x, age}, {{x, age}, 0, 10, 1}]
]

Can you explain why you want to have everything inside the Manipulate (if you're not manipulating age), and why you can't just wrap it with the With?

Community
  • 1
  • 1
Szabolcs
  • 24,728
  • 9
  • 85
  • 174
  • thanks, but as I mentioned, I can't wrap `With` around `Manipulate`, as this is for a demonstration, and rules are that `Manipulate` has to be outside most construct. I will look now more at your Rules set up, but I use `With` every where now in Manipulate to set up things, and so I can't really change that part of it, like in this link: http://stackoverflow.com/questions/7551647/how-to-define-part-of-a-manipulate-control-variable-definition-to-reduce-code-du – Nasser Jan 07 '12 at 10:14
  • @Nasser I never submitted a demonstration myself, but I not allowing an enclosing `With` sounds unusual ... can you link to the rules which say this? How about using an enclosing `With` while developing, but inlining all constants before you submit. Will it help you if we automate inlining them? – Szabolcs Jan 07 '12 at 10:19
  • The rules are here: http://demonstrations.wolfram.com/guidelines.html . I also asked the WRI demo editor that I work with to be clear, and he was clear on this and said `Manipulate has to be the outside function.` so these are the rules. I think they are set like this make the internal testing/editing/building of demos submitted the same for all demos. Your idea of using outside `With` during development and at end copy everything sounds interesting, will use if there is no other choice. but I have huge list of constants, and worry about breaking something at the end and not catch it. thanks – Nasser Jan 07 '12 at 10:37
  • @Nasser Ideally you wouldn't need `With` at all, and you'd just define the constant in the initialization explicitly: `age = 2`. Of course this won't work if you use `age` at some place where it won't get evaluated (or the code won't work unless you give it a literal). Is this the case? If you really want to stick to your original multiple-With construct, what about my `withRules` solution? – Szabolcs Jan 07 '12 at 11:00
  • @Nasser Anything that works when you do With[{const = val}, f[...]] should also work if you inline it using With[{const = val}, HoldComplete@f[...]], then remove the `HoldComplete` at the end. Disadvantage is that you lose all code formatting, and it will be very annoying to redo it by hand. – Szabolcs Jan 07 '12 at 11:13
  • On your first comment above to use `age=2` as constant in the `Initialization` section: That will not really be ok. First, it is not a constant, `age` is a variable in this case. The second, which is most important, is that it becomes global in context. And global variables not allowed in a demo, since when one makes a snapshot of the demo to make the final thumbnail demos in the notebook, these variables are now all shared between the copies of the demo, and this can cause problems. Either way, not allowed to have variables in `Initialization`. Will now look at your other comment. thanks – Nasser Jan 07 '12 at 11:19
  • @Nasser I checked a [random demonstration](http://demonstrations.wolfram.com/MappingPhotographicImagesTo3DSurfaces/) before writing that. It uses the `Initialization` section for defining constants. Yes, they're global variables, and I was surprised that they're used, but I assumed they have some other mechanism to deal with that (cell contexts?) – Szabolcs Jan 07 '12 at 11:27
  • WRI editor do not like to see global variables in the init section, unless there is a very good reason for them and had to be used. Otherwise, they really should not be used. I had problems myself using them once, due to conflict between copies of demos made. (when moving slider on one demo, the other demo (the copy demo) slider changes at the same time, which is not what is supposed to happen). I added now edit(1) to my post, to give better example why I need a list of constants for use with `With`, may be this makes it more clear. thanks – Nasser Jan 07 '12 at 11:32