3

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?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Richard
  • 31
  • 1
  • ...what sort of behavior were you expecting? – J. M.'s missing motivation Jun 04 '13 at 04:34
  • I want the time argument to default to the current time in this case, ie, they should show the same time. – Richard Jun 04 '13 at 04:40
  • 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[]... – J. M.'s missing motivation Jun 04 '13 at 04:45
  • 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. – Richard Jun 04 '13 at 04:49
  • 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}]... – J. M.'s missing motivation Jun 04 '13 at 04:52
  • That will work but I would prefer to pass the argument normally. I guess I thought this would be a pretty easy thing to do somehow but perhaps not. Thanks for your help – Richard Jun 04 '13 at 04:59

4 Answers4

6
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[]};
Kuba
  • 136,707
  • 13
  • 279
  • 740
  • Ah, it was the Attributes[]... I do think your second solution is much better than the first. – J. M.'s missing motivation Jun 04 '13 at 06:42
  • @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. – Kuba Jun 04 '13 at 06:49
2

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]
masterxilo
  • 5,739
  • 17
  • 39
1
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?

masterxilo
  • 5,739
  • 17
  • 39
1

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.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263