5
$\begingroup$

Is there any way (except using upvalues) to make a function that returns it's input Unevaluated? What I mean by that:

Take the following example:

Apply[foo]@Unevaluated[Print@1]
(* foo[1] *)

(* this function should only "look" at x, but not affect anything else *)
tee[x_] := (Echo@HoldForm@x; Unevaluated@x)

Apply[foo]@*tee@Unevaluated[Print@1]
(* 1 *)    
(* Null *)   

(* not exactly the case I need this for,
   but demonstrates that the issue is not only with Composition *)
Apply[foo]@tee@Unevaluated[Print@1]
(* Print[1] *)    
(* Null *)

The goal is to have the last line behave exactly the same way as the first one (except for the Echo of course). Like I mentioned above, this could probably be done using upvalues, but it feels like there has to be a simpler solution that I'm just not seeing...

Or in other words (in case the above is not clear): How to define a function (tee) with the following properties:

  • Can be composed (@*) with any non-Hold* function (i.e. without any Hold* attribute) without leaking evaluation
  • Should be fully transparent: If the argument is Unevaluated pass it along as such, otherwise don't prevent evaluation
  • Should work with any number of arguments (e.g. (foo@*tee)[a,b])

Update

I seem to have failed state my needs clearly - I am only interested in preserving arguments wrapped in Unevaluated, not in keeping Hold* attributes while using @*.

$\endgroup$
10
  • $\begingroup$ looks like closely related: Why doesn't a Composition hold its arguments and what can be done about it? and related: 78979 $\endgroup$
    – Kuba
    Mar 30, 2018 at 20:18
  • $\begingroup$ @Kuba Thanks for the links, but I don't think they're that closely related - I'm interested in the cases where one explicitly wraps arguments in Unevaluated (which prevents works together with Composition, e.g. (a@*Hold)[Unevaluated@Print@1]). I will try to make the question a bit clearer in that regard. $\endgroup$
    – Lukas Lang
    Mar 30, 2018 at 20:33
  • 1
    $\begingroup$ What if you change all Unevaluated to Inactivate? $\endgroup$
    – swish
    Mar 30, 2018 at 22:26
  • 1
    $\begingroup$ Try SetAttributes[tee, HoldAllComplete]; tee[x_] := (Echo @ HoldForm @ x; Unevaluated[Unevaluated][Unevaluated[x]]) $\endgroup$ Mar 30, 2018 at 22:35
  • 2
    $\begingroup$ I have the feeling that Unevaluated is not meant for what you are using it. I would also suggest to use Inactive in combination with Activate. $\endgroup$ Mar 31, 2018 at 9:07

2 Answers 2

2
$\begingroup$

For anyone coming across this, an answer in the above comments (from J. M.'s ennui) worked for me with minor modifications. All credit goes to him; I'd have never guessed that you'd need two "helpings" of Unevaluated[...] to achieve the effect of returning an unevaluated expression from a function! Maybe it has to do with the function call itself using Compose, causing one "round" of Unevaluated to be "used up"??? Speculating here...

EDIT: UnevaluatedDouble[2] actually did not work for me; trying to use the result downstream results in Unevaluated persisting forever. See links below.

UnevaluatedSingle[x_] := Unevaluated[x + 1]
UnevaluatedDouble[x_] := Unevaluated@Unevaluated[x + 1]
UnevaluatedSingle[2]
UnevaluatedDouble[2]

Results:

3
Unevaluated[2 + 1]

However, per a post I'll link to in a second, apparently "Unevaluated is not meant to be a function or stable data type." So although I've found some cases where the above trick was useful for me (at least prima facie), I'm not certain whether its use in this manner is ever "supported" or not.

As to the OP's original question... per this post:

Unexpected behavior of Unevaluated

I'll quote a small excerpt from Mr. Wizard, who in turn quotes a presentation by Robby Villegas:

Unevaluated must be wrapper before argument evaluation, not after, else it isn't stripped.
...
Unevaluated is not meant to be a function or stable data type. It is to be used as a wrapper on an argument in stage 1, before argument evaluation.

Therefore, unless I'm missing something, the OP's question can't be solved quite as suggested, directly, e.g. via:

tee[x_] := (Echo@HoldForm@x; Unevaluated@Unevaluated@x)

... because you can't actually return an expression whose head is Unevaluated -- it's never meant to be the head of an expression. Instead, one solution would be to make a form of tee where the call-site passes-in the function to be called and/or the "auxiliary" function (e.g. Echo) to tee.

Version 1:

ClearAll[tee]
SetAttributes[tee, HoldAll]
tee[expr_, mainFunc_, auxFunc_ : Echo@*HoldForm] := (auxFunc@Unevaluated@expr; mainFunc@Unevaluated@expr)
tee[Print@3, Apply[foo]]

The following two versions were written to illustrate the syntax difference required when using composition (which was called-out by the OP):

Version 2a

ClearAll[tee]
SetAttributes[tee, HoldAll]
tee[expr_, mainFunc_, 
  auxFunc_ : Function[e, Echo@HoldForm@e, HoldAll]]
  := (auxFunc@expr; mainFunc@expr)
tee[Print@3, Function[e, foo @@ Unevaluated@e, HoldAll]]

Version 2b

ClearAll[tee]
SetAttributes[tee, HoldAll]
tee[expr_, mainFunc_, auxFunc_ : 
   Function[e, Echo@*HoldForm@Unevaluated@e, HoldAll]]
   := (auxFunc@expr; mainFunc@expr)
tee[Print@3, Function[e, foo @@ Unevaluated@e, HoldAll]]

Depending on what the "real" application of this was, and your taste in syntax, the below is a final alternative:

ClearAll[tee]
SetAttributes[tee, HoldAll]
tee[expr_, auxFunc_ : Echo@*HoldForm]
   := (auxFunc@Unevaluated@expr; #[Unevaluated@expr] &)
tee[Print@3][Apply[foo]]
$\endgroup$
1
$\begingroup$

It is possible to write your own Unevaluated-like wrapper using just up-values with slightly different behavior. I called it Held, and it's a part of this small weird ArgumentTools paclet now. It is almost like Unevaluated, but doesn't have weird internal semantics. And I hadn't even considered that it could be used for this scenario until I stumbled upon this question :).

In your example, the tee should just return an expression wrapped with Held (you can install the paclet and use the symbol directly, of course):

tee[x_] := (
  Echo[HoldForm[x]];
  PacletSymbol["Wolfram/ArgumentTools", "Wolfram`ArgumentTools`Held"] @@ Hold[x]
  )

I use Held @@ Hold[x], because Held disappears from any holding argument position (except it's HoldAllComplete), which is CompoundExpression in this case:

Hold[Held[x]] === Hold[x]
HoldComplete[Held[x]] === HoldComplete[Held[x]]

And now, your second example would work as it is:

Apply[foo] @ tee @ Unevaluated[Print@1]
(* foo[1] *)

The thing with Composition (@*), though, is that it also absorbs one Unevaluated before passing its argument to tee, so you need to wrap it twice:

Apply[foo] @* tee @ Unevaluated[Unevaluated@Print@1]
(* foo[1] *)

Held would also work as an outer wrapper here, but not the inner one, because it flattens itself into Unevaluated:

foo[Held[Held[Held@Print@1]]]
(* foo[Unevaluated[Print[1]]] *)

So Held and Unevaluated really complement each other nicely.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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