2
$\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$
9
  • $\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 '18 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 '18 at 20:33
  • $\begingroup$ Ok, thanks for the edit. I will leave those links for interested readers anyway. $\endgroup$ – Kuba Mar 30 '18 at 22:18
  • 1
    $\begingroup$ What if you change all Unevaluated to Inactivate? $\endgroup$ – swish Mar 30 '18 at 22:26
  • 1
    $\begingroup$ Try SetAttributes[tee, HoldAllComplete];; tee[x_] := (Echo @ HoldForm @ x; Unevaluated[Unevaluated][Unevaluated[x]]) $\endgroup$ – J. M.'s ennui Mar 30 '18 at 22:35
0
$\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$

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.