3
$\begingroup$

Suppose I have a property associated with two functions using TagSet:

f1 /: type[f1[n_][x_]] := n
f2 /: type[f2[n_, m_][x_]] := m

But the function are related by

f2[n_, m_] := f1[n]

In other words, I use a second argument in f2 to distinguish the property differences.

Now if we do

type[f2[1, 2][x]]
(* 1 *)

we get 1 instead of 2. this is because f2[1,2] get evaluated into f1[1]. We can fix this by setting the attribute of type:

SetAttributes[type, HoldAll]

and now it behaves as what we desire

type[f2[1, 2][x]]
(* 2 *)

However, think about the situation that f2 is some very long expression with many arguments, and instead of writing the long expression every time, I would like to assign it to a short variable for connivence:

myf := f2[1,2]

But now we are not be able to get the type correctly:

type[myf[x]]
type[Evaluate@myf[x]]
(* type[myf[x]] *)
(* 1 *)

So how should I deal with this?

$\endgroup$
3
$\begingroup$

I think this is a question of organizing the definitions differently. If I understand the goal of type correctly, it seems redundant to define it based on a pattern that includes the second argument. Instead, you should just define type using TagSet by giving only the first argument pattern on which the type actually depends. Then you can reserve the pattern [x] for when you really want to define the functional dependence on x, as you do in the identification of f2 with f1:

ClearAll[f1, f2]

f1 /: type[f1[n_]] := n
f2 /: type[f2[n_, m_]] := m

f2[n_, m_][x_] := f1[n][x]

type[f1[1]]

(* ==> 1 *)

type[f2[1, 2]]

(* ==> 2 *)

myf := f2[1, 2]

type[myf]

(* ==> 2 *)

Now there are no more conflicts. I think it's cleaner this way.

$\endgroup$
3
$\begingroup$

If the application permits, one way might be to add a third definition to type:

type[x_] := type[Evaluate@Block[{f1, f2}, x]]

By temporarily blocking the definitions of f1 and f2, expressions like myf can evaluate to their constituent parts involving those symbols. We then apply type to the result, and the upvalues on f1 and f2 can once again take effect:

type[f1[1][x]]
(* 1 *)

type[f2[1, 2][x]]
(* 2 *)

type[myf[x]]
(* 2 *)

As defined, type will loop indefinitely should it ever be applied to an expression that is completely independent of f1 and f2:

type[z]
(* $IterationLimit::itlim: Iteration limit of 4096 exceeded. >> *)

To guard against this possibility, we make sure that the last definition only takes effect if the evaluation of its argument actually resulted in a change:

ClearAll[type]
SetAttributes[type, HoldAll]
f1 /: type[f1[n_][x_]] := n
f2 /: type[f2[n_, m_][x_]] := m
(* here is the change: *)
type[x_] := With[{e = Block[{f1, f2}, x]}, type[e] /; Hold[e] =!= Hold[x]]

Now:

type[f1[1][x]]
(* 1 *)

type[f2[1, 2][x]]
(* 2 *)

type[myf[x]]
(* 2 *)

type[z]
(* type[z] *)

type[11 + 22]
(* type[33] *)

This strategy might not be suitable if f1 and f2 are themselves HoldAll because Block will temporarily disable that attribute as well, leading to evaluation leaks. It would also be unsuitable if the application will not tolerate the partial evaluation of expressions such as we see in the last example above.

$\endgroup$
  • $\begingroup$ +1. I am using very similar techniques in such cases, too. $\endgroup$ – Leonid Shifrin Jan 31 '15 at 12:30
  • $\begingroup$ That's impressive for me! Thanks for sharing the knowledge, I can imaging using it a lot in the future. I wish I could accept both answers, but Jens's answer seems to be slightly simpler for me. $\endgroup$ – xslittlegrass Jan 31 '15 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.