Hello, I am trying to think about type stability in a function that accepts models from different modules. I thought I might be able to set the DataType accepted by functions produced by a factory dynamically, but it’s not doing what I thought it would:
julia> function foo(A)
myT = typeof(A)
function bar{myT}(b::myT)
return b^2
end
return bar
end
foo (generic function with 1 method)
julia> bar = foo(1.0)
bar (generic function with 1 method)
julia> bar(2.0)
4.0
julia> bar(1) #I expect this to crash due to Int64
1
julia> function foo{T}(A::T)
function bar(b::T)
return b^2
end
return bar
end
foo (generic function with 1 method)
julia> foo(1.0)(1)
ERROR: MethodError: no method matching (::#bar#1{Float64})(::Int64)
Closest candidates are:
bar{T}(::T) at REPL[1]:4
Your definition is like defining a function like f{Int}(a::Int) = ...; in there Int is just a typevariable and has nothing to do with the Int datatype.
Thanks, I can see how that caters to the question I asked, but I had oversimplified.
What I was thinking about was more like this:
julia> function foo(m::Module)
model = m.initialise()
modeltype = typeof(model)
function dosomething{modeltype}(model::modeltype)
return true
end
return dosomething
end
foo (generic function with 1 method)
julia> module SomeModel
type Model
a::Float64
b::String
end
function initialise()
return Model(1.0, "hello")
end
end
SomeModel
julia> dosomething = foo(SomeModel)
dosomething (generic function with 1 method)
julia> mymodel = SomeModel.initialise()
SomeModel.Model(1.0,"hello")
julia> dosomething(mymodel)
true
julia>dosomething(2.0) #was hoping this would crash.
true
I could be going about this all wrong, but I was hoping to make a module that could take models implemented in a bunch of other modules (with a common interface) and write type stable code for them.
I think you’re going about this wrong. You’re trying to make
model = m.initialise()
work. That’s probably due to familiarity with object-oriented programming? If you just did
initialize(model)
and wrote different dispatches for initialize, then you’d have a lot less trouble. Or even for this case, initialize should be part of the constructor? Instead of each type being in a different module, you should just name them differently all in the same module, or use a type parameter like Model{:this_model} (symbols are accepted directly), and then dispatch on the symbol. Then for dosomething, you should just be adding dispatches to a dosomething function (or use call-overloading).
This just looks like trying to fit the wrong design into Julia and trying to circumvent multiple-dispatch.
Thanks for the feedback. I’m sure I am going about it wrong as the aim is modest but I’ve not got there! (But not caused by OOP, at least I don’t think so)
you should just name them differently all in the same module
This is the solution I was trying to avoid because the concept I’m trying to automate in foo would be generic across a bunch of models, so I don’t want to unnecessarily hardcode in the names of modules.
I think I get where you’re going though, going to have another crack later.
Thanks again for the feedback (found a solution after looking at the page on type parameters). Here’s a solution that has the desired effect. That is, I can make a model using module with functions that accept any type written to conform to an abstract interface. Without having to import the concrete versions into the model using source code. [gee that sounds abstract…]
module MetaModelModule
export MetaModel, dostuff!
abstract MetaModel
function dostuff!(model::MetaModel, data::Any) model end
end #MetaModelModule
module TargetModelModule
importall MetaModelModule
export TargetModel, dostuff!
type TargetModel <: MetaModel
x::Float64
end
function dostuff!(model::TargetModel, data::Float64)
model.x += data
return model
end
end #TargetModelModule
module ModelUsingModule
importall MetaModelModule
function dosomething{M <: MetaModel}(model::M)
dostuff!(model, 1.0)
end
end
And using this:
julia> include("modmod.jl")
ModelUsingModule
julia> using TargetModelModule
julia> model = TargetModel(0.0)
TargetModelModule.TargetModel(0.0)
julia> ModelUsingModule.dosomething(model)
TargetModelModule.TargetModel(1.0)
julia> ModelUsingModule.dosomething("hey")
ERROR: MethodError: no method matching dosomething(::String)
Not investigated the performance implications (if any) of this.
I’ve been watching this thread since the beginning, trying to understand its problem. Here is my take at it.
The general answer to the topic’s title is generated functions (an advanced topic), as:
These have the capability to generate specialized code depending on the types of their arguments
However, most of the problems around the types of a function’s arguments can be solved “simply” by multiple dispatch, where Julia handles the generation automatically (sometimes guided by parameters), while performing dynamic JIT compilation. The various code posted throughout this thread apparently tries to solve such a problem, so the title of the topic could be changed to something more relevant, e.g. “A case of intermodule interface”.
Accordingly, the latest version of the code gives a solution based solely on multiple dispatch, all function generation being handled on demand by Julia, not by user code. And given that: function dosomething{M <: MetaModel}(model::M)
isn’t different in practice than: function dosomething(model::MetaModel)
the remaining parameter can be removed for an overall simple code result.
It really isn’t. Generated functions aren’t allowed to emit the kind of closure code that the original post was trying produce. There’s no need to suggest a more complex solution to a question that wasn’t asked. There’s nothing that a (valid) generated function can compute that an ordinary function can’t easily also compute. The other answers above already answered the original question about why that code didn’t work as expected and how to modify it.
Modules don’t have any performance penalty . Everything is always inside some module (even if that module is the outermost Main one), and creating submodules can be useful for avoiding naming-collisions, as you found in your solution above.
There was no suggestion and it’s not even about needs. This isn’t a computer service in a Q&A format, it is interaction between humans. But I didn’t see that kind of treatment in other users and I’ve had plenty of it and in an orchestrated form, not just from @jameson. This is from Julia community’s standards:
Ask yourself if a comment or statement might make someone feel unwelcomed or like an outsider.
I’m obviously an outsider. I will think if it’s worthy my time anymore.
My personal policy is to try and ask stupid questions only. Smart questions can usually be solved by reading the docs or raising an issue on some repo. I reach out when I don’t know quite what I’m looking for, and understand that sometimes people get it, and sometimes not