Using up-values and down-values

10

0

I have a problem I'm trying hard to find a solution for, although I don't know if a solution exists.

The following rules produce the result e[2].f[]

x_G . f[] ^:= x
G[x_, ___, y_] := y[x]
G[2, e] . f[]

Would it be possible to get e[2] instead ?

I'm not sure it's possible as x in the left hand side of the definition of f is copied to the right hand side after having been evaluated. A solution would be to put a temporary HoldFirst attribute to Dot but this has undesirable side effects on the language.

Basically the usage I'm looking for is that an expression of the form G[x_, ____ , y_ ] is transformed to y[x], but not if it's part of an expresion of the form G[____].f[___], where in this case an UpValue should first be used, but the DownValues transformation to y[x] can happen in the rhs of f.

Would analyzing the stack help maybe? I tried that without success.

faysou

Posted 2015-02-01T15:48:24.510

Reputation: 10 549

Related: (8558)

– Mr.Wizard – 2015-02-02T07:06:47.167

Answers

9

Yes, you can set both with UpValues:

x_G.f[] ^:= x
h_[a___, G[x_, ___, y_], b___] ^:= h[a, y[x], b]

G[2, e].f[] // Simplify
(* e[2] *)

It seems to work, but it is quite dangerous, as Leonid mention in the comment. For example, it was too easy to make a mistake in my previous revision.

Here Simplify helps to deal with pure G[x_, ___, y_].

ybeltukov

Posted 2015-02-01T15:48:24.510

Reputation: 41 907

2This is indeed a possibility. I intentionally did not mention this, because patterns such as the second one here, are quite dangerous due to their generality. But, you have my upvote. – Leonid Shifrin – 2015-02-01T18:10:46.553

@ybeltukov , your solution works for my use case with this slight modification: h_[a___, G[x_, ___, y_], b___] /; h =!= Dot ^:= h[a, y[x], b] . Thanks a lot for helping me, thanks to Leonid also for trying. – faysou – 2015-02-01T18:51:01.313

I have quite a big project with such data structures as underlying objects as described in my post on structs in Mathematica http://mathematica.stackexchange.com/a/999/66, I have improved the ideas exposed on this site a lot, since posting them here, I will probably release a package publicly at some point. So your solution with my slight modification doesn't crash the system so it's very likely the right solution.

– faysou – 2015-02-01T18:54:21.637

Note that I never have pure patterns in my use cases, so Simplify is not useful in this case, although it's still interesting to know this. – faysou – 2015-02-01T19:02:35.073

1@faysou Well, this is surely a cool example of the power of UpValues, but I'd warn once again, that such constructs are fragile and an invitation to trouble (I tried this kind of stuff in my work more than once, and it always resulted in problems, sooner or later. Right now, I'd try to avoid such constructs for production code as much as possible). For example, for the definitions ClearAll[f, G]; x_G.f[] ^:= x;h_[a___, G[x_, ___ , y_], b___] /; h =!= Dot ^:= h[a, y[x], b], do you really want this behavior: Hold[G[2, e]] -> Hold[e[2]]? – Leonid Shifrin – 2015-02-01T19:42:50.707

e[2] is the main representation of G[2,e] in what I do, so it's ok for me, I agree with you warning, but it doesn't impact me (at least for now, if it backfires I'll go back to my previous way of doing). – faysou – 2015-02-01T19:54:02.990

@faysou If it's only the representation you can define custom formatting. For example, Format@G[x_, y_] := y[x]. – ybeltukov – 2015-02-01T20:20:13.137

Thanks but G[2,e] can be an intermediate state. An object e[2] might call the definition of f stored in G, the call will then be G[2,e].f[]. But if I return just the object, I want it to be returned as e[2], ie it's main representation. – faysou – 2015-02-01T21:28:01.860

8

General considerations

This seems to be tricky, and I don't know how to do this without involving the inspection of Stack. The main problem is the order of rule applications. Since Dot evaluates its arguments, UpValues for f or G are only applied after both f[] and call to G have been fully evaluated - and then it's too late. Therefore, generally you have 3 choices:

  • Make Dot be HoldAll. This is a bad solution.
  • Wrap the entire code you execute into some HoldAll dynamic environment eval, where you could implement some custom evaluation strategies. This might be the cleanest solution, if it is acceptable to you
  • Employ some tricks based on the inspection of Stack. This is the one I will illustrate.

Using Stack

Here is one possible way to use Stack. With this helper function:

ClearAll[stackAbove];
SetAttributes[stackAbove, HoldAll];
stackAbove[call_] := Stack[_] /. {before___, HoldForm[call], ___} :> {before};

You can define

ClearAll[G, f];
x_G.f[] ^:= Block[{G}, x];
call : G[x_, ___, y_] := y[x] /; FreeQ[stackAbove[call], HoldForm[call.f[]]];

Here, Block needed to disable the optimization employed by Mathematica evaluator internally to disable re-evaluation of already evaluated expressions, where the optimizer thinks that nothing has changed and there is no need to re-evaluate. In fact, already the necessity of this trick shows that we are trying to do something highly unusual.

So now:

G[2, e].f[]

(* e[2] *)

G[2, e]

(* e[2] *)

Remarks

I'd actually reconsider the design. Using Stack is a rather radical way to change the evaluation process, and having to use it tells us that we are starting to fight the system and swim upstream. There are usually better solutions within what can be achieved using the standard evaluation process.

Leonid Shifrin

Posted 2015-02-01T15:48:24.510

Reputation: 108 027

The way you handle Stack is interesting in order to just get the useful information from the stack. – faysou – 2015-02-01T19:15:56.423