3
$\begingroup$

I am trying to make a function with an optional argument that may change but mathematica caches the value when the function is defined.

Here is an example where the optional time variable is set when the function is defined.

Remove[CurrentTime]
CurrentTime[time_: DateList[]] := {time, DateList[]};
Pause[1]
CurrentTime[]

The output is split by 1 second

{{2013, 6, 4, 13, 57, 46.149504}, {2013, 6, 4, 13, 57, 47.150726}}

Is there a way to tell it its a delayed set, or to just read the function each time?

$\endgroup$
6
  • $\begingroup$ ...what sort of behavior were you expecting? $\endgroup$ Jun 4, 2013 at 4:34
  • $\begingroup$ I want the time argument to default to the current time in this case, ie, they should show the same time. $\endgroup$
    – Richard
    Jun 4, 2013 at 4:40
  • $\begingroup$ Well, the problem is that you're effectively evaluating {DateList[], DateList[]}; it stands to reason that some amount of time should have to pass in between the two times you call DateList[]... $\endgroup$ Jun 4, 2013 at 4:45
  • $\begingroup$ That is by design to illustrate the problem, there is a pause of 1 second to show that the default value for time is set once and never changed. I would like to make a function in which you can pass in the time, but if you dont it defaults to the current time. $\endgroup$
    – Richard
    Jun 4, 2013 at 4:49
  • $\begingroup$ Well, you might be interested in CurrentTime[OptionsPattern[{"Time" :> DateList[]}]] := {OptionValue["Time"], DateList[]}. One could either do CurrentTime[] or CurrentTime["Time" -> {2013, 6, 4, 12, 0, 0}]... $\endgroup$ Jun 4, 2013 at 4:52

4 Answers 4

6
$\begingroup$
CurrentTime[time_: Hold[DateList[]]] := {ReleaseHold@time, DateList[]};
Pause[1]
CurrentTime[]

{{2013, 6, 4, 8, 29, 34.8437500}, {2013, 6, 4, 8, 29, 34.8437500}}

Edit

This works too:

Remove[CurrentTime]
SetAttributes[CurrentTime, HoldAll]
CurrentTime[time_: DateList[]] := {time, DateList[]};
$\endgroup$
2
  • $\begingroup$ Ah, it was the Attributes[]... I do think your second solution is much better than the first. $\endgroup$ Jun 4, 2013 at 6:42
  • $\begingroup$ @0x4A4D It is more elegant but if, for example, You have 3 arguments with Hold and 4 without and only HoldAll, HoldRest, HoldFirst Attributes avalible then the first is useful. $\endgroup$
    – Kuba
    Jun 4, 2013 at 6:49
2
$\begingroup$

HoldPattern and Unevaluated work too. You can also remove the HoldAll once you made the definition if you like.

ClearAll[f, g, h]
f[HoldPattern@Optional[x_, RandomReal[]]] := x
g[Unevaluated@Optional[x_, RandomReal[]]] := x
h~SetAttributes~HoldAll
h[Optional[x_, RandomReal[]]] := x
h~SetAttributes~{}
h~ClearAttributes~HoldAll
{f[], f[]}
f[1]
{g[], g[]}
g[1]
{h[], h[]}
h[1]


{0.683758, 0.584808}

1

{0.793986, 0.0783635}

1

{0.518827, 0.806583}

1

You just need to get the function you want to give the default argument unevaluated into the replacement rule that is created: DownValues@g now gives

{HoldPattern[g[x_ : RandomReal[]]] :> x}

You could even do

Unprotect@Optional
SetAttributes[Optional, HoldRest]
ll[Optional[x_, RandomReal[]]] := x
ll[]
ll[1]
$\endgroup$
2
  • $\begingroup$ @Kuba Unfortunately, that does not work: Calling g[] then gives Unevaluated[RandomReal[]] $\endgroup$
    – masterxilo
    Aug 18, 2016 at 16:23
  • $\begingroup$ Agreed, I've realised this today morning but forgot to delete the comment :) $\endgroup$
    – Kuba
    Aug 18, 2016 at 16:25
1
$\begingroup$
HeldOptional = Function[, HoldPattern@Optional[#1, #2], HoldRest]

Remove[CurrentTime]
CurrentTime[time_~HeldOptional~DateList[]] := {time, DateList[]};
Pause[1]
CurrentTime[]

c.f. How to write a `HeldOptional` variant of `Optional` that does not evaluate its second argument?

$\endgroup$
1
  • $\begingroup$ Sure wish they'd put this in the Help. As of 11.2 there is no mention of such: ref/Optional. $\endgroup$ Jun 13, 2019 at 13:04
1
$\begingroup$

I would use Automatic as the default value, then do this:

currentTime[time_: Automatic] := Replace[time, Automatic -> DateList[]]

I used Replace, not ReplaceAll. Unlike ReplaceAll, Replace is safe because it only does the replacement when the whole expression matches. It won't replace subexpressions.

$\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.