rand
works with ranges:
rand(1:10)
I'd like to make rand
work with Array
, and anything that is indexable and has length
:
import Base.Random
rand(thing) = thing[rand(1:length(thing))]
array = {1, 2, 3}
myRand(array)
range = 1:8
myRand(range)
tupple = (1, 2, 3, "a", "b", "c")
myRand(tupple)
… but if I try this, my implementation stack overflows, presumably because it is completely general and matches everything passed, so it ends up calling itself?
Is there a way to fix this? I want to better understand Julia's polymorphic functions rather than get a fix for this particular (probably silly) function specialisation.
Is there also a tool to discover the various implementations that are available, and debug which will be called with particular arguments?
Okay, some digging. This is interesting…
I'll start up a fresh REPL, and:
julia> import Base.Random
julia> rand(thing) = thing[rand(1:length(thing))]
rand (generic function with 1 method)
julia> rand({1,2,3})
ERROR: stack overflow
in rand at none:1 (repeats 80000 times)
…Oh dear, that's the recursive call and stack overflow I was talking about.
But, watch this. I kill Julia and start the REPL again. This time I import Base.Random.rand
:
julia> import Base.Random.rand
julia> rand(thing) = thing[rand(1:length(thing))]
rand (generic function with 33 methods)
julia> rand({1,2,3})
3
It works – it added my new implementation to all the others, and picked the right one.
So, the answer to my first question seems to be – "it just works". Which is amazing. How does that work?!
But there's a slightly less interesting sounding question about modules, and why import Base.Random
doesn't pull in the rand
method or give an error, but import Base.Random.rand
does.
As some have pointed, Julia let you extend functions: you can have functions that work differently for different types (see this part of the documentation).
For example:
f(x) = 2
f(x::Int) = x
In this example, we have a version (or method) of the function that is called if (and only if) the argument is of the type Int
. The first one is called otherwise.
We say that we have extended the f
function, and now it has 2 methods.
What you want, then, is to extend the rand
function.
You want your rand
function, if called with a argument that was not caught by other methods, to execute thing[rand(1:length(thing))]
. If done correctly, you would call the rand
method that is applied to a Range
object, since you are passing 1:length(thing)
as argument.
Although flawed (what if thing
doesn't have a length, e.g. a complex number?), I would say your first attempt was very reasonable.
The problem: rand
couldn't be extended on the first version of your program. According to this piece of documentation, writing import Base.Random
doesn't make rand
available for method extension.
While trying to extend rand
, you actually overwrite the rand
function. After this, when you call the function rand
, there is only your method!
Remember, you were relying on the fact that a method for ranges (e.g. rand(1:10)
) was defined elsewere, and that it gave the result you expected. What happened was that this method was overwritten by yours, so your method is called again (recursively).
The solution: import rand
such as it is available to extension. You can see that on the table on the documentation.
Notice that your second program (the one with import Base.Random.rand
) and Colin's program (the one with importall Base.Random
) did exactly that. That's why they work.
Keep in mind what methods are or are not available for extension, and if the documentation isn't clear enough, a bug report (or maybe a fix) will be welcomed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With