13

Suppose I have a list of names of Symbols:

f1 := Print["f1 is evaluated!"];
list = {"f1", "f2"};

The obvious way to Block these Symbols leads to evaluation of them:

In[19]:= With[{list=Symbol/@list},Block[list,f1//ToString]]
During evaluation of In[19]:= f1 is evaluated!
During evaluation of In[19]:= f1 is evaluated!
Out[19]= Null

But without evaluation we could Block them without any problem:

In[20]:= Block[{f1, f2}, f1 // ToString]
Out[20]= "f1"

Is it possible to inject this list into the Block scope without evaluating the Symbols?

Alexey Popkov
  • 9,355
  • 4
  • 42
  • 93

4 Answers4

11

Here is yet another technique to do this:

SetAttributes[blockAlt,HoldRest];
blockAlt[s : {__String}, body_] :=
   Replace[Join @@ ToHeldExpression[s], Hold[x__] :> Block[{x}, body]]

We save here on pure functions, due to the disruptive nature of rules (they don't respect other scoping constructs, including themselves)

EDIT

Yet another alternative (even shorter):

SetAttributes[blockAlt1, HoldRest];
blockAlt1[s : {__String}, body_] :=
   Block @@ Append[ToHeldExpression@ToString[s], Unevaluated[body]] 
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • 4
    +1 One could re-express `ToHeldExpression[s]` (deprecated in V3) as `ToExpression[s, InputForm, Hold]`. – WReach Jun 04 '11 at 15:26
  • @WReach Sure, I am well aware of that (this is what I usually do myself) - just wanted to make the code shorter :). Not too nice of me, as `ToHeldExpression` is indeed deprecated. – Leonid Shifrin Jun 04 '11 at 15:29
  • Nice! Is there any risk from that fact that `ToHeldExpression` says "Since Version 3.0 (released in 1996), ToHeldExpression has been superseded by ToExpression."? Well, it seems WReach beat me to it, but again, is there any risk to this? Is it going to disappear in a future version? – Mr.Wizard Jun 04 '11 at 15:30
  • @Mr.Wizard IMO, no risk at all - WRI can not drop the support for this, since no one knows how much of the code using it will be then broken. – Leonid Shifrin Jun 04 '11 at 15:31
  • @Mr.Wizard I agree, no risk. I just chimed in case someone was looking for current documentation on `ToHeldExpression` and came up empty. – WReach Jun 04 '11 at 15:54
  • @Leonid New version in the EDIT section is very elegant and clever! Another good instrument for my toolbag. Thank you. – Alexey Popkov Jun 05 '11 at 11:39
  • @Leonid BTW, I just noticed that changing the default value of the `FormatType` option of `ToString` breaks the mechanism of generation labels of cells for the FrontEnd. Try `SetOptions[ToString, FormatType -> StandardForm]` and see on the label of the output `Cell` (in v.7.0.1 I get `CellLabel-> "TagBox[InterpretationBox[\"\" %3 = \"\", SequenceForm[DialogIndent[0], \ PromptForm[Out[3], \"\"]], Rule[Editable, False]], HoldForm]"`). – Alexey Popkov Jun 05 '11 at 11:48
  • I like that last one a lot. See my take on it below. – Mr.Wizard Jun 05 '11 at 11:49
  • @Alexey Interesting. I wonder whether this is a bug or a feature. – Leonid Shifrin Jun 05 '11 at 16:11
  • @Mr.Wizard I saw your version - nice! Does not type-check, but you can't have everything. – Leonid Shifrin Jun 05 '11 at 16:12
  • @Leonid This issue just illustrates that the developers also use the one-argument form of `ToString` without thoughts on the fact that its default behavior may be easily changed by a user. – Alexey Popkov Jun 06 '11 at 03:07
  • 1
    @Alexey The problem is actually more general. Take `Map`, for example. I am pretty sure that if you set `Heads->True` globally, more than one feature will be broken. The solution is straightforward: pass options explicitly, but alas, almost no one does this in practice. The so widely used shortcuts `@@` and `@@@` don't even allow to do this. And I agree that developers must be much more careful than users. – Leonid Shifrin Jun 06 '11 at 07:54
  • @Leonid You are right about `Map`: evaluation `SetOptions[Map, Heads -> True]` breaks the Documentation Center. And `f /@ {a, b, c}` gives `f[List][f[a], f[b], f[c]]` after this. So the conclusion: changing the default options of system functions is possible but breaks the system. It is a kind of system defect, of course. – Alexey Popkov Jun 06 '11 at 09:09
  • 1
    @Alexey This is not a system defect - this is just a class of bugs, coming from carelessness (at least this is my perception of it). The practical solution for this problem is quite simple. If you develop a package say, define a private function `map[f_,x_,levspec_:1]:=Map[f,x,levspec,Heads->False]` and use `map` consistently in place of `Map`, and the same for other functions. But this requires more discipline. In the context of Mathematica, I am pretty sure that automatic tools similar to `lint` for C and `FindBugs` for Java could be easily developed to detect potential bugs like this. – Leonid Shifrin Jun 06 '11 at 09:49
  • @Leonid I agree that it is very easy to catch and correct such kind of bugs in *Mathematica*. This kind of bugs would be absent if the developers have an IDE that automatically replaces (or just highlights) all one-argument forms of functions with changeable default behavior. What is really strange is that they have not it jet. This indicates a low level of organization of the development process and in general a low level of culture of programming. – Alexey Popkov Jun 06 '11 at 12:10
  • @Alexey You'd be surprised to see how things are in most places regarding this sort of things - in fact, from what I can tell WRI is far ahead of most. One problem here is that the IDE for Mathematica (Workbench) is relatively recent, while Mathematica is written in itself and C. Also, the built-in rules that are not top-level are probably also written in C. The code - highlighting, analysis and navigation capabilities of modern IDEs are recent, and it is very hard to not just change the current development process, but also go through millions of lines of existing code, even with IDE. – Leonid Shifrin Jun 06 '11 at 12:20
  • @Leonid Replacement of one-argument forms by safe forms can be fully automatized. In really `FullForm` is very close to be able to do this already for a long time. It converts infix, prefix and postfix notation to the explicit form. What should be added is the ability to add to this explicit form the necessary set of additional default arguments and/or default options. It is not too complex task. – Alexey Popkov Jun 06 '11 at 12:45
  • @Leonid I think it would be worth to add to the FrontEnd something like "Development style environment" as an alternative to the "Working" style environment. In this new style environment all potentially unsafe forms would be highlighted. And it is also not too hard to make an analog of existing "Mathematica version advisory" functionality that will check the code for such problems. – Alexey Popkov Jun 06 '11 at 13:04
  • @Alexey You can not make it *fully* automatic since sometimes you do need to pass `Heads->True` option, and we can not exclude that it is not passed explicitly but is in some named options list. Besides, you are forgetting that the manipulations with the source code are done in the IDE, not FrontEnd, and `FullForm` (or M kernel generally) is not available there. Also, as I said, some part of mma code is not really the top-level code, but may still reference these functions. I am not saying that the checks are impossible to implement, it just seems to me to be a harder problem than you suggest. – Leonid Shifrin Jun 06 '11 at 13:04
  • @Alexey Yes, I agree with this - development style would be nice. I once even attempted to write my own FE-based IDE, but was stuck due to the lack of time and a few technical issues (such as the absense of a good way to use InputField or similar to edit code. I did not yet try the solution of Chris Degnen, so it may work out). – Leonid Shifrin Jun 06 '11 at 13:07
  • @Leonid Now I see what you mean: `Insert[Hold[f /@ {a}], Heads -> True, {1, 3}]`. Interesting... – Alexey Popkov Jun 06 '11 at 13:15
  • @Leonid Please give a link to Chris Degnen's solution. – Alexey Popkov Jun 06 '11 at 13:25
  • @Alexey I can not find it on mathgroup now, although I got the thread in my mailbox. I will forward that to you, although I am a bit puzzled - you were on the thread, so you should know. – Leonid Shifrin Jun 06 '11 at 13:44
  • @Leonid Now I understand what you mean: "[New lines in InputField](http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/3a2b1cfa8efa6df9/f81f90a18921a5dd)". I just thought that Chris Degnen also tried to make an alternative IDE. – Alexey Popkov Jun 06 '11 at 14:08
9

Disclaimer: While my response provides a solution to the problem as expressed, I do not recommend it for regular use. I offer it up because it may be of some academic interest.

From time-to-time, usually in a debugging context, I have looked longingly at Lisp's MACROEXPAND-1 and wished for a Mathematica function which applies only one level of evaluation to its argument(s). Let's call this mythical function EvaluateOnce. It would find the transformation rule applicable to the expression and apply only that rule, something like this:

In[19]:= fact[0] = 1; fact[x_] := x * fact[x - 1]
         EvaluateOnce[fact[5]]
Out[19]= Hold[5 fact[5-1]]

In[20]:= f1 := Print["f1 is evaluated!"];
         EvaluateOnce[Symbol["f1"]]
Out[20]= Hold[f1]

It would work on multiple expressions as well:

In[21]:= EvaluateOnce[1 + 2 * 3, Sqrt @ Sin @ Pi]
Out[22]= Hold[1+6, Sqrt[0]]

The current question could benefit from such a capability for then the solution could be expressed as:

EvaluateOnce @@ Symbol /@ Hold @@ list /.
  Hold[args__] :> Block[{args}, f1 // ToString]

Alas, there are a number of technical obstacles to writing such a function -- not least of which is a certain amount of fuzziness about what exactly constitutes a "single level of evaluation" in Mathematica. But fools rush in where angels fear to tread, so I offer this hack:

ClearAll@EvaluateOnce
SetAttributes[EvaluateOnce, HoldAllComplete]
EvaluateOnce[exprs:PatternSequence[_, __]] :=
  Replace[Hold @@ Evaluate /@ EvaluateOnce /@ Hold[exprs], Hold[e_] :> e, 1]
EvaluateOnce[expr_] :=
  Module[{depth = 0, length = 1+Length@Unevaluated@expr, tag, enter, exit}
  , SetAttributes[exit, HoldAllComplete]
  ; enter[in_]:= If[1 === depth && 0 === length, Throw[in, tag], ++depth]
  ; exit[in_, out_] := (If[2 === depth, length--]; depth--)
  ; Hold @@ Catch[With[{r = TraceScan[enter, expr, _, exit]}, Hold[r]], tag]
  ]

This function comes without a warranty :) It uses TraceScan and some heuristics to guess when a "single level of evaluation" is complete and then uses Throw and Catch to terminate the evaluation sequence early.

The heuristics appear to work satisfactorily for function definitions whose "first level of evaluation" stays within the bounds of standard evaluation. It also fails miserably for those that don't. I'm also certain that it will get confused with the application of some evaluation attributes.

Notwithstanding these faults, I still find this function handy when trying to debug or even just understand functions with lots of standard pattern-matching definitions.

WReach
  • 18,098
  • 3
  • 49
  • 93
  • +1 - very interesting function, will experiment with it. I was always thinking that one can use `Trace`- family functions for something like this, but never came up with something useful. FWIW, I was (and am) also interested in one-step evaluation, but addressed this problem in a different way, by constructing a custom evaluator. May be, you may find my post in this thread of some interest: http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/e9e7551253d39031 – Leonid Shifrin Jun 04 '11 at 19:02
  • @Leonid Thanks for the interesting link. I, too, have tried to reproduce the standard evaluator in Mma code. It's those pesky built-in definitions that make things difficult. I'd like to see WRI implement some form of partial evaluation although I confess that the feature is not particularly high on my Mma wish list. – WReach Jun 04 '11 at 19:17
6

You could try to use ToExpression:

In[9]:= list = {"f1", "f2"};

In[19]:= f1 = 25;

In[20]:= ToExpression[
 StringJoin["{", Riffle[list, ","], "}"], InputForm, 
 Function[vars, Block[vars, f1], HoldAll]]

Out[20]= 25
Sasha
  • 5,935
  • 1
  • 25
  • 33
  • +1 The inner `Block` expression should be `Block[vars, f1 // ToString]` -- otherwise an unwanted evaluation will occur if `f1` is defined as in the original question. – WReach Jun 04 '11 at 15:33
  • @WReach I think that here `f1` is the body of `Block`, so its evaluation is intended. The point is that it only evaluates once, as it should. A better example would be `Block[vars,Print[f1]]`, this indeed prints `f1` as it should. – Leonid Shifrin Jun 04 '11 at 20:46
  • @Leonid With the original definition of `f1`, the example as written prints out `f1 is evaluated!` which the OP was trying to avoid by means of `Block`. Sasha's solution is correct, but I was confused at first until I realized that the printed output was generated after the `ToExpression` form is completely evaluated. I suggested `ToString` to match the original question and because others may be similarly confused (but maybe I'm the only one). – WReach Jun 04 '11 at 20:57
  • @WReach Oh I see. I think using `f1` as the body of `Block` was probably not the best choice indeed. – Leonid Shifrin Jun 04 '11 at 21:28
6

You may consider this construct:

SetAttributes[block, HoldRest]

block[s : {__String}, body_] := 
 Function[, Block[{##}, body], HoldAll] @@ 
  Join @@ MakeExpression /@ s

Second attempt at a shorter version of Leonid's second function:

block =
  Function[, Block @@ ToHeldExpression@ToString@#~Join~Hold@#2, HoldRest]
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • 5
    It is better to set `HoldRest` attribute for `block` instead of `HoldAll`. +1 – Alexey Popkov Jun 04 '11 at 07:11
  • @Mr.Wizard How new `block` should be used? I tried `block[list, f1 // ToString]` but it does not work with new version. – Alexey Popkov Jun 05 '11 at 12:36
  • 1
    @Alexey, it seems I broke it. ;-) Or not? Does this work? `list = {"f1", "f2"}; f1 := Print["Evaluated!"]; block[list, f1 // ToString]` Anyway, this is just for fun. – Mr.Wizard Jun 05 '11 at 13:15
  • @Mr.Wizard It works. It seems that I just did not restart the kernel before using new definition. Nice solution! – Alexey Popkov Jun 05 '11 at 14:07
  • Upon closer look, your terse version works differently, and contains a feature that I tried to avoid in mine - namely, converting the body of the `Block` to string. Conversion to string is not always innocent, for example, here `block[{}, Hold[Print["a", "b"]]]`, we get back `a` and `b` as symbols, so we lose their string nature in the `ToString-ToExpression` cycle. In fact, I was using (constructively) exactly this loss of string quotes when I did `ToString[s]`, but this was safe since `s` was a list of strings. So, sorry to bring it to you, but your version won't always work correctly. – Leonid Shifrin Jun 05 '11 at 16:20
  • @Leonid, please see my second attempt, and tell me if there are any problems. – Mr.Wizard Jun 06 '11 at 20:02