5

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 @*.

Lukas Lang
  • 33,963
  • 1
  • 51
  • 97
  • @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. – Lukas Lang Mar 30 '18 at 20:33
  • Ok, thanks for the edit. I will leave those links for interested readers anyway. – Kuba Mar 30 '18 at 22:18
  • 1
    What if you change all Unevaluated to Inactivate? – swish Mar 30 '18 at 22:26
  • 1
    Try SetAttributes[tee, HoldAllComplete]; tee[x_] := (Echo @ HoldForm @ x; Unevaluated[Unevaluated][Unevaluated[x]]) – J. M.'s missing motivation Mar 30 '18 at 22:35
  • @swish: That changes the input though (what I mean is that now Inactive[Print][x] is returned instead of Print[x] in an unevaluated form) – Lukas Lang Mar 31 '18 at 08:51
  • @J.M. thanks for the idea, but unfortunately this doesn't work - the returned expression stays Unevaluated[Unevaluated][Unevaluated[Print[x]]] forever... – Lukas Lang Mar 31 '18 at 08:53
  • 2
    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. – Henrik Schumacher Mar 31 '18 at 09:07
  • @HenrikSchumacher The issue with that is that I need control over the function handling the return value of my function, which I do not have, so the solution needs to be self-contained – Lukas Lang Apr 05 '18 at 07:12
  • Unevaluated is was never meant to be a stable wrapper that can be returned from a function. You probably can sometimes do this, but I really can't encourage trying to actually do this in practice. It's just asking for trouble. – Sjoerd Smit Sep 19 '23 at 11:28

2 Answers2

2

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]]
Sean
  • 645
  • 4
  • 10
1

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.

swish
  • 7,881
  • 26
  • 48