Can I write a Julia method that works "whenever possible" as a function of a C ++ template?

rand works with ranges:

 rand(1:10) 

I would like to make rand work with Array and everything that is indexed 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, apparently because it is completely general and matches all passed ones, so it ends up calling itself?

Is there any way to fix this? I want to better understand the polymorphic functions of Julia, and not fix this specific (possibly stupid) specialization of functions.

Is there also a tool for detecting various available implementations and debugging that will be called with specific arguments?


Ok, some are digging. It is interesting...

I launched a new 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, what recursive call and stack overflow did I say.

But take a look. I kill Julia and run 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 - he added my new implementation to everyone else and chose the right one.

So the answer to my first question seems to be "it just works." Which is amazing. How it works?!

But there is a slightly less interesting sound question about modules and why import Base.Random does not pull the rand method or does not give an error, but import Base.Random.rand does.

+7
polymorphism julia-lang
source share
3 answers

Method extension

As some pointed out, Julia allows you to extend functions: you may 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 a function that is called if (and only if) the argument is of type Int . The first is called differently.

We say that we have expanded the function f and now we have 2 methods.

Your problem

So you want to extend the rand function.

You need your rand function, if called with an argument that was not caught by other methods, to execute thing[rand(1:length(thing))] . If everything is done correctly, you call the rand method, which is applied to the Range object, since you pass 1:length(thing) as an argument.

Although it’s wrong (what if the thing has no length, for example, a complex number?), I would say that your first attempt was very reasonable.

Problem : rand cannot be expanded in the first version of your program. According to this part of the documentation , the import Base.Random does not make rand available to extend the method.

When you try to extend the rand you are actually overwriting the rand function. After that, when you call the rand function, only your method exists!

Remember that you relied on the fact that the range method (e.g. rand(1:10) ) was defined as a character and it gave the expected result. It so happened that this method was overwritten by yours, so your method is called again (recursively).

Solution : import rand , for example, is available for extension. This can be seen in the documentation table.

Note that your second program (one with import Base.Random.rand ) and the Colin program (one with importall Base.Random ) did just that. That is why they work.

Keep in mind which methods are available or not available for extension, and if the documentation is not clear enough, an error report (or possibly a fix) will be welcomed.

+4
source share

I was going to make this comment, but it turned out to be too long:

Note two other approaches that will also “just work”:

 Base.rand(thing) = thing[rand(1:length(thing))] rand({1,2,3}) 

Or you can also use:

 importall Base.Random rand(thing) = thing[rand(1:length(thing))] rand({1,2,3}) 

When you just use import Base.Random , which will actually not allow you to extend Base.rand locally defined rand . This is because the import statement should only be used with functions, not modules. If you want to import all the functions in a module, you need to use importall (as in my example above). Alternatively (as I said above) you can directly refer to the module in the definition of the Base.rand function.

Admittedly, the last time I looked at documents, this point could certainly be clarified. However, it can be considered in the latest version.

As for the rest of your question (why does this “just work”?), I'm not sure I can give a reasonable short answer to this question so that I can leave it for someone else.

In addition, a partial duplicate .

+2
source share

As already indicated, explicitly specifying Base.rand= instead of rand= adds the method for defining the rand function to Base, rather than completely replacing it.

For example,

 julia> Base.rand(x::Any)=x[rand(1:length(x)] rand (generic function with 33 methods) 

The reason this works is due to the Julia abstract system system . When Julia searches for a function with a signature of the matching method, it starts with a node sheet of certain types (for example, is there x - Int8), and then moves up the type hierarchy until it finds a function with the corresponding signature. The catch type Any is used at the top of the type hierarchy. If at any level of the hierarchy there is no corresponding function, the function call is not performed.

This is best illustrated by a simple example. We will create a function f that responds to several different types in a type hierarchy with a type name:

 julia> f(x::Int)="Int" f (generic function with 1 method) julia> f(x::Real)="Real" f (generic function with 2 methods) julia> f(x::Number)="Number" f (generic function with 3 methods) julia> f(x::Any)="Any" f (generic function with 4 methods) julia> f(x::Array)="Array" f (generic function with 5 methods) julia> f(4) # typeof(4) isInt64 "Int" julia> f(2.0) # typeof(2.0) is Float64 "Real" julia> f(3im) # typeof(3im) is Complex{Int64} "Number" julia> f([1,2]) # typeof([1,2]) is Array{Int64, 1} "Array" julia> f(Dict(1=>3,4=>5)) # typeof(Dict(1=>3,4=>5)) is Dict{Int64, Int64} "Any" 
+2
source share

All Articles