Is there delayed set in Julia? (equivalent of := from Mathematica)

Viewed 131

I want to call something like rand((0, 1), N) (with N some integer assigned previously) many times in different parts of a program (all occurrences of which I might change in the future to, for example, rand((-1, 1), N) or randn(N)). How can I create a variable that, whenever it is referenced, evaluates this function?

I do not want to just write something like rand_thing = rand((0, 1), N); since then the random value would be the same each time, which is not desired.

Of course, I can define rand_func = rand((0, 1), N); and call rand_func() whenever I want to write rand((0, 1), N). I can also do things involving eval like rand_ex = :(rand((0, 1), N)); and then call eval(rand_ex) whenever I want to write rand((0, 1), N). However, is there a way I can get this functionality and only write rand_thing to generate my random number?

This is a specific example that is part of a larger question of whether there is something that directly accomplishes the functionality of SetDelayed (:=) from Mathematica. If I did rand_thing := RandomReal[]; instead of thing = RandomReal[]; in Mathematica, then every time I write rand_thing I get a new random number. (In Mathematica I would not use the underscore for the variable name, but anyways.)

If what I am describing is not possible, then some insight into why something like SetDelayed is possible in Mathematica but not in Julia would be appreciated. Is this a fundamental difference in the languages? Or is it a matter of differing conventions? Or maybe Julia could easily have a delayed set operator but so far it is not part of the language syntax? (If so, what would the implementation look like?) Or something else?

1 Answers

(Let me say first that the only thing I know about the Wolfram language is that is it's based on term rewriting.)

a variable that, whenever it is referenced, evaluates this function

is called... a function, as you rightly observe.

rand_thing() = rand((0, 1), N)

And no, there's no way of making the evaluation of a symbol, rand_thing, behave any other way than returning the value of that symbol. You can only have that if you change the way evaluation works.

Now, in Mathematica, evaluation indeed works differently. There you essentially have a rewriting system. By default, evaluation will work similarly -- "if you see a name x, look up the value of it an replace x by that value, and continue evaluation".

{} (x = 2; x) 
  ~> {x = 2} x   # update environment
  ~> {x = 2} 2   # replace x

(That's pseudo-notation, where I use {} for the carried around environment and ~> stands for "evaluates to".)

But if x was defined by SetDelayed, it's more like "look up the definition of x, replace it by the definition, and continue evaluating":

{N = 42} (x := rand(N); x)
  ~> {N = 42, x = :(rand(N))} x                # update environment
  ~> {N = 42, x = :(rand(N))} rand(N)          # replace x
  ~> {N = 42, x = :(rand(N))} rand(42)         # replace N
  ~> {N = 42, x = :(rand(N))} [0.2342343, ...] # evaluate call

The only way you can change evaluation in Julia is by using a macro. But this is not shorter than a function call; you'd have to write something like

@undelay x .+ 1

expanding to

(rand(N)) .+ 1

but I don't see any reason that would be advantageous. Plus you'd have to sort out which of the values are delayed, and which are normal values, which complicates things.

You could make up syntax like

@delayed let x = rand(N)
    x .+ 1
end

though, but you'd have to take care about preserving correct scoping behaviour yourself, for which I don't know a simple solution. (Even

@delayable begin
    x := rand(N)
    x .+ 1
end

is possible as a macro, but is even more of a hassle. )


Note that there's a concept called a thunk, which goes in the direction you'd like -- but it's a data structure, a semantic abstraction over a function, and does not make syntax easier.

Related