MathGroup Archive 2009

[Date Index] [Thread Index] [Author Index]

Search the Archive

Re: Add syntax highlighting to own command

  • To: mathgroup at
  • Subject: [mg101666] Re: [mg101539] Add syntax highlighting to own command
  • From: Leonid Shifrin <lshifr at>
  • Date: Tue, 14 Jul 2009 05:32:55 -0400 (EDT)
  • References: <>

Hello all,

I have a few remarks:

IMO,  solutions of Simon and Daniel are  conceptually the same as Bastian's,
but seem to me as  yet another step towards brevity and elegance (this is
subjective, of course).

The major technical difference is that <If> is replaced by pattern-matching
and thus there are two global rules instead of one, and in addition this
allows to avoid  Unevaluated. The mechanism that makes this work is however
the same as before, since  SetDelayed creates global delayed rules, and, as
I mentioned before,  RuleDelayed does not respect the scope of inner scoping
constructs, which makes it all possible. The details of this have been
discussed, in particular,  in this thread (this is where I learned it):

>Do our solutions differ in behavior? I wouldn't be surprised at all if
>our codes behaved differently in the details.

In most "normal" situations there probably won't be any difference in the
result (except for the case of empty vars list, see below). But since the
pattern - based function, in terms of evaluation, is different in several
ways from the function with If (also If checks a different condition), it is
not difficult to construct perverse examples where the results will differ.

This is Bastian's solution:

SetAttributes[Let, HoldAll];
Let[vars_, expr_] :=
 If[Length[Unevaluated[vars]] == 1,
With[vars, expr],
  Unevaluated[vars] /. {a_, b__} :>
With[{a}, Let[{b}, expr]]]

Consider this, for instance:

Block[{Length = 1},
 Let[{a = "Hi, ", b = a <> "there!"}, Print[b]]]


 Let[{a = "Hi, ", b = a <> "there!"}, Print[b]]]

Of course, no one in the right mind would do this, but this is just an
illustration. By the way, the solution of Bastian returns an empty list when

called with no variables:

In[1] = Let[{}, a]

Out[1] = {}

which differs from what standard With or other two solutions do.

Regardless of these issues, it seems (to me anyway) that when one tries to
make a "production" code out of this simple and elegant solution, one really
opens a can of worms.   One thing is that argument checks and error messages
have to be added, since without them Let is prone to misuse. This may seem
unnecessary in simple programs but may probably give nasty bugs in larger
ones, which will be hard to track.

Here is my attempt in this direction (based on solutions of Simon and

In[2] =

SetAttributes[Let, HoldAll];

(* Error messages *)

Let::lvset =
  "Local variable specification `1` contains `2`, which is an \
assignment to `3`; only assignments to symbols are allowed.";
Let::lvw =
  "Local variable specification `1` contains `2` which is not an \
assignment to a symbol.";
Let::lvlist =  "Local variable specification `1` is not a List.";
Let::argrx =
  "Let called with `1` arguments; 2 arguments are expected.";

With[{initPattern = HoldPattern[Set[_Symbol, _]]},

(* Main definitions *)

 Let[{}, expr_] := expr;

  Let[{a : initPattern, b : initPattern ...}, expr_] :=
   With[{a}, Let[{b}, expr]];

  (* Error - handling *)

  Let[vars : {x___Set}, _] :=
    "" /; Message[Let::lvset,
     badarg = Select[HoldForm[x],
        Function[arg, ! MatchQ[Unevaluated@arg, initPattern],
         HoldAll], 1],
     First@Extract[badarg, {{1, 1}}, HoldForm]]

  Let[vars : {x___}, _] /; ! MatchQ[Unevaluated[vars], {___Set}] :=
   "" /; Message[
     Let::lvw, HoldForm[vars],
      HoldForm[x], Function[arg, Head[Unevaluated[arg]] =!= Set],

  Let[args___] /; Length[Hold[args]] =!= 2 :=
   "" /; Message[Let::argrx, Length[Hold[args]]];

This seems to catch errors in all cases which I tested (probably can be done
more elegantly).

But this reveals another, and IMO more serious, problem: Let does not nest
nicely neither with itself nor with some (external to it) scoping constructs
such as Function and With (Module and Block seem fine, at least upon the
first look):


{Let[{a="Hi, ",b=a<>"there"},Print[b]],
Print[a,"   ",b]}]

During evaluation of In[3]:= Let::lvset: Local variable specification {1=Hi,
,2=1<>there} contains 1=Hi, , which is an assignment to 1; only assignments
to symbols are allowed.

During evaluation of In[3]:= 1   2

Out[3]= {Let[{1=Hi, ,2=1<>there},Print[2]],Null}

We can also look at inputs like

With[{a = 1, b = 2}, Let[{a = "Hi, ", b = a <> "there"}, Print[b]]]

Function[{a, b}, Let[{a = "Hi, ", b = a <> "there"}, Print[b]]][1, 2]

and see  similar outputs. Contrast this with the behavior of nested built-in
scoping constructs:

With[{a = 1, b = 2},
 With[{a = "Hi, ", b = "there"}, Print[a, "   ", b]]]

Function[{a, b},
  With[{a = "Hi, ", b = "there"}, Print[a, "  ", b]]][1, 2]

where the variables of the outer constructs are shadowed by those
in the inner ones. To be entirely consistent, one should probably add
definitions to With, Function and Let itself, so that similar behavior will
observed in the first three inputs. But for Function and With, even if
such definitions are implemented correctly, they will cause a (possibly
quite large) performance hit.

Of course, we may just say that most such collisions are  pathologies and
bad programming (bugs), and then ignore this problem.


On Wed, Jul 8, 2009 at 11:00 PM, Bastian Erdnuess <earthnut at> wrote:

> I was missing a scoping construct like 'With' but where the local
> variables get assigned linearly.  E. g.
>  In[1] := LinearWith[ {
>               a = "Hi, ",
>               b = a <> "there!" },
>             Print[ b ] ]
>  Out[1] = "Hi, there!"  .
> I'm fairly new to Mathematica, but I know some Lisp, and somehow I got
> it.  I called my new construct Let and defined it as:
>  Let[ vars_, expr_ ] :=
>    If[ Length[ Unevaluated[ vars ] ] == 1,
>      With[ vars, expr ],
>    (* else *)
>      Unevaluated[ vars ] /.
>        { a_, b__ } :>
>          With[ { a },
>            Let[ { b }, expr ] ] ]
>  SetAttributes[ Let, HoldAll ]
> It seems to work fine so far.
> Now, I would like to have this construct load always when I start
> Mathematica and I don't want to get it cleared when I use
> 'Clear["Global`*"]'.  So I put it in the System` context and also added
> the attribute 'Protected'.  I wrote all in a file Let.m and now, I
> wonder where to put it that it gets read automatically at each startup
> of Mathematica.
> However, my first question: Is it a bad idea to add things to the
> System` context?  And if not, where to put my file?  And if, what would
> be better?
> Second, my main question: Is it somehow possible to add this nice syntax
> highlighting to the Let construct like with the With construct, where
> the local variables appear in green?
> Third: Can I somehow add a help page?  I have already the Let::usage.
> And last: Does anybody know how to make the construct better?  Is there
> something like syntax transformation rules in Mathematica?
> Thank you for your answers,
> Bastian

  • Prev by Date: Re: NDSolve problem
  • Next by Date: CityData seems to be dead.
  • Previous by thread: Re: Add syntax highlighting to own command
  • Next by thread: Re: Add syntax highlighting to own command