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
While I won't be able to authoritatively answer most of your questions,
I would not place your function into a System` context. What
I would do is to place your function into a separate package with its
own context, and make that package autoloadable. I am not a big expert
on this, I am pretty sure you will get great advice from other people here.
As for the highlighting, I would also very much like to learn if this can be
extended to user-defined functions.
Regarding your code, I think it is a very clever piece. I don't know why
you would want to improve it - it seems quite a piece of art to me. I was
first somewhat surprised that it works. I think it very cleverly uses the
fact that RuleDelayed, being a scoping construct, does not respect the
possible colliding variables in inner scoping constructs (like With here)
and in this respect is different from other scoping constructs such as
Module, Block, With, or Function (thus your substitution works as intended,
instead of internal variables simply being renamed).
Sorry if I wasn't exactly helpful.
Regards,
Leonid
you should make a file Bastian.m (or so) to look like this:
----cut---------8<----start-------8<----------------
(* :Context: Bastian` *)
BeginPackage["Bastian`"]
Let::usage="Let[vars, expr] does ... "
Begin["`Private`"]
Attributes[Let]={HoldAll};
SyntaxInformation[Let]={"ArgumentsPattern"->{_,_}};
Let[vars_, expr_]:= <your code ... >
SetAttributes[Let,ReadProtected]; Protect[Let];
End[]
EndPackage[]
----cut---------8<----end---------8<----------------
Put this file in the Folder $UserBaseDirectory/Applications.
Look for the file $UserBaseDirectory/Kernel/init.m
If it doesn't exist, create it.
Open it with an editor and add the line
Get["Bastian`"]
Now, your file will always be loaded on startup.
To your other questions:
1. Yes, it's a bad idea to give your own functions the context System`.
Better create your own Bastian.m file and put all your functions in
it; they will have the context Bastian`. (Such a file tends to grow
over time :-))
2. Syntax Highlighting is done with the line SyntaxInformation above.
3. I can't improve your code.
Bastian Erdnuess wrote:
--
_________________________________________________________________
Peter Breitfeld, Bad Saulgau, Germany -- http://www.pBreitfeld.de
> While I won't be able to authoritatively answer most of your questions,
> I would not place your function into a System` context. What
> I would do is to place your function into a separate package with its
> own context, and make that package autoloadable. I am not a big expert
> on this, I am pretty sure you will get great advice from other people here.
It's probably a good idea to make a MyToolBox` context and put things
like that in there. "Autoload Package" was a good key word, with this I
think I can figure out how to do what I want to do.
> As for the highlighting, I would also very much like to learn if this can be
> extended to user-defined functions.
Probably, a Mathematica developer can tell how Mathematica handles
syntax highlighting internally and if there is any kind of interface to
it.
> Regarding your code, I think it is a very clever piece. I don't know why
> you would want to improve it - it seems quite a piece of art to me.
I'm glad you like it. I changed the ReplaceAll (/.) to a Replace. It
seems that it doesn't do so, but i fear the ReplaceAll could mess
something up in a call like
Let[
{
a = { "A", "B", "C" },
b = { "X", "Y", "Z" }
},
Print[ "done" ];
]
> I was first somewhat surprised that it works.
I'm still. I tried different things and I have actually no clue why
only exactly this one was working.
> I think it very cleverly uses the
> fact that RuleDelayed, being a scoping construct, does not respect the
> possible colliding variables in inner scoping constructs (like With here)
> and in this respect is different from other scoping constructs such as
> Module, Block, With, or Function (thus your substitution works as intended,
> instead of internal variables simply being renamed).
Interesting. I need to try to understand how RuleDelayed works.
Probably, I can then try to understand my own code, too.
Bastian
I'm certainly unable to make the construct better, but I can show how
I would do it. I'm also fairly new to Mathematica, so don't use my
code in automated brain surgery...
mywith[{}, expr_] := expr
mywith[{head_, tail___}, expr_] := With[{head}, mywith[{tail}, expr]]
SetAttributes[mywith, HoldAll]
Test:
(* Input *)
mywith[{a = "Hi, ", b = a <> "there"}, Print[b]]
mywith[{a = 1, b = a + 1, c = b + 1, d = a + b + c}, {a, b, c, d}]
(*Output*)
Hi, there
{1, 2, 3, 6}
Do our solutions differ in behavior? I wouldn't be surprised at all if
our codes behaved differently in the details.
All the best,
Daniel Janzon
> System` context? And if not, where to put my file? And if, what wou=
> Bastian,
>
> you should make a file Bastian.m (or so) to look like this:
I did so. Now I have a MyToolBox.m.
> Put this file in the Folder $UserBaseDirectory/Applications.
>
> Look for the file $UserBaseDirectory/Kernel/init.m
> If it doesn't exist, create it.
> Open it with an editor and add the line
>
> Get["Bastian`"]
>
> Now, your file will always be loaded on startup.
That was a good tip. I tried it with $UserBaseDirectory/Autoload first,
but that didn't really worked well.
> To your other questions:
>
> 1. Yes, it's a bad idea to give your own functions the context System`.
> Better create your own Bastian.m file and put all your functions in
> it; they will have the context Bastian`. (Such a file tends to grow
> over time :-))
Yes, makes sense, and seems to be pretty true. It already started. I
wanted to make a usage text with some text formatting. But since I use
an external editor and Mathematicas
"\!\(\*RowBox[\"Let\",\"[\",RowBox[\"{\",RowBox[StyleBox[\"x\", ... "
stuff is pretty ugly I started to make some functions for text makeup.
I used the Let construct in them and right now I'm writing the usage
textes to the makeup functions using themseves to do that...
> 2. Syntax Highlighting is done with the line SyntaxInformation above.
That's already nice. Is there a reason not to use {{__},_} instead of
{_,_}? However, not exactly what I was looking for.
When you write e.g.
With[
{
a = 1,
b = 2
},
a * b
]
Mathematica highlights the symbols a and b green. But it seems that
this cannot be controlled using SyntaxInformation. :-(
> 3. I can't improve your code.
I found two other versions of the same program (one of them here), and
indeed I must say I like my version best. -- Funnily, I found them while
I was looking for syntax highlighting. These guys had the same
question: "I wrote this function and now I wonder if I can add syntax
highlighting..."
Bastian
I'm certainly unable to make a better construct, but I can show how I
would do it. I'm also fairly new to Mathematica, so don't use my code
for automated brain surgery...
mywith[{}, expr_] := expr
mywith[{head_, tail___}, expr_] := With[{head}, mywith[{tail}, expr]]
SetAttributes[mywith, HoldAll]
Test:
(* Input *)
mywith[{a = "Hi, ", b = a <> "there"}, Print[b]]
mywith[{a = 1, b = a + 1, c = b + 1, d = a + b + c}, {a, b, c, d}]
(* Output *)
Hi, there
{1, 2, 3, 6}
Which agrees with your Let function. Do our functions behave
differently in some way? Probably, it would be instructing to sort it
out.
All the best,
Daniel Janzon
PS! (This may be a double post, but I think I accidently pressed
"Discard" instead of "Send" so this is a second try. Sorry it there is
any trouble.)
> System` context? And if not, where to put my file? And if, what wou=
An interesting discussion!
I've also on occasion wanted a similar construct, although the replace
all inside the definition is, as Leonid says, clever, it just didn't
sit well with me.
Ultimately using a Fold construct would be the nicest, but passing
unevaluated arguments around isn't always so simple -- the following
code works, but is definitely not pretty.
ClearAll[Let]; SetAttributes[Let, HoldAll];
Let[sp : {a__Set}, expr_] := Quiet[ReleaseHold@Fold[With[{#2}, #1] &,
Hold@expr, Reverse[Hold /@ Unevaluated@sp]], With::"lvw"]
The simplest code I could come up with was the following:
ClearAll[Let]; SetAttributes[Let, HoldAll];
Let[{}, expr_] := expr
Let[{a_, b___}, expr_] := With[{a}, Let[{b}, expr]]
the nice thing about it is that all of the error handling is passed
through to With properly (just like Bastian's original code).
Thanks Peter for pointing out the SyntaxInformation command, that's a
handy one to know. Do you have any ideas how to get the syntax
highlighting working properly as well? (the constructions in the
documentation centre weren't very enlightening for this case).
Simon
PS
A quick google brought up this, very similar discussion
http://www.mathkb.com/Uwe/Forum.aspx/mathematica/15031/A-version-of-With-that-binds-variables-sequentially
(it's scary how close my Fold construct is to the one that rych
proposed...)
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],
(*else*)
Unevaluated[vars] /. {a_, b__} :>
With[{a}, Let[{b}, expr]]]
Consider this, for instance:
Block[{Length = 1},
Let[{a = "Hi, ", b = a <> "there!"}, Print[b]]]
or
Block[{If},
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
Daniel):
In[2] =
ClearAll[Let];
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}, _] :=
Module[{badarg},
"" /; Message[Let::lvset,
HoldForm[vars],
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],
Select[
HoldForm[x], Function[arg, Head[Unevaluated[arg]] =!= Set],
1]];
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):
In[3]:=
Let[{a=1,b=2},
{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
be
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.
Regards,
Leonid
On Wed, Jul 8, 2009 at 11:00 PM, Bastian Erdnuess <eart...@web.de> 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
with regards to the SyntaxInformation line
There appears to be no difference between using {{__},_} or {_,_} .
This is because you could pass a symbol that evaluates to a list to
the first position in {_,_}.
---- to quote the documentation center ----
The first argument of f should be a list of exactly two elements:
SyntaxInformation[f] = {"ArgumentsPattern" -> {{_, _}, _}};
{f[{a}, x], f[{a, b}, x], f[{a, b, c}, x]}; -- only the last
expression fails the argument pattern.
The first argument is allowed to be an expression such as a symbol,
which could evaluate to a list:
{f[a], f[a, x], f[a, x, y]}; -- the first land last expression here
fail the argument pattern, the middle passes.
---- end quote ----
Simon
> Hi everyone,
>
> An interesting discussion!
> I've also on occasion wanted a similar construct, although the replace
> all inside the definition is, as Leonid says, clever, it just didn't
> sit well with me.
Yes, I also like Daniels and your second solution better.
> Ultimately using a Fold construct would be the nicest, but passing
> unevaluated arguments around isn't always so simple -- the following
> code works, but is definitely not pretty.
>
> ClearAll[Let]; SetAttributes[Let, HoldAll];
> Let[sp : {a__Set}, expr_] := Quiet[ReleaseHold@Fold[With[{#2}, #1] &,
> Hold@expr, Reverse[Hold /@ Unevaluated@sp]], With::"lvw"]
I understand that using the Fold construct should be the prefered way,
but this seems indeed to be a little bit to ugly for me.
> The simplest code I could come up with was the following:
>
> ClearAll[Let]; SetAttributes[Let, HoldAll];
> Let[{}, expr_] := expr
> Let[{a_, b___}, expr_] := With[{a}, Let[{b}, expr]]
I like this best. It's also Daniels solution. I think, I'm going to
use this code in MyToolBox. Thank you.
> the nice thing about it is that all of the error handling is passed
> through to With properly (just like Bastian's original code).
>
> Thanks Peter for pointing out the SyntaxInformation command, that's a
> handy one to know. Do you have any ideas how to get the syntax
> highlighting working properly as well? (the constructions in the
> documentation centre weren't very enlightening for this case).
Dito.
> Simon
>
> PS
> A quick google brought up this, very similar discussion
> <http://www.mathkb.com/Uwe/Forum.aspx/mathematica/15031/
A-version-of-With-that-binds-variables-sequentially>
> (it's scary how close my Fold construct is to the one that rych
> proposed...)
That's what I found because of
"Is there a way to get syntax highlighting for WithMany? I found the
SyntaxInformation stuff, but it doesn't seem to expose the
highlighting that's used for With, Module, or Block."
Bastian
> An interesting discussion!
> Ultimately using a Fold construct would be the nicest, but passing
> unevaluated arguments around isn't always so simple
Indeed.
Here is another solution using Fold. It's probably not the very best
idea to do it like that, but I want to present it anyway, since it was
not here so far, I guess.
ClearAll[take]
SetAttributes[take, HoldAll]
take[ list_, expr_ ] :=
ToExpression @
Fold[
"With[ {" <> #2 <> " }, " <> #1 <> " ]" &,
ToString @ Unevaluated[ expr ],
Reverse[
Function[
lst,
ToString[ Unevaluated[ lst ], InputForm ],
HoldAll
] /@ Unevaluated[ list ]
]
]
Bastian
> Simon <simon...@gmail.com> wrote:
>
> > An interesting discussion!
>
> > Ultimately using a Fold construct would be the nicest, but passing
> > unevaluated arguments around isn't always so simple
>
> Indeed.
>
> Here is another solution using Fold. It's probably not the very best
> idea to do it like that, but I want to present it anyway, since it was
> not here so far, I guess.
>
> ClearAll[ take ]
>
> SetAttributes[ take, HoldAll ]
>
> take[ list_, expr_ ] :=
> ToExpression @
> Fold[
> "With[ { " <> #2 <> " }, " <> #1 <> " ]" &,
> ToString @ Unevaluated[ expr ],
Better to use here
ToString[ Unevaluated[ expr ], InputForm ]
instead.
> Reverse[
> Function[
> lst,
> ToString[ Unevaluated[ lst ], InputForm ],
> HoldAll
> ] /@ Unevaluated[ list ]
> ]
> ]
BTW: No clue, why I called it now "take".
Bastian
PS: Has somebody a hint how to get
lhs := Let[ vars, expr /; cond ]
to work?
> PS: Has somebody a hint how to get
>
> lhs := Let[ vars, expr /; cond ]
>
> to work?
I got it:
Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
Let[ vars, lhs := expr /; cond ]
My whole Let looks now like
SetAttributes[ Let, HoldAll ]
SyntaxInformation[ Let ] =
{ "ArgumentsPattern" -> { _, _ } }
Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
Let[ vars, lhs := expr /; cond ]
Let[ { }, expr_ ] := expr;
Let[ { head_ }, expr_ ] := With[ { head }, expr ]
Let[ { head_, tail__ }, expr_ ] :=
With[ { head }, Let[ { tail }, expr ] ]
It looks like it doesn't matter in which order I give the rules.
Mathematica applies always the most specific. How does it know?
Bastian
I think the line
> Let[ { head_ }, expr_ ] := With[ { head }, expr ]
is unnecessary since tail_ can match the empty sequence in
> Let[ { head_, tail__ }, expr_ ] :=
> With[ { head }, Let[ { tail }, expr ] ]
Or did I miss something?
All the best,
Daniel
On 16 Juli, 14:19, earth...@web.de (Bastian Erdnuess) wrote:
> Hi Bastian,
>
> I think the line
>
> > Let[ { head_ }, expr_ ] := With[ { head }, expr ]
>
> is unnecessary since tail_
tail___ ?
> can match the empty sequence in
>
> > Let[ { head_, tail__ }, expr_ ] :=
> > With[ { head }, Let[ { tail }, expr ] ]
>
> Or did I miss something?
I don't know.
When I run
Let[ { head, tail }, expr ]
then first
Let[ { head_, tail__(_) }, expr_ ]
matches and gives
With[ { head }, Let[ { tail }, expr ] ]
Now, in my case
Let[ { head_ }, expr ]
matches and gives finaly
With[ { head }, With[ { tail }, expr ] ]
after two steps.
In the other case
Let[ { head_, tail___ }, expr_ ]
matches again and gives
With[ { head }, With[ { tail }, Let[ { }, expr ] ] .
Now,
Let[ { }, expr_ ]
has to match, to transform this finally to
With[ { head }, With[ { tail }, expr ] ]
after three steps.
In my case, I actually wouldn't need to have the rule for
Let[ { }, expr_ ]
in. I just put it as backup incase it would somehow actually occour in
an automated process.
I don't know how Mathematica internally works and if it is worth buying
one step less transformation by adding another rule (or to do it at
all). However, when I understood this right, it shouldn't matter wether
the rule for
Let[ { }, expr_ ]
is in or not, when it is on the last position, since the others get
checked before.
So, that's probably a good point to rearrange the rules to
(l_ := Let[ v_, x_ /; c_ ]) ^:= Let[ v, l := x /; c ]
Let[ { h_, t__ }, x_ ] := With[ { h }, Let[ { t }, x ]
Let[ { h_ }, x_ ] := With[ { h }, x ]
Let[ { }, x_ ] := x
then the less likely get checked less often (probably).
BTW: Has someone a clue why Mathematica tries UpRules always before
DownRules? Does this make sense? Or is it just a wrong impression of
mine?
Bastian
>
> Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
> Let[ vars, lhs := expr /; cond ]
>
I'm sorry, Bastian, but can I ask you what you're trying to achieve
with these lines? Actually, I've never seen more than one SetDelayed
in an expression. When I try to test this definition as follows, I get
a "SetDelayed::shape" error,
{x, y, z, w} := Let[{a = 1, b = a + 1, c = b + 1, d = a + b + c}=
, {a,
b, c, d}]
Other than this, thanks to you and Daniel for these nice recursive
versions of With.
Igor
> On Jul 16, 1:19 pm, earth...@web.de (Bastian Erdnuess) wrote:
> > Bastian Erdnuess <earth...@web.de> wrote:
>
> >
> > Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
> > Let[ vars, lhs := expr /; cond ]
> >
>
>
> I'm sorry, Bastian, but can I ask you what you're trying to achieve
> with these lines? Actually, I've never seen more than one SetDelayed
> in an expression.
In the help for With is written
- You can use With[ { vars }, body /; cond ] as the right-hand side
of a transformation rule with a condition attached.
I tried to get the same behaviour to the Let construct.
I found out one can write the upper rule also straight as
( lhs_ := Let[ vars_, expr_ /; cond_ ] ) ^:=
Let[ vars, lhs := expr /; cond ]
as an UpRule.
> When I try to test this definition as follows, I get
> a "SetDelayed::shape" error,
>
> {x, y, z, w} := Let[{a = 1, b = a + 1, c = b + 1, d = a + b + c}=
> , {a,
> b, c, d}]
I don't see what you try to do. This shouldn't match any of the rules
for Let at all. I get the same error, also when I use a symbol like Foo
instead of Let which has no rules attached.
Bastian
If I understand you correctly and the last rule you added
( Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
Let[ vars, lhs := expr /; cond ])
is to allow shared local variables in function definitions (like with
standard
With, Module or Block), then your code does not work for me.
Here is your code:
In[1] =
SetAttributes[ Let, HoldAll ]
SyntaxInformation[ Let ] =
{ "ArgumentsPattern" -> { _, _ } }
Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
Let[ vars, lhs := expr /; cond ]
Let[ { }, expr_ ] := expr;
Let[ { head_ }, expr_ ] := With[ { head }, expr ]
Let[ { head_, tail__ }, expr_ ] :=
With[ { head }, Let[ { tail }, expr ] ]
Consider the following definition:
In[2] =
Clear[f];
f[x_, y_] := With[{xl = x}, With[{yl = y + xl + 1}, xl^2 + yl^2 /; (xl + yl
< 15)]];
f[x_, y_] := x + y;
In[3] = ?f
Out[3] =
Global`f
f[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},xl^2+yl^2/;xl+yl<15]]
f[x_,y_]:=x+y
This is supposed to be equivalent to the following:
In[4] =
Clear[f];
f[x_, y_] := Let[{xl = x, yl = y + xl + 1}, xl^2 + yl^2 /; (xl + yl <
15)];
f[x_, y_] := x + y;
But it is not:
In[5] = ?f
Out[5] =
Global`f
f[x_,y_]:=x+y
I can think of several reasons why this does not work, and will expand this
discussion if you will be interested, but here is the version that I
believe does work:
In[6] =
ClearAll[Let];
SetAttributes[Let, HoldAll];
Let /:
Verbatim[SetDelayed][lhs_, rhs : HoldPattern[Let[{__}, _]]] :=
Block[{With},
Attributes[With] = {HoldAll};
lhs := Evaluate[rhs]];
Let[{}, expr_] := expr;
Let[{head_}, expr_] := With[{head}, expr];
Let[{head_, tail__}, expr_] :=
Block[{With},
Attributes[With] = {HoldAll};
With[{head}, Evaluate[Let[{tail}, expr]]]];
This uses the Block trick. It is actually a true macro, since
it expands the code before SetDelayed acts on it:
In[7] =
Clear[f];
f[x_, y_] := Let[{xl = x, yl = y + xl + 1}, (Print["*"]; xl^2 + yl^2) /;
(xl + yl < 15)];
f[x_, y_] := x + y;
In[8] = ?f
Out[8] =
Global`f
f[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},(Print[*];xl^2+yl^2)/;xl+yl<15]]
f[x_,y_]:=x+y
In[9] = f[1,2]
*
Out[9] = 17
In[10] = f[1,20]
Out[10] = 21
Regards,
Leonid
> Hi Bastian,
>
> If I understand you correctly and the last rule you added
>
> ( Let /: ( lhs_ := Let[ vars_, expr_ /; cond_ ] ) :=
> Let[ vars, lhs := expr /; cond ])
>
> is to allow shared local variables in function definitions (like with
> standard With, Module or Block), then your code does not work for me.
Yes, you are (and understood me) right.
> Consider the following definition:
> [...]
> f[x_, y_] :=
> Let[{xl = x, yl = y + xl + 1}, xl^2 + yl^2 /; (xl + yl < 15)];
I tried first something different than I actually posted. I thought
then I could write it different. But somehow I must have forgotten to
test it correctly.
However, also with my original version (something like
f_[ l_, Let[ r_ ] ] := bla[ l := r ] /; f == SetDelayed )
I would have gotten about
f[ x$_, y$_ ] := x^2 + (1 + x + y)^2 /; 1 + 2x + y < 15 .
I didn't recognized that the local variables in f get renamed.
> I can think of several reasons why this does not work, and will expand this
> discussion if you will be interested, but here is the version that I
> believe does work:
Now, I can too. But if you want to explain some of your reasons, i'll
hear.
I also found a trick to get around the renaming problem (using your
Verbatim instead of mine f_[...] /; f == ...):
Verbatim[ SetDelayed ][ l_,
Let[ v_, Verbatim[ Condition ][ r_, c_ ] ]
] ^:=
Let[ v,
SetDelayed[ #, r /; c ] & @ ToExpression @ #
] & @ ToString[ l, InputForm ]
This passes basically the left hand side as string around the issue with
the renaming.
The last days I often figured out that that what I think of as "syntax
transformation" or Simon called "passing unevaluated arguments around"
works best by passing the things around as strings. Are there
disadvantages doing that? I could imagine that this might be not the
best if one wants to compile the source -- but so far I'm far away from
starting to try to understand Mathematicas compiler.
> [ Block trick ]
> This uses the Block trick. It is actually a true macro, since
> it expands the code before SetDelayed acts on it:
That seems to be a nice trick. Thank you for showing that. I need to
think a little bit about that to decide if I like it for this situation.
Regards,
Bastian
Some comments:
1. About variable renaming in SetDelayed: this happens sometimes
if SetDelayed in lexically scoped inside some other scoping constructs
(Module, With, Function). For example:
In[1] = Module[{a,b},ff[a_,b_]:=a^2+b^2]
In[2] = ?ff
Out[2] =
Global`ff
ff[a_,b_]:=a^2+b^2
No renaming in this case - the formal parameters a,b in SetDelayed
completely shadowed the Module variables, and the r.h.s does not
reference any other local variables. Now:
In[3] =
Module[{a,b,c},ff[a_,b_]:=a^2+b^2+c^2]
In[4] =
?ff
Out[4] =
Global`ff
ff[a$_,b$_]:=a$^2+b$^2+c$39213^2
Mathematica seems to be rather aggressive in variable renaming, doing
it also in cases when it is not strictly necessary, like the last one - but
this should be ok as long as we don't assume that variable names in
scoping constructs will keep exactly those symbolic values we give them.
This makes sense to me, since they are really "dummy" variables and
reliance on their exact symbolic name seems conceptually wrong to me (this
does not refer to Block).
Let me repeat once again, that SetDelayed (or, more correctly, RuleDelayed)
is different from other scoping constructs in that it will not rename its
variables in they conflict with local variables in scopes *inner* to
SetDelayed
(RuleDelayed) - or at least this is my current understanding of it.
2. Your workaround:
Verbatim[ SetDelayed ][ l_,
Let[ v_, Verbatim[ Condition ][ r_, c_ ] ]
] ^:=
Let[ v,
SetDelayed[ #, r /; c ] & @ ToExpression @ #
] & @ ToString[ l, InputForm ]
is interesting and quite inventive, but still not completely satisfactory in
my view. The point is that it solves one problem - how to "hide" the content
of the l.h.s. until the variable collision resolution is done - and thus
your variables will not be renamed. By the way, let me digress a bit: since
the heart of this code is in hiding the argument of SetDelayed from the
outer With
(which will be there once one iteration of Let gets executed), the following
does exactly the same and does not require ToString-ToExpression cycle:
Verbatim[SetDelayed][l_, LetB[v_, Verbatim[Condition][r_, c_]]] ^:=
Module[{a}, LetB[v, Unevaluated[SetDelayed[a, r /; c]] /. a :> l]];
Anyways, while this does solve one problem, it does not solve another, more
subtle one: it is difficult to implement UpValues with SetDelayed and
Condition ourselves. There are internal heads RuleCondition and
$ConditionHold, which get executed when we execute scoping constructs
with conditions attached - we don't want this to happen prematurely.
Taking your code (I renamed Let to LetB):
In[5] =
ClearAll[LetB];
SetAttributes[LetB, HoldAll];
SyntaxInformation[LetB] = {"ArgumentsPattern" -> {_, _}};
Verbatim[SetDelayed][l_, LetB[v_, Verbatim[Condition][r_, c_]]] ^:=
LetB[v, SetDelayed[#, r /; c] &@ToExpression@#] &@
ToString[l, InputForm];
LetB[{}, expr_] := expr;
LetB[{head_}, expr_] := With[{head}, expr];
LetB[{head_, tail__}, expr_] := With[{head}, LetB[{tail}, expr]];
In[6] =
Clear[f];
f[x_,y_]:=LetB[{xl=x,yl=y+xl+1},xl^2+yl^2/;(xl+yl<15)];
f[x_,y_]:=x+y;
In[7] =
?f
Out[7] =
Global`f
f[x_,y_]:=x^2+(1+x+y)^2/;x+(1+x+y)<15
f[x_,y_]:=x+y
So far so good, although the nested With-s are gone.
Now:
In[8] =
Clear[f];
f[x_,y_]:=LetB [{xl=x,yl=y+xl+1},Print["*"];(xl^2+yl^2/;(xl+yl<15))];
f[x_,y_]:=x+y;
In[9] =
?f
Out[9] =
Global`f
f[x_,y_]:=x+y
Not good, in my view. My version does this IMO more consistently:
In[10] =
ClearAll[LetL];
SetAttributes[LetL, HoldAll];
LetL /:
Verbatim[SetDelayed][lhs_, rhs : HoldPattern[LetL[{__}, _]]] :=
Block[{With},
Attributes[With] = {HoldAll};
lhs := Evaluate[rhs]];
LetL[{}, expr_] := expr;
LetL[{head_}, expr_] := With[{head}, expr];
LetL[{head_, tail__}, expr_] :=
Block[{With},
Attributes[With] = {HoldAll};
With[{head}, Evaluate[LetL[{tail}, expr]]]];
In[11] =
Clear[f];
f[x_,y_]:=LetL [{xl=x,yl=y+xl+1},Print["*"];(xl^2+yl^2/;(xl+yl<15))];
f[x_,y_]:=x+y;
In[12] =
?f
Out[12] =
Global`f
f[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},Print[*];xl^2+yl^2/;xl+yl<15]]
f[x_,y_]:=x+y
The reason why my version works is that, with the use of the Block trick,
LetL is really a macro and works at "compile - time" - it is exactly
equivalent
to enter the nested With by hand - it really writes code for us. So, when
SetDelayed is called, the system sees nested With with condition attached,
and keeps both defs.
3. ToString - ToExpression cycles - my personal opinion: I went through the
stage when I was using them extensively as a cheap substitute for more
complex expressions involving Evaluate, Unevaluated, Hold and the like. I
try not to do it anymore in systematic programming. I think it is not a good
style, in particular it undermines possible introspection of the code, and a
few other reasons. If you need to keep code unevaluated just once, use
Unevaluated. If
you need to insert it held in some expression, use, for example, a
combination of local Hold-like wrapper and rules, something like
Module[{myHold},
Attributes[myHold] = HoldAll
With[{heldcode = myHold[code]},
f[g[h[q[heldcode]]]]/.myHold[x__]:>x]]
Or wrap everything in Hold and massage your expression into the
form you want, releasing Hold at the end. This is cleaner. Also, while
I did not do systematic benchmarks to investigate this particular issue,
on general grounds I would expect a performance hit when using
ToString - ToExpression cycles, as compared to non-standard evaluation
constructs.
This is not to say that I am not using this technique - it is quite useful
at times. I just try to use it only when it has a clear advantage in terms
of
code length, development time, readability etc.
4. Block trick
This is a very powerful but also potentially dangerous use of dynamic
scoping provided by Block. It makes virtually every Mathematica symbol, even
built-in protected symbols, to temporarily "forget" many of global
properties
associated with it, such as global rules of various kind and attributes. The
interesting part is that it does this even with built-ins, whose global
rules
we can not otherwise affect, other than defining our own on top of them.
Symbols with Locked attribute can not be Blocked.
Block trick is dangerous since it will block your symbol for an entire
execution stack enclosed in Block, that is, in all functions that may
be called during this execution. One has to control very well the execution
stack of the computation on which Block trick is used, to use it safely.
This is especially true if some system symbol is Blocked, since part of
code for the system functions is written in Mathematica itself, and we
don't know what internal code will be modified by Block in this case.
You can even corrupt Mathematica kernel in subtle ways for a given
session by doing this carelessly - this happened to me a few times,
I can only wish good luck with debugging for a poor person who gets
into this situation.
Probably the most common use of Block trick is to change the order of
evaluation in ways complex enough that the standard Unevaluated - Evaluated
- Hold - etc machinery becomes cumbersome. I tend to consider Block trick,
used in this manner, as a prototyping tool only, in all cases when the
Block-ed head is known at "compile - time" - in all such cases it is
possible (although may be not as easy) to convert the code to Unevaluated -
Evaluated - Hold - etc style.
There are also less trivial uses for it, for which I think it is more
justified.
In my version of Let, its usefulness is magnified by recursion, where it
guarantees that With will "forget about itself" in function definitions
until
the r.h.s of the def is formed, no matter how many levels of With are
nested.
But certainly this could have been accomplished without Block as well.
Hope this helps.
Regards,
Leonid
Leonid Shifrin <lsh...@gmail.com> wrote:
> 1. About variable renaming
Hm, it was a little strange what I did. I told (principally) to
translate
f[ x_ ] := With[ { a = x }, a ]
to
With[ { a = x }, f[ x_ ] := a ]
what needs some goodwill to get a propper interpretation.
Also what I ment to do would translate
f[ x_ ] := With[ { y = aVeryLongCalculation[x] }, y * Exp[ y ] ]
to
f[ x_ ] := aVeryLongCalculation * Exp[ aVeryLongCalculation[ x ] ]
and run the same aVeryLongCalculation unnecessarily twice.
> 2. Your workaround:
> is interesting and quite inventive, but still not completely satisfactory in
> my view.
> 3. ToString - ToExpression cycles - my personal opinion: I went through the
> stage when I was using them extensively as a cheap substitute for more
> complex expressions involving Evaluate, Unevaluated, Hold and the like. I
> try not to do it anymore in systematic programming. I think it is not a good
> style, in particular it undermines possible introspection of the code, and a
> few other reasons.
I see it the same. I would have prefered to play around with the actual
expression, but it is not so easy. So I tried it with the
StringExpression and it worked better. But that's definitely far from
optimal.
With the Block trick I see now how to do it better. Seems to be not
possible to control Mathematicas evaluation circle without some tricks.
> the following
> does exactly the same and does not require ToString-ToExpression cycle:
>
>
> Verbatim[SetDelayed][l_, LetB[v_, Verbatim[Condition][r_, c_]]] ^:=
> Module[{a}, LetB[v, Unevaluated[SetDelayed[a, r /; c]] /. a :> l]];
That is much closer to what I wanted to do. Thanks. I'll save this
peace of code somewhere where I can hopefully find it again when I need
it.
> Anyways, while this does solve one problem, it does not solve another, more
> subtle one: it is difficult to implement UpValues with SetDelayed and
> Condition ourselves. There are internal heads RuleCondition and
> $ConditionHold, which get executed when we execute scoping constructs
> with conditions attached - we don't want this to happen prematurely.
> f[x_,y_]:=Let [{xl=x,yl=y+xl+1},Print["*"];(xl^2+yl^2/;(xl+yl<15))];
> f[x_,y_]:=x+y;
>
> In[9] = ?f
> Out[9] =
> Global`f
> f[x_,y_]:=x+y
>
> Not good, in my view. My version does this IMO more consistently:
> In[12] = ?f
> Out[12] =
> Global`f
> f[x_,y_]:=With[{xl=x},With[{yl=y+xl+1},Print[*];xl^2+yl^2/;xl+yl<15]]
> f[x_,y_]:=x+y
So, it seems to be necessary to unroll the whole With expression to make
this things working.
> 4. Block trick
>
> This is a very powerful but also potentially dangerous [...]
> since it will block your symbol for an entire
> execution stack enclosed in Block, that is, in all functions that may
> be called during this execution.
This is really useful.
You can try
Block[ { with },
foo[ with ] /. with -> With
]
to affect really only the With you want to. This has AFAIS only the
disadvantage that you need to be sure that the symbol with is nowhere
used in foo for something else. But probably one can come around that
with something like Unique.
However, this is not necessary for Let, since there is no code evaluated
before the expression is build up and the block ends -- no code but
transformation code which contains no other With.
In your code
> ClearAll[LetL];
> SetAttributes[LetL, HoldAll];
> LetL /:
> Verbatim[SetDelayed][lhs_, rhs : HoldPattern[LetL[{__}, _]]] :=
> Block[{With},
> Attributes[With] = {HoldAll};
> lhs := Evaluate[rhs]];
> LetL[{}, expr_] := expr;
> LetL[{head_}, expr_] := With[{head}, expr];
> LetL[{head_, tail__}, expr_] :=
> Block[{With},
> Attributes[With] = {HoldAll};
> With[{head}, Evaluate[LetL[{tail}, expr]]]];
are two things which I probably would like to improve a little bit.
First, I don't really like that you start a new Block for each
assignment. In my understanding With's are fast but Blocks and
especially Modules are slow. I hoped to still get a faster code by
using
Let[ { a = ..., b = ..., c = ..., ... }, doSomething ]
compared to
Module[ { a, b, c, ... },
a = ...; b = ...; c = ...; ...;
doSomething
]
but I'm not sure if this still works when there are n With and n Blocks
for n assignments. (I did't tested it.)
And second, for the down rule you also build up the whole expression
before you start evaluating it. The former way without the block worked
more like I would expect it to do from outside to inside.
Beside this, I wonder what the HoldPattern before the Let is good for.
Should I add it to the code a few lines down, too?
I tried to improve this two things. I left the down rule as it was
before and implemented a up rule with the Block trick (but only once)
which Folds up the whole With expression independent of the down rule:
SetAttributes[ Let, HoldAll ]
SyntaxInformation[ Let ] := { ArgumentPattern -> { _, _ } }
Let[ { h_ , t__ }, x_ ] := With[ { h }, Let[ { t }, x ] ]
Let[ { h_ }, x_ ] := With[ { h }, x ]
Let[ { }, x_ ] := x
Verbatim[ SetDelayed ][ l_, Let[ v_, x_ ] ] ^:=
Block[ { With },
SetAttributes[ With, HoldAll ];
l := Evaluate @ ReleaseHold @
Fold[
With[ { #2 }, #1 ] &,
Hold @ x,
Reverse[ Hold /@ Unevaluated @ v ]
]
]
I think the Let is now alredy pretty mature.
Can you think of more situations where it might be useful to fold up the
Let beside in SetDelayed?
I also think about to only use this up rule when there is somewhere a
Condition in the Let, but I don't know how to figure that out. Does
something like
Let[ v_, x : ___ Condition ___ ]
match always?
> Hope this helps.
Yes, it did.
Thank you very much.
Bastian