Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I write I Julia method that works "whenever possible" like a c++ template function?

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.

like image 557
Benjohn Avatar asked Jan 28 '15 09:01

Benjohn


1 Answers

Method extension

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.

Your Problem

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.

like image 184
prcastro Avatar answered Sep 28 '22 04:09

prcastro