3

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?

Grayscale
  • 1,462
  • 1
  • 13
  • 20
  • This is really a general question, which you might just not realize unless you're into PL theory. Most language evalute expression only strictly, like Julia. Some have non-strict evaluation, like Haskell. Very few allow you to choose between both; mostly term rewriting sytems like Mathematica (`:=` vs. `=`, I guess), or TeX (`\def` vs. `\let`). Sometimes, you have special syntax for corner cases, like in Scala (`lazy` vars and by-name parameters). – phipsgabler May 24 '20 at 09:17

1 Answers1

4

(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.

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
  • Helpful! Could you explain a bit further or provide a link for the notation you use that starts with `{}` and uses `~>`? – Grayscale May 24 '20 at 09:06
  • I made it up. It's loosely based on what is used as a metalanguage for describing [lambda calculi](https://en.wikipedia.org/wiki/Lambda_calculus). – phipsgabler May 24 '20 at 09:07
  • With respect to the part where you say "{a variable that, whenever it is referenced, evaluates this function} is called... a _function_, as you rightly observe" — does that mean that in Mathematica, whenever you use `:=`, you have essentially written a function without it looking like you are writing a function? Kind of a technical terminology question I guess, but might help clarify. – Grayscale May 24 '20 at 09:25
  • In term rewriting systems, it is not very useful to talk about functions. You have patterns that are matched against expressions, and produce an output expression from that. "True" functions can be seen as a very limited instance of this, where the only thing you ever can match against is the value resulting from the fully evaluated arguments. But in other languages with non-strict features, yes, essentially you are defining a closure. (Function arguments in R kind of like work like this, see http://adv-r.had.co.nz/Computing-on-the-language.html) – phipsgabler May 24 '20 at 09:58
  • But I'm really not the right person to talk to about Mathematica specifics. I've never written anything in it. – phipsgabler May 24 '20 at 09:59
  • But you might want to have a look at [Symata.jl](https://github.com/jlapeyre/Symata.jl). It's a rewriting system with, IIUC, pretty much mirrors the Wolfram language, and has a [`SetDelayed`](https://github.com/jlapeyre/Symata.jl/blob/75b7986863e2555bdb0d29eaecd21833d4c00b93/src/symbols.jl#L164) implementation (all by using its own evaluator, or course). – phipsgabler May 24 '20 at 10:03
  • This was making more sense until I glanced at Symata.jl... how can that package simply use a different evaluator? I would have thought that such a thing is intrinsic to a language. – Grayscale May 24 '20 at 16:32
  • 1
    Syntax doesn't change the evaluator. It instead changed the code from the code you wrote to code that when evaluated by Julia, produces code that behaves like the code you wrote was evaluated by a different evaluator. – Oscar Smith May 24 '20 at 19:13